From 7badd2b82f67da1a9a0d3e0e3f13ad8ebae99c9b Mon Sep 17 00:00:00 2001 From: Sysix <3897725+Sysix@users.noreply.github.com> Date: Thu, 5 Feb 2026 20:47:38 +0000 Subject: [PATCH] test(oxlint/lsp): add lint tests on js side (#18730) --- .../lsp/lint/__snapshots__/lint.test.ts.snap | 74 +++++++++++++++++++ .../fixtures/config-default/.oxlintrc.json | 2 + .../lsp/lint/fixtures/config-default/test.ts | 1 + .../fixtures/config-disabled/.oxlintrc.json | 5 ++ .../lsp/lint/fixtures/config-disabled/test.ts | 1 + .../fixtures/config-js-plugin/.oxlintrc.json | 7 ++ .../lint/fixtures/config-js-plugin/plugin.js | 22 ++++++ .../lint/fixtures/config-js-plugin/test.js | 1 + .../fixtures/config-severity/.oxlintrc.json | 5 ++ .../lsp/lint/fixtures/config-severity/test.ts | 1 + .../fixtures/custom-config-path/lint.json | 5 ++ .../lint/fixtures/custom-config-path/test.ts | 1 + .../test/lsp/lint/fixtures/default/test.ts | 1 + .../test/lsp/lint/fixtures/default/test.tsx | 1 + apps/oxlint/test/lsp/lint/lint.test.ts | 37 ++++++++++ apps/oxlint/test/lsp/utils.ts | 47 ++++++++++-- crates/oxc_language_server/src/backend.rs | 2 + 17 files changed, 207 insertions(+), 6 deletions(-) create mode 100644 apps/oxlint/test/lsp/lint/__snapshots__/lint.test.ts.snap create mode 100644 apps/oxlint/test/lsp/lint/fixtures/config-default/.oxlintrc.json create mode 100644 apps/oxlint/test/lsp/lint/fixtures/config-default/test.ts create mode 100644 apps/oxlint/test/lsp/lint/fixtures/config-disabled/.oxlintrc.json create mode 100644 apps/oxlint/test/lsp/lint/fixtures/config-disabled/test.ts create mode 100644 apps/oxlint/test/lsp/lint/fixtures/config-js-plugin/.oxlintrc.json create mode 100644 apps/oxlint/test/lsp/lint/fixtures/config-js-plugin/plugin.js create mode 100644 apps/oxlint/test/lsp/lint/fixtures/config-js-plugin/test.js create mode 100644 apps/oxlint/test/lsp/lint/fixtures/config-severity/.oxlintrc.json create mode 100644 apps/oxlint/test/lsp/lint/fixtures/config-severity/test.ts create mode 100644 apps/oxlint/test/lsp/lint/fixtures/custom-config-path/lint.json create mode 100644 apps/oxlint/test/lsp/lint/fixtures/custom-config-path/test.ts create mode 100644 apps/oxlint/test/lsp/lint/fixtures/default/test.ts create mode 100644 apps/oxlint/test/lsp/lint/fixtures/default/test.tsx create mode 100644 apps/oxlint/test/lsp/lint/lint.test.ts diff --git a/apps/oxlint/test/lsp/lint/__snapshots__/lint.test.ts.snap b/apps/oxlint/test/lsp/lint/__snapshots__/lint.test.ts.snap new file mode 100644 index 0000000000000..3536ff9299bd0 --- /dev/null +++ b/apps/oxlint/test/lsp/lint/__snapshots__/lint.test.ts.snap @@ -0,0 +1,74 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`LSP linting > basic linting > should handle default/test.ts 1`] = ` +"--- FILE ----------- +default/test.ts +--- Diagnostics --------- +> 1 | debugger; + | ^^^^^^^^^ Warning: \`debugger\` statement is not allowed +help: Remove the debugger statement + 2 | +--------------------" +`; + +exports[`LSP linting > basic linting > should handle default/test.tsx 1`] = ` +"--- FILE ----------- +default/test.tsx +--- Diagnostics --------- +> 1 | debugger; + | ^^^^^^^^^ Warning: \`debugger\` statement is not allowed +help: Remove the debugger statement + 2 | +--------------------" +`; + +exports[`LSP linting > config options > should apply config from config-default/test.ts 1`] = ` +"--- FILE ----------- +config-default/test.ts +--- Diagnostics --------- +> 1 | debugger; + | ^^^^^^^^^ Warning: \`debugger\` statement is not allowed +help: Remove the debugger statement + 2 | +--------------------" +`; + +exports[`LSP linting > config options > should apply config from config-disabled/test.ts 1`] = ` +"--- FILE ----------- +config-disabled/test.ts +--- Diagnostics --------- + +--------------------" +`; + +exports[`LSP linting > config options > should apply config from config-js-plugin/test.js 1`] = ` +"--- FILE ----------- +config-js-plugin/test.js +--- Diagnostics --------- +> 1 | debugger; + | ^^^^^^^^^ Error: Custom name JS Plugin Test Rule. + 2 | +--------------------" +`; + +exports[`LSP linting > config options > should apply config from config-severity/test.ts 1`] = ` +"--- FILE ----------- +config-severity/test.ts +--- Diagnostics --------- +> 1 | debugger; + | ^^^^^^^^^ Error: \`debugger\` statement is not allowed +help: Remove the debugger statement + 2 | +--------------------" +`; + +exports[`LSP linting > initializationOptions > should use custom config path from configPath 1`] = ` +"--- FILE ----------- +custom-config-path/test.ts +--- Diagnostics --------- +> 1 | debugger; + | ^^^^^^^^^ Error: \`debugger\` statement is not allowed +help: Remove the debugger statement + 2 | +--------------------" +`; diff --git a/apps/oxlint/test/lsp/lint/fixtures/config-default/.oxlintrc.json b/apps/oxlint/test/lsp/lint/fixtures/config-default/.oxlintrc.json new file mode 100644 index 0000000000000..2c63c0851048d --- /dev/null +++ b/apps/oxlint/test/lsp/lint/fixtures/config-default/.oxlintrc.json @@ -0,0 +1,2 @@ +{ +} diff --git a/apps/oxlint/test/lsp/lint/fixtures/config-default/test.ts b/apps/oxlint/test/lsp/lint/fixtures/config-default/test.ts new file mode 100644 index 0000000000000..eab74692130a6 --- /dev/null +++ b/apps/oxlint/test/lsp/lint/fixtures/config-default/test.ts @@ -0,0 +1 @@ +debugger; diff --git a/apps/oxlint/test/lsp/lint/fixtures/config-disabled/.oxlintrc.json b/apps/oxlint/test/lsp/lint/fixtures/config-disabled/.oxlintrc.json new file mode 100644 index 0000000000000..c299636b14ee8 --- /dev/null +++ b/apps/oxlint/test/lsp/lint/fixtures/config-disabled/.oxlintrc.json @@ -0,0 +1,5 @@ +{ + "rules": { + "no-debugger": "off" + } +} diff --git a/apps/oxlint/test/lsp/lint/fixtures/config-disabled/test.ts b/apps/oxlint/test/lsp/lint/fixtures/config-disabled/test.ts new file mode 100644 index 0000000000000..eab74692130a6 --- /dev/null +++ b/apps/oxlint/test/lsp/lint/fixtures/config-disabled/test.ts @@ -0,0 +1 @@ +debugger; diff --git a/apps/oxlint/test/lsp/lint/fixtures/config-js-plugin/.oxlintrc.json b/apps/oxlint/test/lsp/lint/fixtures/config-js-plugin/.oxlintrc.json new file mode 100644 index 0000000000000..6a2c6b1f6a1b8 --- /dev/null +++ b/apps/oxlint/test/lsp/lint/fixtures/config-js-plugin/.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/lint/fixtures/config-js-plugin/plugin.js b/apps/oxlint/test/lsp/lint/fixtures/config-js-plugin/plugin.js new file mode 100644 index 0000000000000..a99cfb1853227 --- /dev/null +++ b/apps/oxlint/test/lsp/lint/fixtures/config-js-plugin/plugin.js @@ -0,0 +1,22 @@ +const plugin = { + meta: { + name: 'js-plugin', + }, + rules: { + 'test-rule': { + create(context) { + return { + DebuggerStatement(debuggerStatement) { + context.report({ + message: 'Custom name JS Plugin Test Rule.', + node: debuggerStatement, + }); + }, + }; + }, + }, + }, +}; + + +export default plugin; diff --git a/apps/oxlint/test/lsp/lint/fixtures/config-js-plugin/test.js b/apps/oxlint/test/lsp/lint/fixtures/config-js-plugin/test.js new file mode 100644 index 0000000000000..eab74692130a6 --- /dev/null +++ b/apps/oxlint/test/lsp/lint/fixtures/config-js-plugin/test.js @@ -0,0 +1 @@ +debugger; diff --git a/apps/oxlint/test/lsp/lint/fixtures/config-severity/.oxlintrc.json b/apps/oxlint/test/lsp/lint/fixtures/config-severity/.oxlintrc.json new file mode 100644 index 0000000000000..a63d73a1442b8 --- /dev/null +++ b/apps/oxlint/test/lsp/lint/fixtures/config-severity/.oxlintrc.json @@ -0,0 +1,5 @@ +{ + "rules": { + "no-debugger": "error" + } +} diff --git a/apps/oxlint/test/lsp/lint/fixtures/config-severity/test.ts b/apps/oxlint/test/lsp/lint/fixtures/config-severity/test.ts new file mode 100644 index 0000000000000..eab74692130a6 --- /dev/null +++ b/apps/oxlint/test/lsp/lint/fixtures/config-severity/test.ts @@ -0,0 +1 @@ +debugger; diff --git a/apps/oxlint/test/lsp/lint/fixtures/custom-config-path/lint.json b/apps/oxlint/test/lsp/lint/fixtures/custom-config-path/lint.json new file mode 100644 index 0000000000000..a63d73a1442b8 --- /dev/null +++ b/apps/oxlint/test/lsp/lint/fixtures/custom-config-path/lint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "no-debugger": "error" + } +} diff --git a/apps/oxlint/test/lsp/lint/fixtures/custom-config-path/test.ts b/apps/oxlint/test/lsp/lint/fixtures/custom-config-path/test.ts new file mode 100644 index 0000000000000..eab74692130a6 --- /dev/null +++ b/apps/oxlint/test/lsp/lint/fixtures/custom-config-path/test.ts @@ -0,0 +1 @@ +debugger; diff --git a/apps/oxlint/test/lsp/lint/fixtures/default/test.ts b/apps/oxlint/test/lsp/lint/fixtures/default/test.ts new file mode 100644 index 0000000000000..eab74692130a6 --- /dev/null +++ b/apps/oxlint/test/lsp/lint/fixtures/default/test.ts @@ -0,0 +1 @@ +debugger; diff --git a/apps/oxlint/test/lsp/lint/fixtures/default/test.tsx b/apps/oxlint/test/lsp/lint/fixtures/default/test.tsx new file mode 100644 index 0000000000000..eab74692130a6 --- /dev/null +++ b/apps/oxlint/test/lsp/lint/fixtures/default/test.tsx @@ -0,0 +1 @@ +debugger; diff --git a/apps/oxlint/test/lsp/lint/lint.test.ts b/apps/oxlint/test/lsp/lint/lint.test.ts new file mode 100644 index 0000000000000..43823afcd1556 --- /dev/null +++ b/apps/oxlint/test/lsp/lint/lint.test.ts @@ -0,0 +1,37 @@ +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { lintFixture } from "../utils"; + +const FIXTURES_DIR = join(import.meta.dirname, "fixtures"); + +describe("LSP linting", () => { + describe("basic linting", () => { + it.each([ + ["default/test.tsx", "typescriptreact"], + ["default/test.ts", "typescript"], + ])("should handle %s", async (path, languageId) => { + expect(await lintFixture(FIXTURES_DIR, path, languageId)).toMatchSnapshot(); + }); + }); + + describe("config options", () => { + it.each([ + ["config-default/test.ts", "typescript"], + ["config-disabled/test.ts", "typescript"], + ["config-severity/test.ts", "typescript"], + ["config-js-plugin/test.js", "javascript"], + ])("should apply config from %s", async (path, languageId) => { + expect(await lintFixture(FIXTURES_DIR, path, languageId)).toMatchSnapshot(); + }); + }); + + describe("initializationOptions", () => { + it("should use custom config path from configPath", async () => { + expect( + await lintFixture(FIXTURES_DIR, "custom-config-path/test.ts", "typescript", { + configPath: "./lint.json", + }), + ).toMatchSnapshot(); + }); + }); +}); diff --git a/apps/oxlint/test/lsp/utils.ts b/apps/oxlint/test/lsp/utils.ts index 4b0425da945f7..534c2adb820b3 100644 --- a/apps/oxlint/test/lsp/utils.ts +++ b/apps/oxlint/test/lsp/utils.ts @@ -4,6 +4,7 @@ import { dirname, join } from "node:path"; import { pathToFileURL } from "node:url"; import { createMessageConnection, + DiagnosticSeverity, DidChangeConfigurationNotification, DidChangeTextDocumentNotification, DidOpenTextDocumentNotification, @@ -27,7 +28,12 @@ import { codeFrameColumns } from "@babel/code-frame"; const CLI_PATH = join(import.meta.dirname, "..", "..", "dist", "cli.js"); export function createLspConnection() { - const proc = spawn(process.execPath, [CLI_PATH, "--lsp"]); + const proc = spawn(process.execPath, [CLI_PATH, "--lsp"], { + env: { + ...process.env, + OXC_LOG: "debug", + }, + }); const connection = createMessageConnection( new StreamMessageReader(proc.stdout), @@ -147,14 +153,43 @@ ${applyDiagnostics(content, diagnostics).join("\n--------------------")} type OxlintLSPConfig = {}; +function getSeverityLabel(severity: number | undefined): string { + if (!severity) return "Unknown"; + + switch (severity) { + case DiagnosticSeverity.Error: + return "Error"; + case DiagnosticSeverity.Warning: + return "Warning"; + case DiagnosticSeverity.Information: + return "Information"; + case DiagnosticSeverity.Hint: + return "Hint"; + default: + return "Unknown"; + } +} + function applyDiagnostics(content: string, report: DocumentDiagnosticReport): string[] { if (report.kind !== "full") { throw new Error("Only full reports are supported by oxlint lsp"); } - return report.items.map((diagnostic) => - codeFrameColumns(content, diagnostic.range, { - message: diagnostic.message, - }), - ); + return report.items.map((diagnostic) => { + const babelLocation = { + start: { + line: diagnostic.range.start.line + 1, + column: diagnostic.range.start.character + 1, + }, + end: { + line: diagnostic.range.end.line + 1, + column: diagnostic.range.end.character + 1, + }, + }; + const severity = getSeverityLabel(diagnostic.severity); + + return codeFrameColumns(content, babelLocation, { + message: `${severity}: ${diagnostic.message}`, + }); + }); } diff --git a/crates/oxc_language_server/src/backend.rs b/crates/oxc_language_server/src/backend.rs index b35c5e32f61aa..3fbeae9f37642 100644 --- a/crates/oxc_language_server/src/backend.rs +++ b/crates/oxc_language_server/src/backend.rs @@ -164,6 +164,7 @@ impl LanguageServer for Backend { .map(|workspace_options| workspace_options.options.clone()) .unwrap_or_default(); + debug!("starting worker in initialize with options: {option:?}"); worker.start_worker(option).await; } } @@ -224,6 +225,7 @@ impl LanguageServer for Backend { for (index, worker) in needed_configurations.values().copied().enumerate() { // get the configuration from the response and start the worker let configuration = configurations.get(index).unwrap_or(&serde_json::Value::Null); + debug!("starting worker in initialize with options: {configuration:?}"); worker.start_worker(configuration.clone()).await; // run diagnostics for all known files in the workspace of the worker.