mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 18:58:04 +00:00

Some checks are pending
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
Summary
--
This PR unifies the remaining differences between `OldDiagnostic` and
`Message` (`OldDiagnostic` was only missing an optional `noqa_offset`
field) and
replaces `Message` with `OldDiagnostic`.
The biggest functional difference is that the combined `OldDiagnostic`
kind no
longer implements `AsRule` for an infallible conversion to `Rule`. This
was
pretty easy to work around with `is_some_and` and `is_none_or` in the
few places
it was needed. In `LintContext::report_diagnostic_if_enabled` we can
just use
the new `Violation::rule` method, which takes care of most cases.
Most of the interesting changes are in [this
range](8156992540
)
before I started renaming.
Test Plan
--
Existing tests
Future Work
--
I think it's time to start shifting some of these fields to the new
`Diagnostic`
kind. I believe we want `Fix` for sure, but I'm less sure about the
others. We
may want to keep a thin wrapper type here anyway to implement a `rule`
method,
so we could leave some of these fields on that too.
96 lines
3.1 KiB
Rust
96 lines
3.1 KiB
Rust
//! Fuzzer harness which actively tries to find testcases that cause Ruff to introduce errors into
|
|
//! the resulting file.
|
|
|
|
#![no_main]
|
|
|
|
use std::collections::HashMap;
|
|
use std::sync::OnceLock;
|
|
|
|
use libfuzzer_sys::{fuzz_target, Corpus};
|
|
use ruff_linter::linter::ParseSource;
|
|
use ruff_linter::settings::flags::Noqa;
|
|
use ruff_linter::settings::LinterSettings;
|
|
use ruff_linter::source_kind::SourceKind;
|
|
use ruff_python_ast::PySourceType;
|
|
use ruff_python_formatter::{format_module_source, PyFormatOptions};
|
|
use similar::TextDiff;
|
|
|
|
static SETTINGS: OnceLock<LinterSettings> = OnceLock::new();
|
|
|
|
fn do_fuzz(case: &[u8]) -> Corpus {
|
|
// throw away inputs which aren't utf-8
|
|
let Ok(code) = std::str::from_utf8(case) else {
|
|
return Corpus::Reject;
|
|
};
|
|
|
|
// the settings are immutable to test_snippet, so we avoid re-initialising here
|
|
let linter_settings = SETTINGS.get_or_init(LinterSettings::default);
|
|
let format_options = PyFormatOptions::default();
|
|
|
|
let linter_result = ruff_linter::linter::lint_only(
|
|
"fuzzed-source.py".as_ref(),
|
|
None,
|
|
linter_settings,
|
|
Noqa::Enabled,
|
|
&SourceKind::Python(code.to_string()),
|
|
PySourceType::Python,
|
|
ParseSource::None,
|
|
);
|
|
|
|
if linter_result.has_syntax_errors() {
|
|
return Corpus::Keep; // keep, but don't continue
|
|
}
|
|
|
|
let mut warnings = HashMap::new();
|
|
|
|
for msg in &linter_result.diagnostics {
|
|
let count: &mut usize = warnings.entry(msg.name()).or_default();
|
|
*count += 1;
|
|
}
|
|
|
|
// format the code once
|
|
if let Ok(formatted) = format_module_source(code, format_options.clone()) {
|
|
let formatted = formatted.as_code().to_string();
|
|
|
|
let linter_result = ruff_linter::linter::lint_only(
|
|
"fuzzed-source.py".as_ref(),
|
|
None,
|
|
linter_settings,
|
|
Noqa::Enabled,
|
|
&SourceKind::Python(formatted.clone()),
|
|
PySourceType::Python,
|
|
ParseSource::None,
|
|
);
|
|
|
|
assert!(
|
|
!linter_result.has_syntax_errors(),
|
|
"formatter introduced a parse error"
|
|
);
|
|
|
|
for msg in &linter_result.diagnostics {
|
|
if let Some(count) = warnings.get_mut(msg.name()) {
|
|
if let Some(decremented) = count.checked_sub(1) {
|
|
*count = decremented;
|
|
} else {
|
|
panic!(
|
|
"formatter introduced additional linter warning: {msg:?}\ndiff: {}",
|
|
TextDiff::from_lines(code, &formatted)
|
|
.unified_diff()
|
|
.header("Unformatted", "Formatted")
|
|
);
|
|
}
|
|
} else {
|
|
panic!(
|
|
"formatter introduced new linter warning that was not previously present: {msg:?}\ndiff: {}",
|
|
TextDiff::from_lines(code, &formatted)
|
|
.unified_diff()
|
|
.header("Unformatted", "Formatted")
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
Corpus::Keep
|
|
}
|
|
|
|
fuzz_target!(|case: &[u8]| -> Corpus { do_fuzz(case) });
|