diff --git a/apps/oxlint/test/lsp/utils.ts b/apps/oxlint/test/lsp/utils.ts index 534c2adb820b3..1373522728743 100644 --- a/apps/oxlint/test/lsp/utils.ts +++ b/apps/oxlint/test/lsp/utils.ts @@ -27,6 +27,17 @@ import { codeFrameColumns } from "@babel/code-frame"; const CLI_PATH = join(import.meta.dirname, "..", "..", "dist", "cli.js"); +const PULL_DIAGNOSTICS_CAPABILITY = { + textDocument: { + diagnostic: {}, + }, + workspace: { + diagnostics: { + refreshSupport: true, + }, + }, +}; + export function createLspConnection() { const proc = spawn(process.execPath, [CLI_PATH, "--lsp"], { env: { @@ -110,32 +121,64 @@ export async function lintFixture( languageId: string, initializationOptions?: OxlintLSPConfig, ): Promise { - const filePath = join(fixturesDir, fixturePath); - const dirPath = dirname(filePath); - const fileUri = pathToFileURL(filePath).href; - const content = await fs.readFile(filePath, "utf-8"); + return lintMultiWorkspaceFixture( + fixturesDir, + [{ path: fixturePath, languageId }], + initializationOptions ? [initializationOptions] : undefined, + ); +} +export async function lintMultiWorkspaceFixture( + fixturesDir: string, + fixturePaths: { + path: string; + languageId: string; + }[], + initializationOptions?: OxlintLSPConfig[], +): Promise { + const workspaceUris = fixturePaths.map( + ({ path }) => pathToFileURL(dirname(join(fixturesDir, path))).href, + ); await using client = createLspConnection(); await client.initialize( - [{ uri: pathToFileURL(dirPath).href, name: "test" }], - { - textDocument: { - diagnostic: {}, - }, - workspace: { - diagnostics: { - refreshSupport: true, - }, - }, - }, - [ - { - workspaceUri: pathToFileURL(dirPath).href, - options: initializationOptions, - }, - ], + workspaceUris.map((uri, index) => ({ uri, name: `workspace-${index}` })), + PULL_DIAGNOSTICS_CAPABILITY, + workspaceUris.map((workspaceUri, index) => ({ + workspaceUri, + options: initializationOptions?.[index] ?? null, + })), ); + + const snapshots = []; + for (const fixturePath of fixturePaths) { + snapshots.push( + // oxlint-disable-next-line no-await-in-loop -- for snapshot consistency + await getDiagnosticSnapshot( + fixturePath.path, + join(fixturesDir, fixturePath.path), + fixturePath.languageId, + client, + ), + ); + } + + return snapshots.join("\n\n"); +} + +// --- + +type OxlintLSPConfig = {}; + +async function getDiagnosticSnapshot( + fixturePath: string, + filePath: string, + languageId: string, + client: ReturnType, +): Promise { + const fileUri = pathToFileURL(filePath).href; + const content = await fs.readFile(filePath, "utf-8"); + await client.didOpen(fileUri, languageId, content); const diagnostics = await client.diagnostic(fileUri); @@ -149,10 +192,6 @@ ${applyDiagnostics(content, diagnostics).join("\n--------------------")} `.trim(); } -// --- - -type OxlintLSPConfig = {}; - function getSeverityLabel(severity: number | undefined): string { if (!severity) return "Unknown"; diff --git a/apps/oxlint/test/lsp/workspaces/__snapshots__/workspace.test.ts.snap b/apps/oxlint/test/lsp/workspaces/__snapshots__/workspace.test.ts.snap new file mode 100644 index 0000000000000..6d1dc79821805 --- /dev/null +++ b/apps/oxlint/test/lsp/workspaces/__snapshots__/workspace.test.ts.snap @@ -0,0 +1,73 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`LSP multi workspace linting > basic linting > should handle { path: 'config-default-both/workspace1/test.ts', languageId: 'typescript' } 1`] = ` +"--- FILE ----------- +config-default-both/workspace1/test.ts +--- Diagnostics --------- +> 1 | debugger; + | ^^^^^^^^^ Warning: \`debugger\` statement is not allowed +help: Remove the debugger statement +-------------------- + +--- FILE ----------- +config-default-both/workspace2/test.ts +--- Diagnostics --------- +> 1 | debugger; + | ^^^^^^^^^ Warning: \`debugger\` statement is not allowed +help: Remove the debugger statement +--------------------" +`; + +exports[`LSP multi workspace linting > basic linting > should handle { path: 'default-both/workspace1/test.ts', languageId: 'typescript' } 1`] = ` +"--- FILE ----------- +default-both/workspace1/test.ts +--- Diagnostics --------- +> 1 | debugger; + | ^^^^^^^^^ Warning: \`debugger\` statement is not allowed +help: Remove the debugger statement +-------------------- + +--- FILE ----------- +default-both/workspace2/test.ts +--- Diagnostics --------- +> 1 | debugger; + | ^^^^^^^^^ Warning: \`debugger\` statement is not allowed +help: Remove the debugger statement +--------------------" +`; + +exports[`LSP multi workspace linting > basic linting > should handle { path: 'different-js-plugin/workspace1/test.js', languageId: 'javascript' } 1`] = ` +"--- FILE ----------- +different-js-plugin/workspace1/test.js +--- Diagnostics --------- +> 1 | debugger; + | ^^^^^^^^^ Error: Workspace 1 Plugin + 2 | +-------------------- + +--- FILE ----------- +different-js-plugin/workspace2/test.js +--- Diagnostics --------- +> 1 | debugger; + | ^^^^^^^^^ Error: Workspace 2 Plugin + 2 | +--------------------" +`; + +exports[`LSP multi workspace linting > basic linting > should handle { path: 'different-severity/workspace1/test.ts', languageId: 'typescript' } 1`] = ` +"--- FILE ----------- +different-severity/workspace1/test.ts +--- Diagnostics --------- +> 1 | debugger; + | ^^^^^^^^^ Error: \`debugger\` statement is not allowed +help: Remove the debugger statement +-------------------- + +--- FILE ----------- +different-severity/workspace2/test.ts +--- Diagnostics --------- +> 1 | debugger; + | ^^^^^^^^^ Warning: \`debugger\` statement is not allowed +help: Remove the debugger statement +--------------------" +`; diff --git a/apps/oxlint/test/lsp/workspaces/fixtures/config-default-both/workspace1/.oxlintrc.json b/apps/oxlint/test/lsp/workspaces/fixtures/config-default-both/workspace1/.oxlintrc.json new file mode 100644 index 0000000000000..2c63c0851048d --- /dev/null +++ b/apps/oxlint/test/lsp/workspaces/fixtures/config-default-both/workspace1/.oxlintrc.json @@ -0,0 +1,2 @@ +{ +} diff --git a/apps/oxlint/test/lsp/workspaces/fixtures/config-default-both/workspace1/test.ts b/apps/oxlint/test/lsp/workspaces/fixtures/config-default-both/workspace1/test.ts new file mode 100644 index 0000000000000..9f5fb105ad0c2 --- /dev/null +++ b/apps/oxlint/test/lsp/workspaces/fixtures/config-default-both/workspace1/test.ts @@ -0,0 +1 @@ +debugger; \ No newline at end of file diff --git a/apps/oxlint/test/lsp/workspaces/fixtures/config-default-both/workspace2/.oxlintrc.json b/apps/oxlint/test/lsp/workspaces/fixtures/config-default-both/workspace2/.oxlintrc.json new file mode 100644 index 0000000000000..2c63c0851048d --- /dev/null +++ b/apps/oxlint/test/lsp/workspaces/fixtures/config-default-both/workspace2/.oxlintrc.json @@ -0,0 +1,2 @@ +{ +} diff --git a/apps/oxlint/test/lsp/workspaces/fixtures/config-default-both/workspace2/test.ts b/apps/oxlint/test/lsp/workspaces/fixtures/config-default-both/workspace2/test.ts new file mode 100644 index 0000000000000..9f5fb105ad0c2 --- /dev/null +++ b/apps/oxlint/test/lsp/workspaces/fixtures/config-default-both/workspace2/test.ts @@ -0,0 +1 @@ +debugger; \ No newline at end of file diff --git a/apps/oxlint/test/lsp/workspaces/fixtures/default-both/workspace1/test.ts b/apps/oxlint/test/lsp/workspaces/fixtures/default-both/workspace1/test.ts new file mode 100644 index 0000000000000..9f5fb105ad0c2 --- /dev/null +++ b/apps/oxlint/test/lsp/workspaces/fixtures/default-both/workspace1/test.ts @@ -0,0 +1 @@ +debugger; \ No newline at end of file diff --git a/apps/oxlint/test/lsp/workspaces/fixtures/default-both/workspace2/test.ts b/apps/oxlint/test/lsp/workspaces/fixtures/default-both/workspace2/test.ts new file mode 100644 index 0000000000000..9f5fb105ad0c2 --- /dev/null +++ b/apps/oxlint/test/lsp/workspaces/fixtures/default-both/workspace2/test.ts @@ -0,0 +1 @@ +debugger; \ No newline at end of file diff --git a/apps/oxlint/test/lsp/workspaces/fixtures/different-js-plugin/workspace1/.oxlintrc.json b/apps/oxlint/test/lsp/workspaces/fixtures/different-js-plugin/workspace1/.oxlintrc.json new file mode 100644 index 0000000000000..6a2c6b1f6a1b8 --- /dev/null +++ b/apps/oxlint/test/lsp/workspaces/fixtures/different-js-plugin/workspace1/.oxlintrc.json @@ -0,0 +1,7 @@ +{ + "jsPlugins": ["./plugin.js"], + "rules": { + "js-plugin/test-rule": "error", + "no-debugger": "off", + } +} diff --git a/apps/oxlint/test/lsp/workspaces/fixtures/different-js-plugin/workspace1/plugin.js b/apps/oxlint/test/lsp/workspaces/fixtures/different-js-plugin/workspace1/plugin.js new file mode 100644 index 0000000000000..c83227f032333 --- /dev/null +++ b/apps/oxlint/test/lsp/workspaces/fixtures/different-js-plugin/workspace1/plugin.js @@ -0,0 +1,22 @@ +const plugin = { + meta: { + name: 'js-plugin', + }, + rules: { + 'test-rule': { + create(context) { + return { + DebuggerStatement(debuggerStatement) { + context.report({ + message: 'Workspace 1 Plugin', + node: debuggerStatement, + }); + }, + }; + }, + }, + }, +}; + + +export default plugin; diff --git a/apps/oxlint/test/lsp/workspaces/fixtures/different-js-plugin/workspace1/test.js b/apps/oxlint/test/lsp/workspaces/fixtures/different-js-plugin/workspace1/test.js new file mode 100644 index 0000000000000..eab74692130a6 --- /dev/null +++ b/apps/oxlint/test/lsp/workspaces/fixtures/different-js-plugin/workspace1/test.js @@ -0,0 +1 @@ +debugger; diff --git a/apps/oxlint/test/lsp/workspaces/fixtures/different-js-plugin/workspace2/.oxlintrc.json b/apps/oxlint/test/lsp/workspaces/fixtures/different-js-plugin/workspace2/.oxlintrc.json new file mode 100644 index 0000000000000..6a2c6b1f6a1b8 --- /dev/null +++ b/apps/oxlint/test/lsp/workspaces/fixtures/different-js-plugin/workspace2/.oxlintrc.json @@ -0,0 +1,7 @@ +{ + "jsPlugins": ["./plugin.js"], + "rules": { + "js-plugin/test-rule": "error", + "no-debugger": "off", + } +} diff --git a/apps/oxlint/test/lsp/workspaces/fixtures/different-js-plugin/workspace2/plugin.js b/apps/oxlint/test/lsp/workspaces/fixtures/different-js-plugin/workspace2/plugin.js new file mode 100644 index 0000000000000..5ca9a3845fb12 --- /dev/null +++ b/apps/oxlint/test/lsp/workspaces/fixtures/different-js-plugin/workspace2/plugin.js @@ -0,0 +1,22 @@ +const plugin = { + meta: { + name: 'js-plugin', + }, + rules: { + 'test-rule': { + create(context) { + return { + DebuggerStatement(debuggerStatement) { + context.report({ + message: 'Workspace 2 Plugin', + node: debuggerStatement, + }); + }, + }; + }, + }, + }, +}; + + +export default plugin; diff --git a/apps/oxlint/test/lsp/workspaces/fixtures/different-js-plugin/workspace2/test.js b/apps/oxlint/test/lsp/workspaces/fixtures/different-js-plugin/workspace2/test.js new file mode 100644 index 0000000000000..eab74692130a6 --- /dev/null +++ b/apps/oxlint/test/lsp/workspaces/fixtures/different-js-plugin/workspace2/test.js @@ -0,0 +1 @@ +debugger; diff --git a/apps/oxlint/test/lsp/workspaces/fixtures/different-severity/workspace1/.oxlintrc.json b/apps/oxlint/test/lsp/workspaces/fixtures/different-severity/workspace1/.oxlintrc.json new file mode 100644 index 0000000000000..a63d73a1442b8 --- /dev/null +++ b/apps/oxlint/test/lsp/workspaces/fixtures/different-severity/workspace1/.oxlintrc.json @@ -0,0 +1,5 @@ +{ + "rules": { + "no-debugger": "error" + } +} diff --git a/apps/oxlint/test/lsp/workspaces/fixtures/different-severity/workspace1/test.ts b/apps/oxlint/test/lsp/workspaces/fixtures/different-severity/workspace1/test.ts new file mode 100644 index 0000000000000..9f5fb105ad0c2 --- /dev/null +++ b/apps/oxlint/test/lsp/workspaces/fixtures/different-severity/workspace1/test.ts @@ -0,0 +1 @@ +debugger; \ No newline at end of file diff --git a/apps/oxlint/test/lsp/workspaces/fixtures/different-severity/workspace2/.oxlintrc.json b/apps/oxlint/test/lsp/workspaces/fixtures/different-severity/workspace2/.oxlintrc.json new file mode 100644 index 0000000000000..cfc6c7217e7d1 --- /dev/null +++ b/apps/oxlint/test/lsp/workspaces/fixtures/different-severity/workspace2/.oxlintrc.json @@ -0,0 +1,5 @@ +{ + "rules": { + "no-debugger": "warn" + } +} diff --git a/apps/oxlint/test/lsp/workspaces/fixtures/different-severity/workspace2/test.ts b/apps/oxlint/test/lsp/workspaces/fixtures/different-severity/workspace2/test.ts new file mode 100644 index 0000000000000..9f5fb105ad0c2 --- /dev/null +++ b/apps/oxlint/test/lsp/workspaces/fixtures/different-severity/workspace2/test.ts @@ -0,0 +1 @@ +debugger; \ No newline at end of file diff --git a/apps/oxlint/test/lsp/workspaces/workspace.test.ts b/apps/oxlint/test/lsp/workspaces/workspace.test.ts new file mode 100644 index 0000000000000..9fdc73fd294bc --- /dev/null +++ b/apps/oxlint/test/lsp/workspaces/workspace.test.ts @@ -0,0 +1,30 @@ +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { lintMultiWorkspaceFixture } from "../utils"; + +const FIXTURES_DIR = join(import.meta.dirname, "fixtures"); + +describe("LSP multi workspace linting", () => { + describe("basic linting", () => { + it.each([ + [ + { path: "default-both/workspace1/test.ts", languageId: "typescript" }, + { path: "default-both/workspace2/test.ts", languageId: "typescript" }, + ], + [ + { path: "config-default-both/workspace1/test.ts", languageId: "typescript" }, + { path: "config-default-both/workspace2/test.ts", languageId: "typescript" }, + ], + [ + { path: "different-severity/workspace1/test.ts", languageId: "typescript" }, + { path: "different-severity/workspace2/test.ts", languageId: "typescript" }, + ], + [ + { path: "different-js-plugin/workspace1/test.js", languageId: "javascript" }, + { path: "different-js-plugin/workspace2/test.js", languageId: "javascript" }, + ], + ])("should handle %s", async (...paths) => { + expect(await lintMultiWorkspaceFixture(FIXTURES_DIR, paths)).toMatchSnapshot(); + }); + }); +});