feat: add ruff --statistics (#2284)

Closes #2284.
This commit is contained in:
Florian Best 2023-01-29 19:44:56 +01:00 committed by GitHub
parent 945a9e187c
commit f41796d559
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 115 additions and 1 deletions

View file

@ -211,6 +211,15 @@ pub struct CheckArgs {
update_check: bool,
#[clap(long, overrides_with("update_check"), hide = true)]
no_update_check: bool,
/// Show counts for every rule with at least one violation.
#[arg(
long,
// Unsupported default-command arguments.
conflicts_with = "diff",
conflicts_with = "show_source",
conflicts_with = "watch",
)]
pub statistics: bool,
/// Enable automatic additions of `noqa` directives to failing lines.
#[arg(
long,
@ -218,6 +227,7 @@ pub struct CheckArgs {
conflicts_with = "show_files",
conflicts_with = "show_settings",
// Unsupported default-command arguments.
conflicts_with = "statistics",
conflicts_with = "stdin_filename",
conflicts_with = "watch",
)]
@ -230,6 +240,7 @@ pub struct CheckArgs {
// conflicts_with = "show_files",
conflicts_with = "show_settings",
// Unsupported default-command arguments.
conflicts_with = "statistics",
conflicts_with = "stdin_filename",
conflicts_with = "watch",
)]
@ -242,6 +253,7 @@ pub struct CheckArgs {
conflicts_with = "show_files",
// conflicts_with = "show_settings",
// Unsupported default-command arguments.
conflicts_with = "statistics",
conflicts_with = "stdin_filename",
conflicts_with = "watch",
)]
@ -316,6 +328,7 @@ impl CheckArgs {
no_cache: self.no_cache,
show_files: self.show_files,
show_settings: self.show_settings,
statistics: self.statistics,
stdin_filename: self.stdin_filename,
watch: self.watch,
},
@ -371,6 +384,7 @@ pub struct Arguments {
pub no_cache: bool,
pub show_files: bool,
pub show_settings: bool,
pub statistics: bool,
pub stdin_filename: Option<PathBuf>,
pub watch: bool,
}

View file

@ -244,7 +244,11 @@ fn check(args: CheckArgs, log_level: LogLevel) -> Result<ExitCode> {
// unless we're writing fixes via stdin (in which case, the transformed
// source code goes to stdout).
if !(is_stdin && matches!(autofix, fix::FixMode::Apply | fix::FixMode::Diff)) {
printer.write_once(&diagnostics)?;
if cli.statistics {
printer.write_statistics(&diagnostics)?;
} else {
printer.write_once(&diagnostics)?;
}
}
// Check for updates if we're in a non-silent log level.

View file

@ -43,6 +43,13 @@ struct ExpandedMessage<'a> {
filename: &'a str,
}
#[derive(Serialize)]
struct ExpandedStatistics<'a> {
count: usize,
code: &'a str,
message: String,
}
struct SerializeRuleAsCode<'a>(&'a Rule);
impl Serialize for SerializeRuleAsCode<'_> {
@ -336,6 +343,77 @@ impl<'a> Printer<'a> {
Ok(())
}
pub fn write_statistics(&self, diagnostics: &Diagnostics) -> Result<()> {
let mut violations = diagnostics
.messages
.iter()
.map(|message| message.kind.rule())
.collect::<Vec<_>>();
violations.sort();
violations.dedup();
let statistics = violations
.iter()
.map(|rule| ExpandedStatistics {
code: rule.code(),
count: diagnostics
.messages
.iter()
.filter(|message| message.kind.rule() == *rule)
.count(),
message: diagnostics
.messages
.iter()
.find(|message| message.kind.rule() == *rule)
.map(|message| message.kind.body())
.unwrap(),
})
.collect::<Vec<_>>();
let mut stdout = BufWriter::new(io::stdout().lock());
match self.format {
SerializationFormat::Text => {
// Compute the maximum number of digits in the count and code, for all messages, to enable
// pretty-printing.
let count_width = num_digits(
statistics
.iter()
.map(|statistic| statistic.count)
.max()
.unwrap(),
);
let code_width = statistics
.iter()
.map(|statistic| statistic.code.len())
.max()
.unwrap();
// By default, we mimic Flake8's `--statistics` format.
for msg in statistics {
writeln!(
stdout,
"{:>count_width$}\t{:<code_width$}\t{}",
msg.count, msg.code, msg.message
)?;
}
return Ok(());
}
SerializationFormat::Json => {
writeln!(stdout, "{}", serde_json::to_string_pretty(&statistics)?)?;
}
_ => {
anyhow::bail!(
"Unsupported serialization format for statistics: {:?}",
self.format
)
}
}
stdout.flush()?;
Ok(())
}
pub fn write_continuously(&self, diagnostics: &Diagnostics) -> Result<()> {
if matches!(self.log_level, LogLevel::Silent) {
return Ok(());

View file

@ -182,3 +182,21 @@ fn explain_status_codes() -> Result<()> {
cmd.args(["--explain", "RUF404"]).assert().failure();
Ok(())
}
#[test]
fn show_statistics() -> Result<()> {
let mut cmd = Command::cargo_bin(BIN_NAME)?;
let output = cmd
.args(["-", "--format", "text", "--select", "F401", "--statistics"])
.write_stdin("import sys\nimport os\n\nprint(os.getuid())\n")
.assert()
.failure();
assert_eq!(
str::from_utf8(&output.get_output().stdout)?
.lines()
.last()
.unwrap(),
"1\tF401\t`sys` imported but unused"
);
Ok(())
}