mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 13:51:37 +00:00
Enable prefix-based check code selection (#493)
This commit is contained in:
parent
9ca1a2c273
commit
8fc5e91ec7
9 changed files with 1233 additions and 55 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -466,6 +466,15 @@ dependencies = [
|
|||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codegen"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff61280aed771c3070e7dcc9e050c66f1eb1e3b96431ba66f9f74641d02fc41d"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.11.1"
|
||||
|
@ -2054,6 +2063,7 @@ dependencies = [
|
|||
"chrono",
|
||||
"clap",
|
||||
"clearscreen",
|
||||
"codegen",
|
||||
"colored",
|
||||
"common-path",
|
||||
"dirs 4.0.0",
|
||||
|
|
|
@ -50,6 +50,7 @@ getrandom = { version = "0.2.7", features = ["js"] }
|
|||
|
||||
[dev-dependencies]
|
||||
assert_cmd = { version = "2.0.4" }
|
||||
codegen = { version = "0.2.0" }
|
||||
insta = { version = "1.19.1", features = ["yaml"] }
|
||||
test-case = { version = "2.2.2" }
|
||||
|
||||
|
|
105
examples/generate_check_code_prefix.rs
Normal file
105
examples/generate_check_code_prefix.rs
Normal file
|
@ -0,0 +1,105 @@
|
|||
//! Generate the CheckCodePrefix enum.
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use codegen::{Scope, Type, Variant};
|
||||
use itertools::Itertools;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use ruff::checks::CheckCode;
|
||||
|
||||
fn main() {
|
||||
// Build up a map from prefix to matching CheckCodes.
|
||||
let mut prefix_to_codes: BTreeMap<String, BTreeSet<CheckCode>> = Default::default();
|
||||
for check_code in CheckCode::iter() {
|
||||
let as_ref = check_code.as_ref().to_string();
|
||||
for i in 1..=as_ref.len() {
|
||||
let prefix = as_ref[..i].to_string();
|
||||
let entry = prefix_to_codes
|
||||
.entry(prefix)
|
||||
.or_insert_with(|| Default::default());
|
||||
entry.insert(check_code.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Create the `CheckCodePrefix` definition.
|
||||
let mut gen = scope
|
||||
.new_enum("CheckCodePrefix")
|
||||
.vis("pub")
|
||||
.derive("EnumString")
|
||||
.derive("Debug")
|
||||
.derive("PartialEq")
|
||||
.derive("Eq")
|
||||
.derive("Clone")
|
||||
.derive("Serialize")
|
||||
.derive("Deserialize");
|
||||
for (prefix, _) in &prefix_to_codes {
|
||||
gen = gen.push_variant(Variant::new(prefix.to_string()));
|
||||
}
|
||||
|
||||
// Create the `PrefixSpecificity` definition.
|
||||
scope
|
||||
.new_enum("PrefixSpecificity")
|
||||
.vis("pub")
|
||||
.derive("PartialEq")
|
||||
.derive("Eq")
|
||||
.derive("PartialOrd")
|
||||
.derive("Ord")
|
||||
.push_variant(Variant::new("Category"))
|
||||
.push_variant(Variant::new("Hundreds"))
|
||||
.push_variant(Variant::new("Tens"))
|
||||
.push_variant(Variant::new("Explicit"));
|
||||
|
||||
// Create the `match` statement, to map from definition to relevant codes.
|
||||
let mut gen = scope
|
||||
.new_impl("CheckCodePrefix")
|
||||
.new_fn("codes")
|
||||
.arg_ref_self()
|
||||
.ret(Type::new("Vec<CheckCode>"))
|
||||
.vis("pub")
|
||||
.line("match self {");
|
||||
for (prefix, codes) in &prefix_to_codes {
|
||||
gen = gen.line(format!(
|
||||
"CheckCodePrefix::{prefix} => vec![{}],",
|
||||
codes
|
||||
.iter()
|
||||
.map(|code| format!("CheckCode::{}", code.as_ref()))
|
||||
.join(", ")
|
||||
));
|
||||
}
|
||||
gen.line("}");
|
||||
|
||||
// Create the `match` statement, to map from definition to specificity.
|
||||
let mut gen = scope
|
||||
.new_impl("CheckCodePrefix")
|
||||
.new_fn("specificity")
|
||||
.arg_ref_self()
|
||||
.ret(Type::new("PrefixSpecificity"))
|
||||
.vis("pub")
|
||||
.line("match self {");
|
||||
for (prefix, _) in &prefix_to_codes {
|
||||
let specificity = match prefix.len() {
|
||||
4 => "Explicit",
|
||||
3 => "Tens",
|
||||
2 => "Hundreds",
|
||||
1 => "Category",
|
||||
_ => panic!("Invalid prefix: {}", prefix),
|
||||
};
|
||||
gen = gen.line(format!(
|
||||
"CheckCodePrefix::{prefix} => PrefixSpecificity::{},",
|
||||
specificity
|
||||
));
|
||||
}
|
||||
gen.line("}");
|
||||
|
||||
println!("//! File automatically generated by examples/generate_check_code_prefix.rs.");
|
||||
println!();
|
||||
println!("use serde::{{Deserialize, Serialize}};");
|
||||
println!("use strum_macros::EnumString;");
|
||||
println!();
|
||||
println!("use crate::checks::CheckCode;");
|
||||
println!();
|
||||
println!("{}", scope.to_string());
|
||||
}
|
1031
src/checks_gen.rs
Normal file
1031
src/checks_gen.rs
Normal file
File diff suppressed because it is too large
Load diff
16
src/cli.rs
16
src/cli.rs
|
@ -5,7 +5,7 @@ use clap::{command, Parser};
|
|||
use log::warn;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::printer::SerializationFormat;
|
||||
use crate::pyproject::StrCheckCodePair;
|
||||
use crate::settings::PythonVersion;
|
||||
|
@ -43,16 +43,16 @@ pub struct Cli {
|
|||
pub no_cache: bool,
|
||||
/// List of error codes to enable.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub select: Vec<CheckCode>,
|
||||
pub select: Vec<CheckCodePrefix>,
|
||||
/// Like --select, but adds additional error codes on top of the selected ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extend_select: Vec<CheckCode>,
|
||||
pub extend_select: Vec<CheckCodePrefix>,
|
||||
/// List of error codes to ignore.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub ignore: Vec<CheckCode>,
|
||||
pub ignore: Vec<CheckCodePrefix>,
|
||||
/// Like --ignore, but adds additional error codes on top of the ignored ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extend_ignore: Vec<CheckCode>,
|
||||
pub extend_ignore: Vec<CheckCodePrefix>,
|
||||
/// List of paths, used to exclude files and/or directories from checks.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub exclude: Vec<String>,
|
||||
|
@ -106,9 +106,9 @@ impl fmt::Display for Warnable {
|
|||
/// Warn the user if they attempt to enable a code that won't be respected.
|
||||
pub fn warn_on(
|
||||
flag: Warnable,
|
||||
codes: &[CheckCode],
|
||||
cli_ignore: &[CheckCode],
|
||||
cli_extend_ignore: &[CheckCode],
|
||||
codes: &[CheckCodePrefix],
|
||||
cli_ignore: &[CheckCodePrefix],
|
||||
cli_extend_ignore: &[CheckCodePrefix],
|
||||
pyproject_settings: &RawSettings,
|
||||
pyproject_path: &Option<PathBuf>,
|
||||
) {
|
||||
|
|
|
@ -133,7 +133,7 @@ pub fn ignores_from_path<'a>(
|
|||
[&pattern_code_pair.pattern].into_iter(),
|
||||
)
|
||||
})
|
||||
.map(|pattern_code_pair| &pattern_code_pair.code)
|
||||
.flat_map(|pattern_code_pair| &pattern_code_pair.codes)
|
||||
.collect())
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ pub mod check_ast;
|
|||
mod check_lines;
|
||||
mod check_tokens;
|
||||
pub mod checks;
|
||||
mod checks_gen;
|
||||
pub mod cli;
|
||||
pub mod code_gen;
|
||||
mod cst;
|
||||
|
|
|
@ -7,7 +7,7 @@ use path_absolutize::Absolutize;
|
|||
use serde::de;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::settings::PythonVersion;
|
||||
use crate::{flake8_quotes, fs};
|
||||
|
||||
|
@ -34,13 +34,13 @@ pub struct Config {
|
|||
pub exclude: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
pub extend_exclude: Vec<String>,
|
||||
pub select: Option<Vec<CheckCode>>,
|
||||
pub select: Option<Vec<CheckCodePrefix>>,
|
||||
#[serde(default)]
|
||||
pub extend_select: Vec<CheckCode>,
|
||||
pub extend_select: Vec<CheckCodePrefix>,
|
||||
#[serde(default)]
|
||||
pub ignore: Vec<CheckCode>,
|
||||
pub ignore: Vec<CheckCodePrefix>,
|
||||
#[serde(default)]
|
||||
pub extend_ignore: Vec<CheckCode>,
|
||||
pub extend_ignore: Vec<CheckCodePrefix>,
|
||||
#[serde(default)]
|
||||
pub per_file_ignores: Vec<StrCheckCodePair>,
|
||||
pub dummy_variable_rgx: Option<String>,
|
||||
|
@ -51,7 +51,7 @@ pub struct Config {
|
|||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct StrCheckCodePair {
|
||||
pub pattern: String,
|
||||
pub code: CheckCode,
|
||||
pub code: CheckCodePrefix,
|
||||
}
|
||||
|
||||
impl StrCheckCodePair {
|
||||
|
@ -84,7 +84,7 @@ impl FromStr for StrCheckCodePair {
|
|||
}
|
||||
(tokens[0].trim(), tokens[1].trim())
|
||||
};
|
||||
let code = CheckCode::from_str(code_string)?;
|
||||
let code = CheckCodePrefix::from_str(code_string)?;
|
||||
let pattern = pattern_str.into();
|
||||
Ok(Self { pattern, code })
|
||||
}
|
||||
|
@ -157,7 +157,7 @@ mod tests {
|
|||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::flake8_quotes;
|
||||
use crate::flake8_quotes::settings::Quote;
|
||||
use crate::pyproject::{
|
||||
|
@ -269,7 +269,7 @@ select = ["E501"]
|
|||
line_length: None,
|
||||
exclude: None,
|
||||
extend_exclude: vec![],
|
||||
select: Some(vec![CheckCode::E501]),
|
||||
select: Some(vec![CheckCodePrefix::E501]),
|
||||
extend_select: vec![],
|
||||
ignore: vec![],
|
||||
extend_ignore: vec![],
|
||||
|
@ -297,8 +297,8 @@ ignore = ["E501"]
|
|||
exclude: None,
|
||||
extend_exclude: vec![],
|
||||
select: None,
|
||||
extend_select: vec![CheckCode::M001],
|
||||
ignore: vec![CheckCode::E501],
|
||||
extend_select: vec![CheckCodePrefix::M001],
|
||||
ignore: vec![CheckCodePrefix::E501],
|
||||
extend_ignore: vec![],
|
||||
per_file_ignores: vec![],
|
||||
dummy_variable_rgx: None,
|
||||
|
@ -372,7 +372,7 @@ other-attribute = 1
|
|||
extend_ignore: vec![],
|
||||
per_file_ignores: vec![StrCheckCodePair {
|
||||
pattern: "__init__.py".to_string(),
|
||||
code: CheckCode::F401
|
||||
code: CheckCodePrefix::F401
|
||||
}],
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
|
|
|
@ -8,9 +8,9 @@ use glob::Pattern;
|
|||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::checks::{CheckCategory, CheckCode};
|
||||
use crate::checks::CheckCode;
|
||||
use crate::checks_gen::{CheckCodePrefix, PrefixSpecificity};
|
||||
use crate::pyproject::{load_config, StrCheckCodePair};
|
||||
use crate::{flake8_quotes, fs};
|
||||
|
||||
|
@ -74,14 +74,14 @@ impl FilePattern {
|
|||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct PerFileIgnore {
|
||||
pub pattern: FilePattern,
|
||||
pub code: CheckCode,
|
||||
pub codes: BTreeSet<CheckCode>,
|
||||
}
|
||||
|
||||
impl PerFileIgnore {
|
||||
pub fn new(user_in: StrCheckCodePair, project_root: &Option<PathBuf>) -> Self {
|
||||
let pattern = FilePattern::from_user(user_in.pattern.as_str(), project_root);
|
||||
let code = user_in.code;
|
||||
Self { pattern, code }
|
||||
let codes = BTreeSet::from_iter(user_in.code.codes());
|
||||
Self { pattern, codes }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,12 +90,12 @@ pub struct RawSettings {
|
|||
pub dummy_variable_rgx: Regex,
|
||||
pub exclude: Vec<FilePattern>,
|
||||
pub extend_exclude: Vec<FilePattern>,
|
||||
pub extend_ignore: Vec<CheckCode>,
|
||||
pub extend_select: Vec<CheckCode>,
|
||||
pub ignore: Vec<CheckCode>,
|
||||
pub extend_ignore: Vec<CheckCodePrefix>,
|
||||
pub extend_select: Vec<CheckCodePrefix>,
|
||||
pub ignore: Vec<CheckCodePrefix>,
|
||||
pub line_length: usize,
|
||||
pub per_file_ignores: Vec<PerFileIgnore>,
|
||||
pub select: Vec<CheckCode>,
|
||||
pub select: Vec<CheckCodePrefix>,
|
||||
pub target_version: PythonVersion,
|
||||
// Plugins
|
||||
pub flake8_quotes: flake8_quotes::settings::Settings,
|
||||
|
@ -157,16 +157,9 @@ impl RawSettings {
|
|||
.map(|path| FilePattern::from_user(path, project_root))
|
||||
.collect(),
|
||||
extend_ignore: config.extend_ignore,
|
||||
select: config.select.unwrap_or_else(|| {
|
||||
CheckCode::iter()
|
||||
.filter(|code| {
|
||||
matches!(
|
||||
code.category(),
|
||||
CheckCategory::PycodestyleError | CheckCategory::Pyflakes
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}),
|
||||
select: config
|
||||
.select
|
||||
.unwrap_or_else(|| vec![CheckCodePrefix::E, CheckCodePrefix::F]),
|
||||
extend_select: config.extend_select,
|
||||
ignore: config.ignore,
|
||||
line_length: config.line_length.unwrap_or(88),
|
||||
|
@ -197,21 +190,58 @@ pub struct Settings {
|
|||
pub flake8_quotes: flake8_quotes::settings::Settings,
|
||||
}
|
||||
|
||||
/// Given a set of selected and ignored prefixes, resolve the set of enabled error codes.
|
||||
fn resolve_codes(
|
||||
select: &[CheckCodePrefix],
|
||||
extend_select: &[CheckCodePrefix],
|
||||
ignore: &[CheckCodePrefix],
|
||||
extend_ignore: &[CheckCodePrefix],
|
||||
) -> BTreeSet<CheckCode> {
|
||||
let mut codes: BTreeSet<CheckCode> = BTreeSet::new();
|
||||
for specificity in [
|
||||
PrefixSpecificity::Category,
|
||||
PrefixSpecificity::Hundreds,
|
||||
PrefixSpecificity::Tens,
|
||||
PrefixSpecificity::Explicit,
|
||||
] {
|
||||
for prefix in select {
|
||||
if prefix.specificity() == specificity {
|
||||
codes.extend(prefix.codes());
|
||||
}
|
||||
}
|
||||
for prefix in extend_select {
|
||||
if prefix.specificity() == specificity {
|
||||
codes.extend(prefix.codes());
|
||||
}
|
||||
}
|
||||
for prefix in ignore {
|
||||
if prefix.specificity() == specificity {
|
||||
for code in prefix.codes() {
|
||||
codes.remove(&code);
|
||||
}
|
||||
}
|
||||
}
|
||||
for prefix in extend_ignore {
|
||||
if prefix.specificity() == specificity {
|
||||
for code in prefix.codes() {
|
||||
codes.remove(&code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
codes
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn from_raw(settings: RawSettings) -> Self {
|
||||
// Materialize the set of enabled CheckCodes.
|
||||
let mut enabled: BTreeSet<CheckCode> = BTreeSet::new();
|
||||
enabled.extend(settings.select);
|
||||
enabled.extend(settings.extend_select);
|
||||
for code in &settings.ignore {
|
||||
enabled.remove(code);
|
||||
}
|
||||
for code in &settings.extend_ignore {
|
||||
enabled.remove(code);
|
||||
}
|
||||
Self {
|
||||
dummy_variable_rgx: settings.dummy_variable_rgx,
|
||||
enabled,
|
||||
enabled: resolve_codes(
|
||||
&settings.select,
|
||||
&settings.extend_select,
|
||||
&settings.ignore,
|
||||
&settings.extend_ignore,
|
||||
),
|
||||
exclude: settings.exclude,
|
||||
extend_exclude: settings.extend_exclude,
|
||||
flake8_quotes: settings.flake8_quotes,
|
||||
|
@ -290,12 +320,12 @@ pub struct CurrentSettings {
|
|||
pub dummy_variable_rgx: Regex,
|
||||
pub exclude: Vec<Exclusion>,
|
||||
pub extend_exclude: Vec<Exclusion>,
|
||||
pub extend_ignore: Vec<CheckCode>,
|
||||
pub extend_select: Vec<CheckCode>,
|
||||
pub ignore: Vec<CheckCode>,
|
||||
pub extend_ignore: Vec<CheckCodePrefix>,
|
||||
pub extend_select: Vec<CheckCodePrefix>,
|
||||
pub ignore: Vec<CheckCodePrefix>,
|
||||
pub line_length: usize,
|
||||
pub per_file_ignores: Vec<PerFileIgnore>,
|
||||
pub select: Vec<CheckCode>,
|
||||
pub select: Vec<CheckCodePrefix>,
|
||||
pub target_version: PythonVersion,
|
||||
// Plugins
|
||||
pub flake8_quotes: flake8_quotes::settings::Settings,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue