diff --git a/crates/oxc_language_server/src/code_actions.rs b/crates/oxc_language_server/src/code_actions.rs index a5a4763d21705..23084de0863ce 100644 --- a/crates/oxc_language_server/src/code_actions.rs +++ b/crates/oxc_language_server/src/code_actions.rs @@ -80,7 +80,32 @@ pub fn apply_all_fix_code_action<'a>( reports: impl Iterator, uri: &Uri, ) -> Option { - let mut quick_fixes: Vec = vec![]; + let quick_fixes: Vec = fix_all_text_edit(reports); + + if quick_fixes.is_empty() { + return None; + } + + Some(CodeAction { + title: "quick fix".to_string(), + kind: Some(CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC), + is_preferred: Some(true), + edit: Some(WorkspaceEdit { + #[expect(clippy::disallowed_types)] + changes: Some(std::collections::HashMap::from([(uri.clone(), quick_fixes)])), + ..WorkspaceEdit::default() + }), + disabled: None, + data: None, + diagnostics: None, + command: None, + }) +} + +/// Collect all text edits from the provided diagnostic reports, which can be applied at once. +/// This is useful for implementing a "fix all" code action / command that applies multiple fixes in one go. +pub fn fix_all_text_edit<'a>(reports: impl Iterator) -> Vec { + let mut text_edits: Vec = vec![]; for report in reports { let fix = match &report.fixed_content { @@ -119,29 +144,12 @@ pub fn apply_all_fix_code_action<'a>( // and return them as one workspace edit. // it is possible that one fix will change the range for the next fix // see oxc-project/oxc#10422 - quick_fixes.push(TextEdit { + text_edits.push(TextEdit { range: fixed_content.range, new_text: fixed_content.code.clone(), }); } } - if quick_fixes.is_empty() { - return None; - } - - Some(CodeAction { - title: "quick fix".to_string(), - kind: Some(CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC), - is_preferred: Some(true), - edit: Some(WorkspaceEdit { - #[expect(clippy::disallowed_types)] - changes: Some(std::collections::HashMap::from([(uri.clone(), quick_fixes)])), - ..WorkspaceEdit::default() - }), - disabled: None, - data: None, - diagnostics: None, - command: None, - }) + text_edits } diff --git a/crates/oxc_language_server/src/worker.rs b/crates/oxc_language_server/src/worker.rs index d21a2377636b2..b9464a8cecd2f 100644 --- a/crates/oxc_language_server/src/worker.rs +++ b/crates/oxc_language_server/src/worker.rs @@ -12,10 +12,10 @@ use tower_lsp_server::{ use crate::{ ConcurrentHashMap, - code_actions::{apply_all_fix_code_action, apply_fix_code_actions}, + code_actions::{apply_all_fix_code_action, apply_fix_code_actions, fix_all_text_edit}, formatter::server_formatter::ServerFormatter, linter::{ - error_with_position::{DiagnosticReport, PossibleFixContent}, + error_with_position::DiagnosticReport, server_linter::{ServerLinter, ServerLinterRun, normalize_path}, }, options::Options, @@ -286,48 +286,7 @@ impl WorkspaceWorker { return vec![]; } - let mut text_edits = vec![]; - - for report in value { - 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) => { - // for a real linter fix, we expect at least 3 fixes - if multi.len() > 2 { - multi.first() - } else { - debug!("Multiple fixes found, but only ignore fixes available"); - #[cfg(debug_assertions)] - { - if !multi.is_empty() { - debug_assert!(multi[0].message.as_ref().is_some()); - debug_assert!( - multi[0].message.as_ref().unwrap().starts_with("Disable") - ); - debug_assert!( - multi[0].message.as_ref().unwrap().ends_with("for this line") - ); - } - } - // this fix is only for "ignore this line/file" fixes - // do not apply them for "fix all" code action - None - } - } - }; - - if let Some(fixed_content) = &fix { - text_edits.push(TextEdit { - range: fixed_content.range, - new_text: fixed_content.code.clone(), - }); - } - } - - text_edits + fix_all_text_edit(value.iter()) } /// Handle file changes that are watched by the client