diff --git a/javascript/packages/language-server/src/linter_service.ts b/javascript/packages/language-server/src/linter_service.ts index d739e1e8b..514e13d44 100644 --- a/javascript/packages/language-server/src/linter_service.ts +++ b/javascript/packages/language-server/src/linter_service.ts @@ -113,7 +113,27 @@ export class LinterService { } } + private shouldLintFile(uri: string): boolean { + const filePath = uri.replace(/^file:\/\//, '') + + if (filePath.endsWith('.herb.yml')) return false + + const config = this.settings.projectConfig + if (!config) return true + + const hasConfigFile = Config.exists(config.projectPath) + if (!hasConfigFile) return true + + const relativePath = filePath.replace(this.project.projectPath + '/', '') + + return config.isLinterEnabledForPath(relativePath) + } + async lintDocument(textDocument: TextDocument): Promise { + if (!this.shouldLintFile(textDocument.uri)) { + return { diagnostics: [] } + } + const settings = await this.settings.getDocumentSettings(textDocument.uri) const linterEnabled = settings?.linter?.enabled ?? true diff --git a/javascript/packages/language-server/test/linter_service.test.ts b/javascript/packages/language-server/test/linter_service.test.ts index e083fca02..f59e3bea2 100644 --- a/javascript/packages/language-server/test/linter_service.test.ts +++ b/javascript/packages/language-server/test/linter_service.test.ts @@ -6,6 +6,7 @@ import { LinterService } from "../src/linter_service" import { Settings } from "../src/settings" import { Project } from "../src/project" import { Herb } from "@herb-tools/node-wasm" +import { Config } from "@herb-tools/config" import type { Connection, InitializeParams } from "vscode-languageserver/node" @@ -130,6 +131,97 @@ describe("LinterService", () => { expect(parserErrorDiagnostics).toHaveLength(0) }) + test("respects files.exclude patterns from config", async () => { + vi.spyOn(Config, "exists").mockReturnValue(true) + + const settings = new Settings(mockParams, mockConnection) + settings.getDocumentSettings = vi.fn().mockResolvedValue({ + linter: { enabled: true } + }) + + settings.projectConfig = Config.fromObject({ + files: { + exclude: ["vendor/**/*"] + }, + linter: { + enabled: true, + rules: {} + } + }, { projectPath: "/test/project" }) + + const mockProjectWithPath = { + projectPath: "/test/project" + } as Project + + const linterService = new LinterService(mockConnection, settings, mockProjectWithPath) + const textDocument = TextDocument.create("file:///test/project/vendor/cache/file.html.erb", "erb", 1, "
Content
") + const result = await linterService.lintDocument(textDocument) + + expect(result.diagnostics).toEqual([]) + + vi.restoreAllMocks() + }) + + test("respects linter.exclude patterns from config", async () => { + vi.spyOn(Config, "exists").mockReturnValue(true) + + const settings = new Settings(mockParams, mockConnection) + settings.getDocumentSettings = vi.fn().mockResolvedValue({ + linter: { enabled: true } + }) + + settings.projectConfig = Config.fromObject({ + linter: { + enabled: true, + exclude: ["something/**/*"], + rules: {} + } + }, { projectPath: "/test/project" }) + + const mockProjectWithPath = { + projectPath: "/test/project" + } as Project + + const linterService = new LinterService(mockConnection, settings, mockProjectWithPath) + const textDocument = TextDocument.create("file:///test/project/something/file.html.erb", "erb", 1, "
Content
") + const result = await linterService.lintDocument(textDocument) + + expect(result.diagnostics).toEqual([]) + + vi.restoreAllMocks() + }) + + test("lints files not matching exclude patterns", async () => { + vi.spyOn(Config, "exists").mockReturnValue(true) + + const settings = new Settings(mockParams, mockConnection) + settings.getDocumentSettings = vi.fn().mockResolvedValue({ + linter: { enabled: true } + }) + + settings.projectConfig = Config.fromObject({ + files: { + exclude: ["vendor/**/*"] + }, + linter: { + enabled: true, + rules: {} + } + }, { projectPath: "/test/project" }) + + const mockProjectWithPath = { + projectPath: "/test/project" + } as Project + + const linterService = new LinterService(mockConnection, settings, mockProjectWithPath) + const textDocument = TextDocument.create("file:///test/project/app/views/file.html.erb", "erb", 1, "
Content
") + const result = await linterService.lintDocument(textDocument) + + expect(result.diagnostics.length).toBeGreaterThan(0) + + vi.restoreAllMocks() + }) + test("respects custom disabled rules configuration", async () => { const settings = new Settings(mockParams, mockConnection) settings.getDocumentSettings = vi.fn().mockResolvedValue({ @@ -149,12 +241,11 @@ describe("LinterService", () => { }, toJSON: () => "{}", getConfiguredSeverity: () => "error", - applySeverityOverrides: (offenses) => offenses + applySeverityOverrides: (offenses: any) => offenses } as any const linterService = new LinterService(mockConnection, settings, mockProject) const textDocument = createTestDocument("
Content
") - const result = await linterService.lintDocument(textDocument) const lowercaseDiagnostics = result.diagnostics.filter(