From 5bf8b136441e685bed54ee28e74d852ceef5bd57 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 4 Oct 2022 20:07:17 -0400 Subject: [PATCH] Properly combine CLI and pyproject.toml ignores and selects (#329) --- src/check_ast.rs | 122 +++++++++++++++-------------- src/check_lines.rs | 4 +- src/lib.rs | 4 +- src/linter.rs | 4 +- src/main.rs | 27 ++++--- src/pyproject.rs | 8 ++ src/settings.rs | 188 +++++++++++++++++++++++++-------------------- 7 files changed, 195 insertions(+), 162 deletions(-) diff --git a/src/check_ast.rs b/src/check_ast.rs index 37c000eea3..9a28206173 100644 --- a/src/check_ast.rs +++ b/src/check_ast.rs @@ -234,7 +234,7 @@ where } } - if self.settings.select.contains(&CheckCode::E741) { + if self.settings.enabled.contains(&CheckCode::E741) { let location = self.locate_check(Range::from_located(stmt)); self.checks.extend( names.iter().filter_map(|name| { @@ -244,7 +244,7 @@ where } } StmtKind::Break => { - if self.settings.select.contains(&CheckCode::F701) { + if self.settings.enabled.contains(&CheckCode::F701) { if let Some(check) = checks::check_break_outside_loop( stmt, &self.parents, @@ -256,7 +256,7 @@ where } } StmtKind::Continue => { - if self.settings.select.contains(&CheckCode::F702) { + if self.settings.enabled.contains(&CheckCode::F702) { if let Some(check) = checks::check_continue_outside_loop( stmt, &self.parents, @@ -281,7 +281,7 @@ where args, .. } => { - if self.settings.select.contains(&CheckCode::E743) { + if self.settings.enabled.contains(&CheckCode::E743) { if let Some(check) = checks::check_ambiguous_function_name( name, self.locate_check(Range::from_located(stmt)), @@ -342,7 +342,7 @@ where StmtKind::Return { .. } => { if self .settings - .select + .enabled .contains(CheckKind::ReturnOutsideFunction.code()) { if let Some(scope_index) = self.scope_stack.last().cloned() { @@ -365,7 +365,7 @@ where decorator_list, .. } => { - if self.settings.select.contains(&CheckCode::R001) { + if self.settings.enabled.contains(&CheckCode::R001) { let scope = &self.scopes[*(self.scope_stack.last().expect("No current scope found."))]; if let Some(check) = checks::check_useless_object_inheritance( @@ -381,7 +381,7 @@ where } } - if self.settings.select.contains(&CheckCode::E742) { + if self.settings.enabled.contains(&CheckCode::E742) { if let Some(check) = checks::check_ambiguous_class_name( name, self.locate_check(Range::from_located(stmt)), @@ -410,7 +410,7 @@ where StmtKind::Import { names } => { if self .settings - .select + .enabled .contains(CheckKind::ModuleImportNotAtTopOfFile.code()) && self.seen_non_import && stmt.location.column() == 1 @@ -470,7 +470,7 @@ where } => { if self .settings - .select + .enabled .contains(CheckKind::ModuleImportNotAtTopOfFile.code()) && self.seen_non_import && stmt.location.column() == 1 @@ -508,7 +508,7 @@ where self.annotations_future_enabled = true; } - if self.settings.select.contains(&CheckCode::F407) + if self.settings.enabled.contains(&CheckCode::F407) && !ALL_FEATURE_NAMES.contains(&alias.node.name.deref()) { self.checks.push(Check::new( @@ -517,7 +517,7 @@ where )); } - if self.settings.select.contains(&CheckCode::F404) && !self.futures_allowed + if self.settings.enabled.contains(&CheckCode::F404) && !self.futures_allowed { self.checks.push(Check::new( CheckKind::LateFutureImport, @@ -540,7 +540,7 @@ where }, ); - if self.settings.select.contains(&CheckCode::F406) { + if self.settings.enabled.contains(&CheckCode::F406) { let scope = &self.scopes [*(self.scope_stack.last().expect("No current scope found."))]; if !matches!(scope.kind, ScopeKind::Module) { @@ -551,7 +551,7 @@ where } } - if self.settings.select.contains(&CheckCode::F403) { + if self.settings.enabled.contains(&CheckCode::F403) { self.checks.push(Check::new( CheckKind::ImportStarUsed(module_name.to_string()), self.locate_check(Range::from_located(stmt)), @@ -584,7 +584,7 @@ where } } StmtKind::Raise { exc, .. } => { - if self.settings.select.contains(&CheckCode::F901) { + if self.settings.enabled.contains(&CheckCode::F901) { if let Some(expr) = exc { if let Some(check) = checks::check_raise_not_implemented(expr) { self.checks.push(check); @@ -596,7 +596,7 @@ where self.handle_node_load(target); } StmtKind::If { test, .. } => { - if self.settings.select.contains(&CheckCode::F634) { + if self.settings.enabled.contains(&CheckCode::F634) { if let Some(check) = checks::check_if_tuple(test, self.locate_check(Range::from_located(stmt))) { @@ -605,7 +605,11 @@ where } } StmtKind::Assert { test, .. } => { - if self.settings.select.contains(CheckKind::AssertTuple.code()) { + if self + .settings + .enabled + .contains(CheckKind::AssertTuple.code()) + { if let Some(check) = checks::check_assert_tuple( test, self.locate_check(Range::from_located(stmt)), @@ -615,14 +619,14 @@ where } } StmtKind::Try { handlers, .. } => { - if self.settings.select.contains(&CheckCode::F707) { + if self.settings.enabled.contains(&CheckCode::F707) { if let Some(check) = checks::check_default_except_not_last(handlers) { self.checks.push(check); } } } StmtKind::Assign { targets, value, .. } => { - if self.settings.select.contains(&CheckCode::E731) { + if self.settings.enabled.contains(&CheckCode::E731) { if let Some(check) = checks::check_do_not_assign_lambda( value, self.locate_check(Range::from_located(stmt)), @@ -630,7 +634,7 @@ where self.checks.push(check); } } - if self.settings.select.contains(&CheckCode::U001) { + if self.settings.enabled.contains(&CheckCode::U001) { if let Some(mut check) = checks::check_useless_metaclass_type( targets, value, @@ -663,7 +667,7 @@ where } } StmtKind::AnnAssign { value, .. } => { - if self.settings.select.contains(&CheckCode::E731) { + if self.settings.enabled.contains(&CheckCode::E731) { if let Some(value) = value { if let Some(check) = checks::check_do_not_assign_lambda( value, @@ -743,9 +747,9 @@ where ExprKind::Tuple { elts, ctx } | ExprKind::List { elts, ctx } => { if matches!(ctx, ExprContext::Store) { let check_too_many_expressions = - self.settings.select.contains(&CheckCode::F621); + self.settings.enabled.contains(&CheckCode::F621); let check_two_starred_expressions = - self.settings.select.contains(&CheckCode::F622); + self.settings.enabled.contains(&CheckCode::F622); if let Some(check) = checks::check_starred_expressions( elts, check_too_many_expressions, @@ -759,7 +763,7 @@ where ExprKind::Name { id, ctx } => match ctx { ExprContext::Load => self.handle_node_load(expr), ExprContext::Store => { - if self.settings.select.contains(&CheckCode::E741) { + if self.settings.enabled.contains(&CheckCode::E741) { if let Some(check) = checks::check_ambiguous_variable_name( id, self.locate_check(Range::from_located(expr)), @@ -777,14 +781,14 @@ where ExprContext::Del => self.handle_node_delete(expr), }, ExprKind::Call { func, args, .. } => { - if self.settings.select.contains(&CheckCode::R002) { + if self.settings.enabled.contains(&CheckCode::R002) { if let Some(check) = checks::check_assert_equals(func, self.autofix) { self.checks.push(check) } } // flake8-super - if self.settings.select.contains(&CheckCode::SPR001) { + if self.settings.enabled.contains(&CheckCode::SPR001) { // Only bother going through the super check at all if we're in a `super` call. // (We check this in `check_super_args` too, so this is just an optimization.) if checks::is_super_call_with_arguments(func, args) { @@ -811,8 +815,8 @@ where } // flake8-print - if self.settings.select.contains(&CheckCode::T201) - || self.settings.select.contains(&CheckCode::T203) + if self.settings.enabled.contains(&CheckCode::T201) + || self.settings.enabled.contains(&CheckCode::T203) { if let Some(mut check) = checks::check_print_call(expr, func) { if matches!(self.autofix, fixer::Mode::Generate | fixer::Mode::Apply) { @@ -863,8 +867,8 @@ where } } ExprKind::Dict { keys, .. } => { - let check_repeated_literals = self.settings.select.contains(&CheckCode::F601); - let check_repeated_variables = self.settings.select.contains(&CheckCode::F602); + let check_repeated_literals = self.settings.enabled.contains(&CheckCode::F601); + let check_repeated_variables = self.settings.enabled.contains(&CheckCode::F602); if check_repeated_literals || check_repeated_variables { self.checks.extend(checks::check_repeated_keys( keys, @@ -879,7 +883,7 @@ where &self.scopes[*(self.scope_stack.last().expect("No current scope found."))]; if self .settings - .select + .enabled .contains(CheckKind::YieldOutsideFunction.code()) && matches!(scope.kind, ScopeKind::Class | ScopeKind::Module) { @@ -893,7 +897,7 @@ where if self.in_f_string.is_none() && self .settings - .select + .enabled .contains(CheckKind::FStringMissingPlaceholders.code()) && !values .iter() @@ -911,7 +915,7 @@ where op: Operator::RShift, .. } => { - if self.settings.select.contains(&CheckCode::F633) { + if self.settings.enabled.contains(&CheckCode::F633) { if let ExprKind::Name { id, .. } = &left.node { if id == "print" { let scope = &self.scopes @@ -931,8 +935,8 @@ where } } ExprKind::UnaryOp { op, operand } => { - let check_not_in = self.settings.select.contains(&CheckCode::E713); - let check_not_is = self.settings.select.contains(&CheckCode::E714); + let check_not_in = self.settings.enabled.contains(&CheckCode::E713); + let check_not_is = self.settings.enabled.contains(&CheckCode::E714); if check_not_in || check_not_is { self.checks.extend(checks::check_not_tests( op, @@ -948,8 +952,8 @@ where ops, comparators, } => { - let check_none_comparisons = self.settings.select.contains(&CheckCode::E711); - let check_true_false_comparisons = self.settings.select.contains(&CheckCode::E712); + let check_none_comparisons = self.settings.enabled.contains(&CheckCode::E711); + let check_true_false_comparisons = self.settings.enabled.contains(&CheckCode::E712); if check_none_comparisons || check_true_false_comparisons { self.checks.extend(checks::check_literal_comparisons( left, @@ -961,7 +965,7 @@ where )); } - if self.settings.select.contains(&CheckCode::F632) { + if self.settings.enabled.contains(&CheckCode::F632) { self.checks.extend(checks::check_is_literal( left, ops, @@ -970,7 +974,7 @@ where )); } - if self.settings.select.contains(&CheckCode::E721) { + if self.settings.enabled.contains(&CheckCode::E721) { self.checks.extend(checks::check_type_comparison( ops, comparators, @@ -1164,7 +1168,7 @@ where fn visit_excepthandler(&mut self, excepthandler: &'b Excepthandler) { match &excepthandler.node { ExcepthandlerKind::ExceptHandler { type_, name, .. } => { - if self.settings.select.contains(&CheckCode::E722) && type_.is_none() { + if self.settings.enabled.contains(&CheckCode::E722) && type_.is_none() { self.checks.push(Check::new( CheckKind::DoNotUseBareExcept, Range::from_located(excepthandler), @@ -1172,7 +1176,7 @@ where } match name { Some(name) => { - if self.settings.select.contains(&CheckCode::E741) { + if self.settings.enabled.contains(&CheckCode::E741) { if let Some(check) = checks::check_ambiguous_variable_name( name, self.locate_check(Range::from_located(excepthandler)), @@ -1227,7 +1231,7 @@ where let scope = &mut self.scopes [*(self.scope_stack.last().expect("No current scope found."))]; if let Some(binding) = &scope.values.remove(name) { - if self.settings.select.contains(&CheckCode::F841) + if self.settings.enabled.contains(&CheckCode::F841) && binding.used.is_none() { self.checks.push(Check::new( @@ -1248,7 +1252,7 @@ where } fn visit_arguments(&mut self, arguments: &'b Arguments) { - if self.settings.select.contains(&CheckCode::F831) { + if self.settings.enabled.contains(&CheckCode::F831) { self.checks .extend(checks::check_duplicate_arguments(arguments)); } @@ -1282,7 +1286,7 @@ where }, ); - if self.settings.select.contains(&CheckCode::E741) { + if self.settings.enabled.contains(&CheckCode::E741) { if let Some(check) = checks::check_ambiguous_variable_name( &arg.node.arg, self.locate_check(Range::from_located(arg)), @@ -1368,7 +1372,7 @@ impl<'a> Checker<'a> { let binding = match scope.values.get(&name) { None => binding, Some(existing) => { - if self.settings.select.contains(&CheckCode::F402) + if self.settings.enabled.contains(&CheckCode::F402) && matches!(binding.kind, BindingKind::LoopVar) && matches!( existing.kind, @@ -1422,7 +1426,7 @@ impl<'a> Checker<'a> { } if import_starred { - if self.settings.select.contains(&CheckCode::F405) { + if self.settings.enabled.contains(&CheckCode::F405) { let mut from_list = vec![]; for scope_index in self.scope_stack.iter().rev() { let scope = &self.scopes[*scope_index]; @@ -1442,7 +1446,7 @@ impl<'a> Checker<'a> { return; } - if self.settings.select.contains(&CheckCode::F821) { + if self.settings.enabled.contains(&CheckCode::F821) { // Allow __path__. if self.path.ends_with("__init__.py") && id == "__path__" { return; @@ -1460,7 +1464,7 @@ impl<'a> Checker<'a> { let current = &self.scopes[*(self.scope_stack.last().expect("No current scope found."))]; - if self.settings.select.contains(&CheckCode::F823) + if self.settings.enabled.contains(&CheckCode::F823) && matches!(current.kind, ScopeKind::Function(_)) && !current.values.contains_key(id) { @@ -1560,7 +1564,7 @@ impl<'a> Checker<'a> { let scope = &mut self.scopes[*(self.scope_stack.last().expect("No current scope found."))]; - if scope.values.remove(id).is_none() && self.settings.select.contains(&CheckCode::F821) + if scope.values.remove(id).is_none() && self.settings.enabled.contains(&CheckCode::F821) { self.checks.push(Check::new( CheckKind::UndefinedName(id.clone()), @@ -1586,7 +1590,7 @@ impl<'a> Checker<'a> { if let Ok(mut expr) = parser::parse_expression(expression, "") { relocate_expr(&mut expr, location); allocator.push(expr); - } else if self.settings.select.contains(&CheckCode::F722) { + } else if self.settings.enabled.contains(&CheckCode::F722) { self.checks.push(Check::new( CheckKind::ForwardAnnotationSyntaxError(expression.to_string()), self.locate_check(location), @@ -1641,7 +1645,7 @@ impl<'a> Checker<'a> { } fn check_deferred_assignments(&mut self) { - if self.settings.select.contains(&CheckCode::F841) { + if self.settings.enabled.contains(&CheckCode::F841) { while let Some(index) = self.deferred_assignments.pop() { self.checks.extend(checks::check_unused_variables( &self.scopes[index], @@ -1653,9 +1657,9 @@ impl<'a> Checker<'a> { } fn check_dead_scopes(&mut self) { - if !self.settings.select.contains(&CheckCode::F401) - && !self.settings.select.contains(&CheckCode::F405) - && !self.settings.select.contains(&CheckCode::F822) + if !self.settings.enabled.contains(&CheckCode::F401) + && !self.settings.enabled.contains(&CheckCode::F405) + && !self.settings.enabled.contains(&CheckCode::F822) { return; } @@ -1669,7 +1673,7 @@ impl<'a> Checker<'a> { _ => None, }); - if self.settings.select.contains(&CheckCode::F822) + if self.settings.enabled.contains(&CheckCode::F822) && !scope.import_starred && !self.path.ends_with("__init__.py") { @@ -1687,7 +1691,7 @@ impl<'a> Checker<'a> { } } - if self.settings.select.contains(&CheckCode::F405) && scope.import_starred { + if self.settings.enabled.contains(&CheckCode::F405) && scope.import_starred { if let Some(all_binding) = all_binding { if let Some(names) = all_names { let mut from_list = vec![]; @@ -1710,7 +1714,7 @@ impl<'a> Checker<'a> { } } - if self.settings.select.contains(&CheckCode::F401) { + if self.settings.enabled.contains(&CheckCode::F401) { // Collect all unused imports by location. (Multiple unused imports at the same // location indicates an `import from`.) let mut unused: BTreeMap<(ImportKind, usize, Option), Vec<&str>> = @@ -1793,7 +1797,7 @@ impl<'a> Checker<'a> { // flake8-builtins if is_attribute && matches!(scope.kind, ScopeKind::Class) { - if self.settings.select.contains(&CheckCode::A003) { + if self.settings.enabled.contains(&CheckCode::A003) { if let Some(check) = checks::check_builtin_shadowing( name, self.locate_check(location), @@ -1802,7 +1806,7 @@ impl<'a> Checker<'a> { self.checks.push(check); } } - } else if self.settings.select.contains(&CheckCode::A001) { + } else if self.settings.enabled.contains(&CheckCode::A001) { if let Some(check) = checks::check_builtin_shadowing( name, self.locate_check(location), @@ -1814,7 +1818,7 @@ impl<'a> Checker<'a> { } fn check_builtin_arg_shadowing(&mut self, name: &str, location: Range) { - if self.settings.select.contains(&CheckCode::A002) { + if self.settings.enabled.contains(&CheckCode::A002) { if let Some(check) = checks::check_builtin_shadowing( name, self.locate_check(location), diff --git a/src/check_lines.rs b/src/check_lines.rs index 1c95c1284d..3800836b23 100644 --- a/src/check_lines.rs +++ b/src/check_lines.rs @@ -32,8 +32,8 @@ pub fn check_lines( settings: &Settings, autofix: &fixer::Mode, ) { - let enforce_line_too_long = settings.select.contains(&CheckCode::E501); - let enforce_noqa = settings.select.contains(&CheckCode::M001); + let enforce_line_too_long = settings.enabled.contains(&CheckCode::E501); + let enforce_noqa = settings.enabled.contains(&CheckCode::M001); let mut noqa_directives: BTreeMap)> = BTreeMap::new(); diff --git a/src/lib.rs b/src/lib.rs index 05fd8dbafb..892ea209f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ use rustpython_parser::lexer::LexResult; use crate::autofix::fixer::Mode; use crate::linter::{check_path, tokenize}; use crate::message::Message; -use crate::settings::Settings; +use crate::settings::{RawSettings, Settings}; mod ast; mod autofix; @@ -40,7 +40,7 @@ pub fn check(path: &Path, contents: &str) -> Result> { None => debug!("Unable to find pyproject.toml; using default settings..."), }; - let settings = Settings::from_pyproject(pyproject, project_root)?; + let settings = Settings::from_raw(RawSettings::from_pyproject(pyproject, project_root)?); // Tokenize once. let tokens: Vec = tokenize(contents); diff --git a/src/linter.rs b/src/linter.rs index fca442a3c8..d153e224da 100644 --- a/src/linter.rs +++ b/src/linter.rs @@ -44,7 +44,7 @@ pub(crate) fn check_path( // Run the AST-based checks. if settings - .select + .enabled .iter() .any(|check_code| matches!(check_code.lint_source(), LintSource::AST)) { @@ -53,7 +53,7 @@ pub(crate) fn check_path( checks.extend(check_ast(&python_ast, contents, settings, autofix, path)) } Err(parse_error) => { - if settings.select.contains(&CheckCode::E999) { + if settings.enabled.contains(&CheckCode::E999) { checks.push(Check::new( CheckKind::SyntaxError(parse_error.error.to_string()), Range { diff --git a/src/main.rs b/src/main.rs index 75815293e5..90688110f4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,6 +27,7 @@ use ::ruff::settings::CurrentSettings; use ::ruff::settings::{FilePattern, PerFileIgnore, Settings}; use ::ruff::tell_user; use ruff::linter::autoformat_path; +use ruff::settings::RawSettings; const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME"); const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -120,7 +121,7 @@ fn check_for_updates() { } } -fn show_settings(settings: Settings) { +fn show_settings(settings: RawSettings) { println!("{:#?}", CurrentSettings::from_settings(settings)); } @@ -169,7 +170,7 @@ fn run_once( } .unwrap_or_else(|(path, message)| { if let Some(path) = path { - if settings.select.contains(&CheckCode::E902) { + if settings.enabled.contains(&CheckCode::E902) { vec![Message { kind: CheckKind::IOError(message), fixed: false, @@ -287,7 +288,7 @@ fn inner_main() -> Result { .map(|pair| PerFileIgnore::new(pair, &project_root)) .collect(); - let mut settings = Settings::from_pyproject(pyproject, project_root)?; + let mut settings = RawSettings::from_pyproject(pyproject, project_root)?; if !exclude.is_empty() { settings.exclude = exclude; } @@ -298,17 +299,16 @@ fn inner_main() -> Result { settings.per_file_ignores = per_file_ignores; } if !cli.select.is_empty() { - settings.clear(); - settings.select(cli.select); + settings.select = cli.select; } if !cli.extend_select.is_empty() { - settings.select(cli.extend_select); + settings.extend_select = cli.extend_select; } if !cli.ignore.is_empty() { - settings.ignore(&cli.ignore); + settings.ignore = cli.ignore; } if !cli.extend_ignore.is_empty() { - settings.ignore(&cli.extend_ignore); + settings.extend_ignore = cli.extend_ignore; } if let Some(dummy_variable_rgx) = cli.dummy_variable_rgx { settings.dummy_variable_rgx = dummy_variable_rgx; @@ -318,15 +318,18 @@ fn inner_main() -> Result { eprintln!("Error: specify --show-settings or show-files (not both)."); return Ok(ExitCode::FAILURE); } - if cli.show_files { - show_files(&cli.files, &settings); - return Ok(ExitCode::SUCCESS); - } if cli.show_settings { show_settings(settings); return Ok(ExitCode::SUCCESS); } + let settings = Settings::from_raw(settings); + + if cli.show_files { + show_files(&cli.files, &settings); + return Ok(ExitCode::SUCCESS); + } + cache::init()?; let mut printer = Printer::new(cli.format, cli.verbose); diff --git a/src/pyproject.rs b/src/pyproject.rs index 5967cf2175..e23ce0cf8b 100644 --- a/src/pyproject.rs +++ b/src/pyproject.rs @@ -37,6 +37,8 @@ pub struct Config { #[serde(default)] pub ignore: Vec, #[serde(default)] + pub extend_ignore: Vec, + #[serde(default)] pub per_file_ignores: Vec, pub dummy_variable_rgx: Option, } @@ -184,6 +186,7 @@ mod tests { select: None, extend_select: vec![], ignore: vec![], + extend_ignore: vec![], per_file_ignores: vec![], dummy_variable_rgx: None, }) @@ -207,6 +210,7 @@ line-length = 79 select: None, extend_select: vec![], ignore: vec![], + extend_ignore: vec![], per_file_ignores: vec![], dummy_variable_rgx: None, }) @@ -230,6 +234,7 @@ exclude = ["foo.py"] select: None, extend_select: vec![], ignore: vec![], + extend_ignore: vec![], per_file_ignores: vec![], dummy_variable_rgx: None, }) @@ -253,6 +258,7 @@ select = ["E501"] select: Some(vec![CheckCode::E501]), extend_select: vec![], ignore: vec![], + extend_ignore: vec![], per_file_ignores: vec![], dummy_variable_rgx: None, }) @@ -277,6 +283,7 @@ ignore = ["E501"] select: None, extend_select: vec![CheckCode::M001], ignore: vec![CheckCode::E501], + extend_ignore: vec![], per_file_ignores: vec![], dummy_variable_rgx: None, }) @@ -344,6 +351,7 @@ other-attribute = 1 select: None, extend_select: vec![], ignore: vec![], + extend_ignore: vec![], per_file_ignores: vec![], dummy_variable_rgx: None, } diff --git a/src/settings.rs b/src/settings.rs index b40631c9b1..3a5ebb68a6 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -51,56 +51,18 @@ impl PerFileIgnore { } #[derive(Debug)] -pub struct Settings { - pub pyproject: Option, - pub project_root: Option, - pub line_length: usize, +pub struct RawSettings { + pub dummy_variable_rgx: Regex, pub exclude: Vec, pub extend_exclude: Vec, - pub select: BTreeSet, + pub extend_ignore: Vec, + pub extend_select: Vec, + pub ignore: Vec, + pub line_length: usize, pub per_file_ignores: Vec, - pub dummy_variable_rgx: Regex, -} - -impl Settings { - pub fn for_rule(check_code: CheckCode) -> Self { - Self { - pyproject: None, - project_root: None, - line_length: 88, - exclude: vec![], - extend_exclude: vec![], - select: BTreeSet::from([check_code]), - per_file_ignores: vec![], - dummy_variable_rgx: DEFAULT_DUMMY_VARIABLE_RGX.clone(), - } - } - - pub fn for_rules(check_codes: Vec) -> Self { - Self { - pyproject: None, - project_root: None, - line_length: 88, - exclude: vec![], - extend_exclude: vec![], - select: BTreeSet::from_iter(check_codes), - per_file_ignores: vec![], - dummy_variable_rgx: DEFAULT_DUMMY_VARIABLE_RGX.clone(), - } - } -} - -impl Hash for Settings { - fn hash(&self, state: &mut H) { - self.line_length.hash(state); - self.dummy_variable_rgx.as_str().hash(state); - for value in self.select.iter() { - value.hash(state); - } - for value in self.per_file_ignores.iter() { - value.hash(state); - } - } + pub project_root: Option, + pub pyproject: Option, + pub select: Vec, } static DEFAULT_EXCLUDE: Lazy> = Lazy::new(|| { @@ -130,14 +92,18 @@ static DEFAULT_EXCLUDE: Lazy> = Lazy::new(|| { static DEFAULT_DUMMY_VARIABLE_RGX: Lazy = Lazy::new(|| Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap()); -impl Settings { +impl RawSettings { pub fn from_pyproject( pyproject: Option, project_root: Option, ) -> Result { let config = load_config(&pyproject)?; - let mut settings = Settings { - line_length: config.line_length.unwrap_or(88), + Ok(RawSettings { + dummy_variable_rgx: match config.dummy_variable_rgx { + Some(pattern) => Regex::new(&pattern) + .map_err(|e| anyhow!("Invalid dummy-variable-rgx value: {e}"))?, + None => DEFAULT_DUMMY_VARIABLE_RGX.clone(), + }, exclude: config .exclude .map(|paths| { @@ -152,42 +118,88 @@ impl Settings { .iter() .map(|path| FilePattern::from_user(path, &project_root)) .collect(), - select: if let Some(select) = config.select { - BTreeSet::from_iter(select) - } else { - BTreeSet::from_iter(DEFAULT_CHECK_CODES) - }, + extend_ignore: config.extend_ignore, + extend_select: config.extend_select, + ignore: config.ignore, + line_length: config.line_length.unwrap_or(88), per_file_ignores: config .per_file_ignores .into_iter() .map(|pair| PerFileIgnore::new(pair, &project_root)) .collect(), - dummy_variable_rgx: match config.dummy_variable_rgx { - Some(pattern) => Regex::new(&pattern) - .map_err(|e| anyhow!("Invalid dummy-variable-rgx value: {e}"))?, - None => DEFAULT_DUMMY_VARIABLE_RGX.clone(), - }, - pyproject, project_root, - }; - settings.select(config.extend_select); - settings.ignore(&config.ignore); - Ok(settings) + pyproject, + select: config + .select + .unwrap_or_else(|| DEFAULT_CHECK_CODES.to_vec()), + }) } +} - pub fn clear(&mut self) { - self.select.clear(); - } +#[derive(Debug)] +pub struct Settings { + pub dummy_variable_rgx: Regex, + pub enabled: BTreeSet, + pub exclude: Vec, + pub extend_exclude: Vec, + pub line_length: usize, + pub per_file_ignores: Vec, +} - pub fn select(&mut self, codes: Vec) { - for code in codes { - self.select.insert(code); +impl Settings { + pub fn from_raw(settings: RawSettings) -> Self { + // Materialize the set of enabled CheckCodes. + let mut enabled: BTreeSet = 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, + exclude: settings.exclude, + extend_exclude: settings.extend_exclude, + line_length: settings.line_length, + per_file_ignores: settings.per_file_ignores, } } - pub fn ignore(&mut self, codes: &[CheckCode]) { - for code in codes { - self.select.remove(code); + pub fn for_rule(check_code: CheckCode) -> Self { + Self { + dummy_variable_rgx: DEFAULT_DUMMY_VARIABLE_RGX.clone(), + enabled: BTreeSet::from([check_code]), + exclude: vec![], + extend_exclude: vec![], + line_length: 88, + per_file_ignores: vec![], + } + } + + pub fn for_rules(check_codes: Vec) -> Self { + Self { + dummy_variable_rgx: DEFAULT_DUMMY_VARIABLE_RGX.clone(), + enabled: BTreeSet::from_iter(check_codes), + exclude: vec![], + extend_exclude: vec![], + line_length: 88, + per_file_ignores: vec![], + } + } +} + +impl Hash for Settings { + fn hash(&self, state: &mut H) { + self.line_length.hash(state); + self.dummy_variable_rgx.as_str().hash(state); + for value in self.enabled.iter() { + value.hash(state); + } + for value in self.per_file_ignores.iter() { + value.hash(state); } } } @@ -218,22 +230,23 @@ impl Exclusion { /// Struct to render user-facing Settings. #[derive(Debug)] pub struct CurrentSettings { - pub pyproject: Option, - pub project_root: Option, - pub line_length: usize, + pub dummy_variable_rgx: Regex, pub exclude: Vec, pub extend_exclude: Vec, - pub select: BTreeSet, + pub extend_ignore: Vec, + pub extend_select: Vec, + pub ignore: Vec, + pub line_length: usize, pub per_file_ignores: Vec, - pub dummy_variable_rgx: Regex, + pub project_root: Option, + pub pyproject: Option, + pub select: Vec, } impl CurrentSettings { - pub fn from_settings(settings: Settings) -> Self { + pub fn from_settings(settings: RawSettings) -> Self { Self { - pyproject: settings.pyproject, - project_root: settings.project_root, - line_length: settings.line_length, + dummy_variable_rgx: settings.dummy_variable_rgx, exclude: settings .exclude .into_iter() @@ -244,9 +257,14 @@ impl CurrentSettings { .into_iter() .map(Exclusion::from_file_pattern) .collect(), - select: settings.select, + extend_ignore: settings.extend_ignore, + extend_select: settings.extend_select, + ignore: settings.ignore, + line_length: settings.line_length, per_file_ignores: settings.per_file_ignores, - dummy_variable_rgx: settings.dummy_variable_rgx, + project_root: settings.project_root, + pyproject: settings.pyproject, + select: settings.select, } } }