diff --git a/crates/oxc_language_server/src/backend.rs b/crates/oxc_language_server/src/backend.rs index 42582dd6d12a2..be5635fb42845 100644 --- a/crates/oxc_language_server/src/backend.rs +++ b/crates/oxc_language_server/src/backend.rs @@ -214,15 +214,13 @@ impl LanguageServer for Backend { continue; } let content = self.file_system.read().await.get(uri); - if let Some(diagnostics) = worker.run_diagnostic(uri, content.as_deref()).await - { - new_diagnostics.push((uri.clone(), diagnostics)); - } + let diagnostics = worker.run_diagnostic(uri, content.as_deref()).await; + new_diagnostics.extend(diagnostics); } } if !new_diagnostics.is_empty() { - self.publish_all_diagnostics(new_diagnostics).await; + self.publish_all_diagnostics(new_diagnostics, ConcurrentHashMap::default()).await; } } @@ -353,7 +351,7 @@ impl LanguageServer for Backend { } if !new_diagnostics.is_empty() { - self.publish_all_diagnostics(new_diagnostics).await; + self.publish_all_diagnostics(new_diagnostics, ConcurrentHashMap::default()).await; } if !removing_registrations.is_empty() @@ -401,7 +399,7 @@ impl LanguageServer for Backend { } if !new_diagnostics.is_empty() { - self.publish_all_diagnostics(new_diagnostics).await; + self.publish_all_diagnostics(new_diagnostics, ConcurrentHashMap::default()).await; } if self.capabilities.get().is_some_and(|capabilities| capabilities.dynamic_watchers) { @@ -504,9 +502,9 @@ impl LanguageServer for Backend { return; }; - if let Some(diagnostics) = worker.run_diagnostic_on_save(&uri, params.text.as_deref()).await - { - self.client.publish_diagnostics(uri, diagnostics, None).await; + let diagnostics = worker.run_diagnostic_on_save(&uri, params.text.as_deref()).await; + if !diagnostics.is_empty() { + self.publish_all_diagnostics(diagnostics, ConcurrentHashMap::default()).await; } } /// It will update the in-memory file content if the client supports dynamic formatting. @@ -525,10 +523,11 @@ impl LanguageServer for Backend { self.file_system.write().await.set(uri.clone(), content.clone()); } - if let Some(diagnostics) = worker.run_diagnostic_on_change(&uri, content.as_deref()).await { - self.client - .publish_diagnostics(uri, diagnostics, Some(params.text_document.version)) - .await; + let diagnostics = worker.run_diagnostic_on_change(&uri, content.as_deref()).await; + if !diagnostics.is_empty() { + let version_map = ConcurrentHashMap::default(); + version_map.pin().insert(uri.clone(), params.text_document.version); + self.publish_all_diagnostics(diagnostics, version_map).await; } } @@ -547,10 +546,11 @@ impl LanguageServer for Backend { self.file_system.write().await.set(uri.clone(), content.clone()); - if let Some(diagnostics) = worker.run_diagnostic(&uri, Some(&content)).await { - self.client - .publish_diagnostics(uri, diagnostics, Some(params.text_document.version)) - .await; + let diagnostics = worker.run_diagnostic(&uri, Some(&content)).await; + if !diagnostics.is_empty() { + let version_map = ConcurrentHashMap::default(); + version_map.pin().insert(uri.clone(), params.text_document.version); + self.publish_all_diagnostics(diagnostics, version_map).await; } } @@ -677,16 +677,23 @@ impl Backend { } async fn clear_diagnostics(&self, uris: Vec) { - self.publish_all_diagnostics(uris.into_iter().map(|uri| (uri, vec![])).collect()).await; + self.publish_all_diagnostics( + uris.into_iter().map(|uri| (uri, vec![])).collect(), + ConcurrentHashMap::default(), + ) + .await; } /// Publish diagnostics for all files. - async fn publish_all_diagnostics(&self, result: Vec<(Uri, Vec)>) { - join_all( - result - .into_iter() - .map(|(uri, diagnostics)| self.client.publish_diagnostics(uri, diagnostics, None)), - ) + async fn publish_all_diagnostics( + &self, + result: Vec<(Uri, Vec)>, + version_map: ConcurrentHashMap, + ) { + join_all(result.into_iter().map(|(uri, diagnostics)| { + let version = version_map.pin().get(&uri).copied(); + self.client.publish_diagnostics(uri, diagnostics, version) + })) .await; } } diff --git a/crates/oxc_language_server/src/linter/server_linter.rs b/crates/oxc_language_server/src/linter/server_linter.rs index 30dbcc0c1a0a0..a2c2caa8cf8b2 100644 --- a/crates/oxc_language_server/src/linter/server_linter.rs +++ b/crates/oxc_language_server/src/linter/server_linter.rs @@ -18,7 +18,6 @@ use oxc_linter::{ LintIgnoreMatcher, LintOptions, Oxlintrc, }; -use crate::linter::error_with_position::LinterCodeAction; use crate::{ ConcurrentHashMap, linter::{ @@ -29,10 +28,11 @@ use crate::{ }, commands::{FIX_ALL_COMMAND_ID, FixAllCommandArgs}, config_walker::ConfigWalker, + error_with_position::LinterCodeAction, isolated_lint_handler::{IsolatedLintHandler, IsolatedLintHandlerOptions}, options::{LintOptions as LSPLintOptions, Run, UnusedDisableDirectives}, }, - tool::{Tool, ToolBuilder, ToolRestartChanges, ToolShutdownChanges}, + tool::{DiagnosticResult, Tool, ToolBuilder, ToolRestartChanges, ToolShutdownChanges}, utils::normalize_path, }; @@ -491,34 +491,30 @@ impl Tool for ServerLinter { } /// Lint a file with the current linter - /// - If the file is not lintable or ignored, [`None`] is returned - /// - If the file is lintable, but no diagnostics are found, an empty vector is returned - fn run_diagnostic(&self, uri: &Uri, content: Option<&str>) -> Option> { - self.run_file(uri, content) + /// - If the file is not lintable or ignored, an empty vector is returned + fn run_diagnostic(&self, uri: &Uri, content: Option<&str>) -> DiagnosticResult { + let Some(diagnostics) = self.run_file(uri, content) else { + return vec![]; + }; + vec![(uri.clone(), diagnostics)] } /// Lint a file with the current linter - /// - If the file is not lintable or ignored, [`None`] is returned - /// - If the linter is not set to `OnType`, [`None`] is returned - /// - If the file is lintable, but no diagnostics are found, an empty vector is returned - fn run_diagnostic_on_change( - &self, - uri: &Uri, - content: Option<&str>, - ) -> Option> { + /// - If the file is not lintable or ignored, an empty vector is returned + /// - If the linter is not set to `OnType`, an empty vector is returned + fn run_diagnostic_on_change(&self, uri: &Uri, content: Option<&str>) -> DiagnosticResult { if self.run != Run::OnType { - return None; + return vec![]; } self.run_diagnostic(uri, content) } /// Lint a file with the current linter - /// - If the file is not lintable or ignored, [`None`] is returned - /// - If the linter is not set to `OnSave`, [`None`] is returned - /// - If the file is lintable, but no diagnostics are found, an empty vector is returned - fn run_diagnostic_on_save(&self, uri: &Uri, content: Option<&str>) -> Option> { + /// - If the file is not lintable or ignored, an empty vector is returned + /// - If the linter is not set to `OnSave`, an empty vector is returned + fn run_diagnostic_on_save(&self, uri: &Uri, content: Option<&str>) -> DiagnosticResult { if self.run != Run::OnSave { - return None; + return vec![]; } self.run_diagnostic(uri, content) } diff --git a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_cross_module@debugger.ts.snap b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_cross_module@debugger.ts.snap index 5a5a7b0c1e9fb..567fb686df3f0 100644 --- a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_cross_module@debugger.ts.snap +++ b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_cross_module@debugger.ts.snap @@ -2,9 +2,10 @@ source: crates/oxc_language_server/src/linter/tester.rs --- ########## -file: fixtures/linter/cross_module/debugger.ts +Linted file: fixtures/linter/cross_module/debugger.ts ---------- ########## Diagnostic Reports +File URI: file:///fixtures/linter/cross_module/debugger.ts code: "eslint(no-debugger)" code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger.html" @@ -16,6 +17,7 @@ related_information[0].location.range: Range { start: Position { line: 1, charac severity: Some(Warning) source: Some("oxc") tags: None + ########### Code Actions/Commands CodeAction: Title: Remove the debugger statement diff --git a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_cross_module@dep-a.ts.snap b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_cross_module@dep-a.ts.snap index 5bcc7eb4a1fd7..3d393e9879978 100644 --- a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_cross_module@dep-a.ts.snap +++ b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_cross_module@dep-a.ts.snap @@ -2,9 +2,10 @@ source: crates/oxc_language_server/src/linter/tester.rs --- ########## -file: fixtures/linter/cross_module/dep-a.ts +Linted file: fixtures/linter/cross_module/dep-a.ts ---------- ########## Diagnostic Reports +File URI: file:///fixtures/linter/cross_module/dep-a.ts code: "eslint-plugin-import(no-cycle)" code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/import/no-cycle.html" @@ -16,6 +17,7 @@ related_information[0].location.range: Range { start: Position { line: 1, charac severity: Some(Error) source: Some("oxc") tags: None + ########### Code Actions/Commands CodeAction: Title: Disable no-cycle for this line diff --git a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_cross_module_extended_config@dep-a.ts.snap b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_cross_module_extended_config@dep-a.ts.snap index c4cc3e83e0f33..41dfcbad490cd 100644 --- a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_cross_module_extended_config@dep-a.ts.snap +++ b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_cross_module_extended_config@dep-a.ts.snap @@ -2,9 +2,10 @@ source: crates/oxc_language_server/src/linter/tester.rs --- ########## -file: fixtures/linter/cross_module_extended_config/dep-a.ts +Linted file: fixtures/linter/cross_module_extended_config/dep-a.ts ---------- ########## Diagnostic Reports +File URI: file:///fixtures/linter/cross_module_extended_config/dep-a.ts code: "eslint-plugin-import(no-cycle)" code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/import/no-cycle.html" @@ -16,6 +17,7 @@ related_information[0].location.range: Range { start: Position { line: 1, charac severity: Some(Error) source: Some("oxc") tags: None + ########### Code Actions/Commands CodeAction: Title: Disable no-cycle for this line diff --git a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_cross_module_nested_config@dep-a.ts_folder__folder-dep-a.ts.snap b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_cross_module_nested_config@dep-a.ts_folder__folder-dep-a.ts.snap index 0afc7b9116569..cb26e8ec0c3e6 100644 --- a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_cross_module_nested_config@dep-a.ts_folder__folder-dep-a.ts.snap +++ b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_cross_module_nested_config@dep-a.ts_folder__folder-dep-a.ts.snap @@ -2,16 +2,18 @@ source: crates/oxc_language_server/src/linter/tester.rs --- ########## -file: fixtures/linter/cross_module_nested_config/dep-a.ts +Linted file: fixtures/linter/cross_module_nested_config/dep-a.ts ---------- ########## Diagnostic Reports +File URI: file:///fixtures/linter/cross_module_nested_config/dep-a.ts ########### Code Actions/Commands ########## -file: fixtures/linter/cross_module_nested_config/folder/folder-dep-a.ts +Linted file: fixtures/linter/cross_module_nested_config/folder/folder-dep-a.ts ---------- ########## Diagnostic Reports +File URI: file:///fixtures/linter/cross_module_nested_config/folder/folder-dep-a.ts code: "eslint-plugin-import(no-cycle)" code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/import/no-cycle.html" @@ -23,6 +25,7 @@ related_information[0].location.range: Range { start: Position { line: 1, charac severity: Some(Error) source: Some("oxc") tags: None + ########### Code Actions/Commands CodeAction: Title: Disable no-cycle for this line diff --git a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_deny_no_console@hello_world.js.snap b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_deny_no_console@hello_world.js.snap index 6b3c7f09b050c..1a990af5eec92 100644 --- a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_deny_no_console@hello_world.js.snap +++ b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_deny_no_console@hello_world.js.snap @@ -2,9 +2,10 @@ source: crates/oxc_language_server/src/linter/tester.rs --- ########## -file: fixtures/linter/deny_no_console/hello_world.js +Linted file: fixtures/linter/deny_no_console/hello_world.js ---------- ########## Diagnostic Reports +File URI: file:///fixtures/linter/deny_no_console/hello_world.js code: "eslint(no-console)" code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-console.html" @@ -16,6 +17,7 @@ related_information[0].location.range: Range { start: Position { line: 0, charac severity: Some(Error) source: Some("oxc") tags: None + ########### Code Actions/Commands CodeAction: Title: Disable no-console for this line diff --git a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_frameworks@astro__debugger.astro_vue__debugger.vue_svelte__debugger.svelte_nextjs__[[..rest]]__debugger.ts.snap b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_frameworks@astro__debugger.astro_vue__debugger.vue_svelte__debugger.svelte_nextjs__[[..rest]]__debugger.ts.snap index f393e6fd1c374..4c538582b4de5 100644 --- a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_frameworks@astro__debugger.astro_vue__debugger.vue_svelte__debugger.svelte_nextjs__[[..rest]]__debugger.ts.snap +++ b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_frameworks@astro__debugger.astro_vue__debugger.vue_svelte__debugger.svelte_nextjs__[[..rest]]__debugger.ts.snap @@ -2,9 +2,10 @@ source: crates/oxc_language_server/src/linter/tester.rs --- ########## -file: fixtures/linter/frameworks/astro/debugger.astro +Linted file: fixtures/linter/frameworks/astro/debugger.astro ---------- ########## Diagnostic Reports +File URI: file:///fixtures/linter/frameworks/astro/debugger.astro code: "eslint(no-debugger)" code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger.html" @@ -49,6 +50,7 @@ related_information[0].location.range: Range { start: Position { line: 18, chara severity: Some(Warning) source: Some("oxc") tags: None + ########### Code Actions/Commands CodeAction: Title: Remove the debugger statement @@ -267,9 +269,10 @@ TextEdit: TextEdit { ########## -file: fixtures/linter/frameworks/vue/debugger.vue +Linted file: fixtures/linter/frameworks/vue/debugger.vue ---------- ########## Diagnostic Reports +File URI: file:///fixtures/linter/frameworks/vue/debugger.vue code: "eslint(no-debugger)" code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger.html" @@ -292,6 +295,7 @@ related_information[0].location.range: Range { start: Position { line: 8, charac severity: Some(Warning) source: Some("oxc") tags: None + ########### Code Actions/Commands CodeAction: Title: Remove the debugger statement @@ -402,9 +406,10 @@ TextEdit: TextEdit { ########## -file: fixtures/linter/frameworks/svelte/debugger.svelte +Linted file: fixtures/linter/frameworks/svelte/debugger.svelte ---------- ########## Diagnostic Reports +File URI: file:///fixtures/linter/frameworks/svelte/debugger.svelte code: "eslint(no-unassigned-vars)" code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-unassigned-vars.html" @@ -438,6 +443,7 @@ related_information[0].location.range: Range { start: Position { line: 1, charac severity: Some(Warning) source: Some("oxc") tags: None + ########### Code Actions/Commands CodeAction: Title: Disable no-unassigned-vars for this line @@ -566,9 +572,10 @@ TextEdit: TextEdit { ########## -file: fixtures/linter/frameworks/nextjs/[[..rest]]/debugger.ts +Linted file: fixtures/linter/frameworks/nextjs/[[..rest]]/debugger.ts ---------- ########## Diagnostic Reports +File URI: file:///fixtures/linter/frameworks/nextjs/%5B%5B..rest%5D%5D/debugger.ts code: "eslint(no-debugger)" code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger.html" @@ -580,6 +587,7 @@ related_information[0].location.range: Range { start: Position { line: 0, charac severity: Some(Warning) source: Some("oxc") tags: None + ########### Code Actions/Commands CodeAction: Title: Remove the debugger statement diff --git a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_ignore_patterns@ignored-file.ts_another_config__not-ignored-file.ts.snap b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_ignore_patterns@ignored-file.ts_another_config__not-ignored-file.ts.snap index 3658eecd59cea..82fa22b075d36 100644 --- a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_ignore_patterns@ignored-file.ts_another_config__not-ignored-file.ts.snap +++ b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_ignore_patterns@ignored-file.ts_another_config__not-ignored-file.ts.snap @@ -2,13 +2,14 @@ source: crates/oxc_language_server/src/linter/tester.rs --- ########## -file: fixtures/linter/ignore_patterns/ignored-file.ts +Linted file: fixtures/linter/ignore_patterns/ignored-file.ts ---------- -File is ignored +No diagnostics / Files are ignored ########## -file: fixtures/linter/ignore_patterns/another_config/not-ignored-file.ts +Linted file: fixtures/linter/ignore_patterns/another_config/not-ignored-file.ts ---------- ########## Diagnostic Reports +File URI: file:///fixtures/linter/ignore_patterns/another_config/not-ignored-file.ts code: "eslint(no-debugger)" code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger.html" @@ -20,6 +21,7 @@ related_information[0].location.range: Range { start: Position { line: 0, charac severity: Some(Error) source: Some("oxc") tags: None + ########### Code Actions/Commands CodeAction: Title: Remove the debugger statement diff --git a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_invalid_syntax@debugger.ts_invalid.vue.snap b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_invalid_syntax@debugger.ts_invalid.vue.snap index ebd840bed7103..b7e8478bfb1bd 100644 --- a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_invalid_syntax@debugger.ts_invalid.vue.snap +++ b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_invalid_syntax@debugger.ts_invalid.vue.snap @@ -2,9 +2,10 @@ source: crates/oxc_language_server/src/linter/tester.rs --- ########## -file: fixtures/linter/invalid_syntax/debugger.ts +Linted file: fixtures/linter/invalid_syntax/debugger.ts ---------- ########## Diagnostic Reports +File URI: file:///fixtures/linter/invalid_syntax/debugger.ts code: "" code_description.href: "None" @@ -16,12 +17,14 @@ related_information[0].location.range: Range { start: Position { line: 0, charac severity: Some(Error) source: Some("oxc") tags: None + ########### Code Actions/Commands ########## -file: fixtures/linter/invalid_syntax/invalid.vue +Linted file: fixtures/linter/invalid_syntax/invalid.vue ---------- ########## Diagnostic Reports +File URI: file:///fixtures/linter/invalid_syntax/invalid.vue code: "" code_description.href: "None" @@ -33,4 +36,5 @@ related_information[0].location.range: Range { start: Position { line: 2, charac severity: Some(Error) source: Some("oxc") tags: None + ########### Code Actions/Commands diff --git a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_issue_14565@foo-bar.astro.snap b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_issue_14565@foo-bar.astro.snap index ddc14e2157f2f..f00d09b125538 100644 --- a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_issue_14565@foo-bar.astro.snap +++ b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_issue_14565@foo-bar.astro.snap @@ -2,9 +2,10 @@ source: crates/oxc_language_server/src/linter/tester.rs --- ########## -file: fixtures/linter/issue_14565/foo-bar.astro +Linted file: fixtures/linter/issue_14565/foo-bar.astro ---------- ########## Diagnostic Reports +File URI: file:///fixtures/linter/issue_14565/foo-bar.astro code: "eslint-plugin-unicorn(filename-case)" code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/unicorn/filename-case.html" @@ -16,4 +17,5 @@ related_information[0].location.range: Range { start: Position { line: 0, charac severity: Some(Error) source: Some("oxc") tags: None + ########### Code Actions/Commands diff --git a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_issue_9958@issue.ts.snap b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_issue_9958@issue.ts.snap index fd28e348e2125..033c946a3624a 100644 --- a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_issue_9958@issue.ts.snap +++ b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_issue_9958@issue.ts.snap @@ -2,9 +2,10 @@ source: crates/oxc_language_server/src/linter/tester.rs --- ########## -file: fixtures/linter/issue_9958/issue.ts +Linted file: fixtures/linter/issue_9958/issue.ts ---------- ########## Diagnostic Reports +File URI: file:///fixtures/linter/issue_9958/issue.ts code: "eslint(no-extra-boolean-cast)" code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-extra-boolean-cast.html" @@ -41,6 +42,7 @@ related_information[0].location.range: Range { start: Position { line: 11, chara severity: Some(Hint) source: Some("oxc") tags: None + ########### Code Actions/Commands CodeAction: Title: Disable no-extra-boolean-cast for this line diff --git a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_js_plugins@index.js.snap b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_js_plugins@index.js.snap index 6438427ad3be1..fa285dd073561 100644 --- a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_js_plugins@index.js.snap +++ b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_js_plugins@index.js.snap @@ -2,9 +2,10 @@ source: crates/oxc_language_server/src/linter/tester.rs --- ########## -file: fixtures/linter/js_plugins/index.js +Linted file: fixtures/linter/js_plugins/index.js ---------- ########## Diagnostic Reports +File URI: file:///fixtures/linter/js_plugins/index.js code: "eslint(no-debugger)" code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger.html" @@ -16,6 +17,7 @@ related_information[0].location.range: Range { start: Position { line: 0, charac severity: Some(Warning) source: Some("oxc") tags: None + ########### Code Actions/Commands CodeAction: Title: Remove the debugger statement diff --git a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_multiple_suggestions@forward_ref.ts.snap b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_multiple_suggestions@forward_ref.ts.snap index deffbe863b080..767394733ebbc 100644 --- a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_multiple_suggestions@forward_ref.ts.snap +++ b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_multiple_suggestions@forward_ref.ts.snap @@ -2,9 +2,10 @@ source: crates/oxc_language_server/src/linter/tester.rs --- ########## -file: fixtures/linter/multiple_suggestions/forward_ref.ts +Linted file: fixtures/linter/multiple_suggestions/forward_ref.ts ---------- ########## Diagnostic Reports +File URI: file:///fixtures/linter/multiple_suggestions/forward_ref.ts code: "eslint-plugin-react(forward-ref-uses-ref)" code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/react/forward-ref-uses-ref.html" @@ -16,6 +17,7 @@ related_information[0].location.range: Range { start: Position { line: 0, charac severity: Some(Error) source: Some("oxc") tags: None + ########### Code Actions/Commands CodeAction: Title: remove `forwardRef` wrapper diff --git a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_no_errors@hello_world.js.snap b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_no_errors@hello_world.js.snap index d6d7ed93d2887..ccdbe679791c7 100644 --- a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_no_errors@hello_world.js.snap +++ b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_no_errors@hello_world.js.snap @@ -2,8 +2,9 @@ source: crates/oxc_language_server/src/linter/tester.rs --- ########## -file: fixtures/linter/no_errors/hello_world.js +Linted file: fixtures/linter/no_errors/hello_world.js ---------- ########## Diagnostic Reports +File URI: file:///fixtures/linter/no_errors/hello_world.js ########### Code Actions/Commands diff --git a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_regexp_feature@index.ts.snap b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_regexp_feature@index.ts.snap index fb14e67d64f26..d64d655b6a9a0 100644 --- a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_regexp_feature@index.ts.snap +++ b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_regexp_feature@index.ts.snap @@ -2,9 +2,10 @@ source: crates/oxc_language_server/src/linter/tester.rs --- ########## -file: fixtures/linter/regexp_feature/index.ts +Linted file: fixtures/linter/regexp_feature/index.ts ---------- ########## Diagnostic Reports +File URI: file:///fixtures/linter/regexp_feature/index.ts code: "eslint(no-control-regex)" code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-control-regex.html" @@ -27,6 +28,7 @@ related_information[0].location.range: Range { start: Position { line: 0, charac severity: Some(Error) source: Some("oxc") tags: None + ########### Code Actions/Commands CodeAction: Title: Disable no-control-regex for this line diff --git a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_ts_path_alias@deep__src__dep-a.ts.snap b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_ts_path_alias@deep__src__dep-a.ts.snap index e21475f9f07eb..52507d8607275 100644 --- a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_ts_path_alias@deep__src__dep-a.ts.snap +++ b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_ts_path_alias@deep__src__dep-a.ts.snap @@ -2,9 +2,10 @@ source: crates/oxc_language_server/src/linter/tester.rs --- ########## -file: fixtures/linter/ts_path_alias/deep/src/dep-a.ts +Linted file: fixtures/linter/ts_path_alias/deep/src/dep-a.ts ---------- ########## Diagnostic Reports +File URI: file:///fixtures/linter/ts_path_alias/deep/src/dep-a.ts code: "eslint-plugin-import(no-cycle)" code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/import/no-cycle.html" @@ -16,6 +17,7 @@ related_information[0].location.range: Range { start: Position { line: 1, charac severity: Some(Error) source: Some("oxc") tags: None + ########### Code Actions/Commands CodeAction: Title: Disable no-cycle for this line diff --git a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_tsgolint@no-floating-promises__index.ts.snap b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_tsgolint@no-floating-promises__index.ts.snap index 8830d462750b7..1a22497a015ab 100644 --- a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_tsgolint@no-floating-promises__index.ts.snap +++ b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_tsgolint@no-floating-promises__index.ts.snap @@ -2,9 +2,10 @@ source: crates/oxc_language_server/src/linter/tester.rs --- ########## -file: fixtures/linter/tsgolint/no-floating-promises/index.ts +Linted file: fixtures/linter/tsgolint/no-floating-promises/index.ts ---------- ########## Diagnostic Reports +File URI: file:///fixtures/linter/tsgolint/no-floating-promises/index.ts code: "eslint(no-unused-expressions)" code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-unused-expressions.html" @@ -71,6 +72,7 @@ related_information[0].location.range: Range { start: Position { line: 13, chara severity: Some(Warning) source: Some("oxc") tags: None + ########### Code Actions/Commands CodeAction: Title: Disable no-unused-expressions for this line diff --git a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_tsgolint_unused_disabled_directives@test.ts.snap b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_tsgolint_unused_disabled_directives@test.ts.snap index 902aa1a38089e..22ade4c016779 100644 --- a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_tsgolint_unused_disabled_directives@test.ts.snap +++ b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_tsgolint_unused_disabled_directives@test.ts.snap @@ -2,9 +2,10 @@ source: crates/oxc_language_server/src/linter/tester.rs --- ########## -file: fixtures/linter/tsgolint/unused_disabled_directives/test.ts +Linted file: fixtures/linter/tsgolint/unused_disabled_directives/test.ts ---------- ########## Diagnostic Reports +File URI: file:///fixtures/linter/tsgolint/unused_disabled_directives/test.ts code: "typescript-eslint(no-floating-promises)" code_description.href: "None" @@ -27,6 +28,7 @@ related_information[0].location.range: Range { start: Position { line: 38, chara severity: Some(Error) source: Some("oxc") tags: None + ########### Code Actions/Commands CodeAction: Title: Disable no-floating-promises for this line diff --git a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_unused_disabled_directives@test.js.snap b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_unused_disabled_directives@test.js.snap index 3c4c0def5989b..650d8906db875 100644 --- a/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_unused_disabled_directives@test.js.snap +++ b/crates/oxc_language_server/src/linter/snapshots/fixtures_linter_unused_disabled_directives@test.js.snap @@ -2,9 +2,10 @@ source: crates/oxc_language_server/src/linter/tester.rs --- ########## -file: fixtures/linter/unused_disabled_directives/test.js +Linted file: fixtures/linter/unused_disabled_directives/test.js ---------- ########## Diagnostic Reports +File URI: file:///fixtures/linter/unused_disabled_directives/test.js code: "eslint(no-console)" code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-console.html" @@ -60,6 +61,7 @@ related_information[0].location.range: Range { start: Position { line: 8, charac severity: Some(Error) source: Some("oxc") tags: None + ########### Code Actions/Commands CodeAction: Title: Disable no-console for this line diff --git a/crates/oxc_language_server/src/linter/tester.rs b/crates/oxc_language_server/src/linter/tester.rs index 72780575ffe96..4908ec2b33e96 100644 --- a/crates/oxc_language_server/src/linter/tester.rs +++ b/crates/oxc_language_server/src/linter/tester.rs @@ -8,7 +8,7 @@ use tower_lsp_server::ls_types::{ use crate::{ ToolRestartChanges, linter::{ServerLinterBuilder, server_linter::ServerLinter}, - tool::Tool, + tool::{DiagnosticResult, Tool}, }; /// Given a file path relative to the crate root directory, return the absolute path of the file. @@ -22,6 +22,34 @@ pub fn get_file_uri(relative_file_path: &str) -> Uri { .expect("failed to convert file path to URL") } +fn get_snapshot_from_diagnostic_result(diagnostic_result: &DiagnosticResult) -> String { + if diagnostic_result.is_empty() { + return "No diagnostics / Files are ignored".to_string(); + } + + diagnostic_result + .iter() + .map(|(uri, diagnostics)| { + let mut result = String::new(); + let _ = writeln!(result, "File URI: {}", get_snapshot_safe_uri(uri)); + for diagnostic in diagnostics { + let _ = writeln!(result, "{}", get_snapshot_from_diagnostic(diagnostic)); + } + result + }) + .collect::>() + .join("\n") +} + +fn get_snapshot_safe_uri(uri: &Uri) -> String { + let mut safe_uri = uri.to_string(); + let start = safe_uri.find("file://").expect("file:// protocol not found in URI"); + let end = safe_uri.find("oxc_language_server").expect("oxc_language_server not found in URI"); + safe_uri + .replace_range(start + "file://".len()..end + "oxc_language_server".len(), ""); + safe_uri +} + fn get_snapshot_from_diagnostic(diagnostic: &Diagnostic) -> String { let code = match &diagnostic.code { Some(NumberOrString::Number(code)) => code.to_string(), @@ -45,16 +73,7 @@ fn get_snapshot_from_diagnostic(diagnostic: &Diagnostic) -> String { .unwrap(); // replace everything between `file://` and `oxc_language_server` with ``, to avoid // the absolute path causing snapshot test failures in different environments - let mut location = info.location.uri.to_string(); - let start = - location.find("file://").expect("file:// protocol not found in URI"); - let end = location - .find("oxc_language_server") - .expect("oxc_language_server not found in URI"); - location.replace_range( - start + "file://".len()..end + "oxc_language_server".len(), - "", - ); + let location = get_snapshot_safe_uri(&info.location.uri); write!(result, "\nrelated_information[{i}].location.uri: {location:?}",) .unwrap(); @@ -128,17 +147,17 @@ fn get_snapshot_from_code_action_or_command(action_or_command: &CodeActionOrComm } } -fn get_snapshot_from_report(report: FileResult) -> String { - let Some(diagnostics) = report.diagnostic else { - return "File is ignored".to_string(); - }; +fn get_snapshot_from_report(report: &FileResult) -> String { + if report.diagnostic.is_empty() { + return "No diagnostics / Files are ignored".to_string(); + } format!( "########## Diagnostic Reports {} ########### Code Actions/Commands {}", - diagnostics.iter().map(get_snapshot_from_diagnostic).collect::>().join("\n"), + get_snapshot_from_diagnostic_result(&report.diagnostic), report .actions .iter() @@ -155,7 +174,7 @@ pub struct Tester<'t> { } struct FileResult { - diagnostic: Option>, + diagnostic: DiagnosticResult, actions: Vec, } @@ -200,9 +219,9 @@ impl Tester<'_> { let _ = write!( snapshot_result, - "########## \nfile: {}/{relative_file_path}\n----------\n{}\n", + "########## \nLinted file: {}/{relative_file_path}\n----------\n{}\n", self.relative_root_dir, - get_snapshot_from_report(reports) + get_snapshot_from_report(&reports) ); } diff --git a/crates/oxc_language_server/src/tests.rs b/crates/oxc_language_server/src/tests.rs index 4d79fe6b2ff9a..bd68067da4bb1 100644 --- a/crates/oxc_language_server/src/tests.rs +++ b/crates/oxc_language_server/src/tests.rs @@ -8,7 +8,7 @@ use tower_lsp_server::{ ls_types::*, }; -use crate::{Tool, ToolBuilder, ToolRestartChanges, backend::Backend}; +use crate::{Tool, ToolBuilder, ToolRestartChanges, backend::Backend, tool::DiagnosticResult}; pub struct FakeToolBuilder; @@ -124,29 +124,28 @@ impl Tool for FakeTool { vec![] } - fn run_diagnostic(&self, uri: &Uri, content: Option<&str>) -> Option> { + fn run_diagnostic(&self, uri: &Uri, content: Option<&str>) -> DiagnosticResult { if uri.as_str().ends_with("diagnostics.config") { - return Some(vec![Diagnostic { - message: format!( - "Fake diagnostic for content: {}", - content.unwrap_or("") - ), - ..Default::default() - }]); + return vec![( + uri.clone(), + vec![Diagnostic { + message: format!( + "Fake diagnostic for content: {}", + content.unwrap_or("") + ), + ..Default::default() + }], + )]; } - None + vec![] } - fn run_diagnostic_on_change( - &self, - uri: &Uri, - content: Option<&str>, - ) -> Option> { + fn run_diagnostic_on_change(&self, uri: &Uri, content: Option<&str>) -> DiagnosticResult { // For this fake tool, we use the same logic as run_diagnostic self.run_diagnostic(uri, content) } - fn run_diagnostic_on_save(&self, uri: &Uri, content: Option<&str>) -> Option> { + fn run_diagnostic_on_save(&self, uri: &Uri, content: Option<&str>) -> DiagnosticResult { // For this fake tool, we use the same logic as run_diagnostic self.run_diagnostic(uri, content) } diff --git a/crates/oxc_language_server/src/tool.rs b/crates/oxc_language_server/src/tool.rs index 51c9b893fee6e..32fe722af5def 100644 --- a/crates/oxc_language_server/src/tool.rs +++ b/crates/oxc_language_server/src/tool.rs @@ -14,6 +14,8 @@ pub trait ToolBuilder: Send + Sync { fn build_boxed(&self, root_uri: &Uri, options: serde_json::Value) -> Box; } +pub type DiagnosticResult = Vec<(Uri, Vec)>; + pub trait Tool: Send + Sync { /// Get the name of the tool. fn name(&self) -> &'static str; @@ -84,34 +86,25 @@ pub trait Tool: Send + Sync { /// Run diagnostics on the content of the given URI. /// If `content` is `None`, the tool should read the content from the file system. - /// Returns a vector of `Diagnostic` representing the diagnostic results. - /// Not all tools will implement diagnostics, so the default implementation returns `None`. - fn run_diagnostic(&self, _uri: &Uri, _content: Option<&str>) -> Option> { - None + /// Not all tools will implement diagnostics, so the default implementation returns an empty vector. + fn run_diagnostic(&self, _uri: &Uri, _content: Option<&str>) -> DiagnosticResult { + Vec::new() } /// Run diagnostics on save for the content of the given URI. /// If `content` is `None`, the tool should read the content from the file system. - /// Returns a vector of `Diagnostic` representing the diagnostic results. - /// Not all tools will implement diagnostics on save, so the default implementation returns `None`. - fn run_diagnostic_on_save( - &self, - _uri: &Uri, - _content: Option<&str>, - ) -> Option> { - None + /// Returns a vector of a Uri-Diagnostic tuple representing the diagnostic results. + /// Not all tools will implement diagnostics on save, so the default implementation returns an empty vector. + fn run_diagnostic_on_save(&self, _uri: &Uri, _content: Option<&str>) -> DiagnosticResult { + Vec::new() } /// Run diagnostics on change for the content of the given URI. /// If `content` is `None`, the tool should read the content from the file system. - /// Returns a vector of `Diagnostic` representing the diagnostic results. - /// Not all tools will implement diagnostics on change, so the default implementation returns `None`. - fn run_diagnostic_on_change( - &self, - _uri: &Uri, - _content: Option<&str>, - ) -> Option> { - None + /// Returns a vector of a Uri-Diagnostic tuple representing the diagnostic results. + /// Not all tools will implement diagnostics on change, so the default implementation returns an empty vector. + fn run_diagnostic_on_change(&self, _uri: &Uri, _content: Option<&str>) -> DiagnosticResult { + Vec::new() } /// Remove internal cache for the given URI, if any. diff --git a/crates/oxc_language_server/src/worker.rs b/crates/oxc_language_server/src/worker.rs index cfeb2caf23b5e..b6bd971ddb063 100644 --- a/crates/oxc_language_server/src/worker.rs +++ b/crates/oxc_language_server/src/worker.rs @@ -1,4 +1,5 @@ use log::debug; +use rustc_hash::FxHashMap; use serde_json::json; use tokio::sync::{Mutex, RwLock}; use tower_lsp_server::{ @@ -13,7 +14,7 @@ use tower_lsp_server::{ use crate::{ ToolRestartChanges, file_system::LSPFileSystem, - tool::{Tool, ToolBuilder}, + tool::{DiagnosticResult, Tool, ToolBuilder}, }; /// A worker that manages the individual tools for a specific workspace @@ -100,21 +101,43 @@ impl WorkspaceWorker { }); } - /// Run different tools to collect diagnostics. - pub async fn run_diagnostic( + /// Common aggregator for tool-provided diagnostics. + async fn collect_diagnostics_with( &self, uri: &Uri, content: Option<&str>, - ) -> Option> { - let mut diagnostics = Vec::new(); - let mut found = false; + run: F, + ) -> Vec<(Uri, Vec)> + where + F: Fn(&Box, &Uri, Option<&str>) -> DiagnosticResult, + { + let mut aggregated: FxHashMap> = FxHashMap::default(); + for tool in self.tools.read().await.iter() { - if let Some(tool_diagnostics) = tool.run_diagnostic(uri, content) { - diagnostics.extend(tool_diagnostics); - found = true; + let tool_diagnostics = run(tool, uri, content); + + for (entry_uri, mut diags) in tool_diagnostics { + aggregated.entry(entry_uri).or_default().append(&mut diags); } } - if found { Some(diagnostics) } else { None } + + let mut result = Vec::with_capacity(aggregated.len()); + for (uri, diags) in aggregated { + result.push((uri, diags)); + } + result + } + + /// Run different tools to collect diagnostics. + pub async fn run_diagnostic( + &self, + uri: &Uri, + content: Option<&str>, + ) -> Vec<(Uri, Vec)> { + self.collect_diagnostics_with(uri, content, |tool, uri, content| { + tool.run_diagnostic(uri, content) + }) + .await } /// Run different tools to collect diagnostics on change. @@ -122,16 +145,11 @@ impl WorkspaceWorker { &self, uri: &Uri, content: Option<&str>, - ) -> Option> { - let mut diagnostics = Vec::new(); - let mut found = false; - for tool in self.tools.read().await.iter() { - if let Some(tool_diagnostics) = tool.run_diagnostic_on_change(uri, content) { - diagnostics.extend(tool_diagnostics); - found = true; - } - } - if found { Some(diagnostics) } else { None } + ) -> Vec<(Uri, Vec)> { + self.collect_diagnostics_with(uri, content, |tool, uri, content| { + tool.run_diagnostic_on_change(uri, content) + }) + .await } /// Run different tools to collect diagnostics on save. @@ -139,16 +157,11 @@ impl WorkspaceWorker { &self, uri: &Uri, content: Option<&str>, - ) -> Option> { - let mut diagnostics = Vec::new(); - let mut found = false; - for tool in self.tools.read().await.iter() { - if let Some(tool_diagnostics) = tool.run_diagnostic_on_save(uri, content) { - diagnostics.extend(tool_diagnostics); - found = true; - } - } - if found { Some(diagnostics) } else { None } + ) -> Vec<(Uri, Vec)> { + self.collect_diagnostics_with(uri, content, |tool, uri, content| { + tool.run_diagnostic_on_save(uri, content) + }) + .await } /// Format a file with the current formatter @@ -312,13 +325,12 @@ impl WorkspaceWorker { *tool = replaced_tool; for uri in file_system.keys() { - if let Some(reports) = - tool.run_diagnostic(&uri, file_system.get(&uri).as_deref()) - { + let mut reports = tool.run_diagnostic(&uri, file_system.get(&uri).as_deref()); + if !reports.is_empty() { if let Some(existing_diagnostics) = &mut diagnostics { - existing_diagnostics.push((uri, reports)); + existing_diagnostics.append(&mut reports); } else { - diagnostics = Some(vec![(uri, reports)]); + diagnostics = Some(reports); } } } @@ -602,70 +614,62 @@ mod tests { async fn test_run_diagnostic() { let worker = WorkspaceWorker::new(Uri::from_str("file:///root/").unwrap()); let tools: Vec> = vec![Box::new(FakeToolBuilder)]; + let uri = Uri::from_str("file:///root/diagnostics.config").unwrap(); + worker.start_worker(serde_json::Value::Null, &tools).await; - let diagnostics_no_content = worker - .run_diagnostic(&Uri::from_str("file:///root/diagnostics.config").unwrap(), None) - .await; + let diagnostics_no_content = worker.run_diagnostic(&uri, None).await; - assert!(diagnostics_no_content.is_some()); - assert_eq!(diagnostics_no_content.as_ref().unwrap().len(), 1); + assert_eq!(diagnostics_no_content.len(), 1); + assert_eq!(diagnostics_no_content[0].0, uri); + assert_eq!(diagnostics_no_content[0].1.len(), 1); assert_eq!( - diagnostics_no_content.unwrap()[0].message, + diagnostics_no_content[0].1[0].message, "Fake diagnostic for content: " ); - let diagnostics_with_content = worker - .run_diagnostic( - &Uri::from_str("file:///root/diagnostics.config").unwrap(), - Some("helloworld"), - ) - .await; + let diagnostics_with_content = worker.run_diagnostic(&uri, Some("helloworld")).await; - assert!(diagnostics_with_content.is_some()); - assert_eq!(diagnostics_with_content.as_ref().unwrap().len(), 1); + assert_eq!(diagnostics_with_content.len(), 1); + assert_eq!(diagnostics_with_content[0].0, uri); + assert_eq!(diagnostics_with_content[0].1.len(), 1); assert_eq!( - diagnostics_with_content.unwrap()[0].message, + diagnostics_with_content[0].1[0].message, "Fake diagnostic for content: helloworld" ); let no_diagnostics = worker.run_diagnostic(&Uri::from_str("file:///root/unknown.file").unwrap(), None).await; - assert!(no_diagnostics.is_none()); + assert!(no_diagnostics.is_empty()); } #[tokio::test] async fn test_run_diagnostic_on_change() { let worker = WorkspaceWorker::new(Uri::from_str("file:///root/").unwrap()); let tools: Vec> = vec![Box::new(FakeToolBuilder)]; + let uri = Uri::from_str("file:///root/diagnostics.config").unwrap(); + worker.start_worker(serde_json::Value::Null, &tools).await; - let diagnostics_no_content = worker - .run_diagnostic_on_change( - &Uri::from_str("file:///root/diagnostics.config").unwrap(), - None, - ) - .await; + let diagnostics_no_content = worker.run_diagnostic_on_change(&uri, None).await; - assert!(diagnostics_no_content.is_some()); - assert_eq!(diagnostics_no_content.as_ref().unwrap().len(), 1); + assert_eq!(diagnostics_no_content.len(), 1); + assert_eq!(diagnostics_no_content[0].0, uri); + assert_eq!(diagnostics_no_content[0].1.len(), 1); assert_eq!( - diagnostics_no_content.unwrap()[0].message, + diagnostics_no_content[0].1[0].message, "Fake diagnostic for content: " ); - let diagnostics_with_content = worker - .run_diagnostic_on_change( - &Uri::from_str("file:///root/diagnostics.config").unwrap(), - Some("helloworld"), - ) - .await; + let diagnostics_with_content = + worker.run_diagnostic_on_change(&uri, Some("helloworld")).await; - assert!(diagnostics_with_content.is_some()); - assert_eq!(diagnostics_with_content.as_ref().unwrap().len(), 1); + assert_eq!(diagnostics_with_content.len(), 1); + assert_eq!(diagnostics_with_content[0].0, uri); + assert_eq!(diagnostics_with_content[0].1.len(), 1); assert_eq!( - diagnostics_with_content.unwrap()[0].message, + diagnostics_with_content[0].1[0].message, "Fake diagnostic for content: helloworld" ); @@ -673,40 +677,34 @@ mod tests { .run_diagnostic_on_change(&Uri::from_str("file:///root/unknown.file").unwrap(), None) .await; - assert!(no_diagnostics.is_none()); + assert!(no_diagnostics.is_empty()); } #[tokio::test] async fn test_run_diagnostic_on_save() { let worker = WorkspaceWorker::new(Uri::from_str("file:///root/").unwrap()); let tools: Vec> = vec![Box::new(FakeToolBuilder)]; + let uri = Uri::from_str("file:///root/diagnostics.config").unwrap(); worker.start_worker(serde_json::Value::Null, &tools).await; - let diagnostics_no_content = worker - .run_diagnostic_on_save( - &Uri::from_str("file:///root/diagnostics.config").unwrap(), - None, - ) - .await; + let diagnostics_no_content = worker.run_diagnostic_on_save(&uri, None).await; - assert!(diagnostics_no_content.is_some()); - assert_eq!(diagnostics_no_content.as_ref().unwrap().len(), 1); + assert_eq!(diagnostics_no_content.len(), 1); + assert_eq!(diagnostics_no_content[0].0, uri); + assert_eq!(diagnostics_no_content[0].1.len(), 1); assert_eq!( - diagnostics_no_content.unwrap()[0].message, + diagnostics_no_content[0].1[0].message, "Fake diagnostic for content: " ); - let diagnostics_with_content = worker - .run_diagnostic_on_save( - &Uri::from_str("file:///root/diagnostics.config").unwrap(), - Some("helloworld"), - ) - .await; + let diagnostics_with_content = + worker.run_diagnostic_on_save(&uri, Some("helloworld")).await; - assert!(diagnostics_with_content.is_some()); - assert_eq!(diagnostics_with_content.as_ref().unwrap().len(), 1); + assert_eq!(diagnostics_with_content.len(), 1); + assert_eq!(diagnostics_with_content[0].0, uri); + assert_eq!(diagnostics_with_content[0].1.len(), 1); assert_eq!( - diagnostics_with_content.unwrap()[0].message, + diagnostics_with_content[0].1[0].message, "Fake diagnostic for content: helloworld" ); @@ -714,6 +712,6 @@ mod tests { .run_diagnostic_on_save(&Uri::from_str("file:///root/unknown.file").unwrap(), None) .await; - assert!(no_diagnostics.is_none()); + assert!(no_diagnostics.is_empty()); } }