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" => { "per-file-ignores" | "per_file_ignores" => {
match parser::parse_files_to_codes_mapping(value.as_ref()) { 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}"), Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
} }
} }

View file

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

View file

@ -5,9 +5,7 @@ extend-exclude = [
"migrations", "migrations",
"directory/also_excluded.py", "directory/also_excluded.py",
] ]
per-file-ignores = [ per-file-ignores = { "__init__.py" = ["F401"] }
"__init__.py:F401",
]
[tool.ruff.flake8-quotes] [tool.ruff.flake8-quotes]
inline-quotes = "single" inline-quotes = "single"

View file

@ -1,3 +1,4 @@
use std::collections::BTreeMap;
use std::fmt; use std::fmt;
use std::path::PathBuf; use std::path::PathBuf;
@ -8,8 +9,8 @@ use regex::Regex;
use crate::checks_gen::CheckCodePrefix; use crate::checks_gen::CheckCodePrefix;
use crate::printer::SerializationFormat; use crate::printer::SerializationFormat;
use crate::settings::configuration::Configuration; use crate::settings::configuration::Configuration;
use crate::settings::types::PatternPrefixPair;
use crate::settings::types::PythonVersion; use crate::settings::types::PythonVersion;
use crate::settings::types::StrCheckCodePair;
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[command(author, about = "ruff: An extremely fast Python linter.")] #[command(author, about = "ruff: An extremely fast Python linter.")]
@ -61,7 +62,7 @@ pub struct Cli {
pub extend_exclude: Vec<String>, pub extend_exclude: Vec<String>,
/// List of mappings from file pattern to code to exclude /// List of mappings from file pattern to code to exclude
#[arg(long, value_delimiter = ',')] #[arg(long, value_delimiter = ',')]
pub per_file_ignores: Vec<StrCheckCodePair>, pub per_file_ignores: Vec<PatternPrefixPair>,
/// Output serialization format for error messages. /// Output serialization format for error messages.
#[arg(long, value_enum, default_value_t=SerializationFormat::Text)] #[arg(long, value_enum, default_value_t=SerializationFormat::Text)]
pub format: SerializationFormat, 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::io::{self, Read};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::ExitCode; use std::process::ExitCode;
@ -17,7 +18,8 @@ use walkdir::DirEntry;
use ruff::cache; use ruff::cache;
use ruff::checks::CheckCode; use ruff::checks::CheckCode;
use ruff::checks::CheckKind; 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::fs::iter_python_files;
use ruff::linter::add_noqa_to_path; use ruff::linter::add_noqa_to_path;
use ruff::linter::autoformat_path; use ruff::linter::autoformat_path;
@ -27,7 +29,7 @@ use ruff::message::Message;
use ruff::printer::{Printer, SerializationFormat}; use ruff::printer::{Printer, SerializationFormat};
use ruff::settings::configuration::Configuration; use ruff::settings::configuration::Configuration;
use ruff::settings::pyproject; use ruff::settings::pyproject;
use ruff::settings::types::{FilePattern, PerFileIgnore}; use ruff::settings::types::FilePattern;
use ruff::settings::user::UserConfiguration; use ruff::settings::user::UserConfiguration;
use ruff::settings::Settings; use ruff::settings::Settings;
use ruff::tell_user; use ruff::tell_user;
@ -255,11 +257,8 @@ fn inner_main() -> Result<ExitCode> {
.iter() .iter()
.map(|path| FilePattern::from_user(path, &project_root)) .map(|path| FilePattern::from_user(path, &project_root))
.collect(); .collect();
let per_file_ignores: Vec<PerFileIgnore> = cli let per_file_ignores: BTreeMap<String, Vec<CheckCodePrefix>> =
.per_file_ignores collect_per_file_ignores(cli.per_file_ignores);
.into_iter()
.map(|pair| PerFileIgnore::new(pair, &project_root))
.collect();
let mut configuration = Configuration::from_pyproject(&pyproject, &project_root)?; let mut configuration = Configuration::from_pyproject(&pyproject, &project_root)?;
if !exclude.is_empty() { if !exclude.is_empty() {

View file

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

View file

@ -1,7 +1,7 @@
//! Effective program settings, taking into account pyproject.toml and command-line options. //! 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. //! 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 std::hash::{Hash, Hasher};
use regex::Regex; use regex::Regex;
@ -47,7 +47,7 @@ impl Settings {
flake8_quotes: config.flake8_quotes, flake8_quotes: config.flake8_quotes,
line_length: config.line_length, line_length: config.line_length,
pep8_naming: config.pep8_naming, 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, target_version: config.target_version,
} }
} }
@ -56,10 +56,10 @@ impl Settings {
Self { Self {
dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(), dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(),
enabled: BTreeSet::from([check_code]), enabled: BTreeSet::from([check_code]),
exclude: vec![], exclude: Default::default(),
extend_exclude: vec![], extend_exclude: Default::default(),
line_length: 88, line_length: 88,
per_file_ignores: vec![], per_file_ignores: Default::default(),
target_version: PythonVersion::Py310, target_version: PythonVersion::Py310,
flake8_quotes: Default::default(), flake8_quotes: Default::default(),
pep8_naming: Default::default(), pep8_naming: Default::default(),
@ -70,10 +70,10 @@ impl Settings {
Self { Self {
dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(), dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(),
enabled: BTreeSet::from_iter(check_codes), enabled: BTreeSet::from_iter(check_codes),
exclude: vec![], exclude: Default::default(),
extend_exclude: vec![], extend_exclude: Default::default(),
line_length: 88, line_length: 88,
per_file_ignores: vec![], per_file_ignores: Default::default(),
target_version: PythonVersion::Py310, target_version: PythonVersion::Py310,
flake8_quotes: Default::default(), flake8_quotes: Default::default(),
pep8_naming: Default::default(), pep8_naming: Default::default(),
@ -136,6 +136,15 @@ fn resolve_codes(
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)] #[cfg(test)]
mod tests { mod tests {
use std::collections::BTreeSet; use std::collections::BTreeSet;

View file

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

View file

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

View file

@ -5,7 +5,7 @@ use std::str::FromStr;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use glob::Pattern; use glob::Pattern;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use serde::{de, Deserialize, Deserializer, Serialize};
use crate::checks::CheckCode; use crate::checks::CheckCode;
use crate::checks_gen::CheckCodePrefix; use crate::checks_gen::CheckCodePrefix;
@ -75,24 +75,28 @@ pub struct PerFileIgnore {
} }
impl PerFileIgnore { impl PerFileIgnore {
pub fn new(user_in: StrCheckCodePair, project_root: &Option<PathBuf>) -> Self { pub fn new(
let pattern = FilePattern::from_user(user_in.pattern.as_str(), project_root); pattern: &str,
let codes = BTreeSet::from_iter(user_in.code.codes()); 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 } Self { pattern, codes }
} }
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct StrCheckCodePair { pub struct PatternPrefixPair {
pub pattern: String, pub pattern: String,
pub code: CheckCodePrefix, pub prefix: CheckCodePrefix,
} }
impl StrCheckCodePair { impl PatternPrefixPair {
const EXPECTED_PATTERN: &'static str = "<FilePattern>:<CheckCode> pattern"; 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> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
@ -107,17 +111,7 @@ impl<'de> Deserialize<'de> for StrCheckCodePair {
} }
} }
impl Serialize for StrCheckCodePair { impl FromStr for PatternPrefixPair {
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 {
type Err = anyhow::Error; type Err = anyhow::Error;
fn from_str(string: &str) -> Result<Self, Self::Err> { fn from_str(string: &str) -> Result<Self, Self::Err> {
@ -128,8 +122,8 @@ impl FromStr for StrCheckCodePair {
} }
(tokens[0].trim(), tokens[1].trim()) (tokens[0].trim(), tokens[1].trim())
}; };
let code = CheckCodePrefix::from_str(code_string)?;
let pattern = pattern_str.into(); 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. //! Structs to render user-facing settings.
use std::collections::BTreeMap;
use std::path::PathBuf; use std::path::PathBuf;
use regex::Regex; use regex::Regex;
use crate::checks_gen::CheckCodePrefix; 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}; use crate::{flake8_quotes, pep8_naming, Configuration};
/// Struct to render user-facing exclusion patterns. /// Struct to render user-facing exclusion patterns.
@ -41,7 +42,7 @@ pub struct UserConfiguration {
pub extend_select: Vec<CheckCodePrefix>, pub extend_select: Vec<CheckCodePrefix>,
pub ignore: Vec<CheckCodePrefix>, pub ignore: Vec<CheckCodePrefix>,
pub line_length: usize, pub line_length: usize,
pub per_file_ignores: Vec<PerFileIgnore>, pub per_file_ignores: BTreeMap<String, Vec<CheckCodePrefix>>,
pub select: Vec<CheckCodePrefix>, pub select: Vec<CheckCodePrefix>,
pub target_version: PythonVersion, pub target_version: PythonVersion,
// Plugins // Plugins