mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-07 00:50:37 +00:00
Gracefully handle lint panics (#3509)
This commit is contained in:
parent
f06dff8af8
commit
9ac9a1c69e
5 changed files with 93 additions and 10 deletions
|
@ -1,6 +1,6 @@
|
|||
use std::fmt::Write;
|
||||
use std::io;
|
||||
use std::io::BufWriter;
|
||||
use std::io::Write;
|
||||
|
||||
use anyhow::Result;
|
||||
use itertools::Itertools;
|
||||
|
@ -41,7 +41,7 @@ pub fn linter(format: HelpFormat) -> Result<()> {
|
|||
.join("/"),
|
||||
prefix => prefix.to_string(),
|
||||
};
|
||||
output.push_str(&format!("{:>4} {}\n", prefix, linter.name()));
|
||||
writeln!(output, "{:>4} {}", prefix, linter.name()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,7 @@ pub fn linter(format: HelpFormat) -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
write!(stdout, "{output}")?;
|
||||
io::Write::write_fmt(&mut stdout, format_args!("{output}"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,24 +1,25 @@
|
|||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::Result;
|
||||
use colored::Colorize;
|
||||
use ignore::Error;
|
||||
use log::{debug, error};
|
||||
use log::{debug, error, warn};
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use rayon::prelude::*;
|
||||
|
||||
use crate::panic::catch_unwind;
|
||||
use ruff::message::{Location, Message};
|
||||
use ruff::registry::Rule;
|
||||
use ruff::resolver::PyprojectDiscovery;
|
||||
use ruff::settings::flags;
|
||||
use ruff::settings::{flags, AllSettings};
|
||||
use ruff::{fix, fs, packaging, resolver, warn_user_once, IOError, Range};
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
|
||||
use crate::args::Overrides;
|
||||
use crate::cache;
|
||||
use crate::diagnostics::{lint_path, Diagnostics};
|
||||
use crate::diagnostics::Diagnostics;
|
||||
|
||||
/// Run the linter over a collection of files.
|
||||
pub fn run(
|
||||
|
@ -83,6 +84,7 @@ pub fn run(
|
|||
.and_then(|parent| package_roots.get(parent))
|
||||
.and_then(|package| *package);
|
||||
let settings = resolver.resolve_all(path, pyproject_strategy);
|
||||
|
||||
lint_path(path, package, settings, cache, noqa, autofix)
|
||||
.map_err(|e| (Some(path.to_owned()), e.to_string()))
|
||||
}
|
||||
|
@ -135,3 +137,39 @@ pub fn run(
|
|||
|
||||
Ok(diagnostics)
|
||||
}
|
||||
|
||||
/// Wraps [`lint_path`](crate::diagnostics::lint_path) in a [`catch_unwind`](std::panic::catch_unwind) and emits
|
||||
/// a diagnostic if the linting the file panics.
|
||||
fn lint_path(
|
||||
path: &Path,
|
||||
package: Option<&Path>,
|
||||
settings: &AllSettings,
|
||||
cache: flags::Cache,
|
||||
noqa: flags::Noqa,
|
||||
autofix: fix::FixMode,
|
||||
) -> Result<Diagnostics> {
|
||||
let result = catch_unwind(|| {
|
||||
crate::diagnostics::lint_path(path, package, settings, cache, noqa, autofix)
|
||||
});
|
||||
|
||||
match result {
|
||||
Ok(inner) => inner,
|
||||
Err(error) => {
|
||||
let message = r#"This indicates a bug in `ruff`. If you could open an issue at:
|
||||
|
||||
https://github.com/charliermarsh/ruff/issues/new?title=%5BLinter%20panic%5D
|
||||
|
||||
with the relevant file contents, the `pyproject.toml` settings, and the following stack trace, we'd be very appreciative!
|
||||
"#;
|
||||
|
||||
warn!(
|
||||
"{}{}{} {message}\n{error}",
|
||||
"Linting panicked ".bold(),
|
||||
fs::relativize_path(path).bold(),
|
||||
":".bold()
|
||||
);
|
||||
|
||||
Ok(Diagnostics::default())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ pub mod args;
|
|||
mod cache;
|
||||
mod commands;
|
||||
mod diagnostics;
|
||||
mod panic;
|
||||
mod printer;
|
||||
mod resolve;
|
||||
|
||||
|
@ -46,7 +47,6 @@ pub fn run(
|
|||
log_level_args,
|
||||
}: Args,
|
||||
) -> Result<ExitStatus> {
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
use colored::Colorize;
|
||||
|
||||
|
|
46
crates/ruff_cli/src/panic.rs
Normal file
46
crates/ruff_cli/src/panic.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
#[derive(Default, Debug)]
|
||||
pub(crate) struct PanicError {
|
||||
pub info: String,
|
||||
pub backtrace: Option<std::backtrace::Backtrace>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for PanicError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "{}", self.info)?;
|
||||
if let Some(backtrace) = &self.backtrace {
|
||||
writeln!(f, "Backtrace: {backtrace}")
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static LAST_PANIC: std::cell::Cell<Option<PanicError>> = std::cell::Cell::new(None);
|
||||
}
|
||||
|
||||
/// [`catch_unwind`](std::panic::catch_unwind) wrapper that sets a custom [`set_hook`](std::panic::set_hook)
|
||||
/// to extract the backtrace. The original panic-hook gets restored before returning.
|
||||
pub(crate) fn catch_unwind<F, R>(f: F) -> Result<R, PanicError>
|
||||
where
|
||||
F: FnOnce() -> R + std::panic::UnwindSafe,
|
||||
{
|
||||
let prev = std::panic::take_hook();
|
||||
std::panic::set_hook(Box::new(|info| {
|
||||
let info = info.to_string();
|
||||
let backtrace = std::backtrace::Backtrace::force_capture();
|
||||
LAST_PANIC.with(|cell| {
|
||||
cell.set(Some(PanicError {
|
||||
info,
|
||||
backtrace: Some(backtrace),
|
||||
}));
|
||||
});
|
||||
}));
|
||||
|
||||
let result = std::panic::catch_unwind(f)
|
||||
.map_err(|_| LAST_PANIC.with(std::cell::Cell::take).unwrap_or_default());
|
||||
|
||||
std::panic::set_hook(prev);
|
||||
|
||||
result
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue