Skip to content

Commit d40e9c6

Browse files
committed
Better handling for panics during linting
- Convert panics to diagnostics with id `Panic`, severity `Fatal`, and the error as the diagnostic message, annotated with a `Span` with empty code block and no range. - Updates the post-linting message diagnostic handling to track the maximum severity seen, and then prints the "report a bug in ruff" message only if the max severity was `Fatal` - Adds a new test rule to exercise panics in lint rules This depends on the sorting changes since it creates diagnostics with no range specified.
1 parent a27c648 commit d40e9c6

File tree

5 files changed

+69
-14
lines changed

5 files changed

+69
-14
lines changed

crates/ruff/src/commands/check.rs

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ use std::time::Instant;
66
use anyhow::Result;
77
use colored::Colorize;
88
use ignore::Error;
9-
use log::{debug, error, warn};
9+
use log::{debug, warn};
1010
#[cfg(not(target_family = "wasm"))]
1111
use rayon::prelude::*;
1212
use rustc_hash::FxHashMap;
1313

14-
use ruff_db::diagnostic::Diagnostic;
14+
use ruff_db::diagnostic::{Annotation, Diagnostic, DiagnosticId, Span};
1515
use ruff_db::panic::catch_unwind;
1616
use ruff_linter::package::PackageRoot;
1717
use ruff_linter::registry::Rule;
@@ -193,21 +193,20 @@ fn lint_path(
193193
match result {
194194
Ok(inner) => inner,
195195
Err(error) => {
196-
let message = r"This indicates a bug in Ruff. If you could open an issue at:
197-
198-
https://github.com/astral-sh/ruff/issues/new?title=%5BLinter%20panic%5D
199-
200-
...with the relevant file contents, the `pyproject.toml` settings, and the following stack trace, we'd be very appreciative!
201-
";
202-
203-
error!(
204-
"{}{}{} {message}\n{error}",
196+
let message = format!(
197+
"{}{}{}\n{error}",
205198
"Panicked while linting ".bold(),
206199
fs::relativize_path(path).bold(),
207200
":".bold()
208201
);
209-
210-
Ok(Diagnostics::default())
202+
let mut diagnostic = Diagnostic::new(
203+
DiagnosticId::Panic,
204+
ruff_db::diagnostic::Severity::Fatal,
205+
message,
206+
);
207+
let span = Span::from(SourceFileBuilder::new(path.to_string_lossy(), "").finish());
208+
diagnostic.annotate(Annotation::primary(span));
209+
Ok(Diagnostics::new(vec![diagnostic], FxHashMap::default()))
211210
}
212211
}
213212
}

crates/ruff/src/lib.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ use std::sync::mpsc::channel;
99
use anyhow::Result;
1010
use clap::CommandFactory;
1111
use colored::Colorize;
12-
use log::warn;
12+
use log::{error, warn};
1313
use notify::{RecursiveMode, Watcher, recommended_watcher};
1414

1515
use args::{GlobalConfigArgs, ServerCommand};
16+
use ruff_db::diagnostic::{Diagnostic, Severity};
1617
use ruff_linter::logging::{LogLevel, set_up_logging};
1718
use ruff_linter::settings::flags::FixMode;
1819
use ruff_linter::settings::types::OutputFormat;
@@ -444,6 +445,22 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
444445
}
445446

446447
if !cli.exit_zero {
448+
let max_severity = diagnostics
449+
.inner
450+
.iter()
451+
.map(Diagnostic::severity)
452+
.max()
453+
.unwrap_or(Severity::Info);
454+
if max_severity.is_fatal() {
455+
let message = "Panic during linting indicates a bug in Ruff. If you could open an issue at:
456+
457+
https://github.com/astral-sh/ruff/issues/new?title=%5BLinter%20panic%5D
458+
459+
...with the relevant file contents, the `pyproject.toml` settings, and the stack trace above, we'd be very appreciative!
460+
";
461+
error!("{message}");
462+
return Ok(ExitStatus::Error);
463+
}
447464
if cli.diff {
448465
// If we're printing a diff, we always want to exit non-zero if there are
449466
// any fixable violations (since we've printed the diff, but not applied the

crates/ruff_linter/src/codes.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,6 +1080,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
10801080
(Ruff, "950") => (RuleGroup::Stable, rules::ruff::rules::RedirectedToTestRule),
10811081
#[cfg(any(feature = "test-rules", test))]
10821082
(Ruff, "960") => (RuleGroup::Removed, rules::ruff::rules::RedirectedFromPrefixTestRule),
1083+
#[cfg(any(feature = "test-rules", test))]
1084+
(Ruff, "990") => (RuleGroup::Preview, rules::ruff::rules::PanicyTestRule),
10831085

10841086

10851087
// flake8-django

crates/ruff_linter/src/linter.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,9 @@ pub fn check_path(
319319
&context,
320320
);
321321
}
322+
Rule::PanicyTestRule => {
323+
test_rules::PanicyTestRule::diagnostic(locator, comment_ranges, &context);
324+
}
322325
_ => unreachable!("All test rules must have an implementation"),
323326
}
324327
}

crates/ruff_linter/src/rules/ruff/rules/test_rules.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ pub(crate) const TEST_RULES: &[Rule] = &[
4646
Rule::RedirectedFromTestRule,
4747
Rule::RedirectedToTestRule,
4848
Rule::RedirectedFromPrefixTestRule,
49+
Rule::PanicyTestRule,
4950
];
5051

5152
pub(crate) trait TestRule {
@@ -477,3 +478,36 @@ impl TestRule for RedirectedFromPrefixTestRule {
477478
);
478479
}
479480
}
481+
482+
/// # What it does
483+
/// Fake rule for testing panics.
484+
///
485+
/// # Why is this bad?
486+
/// Panics are bad.
487+
///
488+
/// # Example
489+
/// ```python
490+
/// foo
491+
/// ```
492+
///
493+
/// Use instead:
494+
/// ```python
495+
/// bar
496+
/// ```
497+
#[derive(ViolationMetadata)]
498+
pub(crate) struct PanicyTestRule;
499+
500+
impl Violation for PanicyTestRule {
501+
const FIX_AVAILABILITY: FixAvailability = FixAvailability::None;
502+
503+
#[derive_message_formats]
504+
fn message(&self) -> String {
505+
"If you see this, maybe panic!".to_string()
506+
}
507+
}
508+
509+
impl TestRule for PanicyTestRule {
510+
fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges, _context: &LintContext) {
511+
panic!("Intentional panic!");
512+
}
513+
}

0 commit comments

Comments
 (0)