mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 10:48:32 +00:00
Server: Allow FixAll
action in presence of version-specific syntax errors (#16848)
The single flag `has_syntax_error` on `LinterResult` is replaced with two (private) flags: `has_valid_syntax` and `has_no_unsupported_syntax_errors`, which record whether there are `ParseError`s or `UnsupportedSyntaxError`s, respectively. Only the former is used to prevent a `FixAll` action. An attempt has been made to make consistent the usage of the phrases "valid syntax" (which seems to be used to refer only to _parser_ errors) and "syntax error" (which refers to both _parser_ errors and version-specific syntax errors). Closes #16841
This commit is contained in:
parent
999fd4f885
commit
74f64d3f96
5 changed files with 111 additions and 73 deletions
|
@ -263,48 +263,56 @@ pub(crate) fn lint_path(
|
||||||
};
|
};
|
||||||
|
|
||||||
// Lint the file.
|
// Lint the file.
|
||||||
let (
|
let (result, transformed, fixed) =
|
||||||
LinterResult {
|
if matches!(fix_mode, flags::FixMode::Apply | flags::FixMode::Diff) {
|
||||||
messages,
|
if let Ok(FixerResult {
|
||||||
has_syntax_error: has_error,
|
result,
|
||||||
},
|
transformed,
|
||||||
transformed,
|
fixed,
|
||||||
fixed,
|
}) = lint_fix(
|
||||||
) = if matches!(fix_mode, flags::FixMode::Apply | flags::FixMode::Diff) {
|
path,
|
||||||
if let Ok(FixerResult {
|
package,
|
||||||
result,
|
noqa,
|
||||||
transformed,
|
unsafe_fixes,
|
||||||
fixed,
|
settings,
|
||||||
}) = lint_fix(
|
&source_kind,
|
||||||
path,
|
source_type,
|
||||||
package,
|
) {
|
||||||
noqa,
|
if !fixed.is_empty() {
|
||||||
unsafe_fixes,
|
match fix_mode {
|
||||||
settings,
|
flags::FixMode::Apply => transformed.write(&mut File::create(path)?)?,
|
||||||
&source_kind,
|
flags::FixMode::Diff => {
|
||||||
source_type,
|
write!(
|
||||||
) {
|
&mut io::stdout().lock(),
|
||||||
if !fixed.is_empty() {
|
"{}",
|
||||||
match fix_mode {
|
source_kind.diff(&transformed, Some(path)).unwrap()
|
||||||
flags::FixMode::Apply => transformed.write(&mut File::create(path)?)?,
|
)?;
|
||||||
flags::FixMode::Diff => {
|
}
|
||||||
write!(
|
flags::FixMode::Generate => {}
|
||||||
&mut io::stdout().lock(),
|
|
||||||
"{}",
|
|
||||||
source_kind.diff(&transformed, Some(path)).unwrap()
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
flags::FixMode::Generate => {}
|
|
||||||
}
|
}
|
||||||
}
|
let transformed = if let Cow::Owned(transformed) = transformed {
|
||||||
let transformed = if let Cow::Owned(transformed) = transformed {
|
transformed
|
||||||
transformed
|
} else {
|
||||||
|
source_kind
|
||||||
|
};
|
||||||
|
(result, transformed, fixed)
|
||||||
} else {
|
} else {
|
||||||
source_kind
|
// If we fail to fix, lint the original source code.
|
||||||
};
|
let result = lint_only(
|
||||||
(result, transformed, fixed)
|
path,
|
||||||
|
package,
|
||||||
|
settings,
|
||||||
|
noqa,
|
||||||
|
&source_kind,
|
||||||
|
source_type,
|
||||||
|
ParseSource::None,
|
||||||
|
);
|
||||||
|
let transformed = source_kind;
|
||||||
|
let fixed = FxHashMap::default();
|
||||||
|
(result, transformed, fixed)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// If we fail to fix, lint the original source code.
|
|
||||||
let result = lint_only(
|
let result = lint_only(
|
||||||
path,
|
path,
|
||||||
package,
|
package,
|
||||||
|
@ -317,21 +325,10 @@ pub(crate) fn lint_path(
|
||||||
let transformed = source_kind;
|
let transformed = source_kind;
|
||||||
let fixed = FxHashMap::default();
|
let fixed = FxHashMap::default();
|
||||||
(result, transformed, fixed)
|
(result, transformed, fixed)
|
||||||
}
|
};
|
||||||
} else {
|
|
||||||
let result = lint_only(
|
let has_error = result.has_syntax_errors();
|
||||||
path,
|
let messages = result.messages;
|
||||||
package,
|
|
||||||
settings,
|
|
||||||
noqa,
|
|
||||||
&source_kind,
|
|
||||||
source_type,
|
|
||||||
ParseSource::None,
|
|
||||||
);
|
|
||||||
let transformed = source_kind;
|
|
||||||
let fixed = FxHashMap::default();
|
|
||||||
(result, transformed, fixed)
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some((cache, relative_path, key)) = caching {
|
if let Some((cache, relative_path, key)) = caching {
|
||||||
// We don't cache parsing errors.
|
// We don't cache parsing errors.
|
||||||
|
|
|
@ -89,7 +89,7 @@ fn benchmark_linter(mut group: BenchmarkGroup, settings: &LinterSettings) {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Assert that file contains no parse errors
|
// Assert that file contains no parse errors
|
||||||
assert!(!result.has_syntax_error);
|
assert!(!result.has_syntax_errors());
|
||||||
},
|
},
|
||||||
criterion::BatchSize::SmallInput,
|
criterion::BatchSize::SmallInput,
|
||||||
);
|
);
|
||||||
|
|
|
@ -40,11 +40,48 @@ use crate::{directives, fs, Locator};
|
||||||
pub struct LinterResult {
|
pub struct LinterResult {
|
||||||
/// A collection of diagnostic messages generated by the linter.
|
/// A collection of diagnostic messages generated by the linter.
|
||||||
pub messages: Vec<Message>,
|
pub messages: Vec<Message>,
|
||||||
/// A flag indicating the presence of syntax errors in the source file.
|
/// Flag indicating that the parsed source code does not contain any
|
||||||
/// If `true`, at least one syntax error was detected in the source file.
|
/// [`ParseError`]s
|
||||||
|
has_valid_syntax: bool,
|
||||||
|
/// Flag indicating that the parsed source code does not contain any
|
||||||
|
/// [`UnsupportedSyntaxError`]s
|
||||||
|
has_no_unsupported_syntax_errors: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LinterResult {
|
||||||
|
/// Returns `true` if the parsed source code contains any [`ParseError`]s *or*
|
||||||
|
/// [`UnsupportedSyntaxError`]s.
|
||||||
///
|
///
|
||||||
/// This includes both [`ParseError`]s and [`UnsupportedSyntaxError`]s.
|
/// See [`LinterResult::has_invalid_syntax`] for a version specific to [`ParseError`]s.
|
||||||
pub has_syntax_error: bool,
|
pub fn has_syntax_errors(&self) -> bool {
|
||||||
|
!self.has_no_syntax_errors()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the parsed source code does not contain any [`ParseError`]s *or*
|
||||||
|
/// [`UnsupportedSyntaxError`]s.
|
||||||
|
///
|
||||||
|
/// See [`LinterResult::has_valid_syntax`] for a version specific to [`ParseError`]s.
|
||||||
|
pub fn has_no_syntax_errors(&self) -> bool {
|
||||||
|
self.has_valid_syntax() && self.has_no_unsupported_syntax_errors
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the parsed source code is valid i.e., it has no [`ParseError`]s.
|
||||||
|
///
|
||||||
|
/// Note that this does not include version-related [`UnsupportedSyntaxError`]s.
|
||||||
|
///
|
||||||
|
/// See [`LinterResult::has_no_syntax_errors`] for a version that takes these into account.
|
||||||
|
pub fn has_valid_syntax(&self) -> bool {
|
||||||
|
self.has_valid_syntax
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the parsed source code is invalid i.e., it has [`ParseError`]s.
|
||||||
|
///
|
||||||
|
/// Note that this does not include version-related [`UnsupportedSyntaxError`]s.
|
||||||
|
///
|
||||||
|
/// See [`LinterResult::has_no_syntax_errors`] for a version that takes these into account.
|
||||||
|
pub fn has_invalid_syntax(&self) -> bool {
|
||||||
|
!self.has_valid_syntax()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type FixTable = FxHashMap<Rule, usize>;
|
pub type FixTable = FxHashMap<Rule, usize>;
|
||||||
|
@ -446,7 +483,8 @@ pub fn lint_only(
|
||||||
|
|
||||||
LinterResult {
|
LinterResult {
|
||||||
messages,
|
messages,
|
||||||
has_syntax_error: parsed.has_syntax_errors(),
|
has_valid_syntax: parsed.has_valid_syntax(),
|
||||||
|
has_no_unsupported_syntax_errors: parsed.unsupported_syntax_errors().is_empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -503,8 +541,11 @@ pub fn lint_fix<'a>(
|
||||||
// As an escape hatch, bail after 100 iterations.
|
// As an escape hatch, bail after 100 iterations.
|
||||||
let mut iterations = 0;
|
let mut iterations = 0;
|
||||||
|
|
||||||
// Track whether the _initial_ source code is valid syntax.
|
// Track whether the _initial_ source code has valid syntax.
|
||||||
let mut is_valid_syntax = false;
|
let mut has_valid_syntax = false;
|
||||||
|
|
||||||
|
// Track whether the _initial_ source code has no unsupported syntax errors.
|
||||||
|
let mut has_no_unsupported_syntax_errors = false;
|
||||||
|
|
||||||
let target_version = settings.resolve_target_version(path);
|
let target_version = settings.resolve_target_version(path);
|
||||||
|
|
||||||
|
@ -547,12 +588,13 @@ pub fn lint_fix<'a>(
|
||||||
);
|
);
|
||||||
|
|
||||||
if iterations == 0 {
|
if iterations == 0 {
|
||||||
is_valid_syntax = parsed.has_no_syntax_errors();
|
has_valid_syntax = parsed.has_valid_syntax();
|
||||||
|
has_no_unsupported_syntax_errors = parsed.unsupported_syntax_errors().is_empty();
|
||||||
} else {
|
} else {
|
||||||
// If the source code was parseable on the first pass, but is no
|
// If the source code had no syntax errors on the first pass, but
|
||||||
// longer parseable on a subsequent pass, then we've introduced a
|
// does on a subsequent pass, then we've introduced a
|
||||||
// syntax error. Return the original code.
|
// syntax error. Return the original code.
|
||||||
if is_valid_syntax {
|
if has_valid_syntax && has_no_unsupported_syntax_errors {
|
||||||
if let Some(error) = parsed.errors().first() {
|
if let Some(error) = parsed.errors().first() {
|
||||||
report_fix_syntax_error(
|
report_fix_syntax_error(
|
||||||
path,
|
path,
|
||||||
|
@ -593,7 +635,8 @@ pub fn lint_fix<'a>(
|
||||||
return Ok(FixerResult {
|
return Ok(FixerResult {
|
||||||
result: LinterResult {
|
result: LinterResult {
|
||||||
messages,
|
messages,
|
||||||
has_syntax_error: !is_valid_syntax,
|
has_valid_syntax,
|
||||||
|
has_no_unsupported_syntax_errors,
|
||||||
},
|
},
|
||||||
transformed,
|
transformed,
|
||||||
fixed,
|
fixed,
|
||||||
|
|
|
@ -10,7 +10,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use ruff_linter::package::PackageRoot;
|
use ruff_linter::package::PackageRoot;
|
||||||
use ruff_linter::{
|
use ruff_linter::{
|
||||||
linter::{FixerResult, LinterResult},
|
linter::FixerResult,
|
||||||
packaging::detect_package_root,
|
packaging::detect_package_root,
|
||||||
settings::{flags, LinterSettings},
|
settings::{flags, LinterSettings},
|
||||||
};
|
};
|
||||||
|
@ -62,9 +62,7 @@ pub(crate) fn fix_all(
|
||||||
// which is inconsistent with how `ruff check --fix` works.
|
// which is inconsistent with how `ruff check --fix` works.
|
||||||
let FixerResult {
|
let FixerResult {
|
||||||
transformed,
|
transformed,
|
||||||
result: LinterResult {
|
result,
|
||||||
has_syntax_error, ..
|
|
||||||
},
|
|
||||||
..
|
..
|
||||||
} = ruff_linter::linter::lint_fix(
|
} = ruff_linter::linter::lint_fix(
|
||||||
&query.virtual_file_path(),
|
&query.virtual_file_path(),
|
||||||
|
@ -76,7 +74,7 @@ pub(crate) fn fix_all(
|
||||||
source_type,
|
source_type,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if has_syntax_error {
|
if result.has_invalid_syntax() {
|
||||||
// If there's a syntax error, then there won't be any fixes to apply.
|
// If there's a syntax error, then there won't be any fixes to apply.
|
||||||
return Ok(Fixes::default());
|
return Ok(Fixes::default());
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ fn do_fuzz(case: &[u8]) -> Corpus {
|
||||||
ParseSource::None,
|
ParseSource::None,
|
||||||
);
|
);
|
||||||
|
|
||||||
if linter_result.has_syntax_error {
|
if linter_result.has_syntax_errors() {
|
||||||
return Corpus::Keep; // keep, but don't continue
|
return Corpus::Keep; // keep, but don't continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ fn do_fuzz(case: &[u8]) -> Corpus {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
!linter_result.has_syntax_error,
|
!linter_result.has_syntax_errors(),
|
||||||
"formatter introduced a parse error"
|
"formatter introduced a parse error"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue