mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 05:15:12 +00:00
ruff server
now supports source.fixAll
source action (#10597)
## Summary
`ruff server` now has source action `source.fixAll` as an available code
action.
This also fixes https://github.com/astral-sh/ruff/issues/10593 in the
process of revising the code for quick fix code actions.
## Test Plan
f4c07425
-e68a-445f-a4ed-949c9197a6be
This commit is contained in:
parent
d467aa78c2
commit
257964a8bc
15 changed files with 564 additions and 191 deletions
|
@ -1,81 +1,131 @@
|
|||
use crate::edit::ToRangeExt;
|
||||
use crate::lint::fixes_for_diagnostics;
|
||||
use crate::server::api::LSPResult;
|
||||
use crate::server::SupportedCodeAction;
|
||||
use crate::server::{client::Notifier, Result};
|
||||
use crate::session::DocumentSnapshot;
|
||||
use crate::DIAGNOSTIC_NAME;
|
||||
use lsp_server::ErrorCode;
|
||||
use lsp_types::{self as types, request as req};
|
||||
use ruff_text_size::Ranged;
|
||||
use rustc_hash::FxHashSet;
|
||||
use types::{CodeActionKind, CodeActionOrCommand};
|
||||
|
||||
pub(crate) struct CodeAction;
|
||||
use super::code_action_resolve::resolve_edit_for_fix_all;
|
||||
|
||||
impl super::RequestHandler for CodeAction {
|
||||
pub(crate) struct CodeActions;
|
||||
|
||||
impl super::RequestHandler for CodeActions {
|
||||
type RequestType = req::CodeActionRequest;
|
||||
}
|
||||
|
||||
impl super::BackgroundDocumentRequestHandler for CodeAction {
|
||||
impl super::BackgroundDocumentRequestHandler for CodeActions {
|
||||
super::define_document_url!(params: &types::CodeActionParams);
|
||||
fn run_with_snapshot(
|
||||
snapshot: DocumentSnapshot,
|
||||
_notifier: Notifier,
|
||||
params: types::CodeActionParams,
|
||||
) -> Result<Option<types::CodeActionResponse>> {
|
||||
let document = snapshot.document();
|
||||
let url = snapshot.url();
|
||||
let encoding = snapshot.encoding();
|
||||
let version = document.version();
|
||||
let actions: Result<Vec<_>> = params
|
||||
.context
|
||||
.diagnostics
|
||||
.into_iter()
|
||||
.map(|diagnostic| {
|
||||
let Some(data) = diagnostic.data else {
|
||||
return Ok(None);
|
||||
};
|
||||
let diagnostic_fix: crate::lint::DiagnosticFix = serde_json::from_value(data)
|
||||
.map_err(|err| anyhow::anyhow!("failed to deserialize diagnostic data: {err}"))
|
||||
.with_failure_code(lsp_server::ErrorCode::ParseError)?;
|
||||
let edits = diagnostic_fix
|
||||
.fix
|
||||
.edits()
|
||||
.iter()
|
||||
.map(|edit| types::TextEdit {
|
||||
range: edit.range().to_range(
|
||||
document.contents(),
|
||||
document.index(),
|
||||
encoding,
|
||||
),
|
||||
new_text: edit.content().unwrap_or_default().to_string(),
|
||||
});
|
||||
let mut response: types::CodeActionResponse = types::CodeActionResponse::default();
|
||||
|
||||
let changes = vec![types::TextDocumentEdit {
|
||||
text_document: types::OptionalVersionedTextDocumentIdentifier::new(
|
||||
url.clone(),
|
||||
version,
|
||||
),
|
||||
edits: edits.map(types::OneOf::Left).collect(),
|
||||
}];
|
||||
let supported_code_actions = supported_code_actions(params.context.only);
|
||||
|
||||
let title = diagnostic_fix
|
||||
.kind
|
||||
.suggestion
|
||||
.unwrap_or(diagnostic_fix.kind.name);
|
||||
Ok(Some(types::CodeAction {
|
||||
title,
|
||||
kind: Some(types::CodeActionKind::QUICKFIX),
|
||||
edit: Some(types::WorkspaceEdit {
|
||||
document_changes: Some(types::DocumentChanges::Edits(changes)),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
}))
|
||||
})
|
||||
.collect();
|
||||
if supported_code_actions.contains(&SupportedCodeAction::QuickFix) {
|
||||
response.extend(
|
||||
quick_fix(&snapshot, params.context.diagnostics)
|
||||
.with_failure_code(ErrorCode::InternalError)?,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(Some(
|
||||
actions?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(types::CodeActionOrCommand::CodeAction)
|
||||
.collect(),
|
||||
))
|
||||
if supported_code_actions.contains(&SupportedCodeAction::SourceFixAll) {
|
||||
response.push(fix_all(&snapshot).with_failure_code(ErrorCode::InternalError)?);
|
||||
}
|
||||
|
||||
if supported_code_actions.contains(&SupportedCodeAction::SourceOrganizeImports) {
|
||||
todo!("Implement the `source.organizeImports` code action");
|
||||
}
|
||||
|
||||
Ok(Some(response))
|
||||
}
|
||||
}
|
||||
|
||||
fn quick_fix(
|
||||
snapshot: &DocumentSnapshot,
|
||||
diagnostics: Vec<types::Diagnostic>,
|
||||
) -> crate::Result<impl Iterator<Item = CodeActionOrCommand> + '_> {
|
||||
let document = snapshot.document();
|
||||
|
||||
let fixes = fixes_for_diagnostics(
|
||||
document,
|
||||
snapshot.url(),
|
||||
snapshot.encoding(),
|
||||
document.version(),
|
||||
diagnostics,
|
||||
)?;
|
||||
|
||||
Ok(fixes.into_iter().map(|fix| {
|
||||
types::CodeActionOrCommand::CodeAction(types::CodeAction {
|
||||
title: format!("{DIAGNOSTIC_NAME} ({}): {}", fix.code, fix.title),
|
||||
kind: Some(types::CodeActionKind::QUICKFIX),
|
||||
edit: Some(types::WorkspaceEdit {
|
||||
document_changes: Some(types::DocumentChanges::Edits(fix.document_edits.clone())),
|
||||
..Default::default()
|
||||
}),
|
||||
diagnostics: Some(vec![fix.fixed_diagnostic.clone()]),
|
||||
data: Some(serde_json::to_value(snapshot.url()).expect("document url to serialize")),
|
||||
..Default::default()
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
fn fix_all(snapshot: &DocumentSnapshot) -> crate::Result<CodeActionOrCommand> {
|
||||
let document = snapshot.document();
|
||||
|
||||
let (edit, data) = if snapshot
|
||||
.resolved_client_capabilities()
|
||||
.code_action_deferred_edit_resolution
|
||||
{
|
||||
// The editor will request the edit in a `CodeActionsResolve` request
|
||||
(
|
||||
None,
|
||||
Some(serde_json::to_value(snapshot.url()).expect("document url to serialize")),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
Some(resolve_edit_for_fix_all(
|
||||
document,
|
||||
snapshot.url(),
|
||||
&snapshot.configuration().linter,
|
||||
snapshot.encoding(),
|
||||
)?),
|
||||
None,
|
||||
)
|
||||
};
|
||||
let action = types::CodeAction {
|
||||
title: format!("{DIAGNOSTIC_NAME}: Fix all auto-fixable problems"),
|
||||
kind: Some(types::CodeActionKind::SOURCE_FIX_ALL),
|
||||
edit,
|
||||
data,
|
||||
..Default::default()
|
||||
};
|
||||
Ok(types::CodeActionOrCommand::CodeAction(action))
|
||||
}
|
||||
|
||||
/// If `action_filter` is `None`, this returns [`SupportedCodeActionKind::all()`]. Otherwise,
|
||||
/// the list is filtered.
|
||||
fn supported_code_actions(
|
||||
action_filter: Option<Vec<CodeActionKind>>,
|
||||
) -> FxHashSet<SupportedCodeAction> {
|
||||
let Some(action_filter) = action_filter else {
|
||||
return SupportedCodeAction::all().collect();
|
||||
};
|
||||
|
||||
SupportedCodeAction::all()
|
||||
.filter(move |action| {
|
||||
action_filter.iter().any(|filter| {
|
||||
action
|
||||
.kinds()
|
||||
.iter()
|
||||
.any(|kind| kind.as_str().starts_with(filter.as_str()))
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue