mirror of https://github.com/astral-sh/ruff
[`ruff`] Handle argfile expansion errors gracefully (#20691)
<!-- Thank you for contributing to Ruff/ty! To help us out with reviewing, please consider the following: - Does this pull request include a summary of the change? (See below.) - Does this pull request include a descriptive title? (Please prefix with `[ty]` for ty pull requests.) - Does this pull request include references to any relevant issues? --> ## Summary <!-- What's the purpose of the change? What does it do, and why? --> Fixes #20655 - Guard `argfile::expand_args_from` with contextual error handling so missing @file arguments surface a friendly failure instead of panicking. - Extract existing stderr reporting into `report_error` for reuse on both CLI parsing failures and runtime errors. ## Test Plan <!-- How was it tested? --> Add a regression test to integration_test.rs. --------- Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
This commit is contained in:
parent
542f080035
commit
7d7237c660
|
|
@ -1,6 +1,7 @@
|
|||
use std::io::Write;
|
||||
use std::process::ExitCode;
|
||||
|
||||
use anyhow::Context;
|
||||
use clap::Parser;
|
||||
use colored::Colorize;
|
||||
|
||||
|
|
@ -39,39 +40,46 @@ pub fn main() -> ExitCode {
|
|||
}
|
||||
|
||||
let args = wild::args_os();
|
||||
let args = argfile::expand_args_from(args, argfile::parse_fromfile, argfile::PREFIX).unwrap();
|
||||
let args = match argfile::expand_args_from(args, argfile::parse_fromfile, argfile::PREFIX)
|
||||
.context("Failed to read CLI arguments from files")
|
||||
{
|
||||
Ok(args) => args,
|
||||
Err(err) => return report_error(&err),
|
||||
};
|
||||
|
||||
let args = Args::parse_from(args);
|
||||
|
||||
match run(args) {
|
||||
Ok(code) => code.into(),
|
||||
Err(err) => {
|
||||
{
|
||||
// Exit "gracefully" on broken pipe errors.
|
||||
//
|
||||
// See: https://github.com/BurntSushi/ripgrep/blob/bf63fe8f258afc09bae6caa48f0ae35eaf115005/crates/core/main.rs#L47C1-L61C14
|
||||
for cause in err.chain() {
|
||||
if let Some(ioerr) = cause.downcast_ref::<std::io::Error>() {
|
||||
if ioerr.kind() == std::io::ErrorKind::BrokenPipe {
|
||||
return ExitCode::from(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use `writeln` instead of `eprintln` to avoid panicking when the stderr pipe is broken.
|
||||
let mut stderr = std::io::stderr().lock();
|
||||
|
||||
// This communicates that this isn't a linter error but ruff itself hard-errored for
|
||||
// some reason (e.g. failed to resolve the configuration)
|
||||
writeln!(stderr, "{}", "ruff failed".red().bold()).ok();
|
||||
// Currently we generally only see one error, but e.g. with io errors when resolving
|
||||
// the configuration it is help to chain errors ("resolving configuration failed" ->
|
||||
// "failed to read file: subdir/pyproject.toml")
|
||||
for cause in err.chain() {
|
||||
writeln!(stderr, " {} {cause}", "Cause:".bold()).ok();
|
||||
}
|
||||
}
|
||||
ExitStatus::Error.into()
|
||||
}
|
||||
Err(err) => report_error(&err),
|
||||
}
|
||||
}
|
||||
|
||||
fn report_error(err: &anyhow::Error) -> ExitCode {
|
||||
{
|
||||
// Exit "gracefully" on broken pipe errors.
|
||||
//
|
||||
// See: https://github.com/BurntSushi/ripgrep/blob/bf63fe8f258afc09bae6caa48f0ae35eaf115005/crates/core/main.rs#L47C1-L61C14
|
||||
for cause in err.chain() {
|
||||
if let Some(ioerr) = cause.downcast_ref::<std::io::Error>() {
|
||||
if ioerr.kind() == std::io::ErrorKind::BrokenPipe {
|
||||
return ExitCode::from(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use `writeln` instead of `eprintln` to avoid panicking when the stderr pipe is broken.
|
||||
let mut stderr = std::io::stderr().lock();
|
||||
|
||||
// This communicates that this isn't a linter error but ruff itself hard-errored for
|
||||
// some reason (e.g. failed to resolve the configuration)
|
||||
writeln!(stderr, "{}", "ruff failed".red().bold()).ok();
|
||||
// Currently we generally only see one error, but e.g. with io errors when resolving
|
||||
// the configuration it is help to chain errors ("resolving configuration failed" ->
|
||||
// "failed to read file: subdir/pyproject.toml")
|
||||
for cause in err.chain() {
|
||||
writeln!(stderr, " {} {cause}", "Cause:".bold()).ok();
|
||||
}
|
||||
}
|
||||
ExitStatus::Error.into()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1688,6 +1688,27 @@ fn check_input_from_argfile() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Regression test for https://github.com/astral-sh/ruff/issues/20655
|
||||
fn missing_argfile_reports_error() {
|
||||
let mut cmd = RuffCheck::default().filename("@!.txt").build();
|
||||
insta::with_settings!({filters => vec![
|
||||
("The system cannot find the file specified.", "No such file or directory")
|
||||
]}, {
|
||||
assert_cmd_snapshot!(cmd, @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
ruff failed
|
||||
Cause: Failed to read CLI arguments from files
|
||||
Cause: failed to open file `!.txt`
|
||||
Cause: No such file or directory (os error 2)
|
||||
");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_hints_hidden_unsafe_fixes() {
|
||||
let mut cmd = RuffCheck::default()
|
||||
|
|
|
|||
Loading…
Reference in New Issue