From 042a3f31f7ea1e6f704452d09e0fe70053868289 Mon Sep 17 00:00:00 2001 From: Sysix <3897725+Sysix@users.noreply.github.com> Date: Thu, 29 May 2025 13:59:22 +0000 Subject: [PATCH] refactor(linter): use `PossibleFixes` instead of `Option` (#11284) related #9561 --- .../oxc_language_server/src/code_actions.rs | 45 ++++++++--- .../src/linter/error_with_position.rs | 45 +++++++---- .../src/linter/isolated_lint_handler.rs | 4 +- .../fixtures_linter_astro@debugger.astro.snap | 8 +- ...tures_linter_cross_module@debugger.ts.snap | 2 +- .../fixtures_linter_issue_9958@issue.ts.snap | 2 +- ...xtures_linter_regexp_feature@index.ts.snap | 2 +- ...ixtures_linter_svelte@debugger.svelte.snap | 2 +- .../fixtures_linter_vue@debugger.vue.snap | 4 +- crates/oxc_language_server/src/worker.rs | 20 +++-- crates/oxc_linter/src/fixer/fix.rs | 56 ++++++++++++++ crates/oxc_linter/src/fixer/mod.rs | 29 ++++--- crates/oxc_linter/src/lib.rs | 2 +- crates/oxc_linter/src/service/runtime.rs | 75 +++++++++++++------ 14 files changed, 218 insertions(+), 78 deletions(-) diff --git a/crates/oxc_language_server/src/code_actions.rs b/crates/oxc_language_server/src/code_actions.rs index 7b5ed164515df..eb8975b37d5f9 100644 --- a/crates/oxc_language_server/src/code_actions.rs +++ b/crates/oxc_language_server/src/code_actions.rs @@ -3,7 +3,7 @@ use tower_lsp_server::lsp_types::{ WorkspaceEdit, }; -use crate::linter::error_with_position::DiagnosticReport; +use crate::linter::error_with_position::{DiagnosticReport, FixedContent, PossibleFixContent}; pub const CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC: CodeActionKind = CodeActionKind::new("source.fixAll.oxc"); @@ -20,18 +20,18 @@ fn get_rule_name(diagnostic: &Diagnostic) -> Option { None } -pub fn apply_fix_code_action(report: &DiagnosticReport, uri: &Uri) -> Option { - let Some(fixed_content) = &report.fixed_content else { - return None; - }; - +fn fix_content_to_code_action( + fixed_content: &FixedContent, + uri: &Uri, + alternative_message: &str, +) -> CodeAction { // 1) Use `fixed_content.message` if it exists // 2) Try to parse the report diagnostic message // 3) Fallback to "Fix this problem" let title = match fixed_content.message.clone() { Some(msg) => msg, None => { - if let Some(code) = report.diagnostic.message.split(':').next() { + if let Some(code) = alternative_message.split(':').next() { format!("Fix this {code} problem") } else { "Fix this problem".to_string() @@ -39,7 +39,7 @@ pub fn apply_fix_code_action(report: &DiagnosticReport, uri: &Uri) -> Option Option Option> { + match &report.fixed_content { + PossibleFixContent::None => None, + PossibleFixContent::Single(fixed_content) => { + Some(vec![fix_content_to_code_action(fixed_content, uri, &report.diagnostic.message)]) + } + PossibleFixContent::Multiple(fixed_contents) => Some( + fixed_contents + .iter() + .map(|fixed_content| { + fix_content_to_code_action(fixed_content, uri, &report.diagnostic.message) + }) + .collect(), + ), + } } pub fn apply_all_fix_code_action<'a>( @@ -65,7 +82,15 @@ pub fn apply_all_fix_code_action<'a>( let mut quick_fixes: Vec = vec![]; for report in reports { - if let Some(fixed_content) = &report.fixed_content { + let fix = match &report.fixed_content { + PossibleFixContent::None => None, + PossibleFixContent::Single(fixed_content) => Some(fixed_content), + // For multiple fixes, we take the first one as a representative fix. + // Applying all possible fixes at once is not possible in this context. + PossibleFixContent::Multiple(multi) => multi.first(), + }; + + if let Some(fixed_content) = &fix { // when source.fixAll.oxc we collect all changes at ones // and return them as one workspace edit. // it is possible that one fix will change the range for the next fix 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 e55c120973d62..e7fc1c6927ff9 100644 --- a/crates/oxc_language_server/src/linter/error_with_position.rs +++ b/crates/oxc_language_server/src/linter/error_with_position.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, str::FromStr}; -use oxc_linter::MessageWithPosition; +use oxc_linter::{FixWithPosition, MessageWithPosition, PossibleFixesWithPosition}; use tower_lsp_server::lsp_types::{ self, CodeDescription, DiagnosticRelatedInformation, NumberOrString, Position, Range, Uri, }; @@ -14,7 +14,7 @@ const LSP_MAX_INT: u32 = 2u32.pow(31) - 1; #[derive(Debug, Clone)] pub struct DiagnosticReport { pub diagnostic: lsp_types::Diagnostic, - pub fixed_content: Option, + pub fixed_content: PossibleFixContent, } #[derive(Debug, Clone)] @@ -24,6 +24,13 @@ pub struct FixedContent { pub range: Range, } +#[derive(Debug, Clone)] +pub enum PossibleFixContent { + None, + Single(FixedContent), + Multiple(Vec), +} + fn cmp_range(first: &Range, other: &Range) -> std::cmp::Ordering { match first.start.cmp(&other.start) { std::cmp::Ordering::Equal => first.end.cmp(&other.end), @@ -101,25 +108,31 @@ fn message_with_position_to_lsp_diagnostic( } } +fn fix_with_position_to_fix_content(fix: &FixWithPosition<'_>) -> FixedContent { + FixedContent { + message: fix.span.message().map(std::string::ToString::to_string), + code: fix.content.to_string(), + range: Range { + start: Position { line: fix.span.start().line, character: fix.span.start().character }, + end: Position { line: fix.span.end().line, character: fix.span.end().character }, + }, + } +} + pub fn message_with_position_to_lsp_diagnostic_report( message: &MessageWithPosition<'_>, uri: &Uri, ) -> DiagnosticReport { DiagnosticReport { diagnostic: message_with_position_to_lsp_diagnostic(message, uri), - fixed_content: message.fix.as_ref().map(|infos| FixedContent { - message: infos.span.message().map(std::string::ToString::to_string), - code: infos.content.to_string(), - range: Range { - start: Position { - line: infos.span.start().line, - character: infos.span.start().character, - }, - end: Position { - line: infos.span.end().line, - character: infos.span.end().character, - }, - }, - }), + fixed_content: match &message.fixes { + PossibleFixesWithPosition::None => PossibleFixContent::None, + PossibleFixesWithPosition::Single(fix) => { + PossibleFixContent::Single(fix_with_position_to_fix_content(fix)) + } + PossibleFixesWithPosition::Multiple(fixes) => PossibleFixContent::Multiple( + fixes.iter().map(fix_with_position_to_fix_content).collect(), + ), + }, } } 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 0bc2ebeb27a69..d4d68185d03be 100644 --- a/crates/oxc_language_server/src/linter/isolated_lint_handler.rs +++ b/crates/oxc_language_server/src/linter/isolated_lint_handler.rs @@ -18,7 +18,7 @@ use oxc_linter::{ }; use super::error_with_position::{ - DiagnosticReport, message_with_position_to_lsp_diagnostic_report, + DiagnosticReport, PossibleFixContent, message_with_position_to_lsp_diagnostic_report, }; /// smaller subset of LintServiceOptions, which is used by IsolatedLintHandler @@ -109,7 +109,7 @@ impl IsolatedLintHandler { tags: None, data: None, }, - fixed_content: None, + fixed_content: PossibleFixContent::None, }); } } diff --git a/crates/oxc_language_server/src/snapshots/fixtures_linter_astro@debugger.astro.snap b/crates/oxc_language_server/src/snapshots/fixtures_linter_astro@debugger.astro.snap index 2f6cfb238244d..35121d21b506e 100644 --- a/crates/oxc_language_server/src/snapshots/fixtures_linter_astro@debugger.astro.snap +++ b/crates/oxc_language_server/src/snapshots/fixtures_linter_astro@debugger.astro.snap @@ -12,7 +12,7 @@ related_information[0].location.range: Range { start: Position { line: 1, charac severity: Some(Warning) source: Some("oxc") tags: None -fixed: Some(FixedContent { message: Some("Remove the debugger statement"), code: "", range: Range { start: Position { line: 1, character: 0 }, end: Position { line: 1, character: 8 } } }) +fixed: Single(FixedContent { message: Some("Remove the debugger statement"), code: "", range: Range { start: Position { line: 1, character: 0 }, end: Position { line: 1, character: 8 } } }) code: "eslint(no-debugger)" @@ -25,7 +25,7 @@ related_information[0].location.range: Range { start: Position { line: 10, chara severity: Some(Warning) source: Some("oxc") tags: None -fixed: Some(FixedContent { message: Some("Remove the debugger statement"), code: "", range: Range { start: Position { line: 10, character: 2 }, end: Position { line: 10, character: 10 } } }) +fixed: Single(FixedContent { message: Some("Remove the debugger statement"), code: "", range: Range { start: Position { line: 10, character: 2 }, end: Position { line: 10, character: 10 } } }) code: "eslint(no-debugger)" @@ -38,7 +38,7 @@ related_information[0].location.range: Range { start: Position { line: 14, chara severity: Some(Warning) source: Some("oxc") tags: None -fixed: Some(FixedContent { message: Some("Remove the debugger statement"), code: "", range: Range { start: Position { line: 14, character: 2 }, end: Position { line: 14, character: 10 } } }) +fixed: Single(FixedContent { message: Some("Remove the debugger statement"), code: "", range: Range { start: Position { line: 14, character: 2 }, end: Position { line: 14, character: 10 } } }) code: "eslint(no-debugger)" @@ -51,4 +51,4 @@ related_information[0].location.range: Range { start: Position { line: 18, chara severity: Some(Warning) source: Some("oxc") tags: None -fixed: Some(FixedContent { message: Some("Remove the debugger statement"), code: "", range: Range { start: Position { line: 18, character: 2 }, end: Position { line: 18, character: 10 } } }) +fixed: Single(FixedContent { message: Some("Remove the debugger statement"), code: "", range: Range { start: Position { line: 18, character: 2 }, end: Position { line: 18, character: 10 } } }) diff --git a/crates/oxc_language_server/src/snapshots/fixtures_linter_cross_module@debugger.ts.snap b/crates/oxc_language_server/src/snapshots/fixtures_linter_cross_module@debugger.ts.snap index 6080785ae7599..45a5cbf047e57 100644 --- a/crates/oxc_language_server/src/snapshots/fixtures_linter_cross_module@debugger.ts.snap +++ b/crates/oxc_language_server/src/snapshots/fixtures_linter_cross_module@debugger.ts.snap @@ -12,4 +12,4 @@ related_information[0].location.range: Range { start: Position { line: 1, charac severity: Some(Warning) source: Some("oxc") tags: None -fixed: Some(FixedContent { message: Some("Remove the debugger statement"), code: "", range: Range { start: Position { line: 1, character: 0 }, end: Position { line: 1, character: 9 } } }) +fixed: Single(FixedContent { message: Some("Remove the debugger statement"), code: "", range: Range { start: Position { line: 1, character: 0 }, end: Position { line: 1, character: 9 } } }) diff --git a/crates/oxc_language_server/src/snapshots/fixtures_linter_issue_9958@issue.ts.snap b/crates/oxc_language_server/src/snapshots/fixtures_linter_issue_9958@issue.ts.snap index 6a62806cf7b61..aa856ef324f1a 100644 --- a/crates/oxc_language_server/src/snapshots/fixtures_linter_issue_9958@issue.ts.snap +++ b/crates/oxc_language_server/src/snapshots/fixtures_linter_issue_9958@issue.ts.snap @@ -28,7 +28,7 @@ related_information[1].location.range: Range { start: Position { line: 11, chara severity: Some(Error) source: Some("oxc") tags: None -fixed: Some(FixedContent { message: Some("Delete this code."), code: "", range: Range { start: Position { line: 11, character: 21 }, end: Position { line: 11, character: 22 } } }) +fixed: Single(FixedContent { message: Some("Delete this code."), code: "", range: Range { start: Position { line: 11, character: 21 }, end: Position { line: 11, character: 22 } } }) code: "None" diff --git a/crates/oxc_language_server/src/snapshots/fixtures_linter_regexp_feature@index.ts.snap b/crates/oxc_language_server/src/snapshots/fixtures_linter_regexp_feature@index.ts.snap index d46ad5cba0688..7093e9cf0bc34 100644 --- a/crates/oxc_language_server/src/snapshots/fixtures_linter_regexp_feature@index.ts.snap +++ b/crates/oxc_language_server/src/snapshots/fixtures_linter_regexp_feature@index.ts.snap @@ -25,4 +25,4 @@ related_information[0].location.range: Range { start: Position { line: 0, charac severity: Some(Error) source: Some("oxc") tags: None -fixed: Some(FixedContent { message: Some("Replace `\\/` with `/`."), code: "/", range: Range { start: Position { line: 0, character: 16 }, end: Position { line: 0, character: 18 } } }) +fixed: Single(FixedContent { message: Some("Replace `\\/` with `/`."), code: "/", range: Range { start: Position { line: 0, character: 16 }, end: Position { line: 0, character: 18 } } }) diff --git a/crates/oxc_language_server/src/snapshots/fixtures_linter_svelte@debugger.svelte.snap b/crates/oxc_language_server/src/snapshots/fixtures_linter_svelte@debugger.svelte.snap index b9806ef90e7ff..e23319706650e 100644 --- a/crates/oxc_language_server/src/snapshots/fixtures_linter_svelte@debugger.svelte.snap +++ b/crates/oxc_language_server/src/snapshots/fixtures_linter_svelte@debugger.svelte.snap @@ -12,4 +12,4 @@ related_information[0].location.range: Range { start: Position { line: 1, charac severity: Some(Warning) source: Some("oxc") tags: None -fixed: Some(FixedContent { message: Some("Remove the debugger statement"), code: "", range: Range { start: Position { line: 1, character: 1 }, end: Position { line: 1, character: 10 } } }) +fixed: Single(FixedContent { message: Some("Remove the debugger statement"), code: "", range: Range { start: Position { line: 1, character: 1 }, end: Position { line: 1, character: 10 } } }) diff --git a/crates/oxc_language_server/src/snapshots/fixtures_linter_vue@debugger.vue.snap b/crates/oxc_language_server/src/snapshots/fixtures_linter_vue@debugger.vue.snap index 609d3b290abe2..c4f2946f3ffa6 100644 --- a/crates/oxc_language_server/src/snapshots/fixtures_linter_vue@debugger.vue.snap +++ b/crates/oxc_language_server/src/snapshots/fixtures_linter_vue@debugger.vue.snap @@ -12,7 +12,7 @@ related_information[0].location.range: Range { start: Position { line: 5, charac severity: Some(Warning) source: Some("oxc") tags: None -fixed: Some(FixedContent { message: Some("Remove the debugger statement"), code: "", range: Range { start: Position { line: 5, character: 4 }, end: Position { line: 5, character: 12 } } }) +fixed: Single(FixedContent { message: Some("Remove the debugger statement"), code: "", range: Range { start: Position { line: 5, character: 4 }, end: Position { line: 5, character: 12 } } }) code: "eslint(no-debugger)" @@ -25,4 +25,4 @@ related_information[0].location.range: Range { start: Position { line: 10, chara severity: Some(Warning) source: Some("oxc") tags: None -fixed: Some(FixedContent { message: Some("Remove the debugger statement"), code: "", range: Range { start: Position { line: 10, character: 4 }, end: Position { line: 10, character: 13 } } }) +fixed: Single(FixedContent { message: Some("Remove the debugger statement"), code: "", range: Range { start: Position { line: 10, character: 4 }, end: Position { line: 10, character: 13 } } }) diff --git a/crates/oxc_language_server/src/worker.rs b/crates/oxc_language_server/src/worker.rs index da14788b20763..9d89ace5e628d 100644 --- a/crates/oxc_language_server/src/worker.rs +++ b/crates/oxc_language_server/src/worker.rs @@ -14,11 +14,11 @@ use tower_lsp_server::{ use crate::{ ConcurrentHashMap, Options, Run, code_actions::{ - apply_all_fix_code_action, apply_fix_code_action, ignore_this_line_code_action, + apply_all_fix_code_action, apply_fix_code_actions, ignore_this_line_code_action, ignore_this_rule_code_action, }, linter::{ - error_with_position::DiagnosticReport, + error_with_position::{DiagnosticReport, PossibleFixContent}, server_linter::{ServerLinter, normalize_path}, }, }; @@ -228,8 +228,9 @@ impl WorkspaceWorker { let mut code_actions_vec: Vec = vec![]; for report in reports { - if let Some(fix_action) = apply_fix_code_action(report, uri) { - code_actions_vec.push(CodeActionOrCommand::CodeAction(fix_action)); + if let Some(fix_actions) = apply_fix_code_actions(report, uri) { + code_actions_vec + .extend(fix_actions.into_iter().map(CodeActionOrCommand::CodeAction)); } code_actions_vec @@ -242,6 +243,7 @@ impl WorkspaceWorker { code_actions_vec } + /// This function is used for executing the `oxc.fixAll` command pub async fn get_diagnostic_text_edits(&self, uri: &Uri) -> Vec { let report_map_ref = self.diagnostics_report_map.pin_owned(); let value = match report_map_ref.get(&uri.to_string()) { @@ -258,7 +260,15 @@ impl WorkspaceWorker { let mut text_edits = vec![]; for report in value { - if let Some(fixed_content) = &report.fixed_content { + let fix = match &report.fixed_content { + PossibleFixContent::None => None, + PossibleFixContent::Single(fixed_content) => Some(fixed_content), + // For multiple fixes, we take the first one as a representative fix. + // Applying all possible fixes at once is not possible in this context. + PossibleFixContent::Multiple(multi) => multi.first(), + }; + + if let Some(fixed_content) = &fix { text_edits.push(TextEdit { range: fixed_content.range, new_text: fixed_content.code.clone(), diff --git a/crates/oxc_linter/src/fixer/fix.rs b/crates/oxc_linter/src/fixer/fix.rs index 1c7c0780247a4..c24e2e5d518cb 100644 --- a/crates/oxc_linter/src/fixer/fix.rs +++ b/crates/oxc_linter/src/fixer/fix.rs @@ -343,6 +343,62 @@ impl<'a> Fix<'a> { } } +#[derive(Clone)] +pub enum PossibleFixes<'a> { + None, + Single(Fix<'a>), + Multiple(Vec>), +} + +impl<'new> CloneIn<'new> for PossibleFixes<'_> { + type Cloned = PossibleFixes<'new>; + + fn clone_in(&self, allocator: &'new Allocator) -> Self::Cloned { + match self { + Self::None => PossibleFixes::None, + Self::Single(fix) => PossibleFixes::Single(fix.clone_in(allocator)), + Self::Multiple(fixes) => { + //ToDo: what about the vec? + PossibleFixes::Multiple(fixes.iter().map(|fix| fix.clone_in(allocator)).collect()) + } + } + } +} + +impl PossibleFixes<'_> { + /// Gets the number of [`Fix`]es contained in this [`PossibleFixes`]. + pub fn len(&self) -> usize { + match self { + PossibleFixes::None => 0, + PossibleFixes::Single(_) => 1, + PossibleFixes::Multiple(fixes) => fixes.len(), + } + } + + /// Returns `true` if this [`PossibleFixes`] contains no [`Fix`]es + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn span(&self) -> Span { + match self { + PossibleFixes::None => SPAN, + PossibleFixes::Single(fix) => fix.span, + PossibleFixes::Multiple(fixes) => { + fixes.iter().map(|fix| fix.span).reduce(Span::merge).unwrap_or(SPAN) + } + } + } +} + +#[cfg(feature = "language_server")] +#[derive(Debug)] +pub enum PossibleFixesWithPosition<'a> { + None, + Single(FixWithPosition<'a>), + Multiple(Vec>), +} + // NOTE (@DonIsaac): having these variants is effectively the same as interning // single or 0-element Vecs. I experimented with using smallvec here, but the // resulting struct size was larger (40 bytes vs 32). So, we're sticking with diff --git a/crates/oxc_linter/src/fixer/mod.rs b/crates/oxc_linter/src/fixer/mod.rs index 8b58663eec4da..171568be338a8 100644 --- a/crates/oxc_linter/src/fixer/mod.rs +++ b/crates/oxc_linter/src/fixer/mod.rs @@ -9,12 +9,12 @@ use crate::LintContext; #[cfg(feature = "language_server")] use crate::service::offset_to_position::SpanPositionMessage; #[cfg(feature = "language_server")] -pub use fix::FixWithPosition; +pub use fix::{FixWithPosition, PossibleFixesWithPosition}; #[cfg(feature = "language_server")] use oxc_diagnostics::{OxcCode, Severity}; mod fix; -pub use fix::{CompositeFix, Fix, FixKind, RuleFix}; +pub use fix::{CompositeFix, Fix, FixKind, PossibleFixes, RuleFix}; use oxc_allocator::{Allocator, CloneIn}; /// Produces [`RuleFix`] instances. Inspired by ESLint's [`RuleFixer`]. @@ -227,7 +227,7 @@ pub struct FixResult<'a> { #[derive(Clone)] pub struct Message<'a> { pub error: OxcDiagnostic, - pub fix: Option>, + pub fixes: PossibleFixes<'a>, span: Span, fixed: bool, } @@ -241,7 +241,7 @@ pub struct MessageWithPosition<'a> { pub severity: Severity, pub code: OxcCode, pub url: Option>, - pub fix: Option>, + pub fixes: PossibleFixesWithPosition<'a>, } #[cfg(feature = "language_server")] @@ -254,7 +254,7 @@ impl From for MessageWithPosition<'_> { severity: from.severity, code: from.code.clone(), url: from.url.clone(), - fix: None, + fixes: PossibleFixesWithPosition::None, } } } @@ -265,7 +265,7 @@ impl<'new> CloneIn<'new> for Message<'_> { fn clone_in(&self, allocator: &'new Allocator) -> Self::Cloned { Message { error: self.error.clone(), - fix: self.fix.clone_in(allocator), + fixes: self.fixes.clone_in(allocator), span: self.span, fixed: self.fixed, } @@ -288,7 +288,9 @@ impl<'a> Message<'a> { } else { (0, 0) }; - Self { error, span: Span::new(start, end), fix, fixed: false } + // ToDo support multiple fixes + let fixes = fix.map_or(PossibleFixes::None, PossibleFixes::Single); + Self { error, span: Span::new(start, end), fixes, fixed: false } } } @@ -321,7 +323,7 @@ impl<'a> Fixer<'a> { /// # Panics pub fn fix(mut self) -> FixResult<'a> { let source_text = self.source_text; - if self.messages.iter().all(|m| m.fix.is_none()) { + if self.messages.iter().all(|m| m.fixes.is_empty()) { return FixResult { fixed: false, fixed_code: Cow::Borrowed(source_text), @@ -329,7 +331,7 @@ impl<'a> Fixer<'a> { }; } - self.messages.sort_unstable_by_key(|m| m.fix.as_ref().unwrap_or(&Fix::default()).span); + self.messages.sort_unstable_by_key(|m| m.fixes.span()); let mut fixed = false; let mut output = String::with_capacity(source_text.len()); let mut last_pos: i64 = -1; @@ -338,7 +340,14 @@ impl<'a> Fixer<'a> { let mut filtered_messages = Vec::with_capacity(self.messages.len()); for mut m in self.messages { - let Some(Fix { content, span, .. }) = m.fix.as_ref() else { + let fix = match &m.fixes { + PossibleFixes::None => None, + PossibleFixes::Single(fix) => Some(fix), + // For multiple fixes, we take the first one as a representative fix. + // Applying all possible fixes at once is not possible in this context. + PossibleFixes::Multiple(multiple) => multiple.first(), + }; + let Some(Fix { content, span, .. }) = fix else { filtered_messages.push(m); continue; }; diff --git a/crates/oxc_linter/src/lib.rs b/crates/oxc_linter/src/lib.rs index 5d053c5e7f272..42129c7e834c7 100644 --- a/crates/oxc_linter/src/lib.rs +++ b/crates/oxc_linter/src/lib.rs @@ -51,7 +51,7 @@ use crate::{ }; #[cfg(feature = "language_server")] -pub use crate::fixer::{FixWithPosition, MessageWithPosition}; +pub use crate::fixer::{FixWithPosition, MessageWithPosition, PossibleFixesWithPosition}; #[cfg(target_pointer_width = "64")] #[test] diff --git a/crates/oxc_linter/src/service/runtime.rs b/crates/oxc_linter/src/service/runtime.rs index 79b22b2775a72..b10cb7f5e9a2a 100644 --- a/crates/oxc_linter/src/service/runtime.rs +++ b/crates/oxc_linter/src/service/runtime.rs @@ -22,21 +22,17 @@ use oxc_resolver::Resolver; use oxc_semantic::{Semantic, SemanticBuilder}; use oxc_span::{CompactStr, SourceType, VALID_EXTENSIONS}; -#[cfg(feature = "language_server")] -use oxc_allocator::CloneIn; - use super::LintServiceOptions; use crate::{ Fixer, Linter, Message, + fixer::PossibleFixes, loader::{JavaScriptSource, LINT_PARTIAL_LOADER_EXTENSIONS, PartialLoader}, module_record::ModuleRecord, utils::read_to_string, }; #[cfg(feature = "language_server")] -use crate::fixer::{FixWithPosition, MessageWithPosition}; -#[cfg(feature = "language_server")] -use crate::service::offset_to_position::{SpanPositionMessage, offset_to_position}; +use crate::fixer::MessageWithPosition; pub struct Runtime<'l> { cwd: Box, @@ -556,8 +552,29 @@ impl<'l> Runtime<'l> { &mut self, allocator: &'a oxc_allocator::Allocator, ) -> Vec> { + use oxc_allocator::CloneIn; use std::sync::Mutex; + use crate::{ + FixWithPosition, + fixer::{Fix, PossibleFixesWithPosition}, + service::offset_to_position::{SpanPositionMessage, offset_to_position}, + }; + + fn fix_to_fix_with_position<'a>( + fix: &Fix<'a>, + offset: u32, + source_text: &str, + ) -> FixWithPosition<'a> { + let start_position = offset_to_position(offset + fix.span.start, source_text); + let end_position = offset_to_position(offset + fix.span.end, source_text); + FixWithPosition { + content: fix.content.clone(), + span: SpanPositionMessage::new(start_position, end_position) + .with_message(fix.message.as_ref().map(|label| Cow::Owned(label.to_string()))), + } + } + let messages = Mutex::new(Vec::>::new()); let (sender, _receiver) = mpsc::channel(); rayon::scope(|scope| { @@ -621,24 +638,34 @@ impl<'l> Runtime<'l> { url: message.error.url.clone(), code: message.error.code.clone(), labels: labels.clone(), - fix: message.fix.map(|fix| FixWithPosition { - content: fix.content, - span: SpanPositionMessage::new( - offset_to_position( - section.source.start + fix.span.start, - &owner.source_text, - ), - offset_to_position( - section.source.start + fix.span.end, - &owner.source_text, - ), - ) - .with_message( - fix.message - .as_ref() - .map(|label| Cow::Owned(label.to_string())), - ), - }), + fixes: match &message.fixes { + PossibleFixes::None => { + PossibleFixesWithPosition::None + } + PossibleFixes::Single(fix) => { + PossibleFixesWithPosition::Single( + fix_to_fix_with_position( + fix, + section.source.start, + &owner.source_text, + ), + ) + } + PossibleFixes::Multiple(fixes) => { + PossibleFixesWithPosition::Multiple( + fixes + .iter() + .map(|fix| { + fix_to_fix_with_position( + fix, + section.source.start, + &owner.source_text, + ) + }) + .collect(), + ) + } + }, } }, ));