Represent per-file ignores as a map (#531)

This commit is contained in:
Charlie Marsh 2022-10-31 22:15:33 -04:00 committed by GitHub
parent 5aa8455258
commit 5797884262
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 166 additions and 133 deletions

View file

@ -42,7 +42,10 @@ pub fn convert(config: HashMap<String, HashMap<String, Option<String>>>) -> Resu
}
"per-file-ignores" | "per_file_ignores" => {
match parser::parse_files_to_codes_mapping(value.as_ref()) {
Ok(per_file_ignores) => options.per_file_ignores = Some(per_file_ignores),
Ok(per_file_ignores) => {
options.per_file_ignores =
Some(parser::collect_per_file_ignores(per_file_ignores))
}
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
}
}

View file

@ -1,3 +1,4 @@
use std::collections::BTreeMap;
use std::str::FromStr;
use anyhow::Result;
@ -5,7 +6,7 @@ use once_cell::sync::Lazy;
use regex::Regex;
use ruff::checks_gen::CheckCodePrefix;
use ruff::settings::types::StrCheckCodePair;
use ruff::settings::types::PatternPrefixPair;
static COMMA_SEPARATED_LIST_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"[,\s]").unwrap());
@ -70,15 +71,15 @@ impl State {
}
/// Generate the list of `StrCheckCodePair` pairs for the current state.
fn parse(&self) -> Vec<StrCheckCodePair> {
let mut codes: Vec<StrCheckCodePair> = vec![];
fn parse(&self) -> Vec<PatternPrefixPair> {
let mut codes: Vec<PatternPrefixPair> = vec![];
for code in &self.codes {
match CheckCodePrefix::from_str(code) {
Ok(code) => {
for filename in &self.filenames {
codes.push(StrCheckCodePair {
codes.push(PatternPrefixPair {
pattern: filename.clone(),
code: code.clone(),
prefix: code.clone(),
});
}
}
@ -127,11 +128,11 @@ fn tokenize_files_to_codes_mapping(value: &str) -> Vec<Token> {
/// Parse a 'files-to-codes' mapping, mimicking Flake8's internal logic.
///
/// See: https://github.com/PyCQA/flake8/blob/7dfe99616fc2f07c0017df2ba5fa884158f3ea8a/src/flake8/utils.py#L45
pub fn parse_files_to_codes_mapping(value: &str) -> Result<Vec<StrCheckCodePair>> {
pub fn parse_files_to_codes_mapping(value: &str) -> Result<Vec<PatternPrefixPair>> {
if value.trim().is_empty() {
return Ok(vec![]);
}
let mut codes: Vec<StrCheckCodePair> = vec![];
let mut codes: Vec<PatternPrefixPair> = vec![];
let mut state = State::new();
for token in tokenize_files_to_codes_mapping(value) {
if matches!(token.token_name, TokenType::Comma | TokenType::Ws) {
@ -166,12 +167,26 @@ pub fn parse_files_to_codes_mapping(value: &str) -> Result<Vec<StrCheckCodePair>
Ok(codes)
}
/// Collect a list of `PatternPrefixPair` structs as a `BTreeMap`.
pub fn collect_per_file_ignores(
pairs: Vec<PatternPrefixPair>,
) -> BTreeMap<String, Vec<CheckCodePrefix>> {
let mut per_file_ignores: BTreeMap<String, Vec<CheckCodePrefix>> = BTreeMap::new();
for pair in pairs {
per_file_ignores
.entry(pair.pattern)
.or_insert_with(Vec::new)
.push(pair.prefix);
}
per_file_ignores
}
#[cfg(test)]
mod tests {
use anyhow::Result;
use ruff::checks_gen::CheckCodePrefix;
use ruff::settings::types::StrCheckCodePair;
use ruff::settings::types::PatternPrefixPair;
use crate::parser::{parse_files_to_codes_mapping, parse_prefix_codes, parse_strings};
@ -232,11 +247,11 @@ mod tests {
#[test]
fn it_parse_files_to_codes_mapping() -> Result<()> {
let actual = parse_files_to_codes_mapping("")?;
let expected: Vec<StrCheckCodePair> = vec![];
let expected: Vec<PatternPrefixPair> = vec![];
assert_eq!(actual, expected);
let actual = parse_files_to_codes_mapping(" ")?;
let expected: Vec<StrCheckCodePair> = vec![];
let expected: Vec<PatternPrefixPair> = vec![];
assert_eq!(actual, expected);
// Ex) locust
@ -248,14 +263,14 @@ mod tests {
.strip_prefix("per-file-ignores =")
.unwrap(),
)?;
let expected: Vec<StrCheckCodePair> = vec![
StrCheckCodePair {
let expected: Vec<PatternPrefixPair> = vec![
PatternPrefixPair {
pattern: "locust/test/*".to_string(),
code: CheckCodePrefix::F841,
prefix: CheckCodePrefix::F841,
},
StrCheckCodePair {
PatternPrefixPair {
pattern: "examples/*".to_string(),
code: CheckCodePrefix::F841,
prefix: CheckCodePrefix::F841,
},
];
assert_eq!(actual, expected);
@ -268,26 +283,26 @@ mod tests {
.strip_prefix("per-file-ignores =")
.unwrap(),
)?;
let expected: Vec<StrCheckCodePair> = vec![
StrCheckCodePair {
let expected: Vec<PatternPrefixPair> = vec![
PatternPrefixPair {
pattern: "t/*".to_string(),
code: CheckCodePrefix::D,
prefix: CheckCodePrefix::D,
},
StrCheckCodePair {
PatternPrefixPair {
pattern: "setup.py".to_string(),
code: CheckCodePrefix::D,
prefix: CheckCodePrefix::D,
},
StrCheckCodePair {
PatternPrefixPair {
pattern: "examples/*".to_string(),
code: CheckCodePrefix::D,
prefix: CheckCodePrefix::D,
},
StrCheckCodePair {
PatternPrefixPair {
pattern: "docs/*".to_string(),
code: CheckCodePrefix::D,
prefix: CheckCodePrefix::D,
},
StrCheckCodePair {
PatternPrefixPair {
pattern: "extra/*".to_string(),
code: CheckCodePrefix::D,
prefix: CheckCodePrefix::D,
},
];
assert_eq!(actual, expected);
@ -306,50 +321,50 @@ mod tests {
.strip_prefix("per-file-ignores =")
.unwrap(),
)?;
let expected: Vec<StrCheckCodePair> = vec![
StrCheckCodePair {
let expected: Vec<PatternPrefixPair> = vec![
PatternPrefixPair {
pattern: "scrapy/__init__.py".to_string(),
code: CheckCodePrefix::E402,
prefix: CheckCodePrefix::E402,
},
StrCheckCodePair {
PatternPrefixPair {
pattern: "scrapy/core/downloader/handlers/http.py".to_string(),
code: CheckCodePrefix::F401,
prefix: CheckCodePrefix::F401,
},
StrCheckCodePair {
PatternPrefixPair {
pattern: "scrapy/http/__init__.py".to_string(),
code: CheckCodePrefix::F401,
prefix: CheckCodePrefix::F401,
},
StrCheckCodePair {
PatternPrefixPair {
pattern: "scrapy/linkextractors/__init__.py".to_string(),
code: CheckCodePrefix::E402,
prefix: CheckCodePrefix::E402,
},
StrCheckCodePair {
PatternPrefixPair {
pattern: "scrapy/linkextractors/__init__.py".to_string(),
code: CheckCodePrefix::F401,
prefix: CheckCodePrefix::F401,
},
StrCheckCodePair {
PatternPrefixPair {
pattern: "scrapy/selector/__init__.py".to_string(),
code: CheckCodePrefix::F401,
prefix: CheckCodePrefix::F401,
},
StrCheckCodePair {
PatternPrefixPair {
pattern: "scrapy/spiders/__init__.py".to_string(),
code: CheckCodePrefix::E402,
prefix: CheckCodePrefix::E402,
},
StrCheckCodePair {
PatternPrefixPair {
pattern: "scrapy/spiders/__init__.py".to_string(),
code: CheckCodePrefix::F401,
prefix: CheckCodePrefix::F401,
},
StrCheckCodePair {
PatternPrefixPair {
pattern: "scrapy/utils/url.py".to_string(),
code: CheckCodePrefix::F403,
prefix: CheckCodePrefix::F403,
},
StrCheckCodePair {
PatternPrefixPair {
pattern: "scrapy/utils/url.py".to_string(),
code: CheckCodePrefix::F405,
prefix: CheckCodePrefix::F405,
},
StrCheckCodePair {
PatternPrefixPair {
pattern: "tests/test_loader.py".to_string(),
code: CheckCodePrefix::E741,
prefix: CheckCodePrefix::E741,
},
];
assert_eq!(actual, expected);

View file

@ -1,13 +1,11 @@
[tool.ruff]
line-length = 88
extend-exclude = [
"excluded.py",
"migrations",
"directory/also_excluded.py",
]
per-file-ignores = [
"__init__.py:F401",
"excluded.py",
"migrations",
"directory/also_excluded.py",
]
per-file-ignores = { "__init__.py" = ["F401"] }
[tool.ruff.flake8-quotes]
inline-quotes = "single"
@ -17,22 +15,22 @@ avoid-escape = true
[tool.ruff.pep8-naming]
ignore-names = [
"setUp",
"tearDown",
"setUpClass",
"tearDownClass",
"setUpModule",
"tearDownModule",
"asyncSetUp",
"asyncTearDown",
"setUpTestData",
"failureException",
"longMessage",
"maxDiff",
"setUp",
"tearDown",
"setUpClass",
"tearDownClass",
"setUpModule",
"tearDownModule",
"asyncSetUp",
"asyncTearDown",
"setUpTestData",
"failureException",
"longMessage",
"maxDiff",
]
classmethod-decorators = [
"classmethod",
"classmethod",
]
staticmethod-decorators = [
"staticmethod",
"staticmethod",
]

View file

@ -1,3 +1,4 @@
use std::collections::BTreeMap;
use std::fmt;
use std::path::PathBuf;
@ -8,8 +9,8 @@ use regex::Regex;
use crate::checks_gen::CheckCodePrefix;
use crate::printer::SerializationFormat;
use crate::settings::configuration::Configuration;
use crate::settings::types::PatternPrefixPair;
use crate::settings::types::PythonVersion;
use crate::settings::types::StrCheckCodePair;
#[derive(Debug, Parser)]
#[command(author, about = "ruff: An extremely fast Python linter.")]
@ -61,7 +62,7 @@ pub struct Cli {
pub extend_exclude: Vec<String>,
/// List of mappings from file pattern to code to exclude
#[arg(long, value_delimiter = ',')]
pub per_file_ignores: Vec<StrCheckCodePair>,
pub per_file_ignores: Vec<PatternPrefixPair>,
/// Output serialization format for error messages.
#[arg(long, value_enum, default_value_t=SerializationFormat::Text)]
pub format: SerializationFormat,
@ -143,3 +144,17 @@ pub fn warn_on(
}
}
}
/// Collect a list of `PatternPrefixPair` structs as a `BTreeMap`.
pub fn collect_per_file_ignores(
pairs: Vec<PatternPrefixPair>,
) -> BTreeMap<String, Vec<CheckCodePrefix>> {
let mut per_file_ignores: BTreeMap<String, Vec<CheckCodePrefix>> = BTreeMap::new();
for pair in pairs {
per_file_ignores
.entry(pair.pattern)
.or_insert_with(Vec::new)
.push(pair.prefix);
}
per_file_ignores
}

View file

@ -1,3 +1,4 @@
use std::collections::BTreeMap;
use std::io::{self, Read};
use std::path::{Path, PathBuf};
use std::process::ExitCode;
@ -17,7 +18,8 @@ use walkdir::DirEntry;
use ruff::cache;
use ruff::checks::CheckCode;
use ruff::checks::CheckKind;
use ruff::cli::{warn_on, Cli, Warnable};
use ruff::checks_gen::CheckCodePrefix;
use ruff::cli::{collect_per_file_ignores, warn_on, Cli, Warnable};
use ruff::fs::iter_python_files;
use ruff::linter::add_noqa_to_path;
use ruff::linter::autoformat_path;
@ -27,7 +29,7 @@ use ruff::message::Message;
use ruff::printer::{Printer, SerializationFormat};
use ruff::settings::configuration::Configuration;
use ruff::settings::pyproject;
use ruff::settings::types::{FilePattern, PerFileIgnore};
use ruff::settings::types::FilePattern;
use ruff::settings::user::UserConfiguration;
use ruff::settings::Settings;
use ruff::tell_user;
@ -255,11 +257,8 @@ fn inner_main() -> Result<ExitCode> {
.iter()
.map(|path| FilePattern::from_user(path, &project_root))
.collect();
let per_file_ignores: Vec<PerFileIgnore> = cli
.per_file_ignores
.into_iter()
.map(|pair| PerFileIgnore::new(pair, &project_root))
.collect();
let per_file_ignores: BTreeMap<String, Vec<CheckCodePrefix>> =
collect_per_file_ignores(cli.per_file_ignores);
let mut configuration = Configuration::from_pyproject(&pyproject, &project_root)?;
if !exclude.is_empty() {

View file

@ -1,6 +1,7 @@
//! User-provided program settings, taking into account pyproject.toml and command-line options.
//! Structure mirrors the user-facing representation of the various parameters.
use std::collections::BTreeMap;
use std::path::PathBuf;
use anyhow::{anyhow, Result};
@ -9,7 +10,7 @@ use regex::Regex;
use crate::checks_gen::CheckCodePrefix;
use crate::settings::pyproject::load_options;
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion};
use crate::settings::types::{FilePattern, PythonVersion};
use crate::{flake8_quotes, pep8_naming};
#[derive(Debug)]
@ -21,7 +22,7 @@ pub struct Configuration {
pub extend_select: Vec<CheckCodePrefix>,
pub ignore: Vec<CheckCodePrefix>,
pub line_length: usize,
pub per_file_ignores: Vec<PerFileIgnore>,
pub per_file_ignores: BTreeMap<String, Vec<CheckCodePrefix>>,
pub select: Vec<CheckCodePrefix>,
pub target_version: PythonVersion,
// Plugins
@ -91,12 +92,7 @@ impl Configuration {
extend_select: options.extend_select.unwrap_or_default(),
ignore: options.ignore.unwrap_or_default(),
line_length: options.line_length.unwrap_or(88),
per_file_ignores: options
.per_file_ignores
.unwrap_or_default()
.into_iter()
.map(|pair| PerFileIgnore::new(pair, project_root))
.collect(),
per_file_ignores: options.per_file_ignores.unwrap_or_default(),
// Plugins
flake8_quotes: options
.flake8_quotes

View file

@ -1,7 +1,7 @@
//! Effective program settings, taking into account pyproject.toml and command-line options.
//! Structure is optimized for internal usage, as opposed to external visibility or parsing.
use std::collections::BTreeSet;
use std::collections::{BTreeMap, BTreeSet};
use std::hash::{Hash, Hasher};
use regex::Regex;
@ -47,7 +47,7 @@ impl Settings {
flake8_quotes: config.flake8_quotes,
line_length: config.line_length,
pep8_naming: config.pep8_naming,
per_file_ignores: config.per_file_ignores,
per_file_ignores: resolve_per_file_ignores(&config.per_file_ignores),
target_version: config.target_version,
}
}
@ -56,10 +56,10 @@ impl Settings {
Self {
dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(),
enabled: BTreeSet::from([check_code]),
exclude: vec![],
extend_exclude: vec![],
exclude: Default::default(),
extend_exclude: Default::default(),
line_length: 88,
per_file_ignores: vec![],
per_file_ignores: Default::default(),
target_version: PythonVersion::Py310,
flake8_quotes: Default::default(),
pep8_naming: Default::default(),
@ -70,10 +70,10 @@ impl Settings {
Self {
dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(),
enabled: BTreeSet::from_iter(check_codes),
exclude: vec![],
extend_exclude: vec![],
exclude: Default::default(),
extend_exclude: Default::default(),
line_length: 88,
per_file_ignores: vec![],
per_file_ignores: Default::default(),
target_version: PythonVersion::Py310,
flake8_quotes: Default::default(),
pep8_naming: Default::default(),
@ -136,6 +136,15 @@ fn resolve_codes(
codes
}
fn resolve_per_file_ignores(
per_file_ignores: &BTreeMap<String, Vec<CheckCodePrefix>>,
) -> Vec<PerFileIgnore> {
per_file_ignores
.iter()
.map(|(pattern, prefixes)| PerFileIgnore::new(pattern, prefixes, &None))
.collect()
}
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;

View file

@ -1,9 +1,11 @@
//! Options that the user can provide via pyproject.toml.
use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use crate::checks_gen::CheckCodePrefix;
use crate::settings::types::{PythonVersion, StrCheckCodePair};
use crate::settings::types::PythonVersion;
use crate::{flake8_quotes, pep8_naming};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
@ -16,7 +18,7 @@ pub struct Options {
pub extend_select: Option<Vec<CheckCodePrefix>>,
pub ignore: Option<Vec<CheckCodePrefix>>,
pub extend_ignore: Option<Vec<CheckCodePrefix>>,
pub per_file_ignores: Option<Vec<StrCheckCodePair>>,
pub per_file_ignores: Option<BTreeMap<String, Vec<CheckCodePrefix>>>,
pub dummy_variable_rgx: Option<String>,
pub target_version: Option<PythonVersion>,
// Plugins

View file

@ -96,6 +96,7 @@ pub fn load_options(pyproject: &Option<PathBuf>) -> Result<Options> {
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use std::env::current_dir;
use std::path::PathBuf;
use std::str::FromStr;
@ -107,7 +108,7 @@ mod tests {
use crate::settings::pyproject::{
find_project_root, find_pyproject_toml, parse_pyproject_toml, Options, Pyproject, Tools,
};
use crate::settings::types::StrCheckCodePair;
use crate::settings::types::PatternPrefixPair;
use crate::{flake8_quotes, pep8_naming};
#[test]
@ -319,10 +320,10 @@ other-attribute = 1
extend_select: None,
ignore: None,
extend_ignore: None,
per_file_ignores: Some(vec![StrCheckCodePair {
pattern: "__init__.py".to_string(),
code: CheckCodePrefix::F401
}]),
per_file_ignores: Some(BTreeMap::from([(
"__init__.py".to_string(),
vec![CheckCodePrefix::F401]
),])),
dummy_variable_rgx: None,
target_version: None,
flake8_quotes: Some(flake8_quotes::settings::Options {
@ -357,21 +358,21 @@ other-attribute = 1
#[test]
fn str_check_code_pair_strings() {
let result = StrCheckCodePair::from_str("foo:E501");
let result = PatternPrefixPair::from_str("foo:E501");
assert!(result.is_ok());
let result = StrCheckCodePair::from_str("foo: E501");
let result = PatternPrefixPair::from_str("foo: E501");
assert!(result.is_ok());
let result = StrCheckCodePair::from_str("E501:foo");
let result = PatternPrefixPair::from_str("E501:foo");
assert!(result.is_err());
let result = StrCheckCodePair::from_str("E501");
let result = PatternPrefixPair::from_str("E501");
assert!(result.is_err());
let result = StrCheckCodePair::from_str("foo");
let result = PatternPrefixPair::from_str("foo");
assert!(result.is_err());
let result = StrCheckCodePair::from_str("foo:E501:E402");
let result = PatternPrefixPair::from_str("foo:E501:E402");
assert!(result.is_err());
let result = StrCheckCodePair::from_str("**/bar:E501");
let result = PatternPrefixPair::from_str("**/bar:E501");
assert!(result.is_ok());
let result = StrCheckCodePair::from_str("bar:E502");
let result = PatternPrefixPair::from_str("bar:E502");
assert!(result.is_err());
}
}

View file

@ -5,7 +5,7 @@ use std::str::FromStr;
use anyhow::{anyhow, Result};
use glob::Pattern;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use serde::{de, Deserialize, Deserializer, Serialize};
use crate::checks::CheckCode;
use crate::checks_gen::CheckCodePrefix;
@ -75,24 +75,28 @@ pub struct PerFileIgnore {
}
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 codes = BTreeSet::from_iter(user_in.code.codes());
pub fn new(
pattern: &str,
prefixes: &[CheckCodePrefix],
project_root: &Option<PathBuf>,
) -> Self {
let pattern = FilePattern::from_user(pattern, project_root);
let codes = BTreeSet::from_iter(prefixes.iter().flat_map(|prefix| prefix.codes()));
Self { pattern, codes }
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StrCheckCodePair {
pub struct PatternPrefixPair {
pub pattern: String,
pub code: CheckCodePrefix,
pub prefix: CheckCodePrefix,
}
impl StrCheckCodePair {
impl PatternPrefixPair {
const EXPECTED_PATTERN: &'static str = "<FilePattern>:<CheckCode> pattern";
}
impl<'de> Deserialize<'de> for StrCheckCodePair {
impl<'de> Deserialize<'de> for PatternPrefixPair {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
@ -107,17 +111,7 @@ impl<'de> Deserialize<'de> for StrCheckCodePair {
}
}
impl Serialize for StrCheckCodePair {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let as_str = format!("{}:{}", self.pattern, self.code.as_ref());
serializer.serialize_str(&as_str)
}
}
impl FromStr for StrCheckCodePair {
impl FromStr for PatternPrefixPair {
type Err = anyhow::Error;
fn from_str(string: &str) -> Result<Self, Self::Err> {
@ -128,8 +122,8 @@ impl FromStr for StrCheckCodePair {
}
(tokens[0].trim(), tokens[1].trim())
};
let code = CheckCodePrefix::from_str(code_string)?;
let pattern = pattern_str.into();
Ok(Self { pattern, code })
let prefix = CheckCodePrefix::from_str(code_string)?;
Ok(Self { pattern, prefix })
}
}

View file

@ -1,11 +1,12 @@
//! Structs to render user-facing settings.
use std::collections::BTreeMap;
use std::path::PathBuf;
use regex::Regex;
use crate::checks_gen::CheckCodePrefix;
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion};
use crate::settings::types::{FilePattern, PythonVersion};
use crate::{flake8_quotes, pep8_naming, Configuration};
/// Struct to render user-facing exclusion patterns.
@ -41,7 +42,7 @@ pub struct UserConfiguration {
pub extend_select: Vec<CheckCodePrefix>,
pub ignore: Vec<CheckCodePrefix>,
pub line_length: usize,
pub per_file_ignores: Vec<PerFileIgnore>,
pub per_file_ignores: BTreeMap<String, Vec<CheckCodePrefix>>,
pub select: Vec<CheckCodePrefix>,
pub target_version: PythonVersion,
// Plugins