Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 5 additions & 29 deletions crates/oxc_language_server/src/linter/code_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,10 @@ pub const CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC: CodeActionKind =
fn fix_content_to_code_action(
fixed_content: &FixedContent,
uri: &Uri,
alternative_message: &str,
is_preferred: bool,
) -> 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) = alternative_message.split(':').next() {
format!("Fix this {code} problem")
} else {
"Fix this problem".to_string()
}
}
};

CodeAction {
title,
title: fixed_content.message.clone(),
kind: Some(CodeActionKind::QUICKFIX),
is_preferred: Some(is_preferred),
edit: Some(WorkspaceEdit {
Expand All @@ -45,17 +30,13 @@ fn fix_content_to_code_action(
}
}

pub fn apply_fix_code_actions(
report: &LinterCodeAction,
alternative_message: &str,
uri: &Uri,
) -> Vec<CodeAction> {
pub fn apply_fix_code_actions(report: &LinterCodeAction, uri: &Uri) -> Vec<CodeAction> {
let mut code_actions = vec![];

// only the first code action is preferred
let mut preferred = true;
for fixed in &report.fixed_content {
let action = fix_content_to_code_action(fixed, uri, alternative_message, preferred);
let action = fix_content_to_code_action(fixed, uri, preferred);
preferred = false;
code_actions.push(action);
}
Expand Down Expand Up @@ -104,13 +85,8 @@ pub fn fix_all_text_edit<'a>(reports: impl Iterator<Item = &'a LinterCodeAction>
debug!("Multiple fixes found, but only ignore fixes available");
#[cfg(debug_assertions)]
{
debug_assert!(report.fixed_content[0].message.as_ref().is_some());
debug_assert!(
report.fixed_content[0].message.as_ref().unwrap().starts_with("Disable")
);
debug_assert!(
report.fixed_content[0].message.as_ref().unwrap().ends_with("for this line")
);
debug_assert!(report.fixed_content[0].message.starts_with("Disable"));
debug_assert!(report.fixed_content[0].message.ends_with("for this line"));
}
continue;
}
Expand Down
47 changes: 32 additions & 15 deletions crates/oxc_language_server/src/linter/error_with_position.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::str::FromStr;
use std::{borrow::Cow, str::FromStr};

use tower_lsp_server::lsp_types::{
self, CodeDescription, Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity,
Expand All @@ -23,7 +23,7 @@ pub struct LinterCodeAction {

#[derive(Debug, Clone, Default)]
pub struct FixedContent {
pub message: Option<String>,
pub message: String,
pub code: String,
pub range: Range,
}
Expand All @@ -32,7 +32,7 @@ pub struct FixedContent {
// we assume that the fix offset will not exceed 2GB in either direction
#[expect(clippy::cast_possible_truncation)]
pub fn message_to_lsp_diagnostic(
message: &Message,
mut message: Message,
uri: &Uri,
source_text: &str,
rope: &Rope,
Expand Down Expand Up @@ -87,6 +87,16 @@ pub fn message_to_lsp_diagnostic(
None => message.error.message.to_string(),
};

// 1) Use `fixed_content.message` if it exists
// 2) Try to parse the report diagnostic message
// 3) Fallback to "Fix this problem"
let alternative_fix_title: Cow<'static, str> =
if let Some(code) = diagnostic_message.split(':').next() {
format!("Fix this {code} problem").into()
} else {
std::borrow::Cow::Borrowed("Fix this problem")
};

let diagnostic = Diagnostic {
range,
severity,
Expand All @@ -101,14 +111,21 @@ pub fn message_to_lsp_diagnostic(

let mut fixed_content = vec![];
// Convert PossibleFixes directly to PossibleFixContent
match &message.fixes {
match &mut message.fixes {
PossibleFixes::None => {}
PossibleFixes::Single(fix) => {
if fix.message.is_none() {
fix.message = Some(alternative_fix_title);
}
fixed_content.push(fix_to_fixed_content(fix, rope, source_text));
}
PossibleFixes::Multiple(fixes) => {
fixed_content
.extend(fixes.iter().map(|fix| fix_to_fixed_content(fix, rope, source_text)));
fixed_content.extend(fixes.iter_mut().map(|fix| {
if fix.message.is_none() {
fix.message = Some(alternative_fix_title.clone());
}
fix_to_fixed_content(fix, rope, source_text)
}));
}
}

Expand Down Expand Up @@ -154,8 +171,13 @@ fn fix_to_fixed_content(fix: &Fix, rope: &Rope, source_text: &str) -> FixedConte
let start_position = offset_to_position(rope, fix.span.start, source_text);
let end_position = offset_to_position(rope, fix.span.end, source_text);

debug_assert!(
fix.message.is_some(),
"Fix message should be present. `message_to_lsp_diagnostic` should modify fixes to include messages."
);

FixedContent {
message: fix.message.as_ref().map(std::string::ToString::to_string),
message: fix.message.as_ref().map(std::string::ToString::to_string).unwrap_or_default(),
code: fix.content.to_string(),
range: Range::new(start_position, end_position),
}
Expand Down Expand Up @@ -220,12 +242,7 @@ fn add_ignore_fixes(
source_text: &str,
) {
// do not append ignore code actions when the error is the ignore action
if fixes.len() == 1
&& fixes[0]
.message
.as_ref()
.is_some_and(|message| message.starts_with("remove unused disable directive"))
{
if fixes.len() == 1 && fixes[0].message.starts_with("remove unused disable directive") {
return;
}

Expand Down Expand Up @@ -281,7 +298,7 @@ fn disable_for_this_line(

let position = offset_to_position(rope, insert_offset, source_text);
FixedContent {
message: Some(format!("Disable {rule_name} for this line")),
message: format!("Disable {rule_name} for this line"),
code: format!(
"{content_prefix}{whitespace_string}// oxlint-disable-next-line {rule_name}\n"
),
Expand All @@ -304,7 +321,7 @@ fn disable_for_this_section(
let position = offset_to_position(rope, insert_offset, source_text);

FixedContent {
message: Some(format!("Disable {rule_name} for this whole file")),
message: format!("Disable {rule_name} for this whole file"),
code: content,
range: Range::new(position, position),
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ impl IsolatedLintHandler {
let mut messages: Vec<DiagnosticReport> = self
.runner
.run_source(&Arc::from(path.as_os_str()), source_text.to_string(), &fs)
.iter()
.into_iter()
.map(|message| message_to_lsp_diagnostic(message, uri, source_text, rope))
.collect();

Expand All @@ -136,7 +136,7 @@ impl IsolatedLintHandler {
{
messages.extend(
create_unused_directives_messages(&directives, severity, source_text)
.iter()
.into_iter()
.map(|message| message_to_lsp_diagnostic(message, uri, source_text, rope)),
);
}
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_language_server/src/linter/server_linter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ impl Tool for ServerLinter {
let Some(ref code_action) = report.code_action else {
continue;
};
let fix_actions = apply_fix_code_actions(code_action, &report.diagnostic.message, uri);
let fix_actions = apply_fix_code_actions(code_action, uri);
code_actions_vec.extend(fix_actions.into_iter().map(CodeActionOrCommand::CodeAction));
}

Expand Down
Loading