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:
Jane Lewis 2024-04-03 09:22:17 -07:00 committed by GitHub
parent d467aa78c2
commit 257964a8bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 564 additions and 191 deletions

View file

@ -0,0 +1,79 @@
use ruff_linter::{
linter::{FixerResult, LinterResult},
settings::{flags, types::UnsafeFixes, LinterSettings},
source_kind::SourceKind,
};
use ruff_python_ast::PySourceType;
use ruff_source_file::LineIndex;
use std::{borrow::Cow, path::Path};
use crate::{
edit::{Replacement, ToRangeExt},
PositionEncoding,
};
pub(crate) fn fix_all(
document: &crate::edit::Document,
linter_settings: &LinterSettings,
encoding: PositionEncoding,
) -> crate::Result<Vec<lsp_types::TextEdit>> {
let source = document.contents();
let source_type = PySourceType::default();
// TODO(jane): Support Jupyter Notebooks
let source_kind = SourceKind::Python(source.to_string());
// We need to iteratively apply all safe fixes onto a single file and then
// create a diff between the modified file and the original source to use as a single workspace
// edit.
// If we simply generated the diagnostics with `check_path` and then applied fixes individually,
// there's a possibility they could overlap or introduce new problems that need to be fixed,
// which is inconsistent with how `ruff check --fix` works.
let FixerResult {
transformed,
result: LinterResult { error, .. },
..
} = ruff_linter::linter::lint_fix(
Path::new("<filename>"),
None,
flags::Noqa::Enabled,
UnsafeFixes::Disabled,
linter_settings,
&source_kind,
source_type,
)?;
if let Some(error) = error {
// abort early if a parsing error occurred
return Err(anyhow::anyhow!(
"A parsing error occurred during `fix_all`: {error}"
));
}
// fast path: if `transformed` is still borrowed, no changes were made and we can return early
if let Cow::Borrowed(_) = transformed {
return Ok(vec![]);
}
let modified = transformed.source_code();
let modified_index = LineIndex::from_source_text(modified);
let source_index = document.index();
let Replacement {
source_range,
modified_range,
} = Replacement::between(
source,
source_index.line_starts(),
modified,
modified_index.line_starts(),
);
Ok(vec![lsp_types::TextEdit {
range: source_range.to_range(source, source_index, encoding),
new_text: modified[modified_range].to_owned(),
}])
}