diff --git a/crates/oxc_language_server/src/linter/server_linter.rs b/crates/oxc_language_server/src/linter/server_linter.rs index 34173c5f54909..e33bcc9679ea3 100644 --- a/crates/oxc_language_server/src/linter/server_linter.rs +++ b/crates/oxc_language_server/src/linter/server_linter.rs @@ -37,9 +37,29 @@ pub struct ServerLinter { ignore_matcher: LintIgnoreMatcher, gitignore_glob: Vec, lint_on_run: Run, + diagnostics: Arc, pub extended_paths: Vec, } +#[derive(Debug, Default)] +struct ServerLinterDiagnostics { + isolated_linter: Arc>>, + tsgo_linter: Arc>>, +} + +impl ServerLinterDiagnostics { + pub fn all_diagnostics(&self, path: &str) -> Vec { + let mut diagnostics = Vec::new(); + if let Some(reports) = self.isolated_linter.pin().get(path) { + diagnostics.extend_from_slice(reports); + } + if let Some(reports) = self.tsgo_linter.pin().get(path) { + diagnostics.extend_from_slice(reports); + } + diagnostics + } +} + impl ServerLinter { pub fn new(root_uri: &Uri, options: &Options) -> Self { let root_path = root_uri.to_file_path().unwrap(); @@ -134,6 +154,7 @@ impl ServerLinter { gitignore_glob: Self::create_ignore_glob(&root_path), extended_paths, lint_on_run: options.run, + diagnostics: Arc::new(ServerLinterDiagnostics::default()), tsgo_linter: if options.type_aware { Arc::new(Some(TsgoLinter::new(&root_path, config_store))) } else { @@ -276,28 +297,26 @@ impl ServerLinter { return None; } - let mut reports = Vec::with_capacity(0); - if oxlint && let Some(oxlint_reports) = self.isolated_linter.lock().await.run_single(uri, content.clone()) { - reports.extend(oxlint_reports); + self.diagnostics.isolated_linter.pin().insert(uri.to_string(), oxlint_reports); } if !tsgolint { - return Some(reports); + return Some(self.diagnostics.all_diagnostics(&uri.to_string())); } let Some(tsgo_linter) = &*self.tsgo_linter else { - return Some(reports); + return Some(self.diagnostics.all_diagnostics(&uri.to_string())); }; if let Some(tsgo_reports) = tsgo_linter.lint_file(uri, content) { - reports.extend(tsgo_reports); + self.diagnostics.tsgo_linter.pin().insert(uri.to_string(), tsgo_reports); } - Some(reports) + Some(self.diagnostics.all_diagnostics(&uri.to_string())) } } diff --git a/editors/vscode/fixtures/lint_on_run/.oxlintrc.json b/editors/vscode/fixtures/lint_on_run/.oxlintrc.json new file mode 100644 index 0000000000000..6342b54f7a1ca --- /dev/null +++ b/editors/vscode/fixtures/lint_on_run/.oxlintrc.json @@ -0,0 +1,5 @@ +{ + "rules": { + "unicorn/no-empty-file": "off" + } +} diff --git a/editors/vscode/fixtures/lint_on_run/onType.ts b/editors/vscode/fixtures/lint_on_run/onType.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/editors/vscode/tests/e2e_server.spec.ts b/editors/vscode/tests/e2e_server.spec.ts index 64ba765f070ff..20b91027f9ea0 100644 --- a/editors/vscode/tests/e2e_server.spec.ts +++ b/editors/vscode/tests/e2e_server.spec.ts @@ -15,11 +15,13 @@ import { createOxlintConfiguration, fixturesWorkspaceUri, getDiagnostics, + getDiagnosticsWithoutClose, loadFixture, sleep, testSingleFolderMode, waitForDiagnosticChange, - WORKSPACE_DIR + WORKSPACE_DIR, + writeToFixtureFile } from './test-helpers'; import assert = require('assert'); @@ -66,6 +68,24 @@ suite('E2E Diagnostics', () => { }); } + testSingleFolderMode('detects diagnostics on run', async () => + { + await loadFixture('lint_on_run'); + const diagnostics = await getDiagnosticsWithoutClose(`onType.ts`); + strictEqual(diagnostics.length, 0); + + await writeToFixtureFile('onType.ts', 'debugger;'); + await waitForDiagnosticChange(); + const updatedDiagnostics = await getDiagnosticsWithoutClose(`onType.ts`); + strictEqual(updatedDiagnostics.length, 1); + + await workspace.saveAll(); + await sleep(500); + + const sameDiagnostics = await getDiagnosticsWithoutClose(`onType.ts`); + strictEqual(updatedDiagnostics.length, sameDiagnostics.length); + }); + test('empty oxlint configuration behaves like default configuration', async () => { await loadFixture('debugger_empty_config'); const diagnostics = await getDiagnostics('debugger.js'); diff --git a/editors/vscode/tests/test-helpers.ts b/editors/vscode/tests/test-helpers.ts index afc4a01d5a2e8..0f3cfbd10cac5 100644 --- a/editors/vscode/tests/test-helpers.ts +++ b/editors/vscode/tests/test-helpers.ts @@ -101,14 +101,29 @@ export async function loadFixture(fixture: string, workspaceDir: Uri = fixturesW } export async function getDiagnostics(file: string, workspaceDir: Uri = fixturesWorkspaceUri()): Promise { + const diagnostics = await getDiagnosticsWithoutClose(file, workspaceDir); + await commands.executeCommand('workbench.action.closeActiveEditor'); + return diagnostics; +} + +export async function getDiagnosticsWithoutClose(file: string, workspaceDir: Uri = fixturesWorkspaceUri()): Promise { const fileUri = Uri.joinPath(workspaceDir, 'fixtures', file); await window.showTextDocument(fileUri); await sleep(500); const diagnostics = languages.getDiagnostics(fileUri); - await commands.executeCommand('workbench.action.closeActiveEditor'); return diagnostics; } +export async function writeToFixtureFile(file: string, content: string, workspaceDir: Uri = fixturesWorkspaceUri()): Promise { + const fileUri = Uri.joinPath(workspaceDir, 'fixtures', file); + await window.showTextDocument(fileUri); + + for (const char of content) { + await commands.executeCommand('type', { text: char }); + await sleep(50); + } +} + export async function waitForDiagnosticChange(): Promise { return new Promise((resolve) => languages.onDidChangeDiagnostics(() => {