diff --git a/crates/oxc_language_server/src/linter/error_with_position.rs b/crates/oxc_language_server/src/linter/error_with_position.rs index a55cae382a55c..73114a46a4a8e 100644 --- a/crates/oxc_language_server/src/linter/error_with_position.rs +++ b/crates/oxc_language_server/src/linter/error_with_position.rs @@ -2,7 +2,8 @@ use std::{borrow::Cow, str::FromStr}; use oxc_linter::{FixWithPosition, MessageWithPosition, PossibleFixesWithPosition}; use tower_lsp_server::lsp_types::{ - self, CodeDescription, DiagnosticRelatedInformation, NumberOrString, Position, Range, Uri, + self, CodeDescription, DiagnosticRelatedInformation, DiagnosticSeverity, NumberOrString, + Position, Range, Uri, }; use oxc_diagnostics::Severity; @@ -146,3 +147,44 @@ pub fn message_with_position_to_lsp_diagnostic_report( }, } } + +pub fn generate_inverted_diagnostics( + diagnostics: &[DiagnosticReport], + uri: &Uri, +) -> Vec { + let mut inverted_diagnostics = vec![]; + for d in diagnostics { + let Some(related_info) = &d.diagnostic.related_information else { + continue; + }; + let related_information = Some(vec![DiagnosticRelatedInformation { + location: lsp_types::Location { uri: uri.clone(), range: d.diagnostic.range }, + message: "original diagnostic".to_string(), + }]); + for r in related_info { + if r.location.range == d.diagnostic.range { + continue; + } + // If there is no message content for this span, then don't produce an additional diagnostic + // which also has no content. This prevents issues where editors expect diagnostics to have messages. + if r.message.is_empty() { + continue; + } + inverted_diagnostics.push(DiagnosticReport { + diagnostic: lsp_types::Diagnostic { + range: r.location.range, + severity: Some(DiagnosticSeverity::HINT), + code: None, + message: r.message.clone(), + source: d.diagnostic.source.clone(), + code_description: None, + related_information: related_information.clone(), + tags: None, + data: None, + }, + fixed_content: PossibleFixContent::None, + }); + } + } + inverted_diagnostics +} diff --git a/crates/oxc_language_server/src/linter/isolated_lint_handler.rs b/crates/oxc_language_server/src/linter/isolated_lint_handler.rs index 06881f8e20dd0..6d491b535f861 100644 --- a/crates/oxc_language_server/src/linter/isolated_lint_handler.rs +++ b/crates/oxc_language_server/src/linter/isolated_lint_handler.rs @@ -5,10 +5,7 @@ use std::{ use log::debug; use rustc_hash::FxHashSet; -use tower_lsp_server::{ - UriExt, - lsp_types::{self, DiagnosticRelatedInformation, DiagnosticSeverity, Uri}, -}; +use tower_lsp_server::{UriExt, lsp_types::Uri}; use oxc_allocator::Allocator; use oxc_linter::{ @@ -18,7 +15,7 @@ use oxc_linter::{ use oxc_linter::{RuntimeFileSystem, read_to_string}; use super::error_with_position::{ - DiagnosticReport, PossibleFixContent, message_with_position_to_lsp_diagnostic_report, + DiagnosticReport, generate_inverted_diagnostics, message_with_position_to_lsp_diagnostic_report, }; /// smaller subset of LintServiceOptions, which is used by IsolatedLintHandler @@ -102,41 +99,7 @@ impl IsolatedLintHandler { let mut diagnostics: Vec = errors.iter().map(|e| message_with_position_to_lsp_diagnostic_report(e, uri)).collect(); - // a diagnostics connected from related_info to original diagnostic - let mut inverted_diagnostics = vec![]; - for d in &diagnostics { - let Some(related_info) = &d.diagnostic.related_information else { - continue; - }; - let related_information = Some(vec![DiagnosticRelatedInformation { - location: lsp_types::Location { uri: uri.clone(), range: d.diagnostic.range }, - message: "original diagnostic".to_string(), - }]); - for r in related_info { - if r.location.range == d.diagnostic.range { - continue; - } - // If there is no message content for this span, then don't produce an additional diagnostic - // which also has no content. This prevents issues where editors expect diagnostics to have messages. - if r.message.is_empty() { - continue; - } - inverted_diagnostics.push(DiagnosticReport { - diagnostic: lsp_types::Diagnostic { - range: r.location.range, - severity: Some(DiagnosticSeverity::HINT), - code: None, - message: r.message.clone(), - source: d.diagnostic.source.clone(), - code_description: None, - related_information: related_information.clone(), - tags: None, - data: None, - }, - fixed_content: PossibleFixContent::None, - }); - } - } + let mut inverted_diagnostics = generate_inverted_diagnostics(&diagnostics, uri); diagnostics.append(&mut inverted_diagnostics); Some(diagnostics) } diff --git a/crates/oxc_language_server/src/linter/tsgo_linter.rs b/crates/oxc_language_server/src/linter/tsgo_linter.rs index 72a9b6ef69f92..45e59edb6253e 100644 --- a/crates/oxc_language_server/src/linter/tsgo_linter.rs +++ b/crates/oxc_language_server/src/linter/tsgo_linter.rs @@ -11,7 +11,7 @@ use rustc_hash::FxHashSet; use tower_lsp_server::{UriExt, lsp_types::Uri}; use crate::linter::error_with_position::{ - DiagnosticReport, message_with_position_to_lsp_diagnostic_report, + DiagnosticReport, generate_inverted_diagnostics, message_with_position_to_lsp_diagnostic_report, }; pub struct TsgoLinter { @@ -35,12 +35,15 @@ impl TsgoLinter { let messages = self.state.lint_source(&Arc::from(path.as_os_str()), source_text).ok()?; - Some( - messages - .iter() - .map(|e| message_with_position_to_lsp_diagnostic_report(e, uri)) - .collect(), - ) + let mut diagnostics: Vec = messages + .iter() + .map(|e| message_with_position_to_lsp_diagnostic_report(e, uri)) + .collect(); + + let mut inverted_diagnostics = generate_inverted_diagnostics(&diagnostics, uri); + diagnostics.append(&mut inverted_diagnostics); + + Some(diagnostics) } fn should_lint_path(path: &Path) -> bool {