diff --git a/README.md b/README.md index 04eb0dc970..25a9ddbf2f 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ default configuration is equivalent to: [tool.ruff] line-length = 88 -# Enable Flake's "E" and "F" codes by default. +# Enable Pyflakes `E` and `F` codes by default. select = ["E", "F"] ignore = [] @@ -157,18 +157,24 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" target-version = "py310" ``` -As an example, the following would configure Ruff to (1) avoid checking for line-length -violations (`E501`) and (2) ignore unused import rules in `__init__.py` files: +As an example, the following would configure Ruff to: (1) avoid checking for line-length +violations (`E501`); (2), always autofix, but never remove unused imports (`F401`); and (3) ignore +import-at-top-of-file errors (`E402`) in `__init__.py` files: ```toml [tool.ruff] +# Enable Pyflakes and pycodestyle rules. select = ["E", "F"] -# Never enforce `E501`. +# Never enforce `E501` (line length violations). ignore = ["E501"] -# Ignore `F401` violations in any `__init__.py` file, and in `path/to/file.py`. -per-file-ignores = {"__init__.py" = ["F401"], "path/to/file.py" = ["F401"]} +# Always autofix, but never try to fix `F401` (unused imports). +fix = true +unfixable = ["F401"] + +# Ignore `E402` (import violations in any `__init__.py` file, and in `path/to/file.py`. +per-file-ignores = {"__init__.py" = ["E402"], "path/to/file.py" = ["E402"]} ``` Plugin configurations should be expressed as subsections, e.g.: @@ -227,6 +233,10 @@ Options: List of paths, used to exclude files and/or directories from checks --extend-exclude Like --exclude, but adds additional files and directories on top of the excluded ones + --fixable + List of error codes to treat as eligible for autofix. Only applicable when autofix itself is enabled (e.g., via `--fix`) + --unfixable + List of error codes to treat as ineligible for autofix. Only applicable when autofix itself is enabled (e.g., via `--fix`) --per-file-ignores List of mappings from file pattern to code to exclude --format diff --git a/flake8_to_ruff/src/converter.rs b/flake8_to_ruff/src/converter.rs index 3e633e28c8..ef23c59b6d 100644 --- a/flake8_to_ruff/src/converter.rs +++ b/flake8_to_ruff/src/converter.rs @@ -243,23 +243,25 @@ mod tests { fn it_converts_empty() -> Result<()> { let actual = convert(&HashMap::from([]), None)?; let expected = Pyproject::new(Options { - line_length: None, - src: None, - fix: None, + dummy_variable_rgx: None, exclude: None, extend_exclude: None, + extend_ignore: None, + extend_select: None, + fix: None, + fixable: None, + ignore: Some(vec![]), + line_length: None, + per_file_ignores: None, select: Some(vec![ CheckCodePrefix::E, CheckCodePrefix::F, CheckCodePrefix::W, ]), - extend_select: None, - ignore: Some(vec![]), - extend_ignore: None, - per_file_ignores: None, - dummy_variable_rgx: None, - target_version: None, show_source: None, + src: None, + target_version: None, + unfixable: None, flake8_annotations: None, flake8_bugbear: None, flake8_quotes: None, @@ -280,23 +282,25 @@ mod tests { Some(vec![]), )?; let expected = Pyproject::new(Options { - line_length: Some(100), - src: None, - fix: None, + dummy_variable_rgx: None, exclude: None, extend_exclude: None, + extend_ignore: None, + extend_select: None, + fix: None, + fixable: None, + ignore: Some(vec![]), + line_length: Some(100), + per_file_ignores: None, select: Some(vec![ CheckCodePrefix::E, CheckCodePrefix::F, CheckCodePrefix::W, ]), - extend_select: None, - ignore: Some(vec![]), - extend_ignore: None, - per_file_ignores: None, - dummy_variable_rgx: None, - target_version: None, show_source: None, + src: None, + target_version: None, + unfixable: None, flake8_annotations: None, flake8_bugbear: None, flake8_quotes: None, @@ -317,23 +321,25 @@ mod tests { Some(vec![]), )?; let expected = Pyproject::new(Options { - line_length: Some(100), - src: None, - fix: None, + dummy_variable_rgx: None, exclude: None, extend_exclude: None, + extend_ignore: None, + extend_select: None, + fix: None, + fixable: None, + ignore: Some(vec![]), + line_length: Some(100), + per_file_ignores: None, select: Some(vec![ CheckCodePrefix::E, CheckCodePrefix::F, CheckCodePrefix::W, ]), - extend_select: None, - ignore: Some(vec![]), - extend_ignore: None, - per_file_ignores: None, - dummy_variable_rgx: None, - target_version: None, show_source: None, + src: None, + target_version: None, + unfixable: None, flake8_annotations: None, flake8_bugbear: None, flake8_quotes: None, @@ -354,23 +360,25 @@ mod tests { Some(vec![]), )?; let expected = Pyproject::new(Options { - line_length: None, - src: None, - fix: None, + dummy_variable_rgx: None, exclude: None, extend_exclude: None, + extend_ignore: None, + extend_select: None, + fix: None, + fixable: None, + ignore: Some(vec![]), + line_length: None, + per_file_ignores: None, select: Some(vec![ CheckCodePrefix::E, CheckCodePrefix::F, CheckCodePrefix::W, ]), - extend_select: None, - ignore: Some(vec![]), - extend_ignore: None, - per_file_ignores: None, - dummy_variable_rgx: None, - target_version: None, show_source: None, + src: None, + target_version: None, + unfixable: None, flake8_annotations: None, flake8_bugbear: None, flake8_quotes: None, @@ -391,23 +399,25 @@ mod tests { Some(vec![]), )?; let expected = Pyproject::new(Options { - line_length: None, - src: None, - fix: None, + dummy_variable_rgx: None, exclude: None, extend_exclude: None, + extend_ignore: None, + extend_select: None, + fix: None, + fixable: None, + ignore: Some(vec![]), + line_length: None, + per_file_ignores: None, select: Some(vec![ CheckCodePrefix::E, CheckCodePrefix::F, CheckCodePrefix::W, ]), - extend_select: None, - ignore: Some(vec![]), - extend_ignore: None, - per_file_ignores: None, - dummy_variable_rgx: None, - target_version: None, show_source: None, + src: None, + target_version: None, + unfixable: None, flake8_annotations: None, flake8_bugbear: None, flake8_quotes: Some(flake8_quotes::settings::Options { @@ -436,11 +446,16 @@ mod tests { Some(vec![Plugin::Flake8Docstrings]), )?; let expected = Pyproject::new(Options { - line_length: None, - src: None, - fix: None, + dummy_variable_rgx: None, exclude: None, extend_exclude: None, + extend_ignore: None, + extend_select: None, + fix: None, + fixable: None, + ignore: Some(vec![]), + line_length: None, + per_file_ignores: None, select: Some(vec![ CheckCodePrefix::D100, CheckCodePrefix::D101, @@ -481,13 +496,10 @@ mod tests { CheckCodePrefix::F, CheckCodePrefix::W, ]), - extend_select: None, - ignore: Some(vec![]), - extend_ignore: None, - per_file_ignores: None, - dummy_variable_rgx: None, - target_version: None, show_source: None, + src: None, + target_version: None, + unfixable: None, flake8_annotations: None, flake8_bugbear: None, flake8_quotes: None, @@ -508,24 +520,26 @@ mod tests { None, )?; let expected = Pyproject::new(Options { - line_length: None, - src: None, - fix: None, + dummy_variable_rgx: None, exclude: None, extend_exclude: None, + extend_ignore: None, + extend_select: None, + fix: None, + fixable: None, + ignore: Some(vec![]), + line_length: None, + per_file_ignores: None, select: Some(vec![ CheckCodePrefix::E, CheckCodePrefix::F, CheckCodePrefix::Q, CheckCodePrefix::W, ]), - extend_select: None, - ignore: Some(vec![]), - extend_ignore: None, - per_file_ignores: None, - dummy_variable_rgx: None, - target_version: None, show_source: None, + src: None, + target_version: None, + unfixable: None, flake8_annotations: None, flake8_bugbear: None, flake8_quotes: Some(flake8_quotes::settings::Options { diff --git a/src/check_ast.rs b/src/check_ast.rs index 01baeb2e88..cda5c6341d 100644 --- a/src/check_ast.rs +++ b/src/check_ast.rs @@ -152,10 +152,10 @@ impl<'a> Checker<'a> { /// Return `true` if a patch should be generated under the given autofix /// `Mode`. - pub fn patch(&self) -> bool { + pub fn patch(&self, code: &CheckCode) -> bool { // TODO(charlie): We can't fix errors in f-strings until RustPython adds // location data. - self.autofix.patch() && self.in_f_string.is_none() + self.autofix.patch() && self.in_f_string.is_none() && self.settings.fixable.contains(code) } /// Return `true` if the `Expr` is a reference to `typing.${target}`. @@ -1256,7 +1256,7 @@ where args, keywords, self.locator, - self.patch(), + self.patch(&CheckCode::C400), Range::from_located(expr), ) { self.add_check(check); @@ -1270,7 +1270,7 @@ where args, keywords, self.locator, - self.patch(), + self.patch(&CheckCode::C401), Range::from_located(expr), ) { self.add_check(check); @@ -1284,7 +1284,7 @@ where args, keywords, self.locator, - self.patch(), + self.patch(&CheckCode::C402), Range::from_located(expr), ) { self.add_check(check); @@ -1299,7 +1299,7 @@ where args, keywords, self.locator, - self.patch(), + self.patch(&CheckCode::C403), Range::from_located(expr), ) { @@ -1315,7 +1315,7 @@ where args, keywords, self.locator, - self.patch(), + self.patch(&CheckCode::C404), Range::from_located(expr), ) { @@ -1330,7 +1330,7 @@ where args, keywords, self.locator, - self.patch(), + self.patch(&CheckCode::C405), Range::from_located(expr), ) { self.add_check(check); @@ -1344,7 +1344,7 @@ where args, keywords, self.locator, - self.patch(), + self.patch(&CheckCode::C406), Range::from_located(expr), ) { self.add_check(check); @@ -1358,7 +1358,7 @@ where args, keywords, self.locator, - self.patch(), + self.patch(&CheckCode::C408), Range::from_located(expr), ) { self.add_check(check); @@ -1372,7 +1372,7 @@ where func, args, self.locator, - self.patch(), + self.patch(&CheckCode::C409), Range::from_located(expr), ) { @@ -1387,7 +1387,7 @@ where func, args, self.locator, - self.patch(), + self.patch(&CheckCode::C410), Range::from_located(expr), ) { @@ -1401,7 +1401,7 @@ where func, args, self.locator, - self.patch(), + self.patch(&CheckCode::C411), Range::from_located(expr), ) { self.add_check(check); @@ -1415,7 +1415,7 @@ where func, args, self.locator, - self.patch(), + self.patch(&CheckCode::C413), Range::from_located(expr), ) { @@ -1667,7 +1667,7 @@ where elt, generators, self.locator, - self.patch(), + self.patch(&CheckCode::C416), Range::from_located(expr), ) { self.add_check(check); @@ -2628,7 +2628,7 @@ impl<'a> Checker<'a> { let child = self.parents[defined_by]; let parent = defined_in.map(|defined_in| self.parents[defined_in]); - let fix = if self.patch() { + let fix = if self.patch(&CheckCode::F401) { let deleted: Vec<&Stmt> = self .deletions .iter() diff --git a/src/check_lines.rs b/src/check_lines.rs index 96056fc06c..ceb95cdce7 100644 --- a/src/check_lines.rs +++ b/src/check_lines.rs @@ -73,7 +73,7 @@ pub fn check_lines( end_location: Location::new(lineno + 1, line_length + 1), }, ); - if autofix.patch() { + if autofix.patch() && settings.fixable.contains(check.kind.code()) { check.amend(Fix::deletion( Location::new(lineno + 1, 0), Location::new(lineno + 1, line_length + 1), @@ -195,7 +195,7 @@ pub fn check_lines( end_location: Location::new(row + 1, end), }, ); - if autofix.patch() { + if autofix.patch() && settings.fixable.contains(check.kind.code()) { check.amend(Fix::deletion( Location::new(row + 1, start), Location::new(row + 1, lines[row].chars().count()), @@ -223,7 +223,7 @@ pub fn check_lines( end_location: Location::new(row + 1, end), }, ); - if autofix.patch() { + if autofix.patch() && settings.fixable.contains(check.kind.code()) { if valid_codes.is_empty() { check.amend(Fix::deletion( Location::new(row + 1, start), diff --git a/src/check_tokens.rs b/src/check_tokens.rs index a8d068ddf4..b09ea660ed 100644 --- a/src/check_tokens.rs +++ b/src/check_tokens.rs @@ -36,7 +36,7 @@ pub fn check_tokens( // RUF001, RUF002, RUF003 if enforce_ambiguous_unicode_character { if matches!(tok, Tok::String { .. } | Tok::Comment) { - for check in rules::checks::ambiguous_unicode_character( + checks.extend(rules::checks::ambiguous_unicode_character( locator, start, end, @@ -49,12 +49,9 @@ pub fn check_tokens( } else { Context::Comment }, - autofix.patch(), - ) { - if settings.enabled.contains(check.kind.code()) { - checks.push(check); - } - } + settings, + autofix, + )); } } diff --git a/src/cli.rs b/src/cli.rs index 42191a7455..b1cd519a1b 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -63,6 +63,14 @@ pub struct Cli { /// excluded ones. #[arg(long, value_delimiter = ',')] pub extend_exclude: Vec, + /// List of error codes to treat as eligible for autofix. Only applicable + /// when autofix itself is enabled (e.g., via `--fix`). + #[arg(long, value_delimiter = ',')] + pub fixable: Vec, + /// List of error codes to treat as ineligible for autofix. Only applicable + /// when autofix itself is enabled (e.g., via `--fix`). + #[arg(long, value_delimiter = ',')] + pub unfixable: Vec, /// List of mappings from file pattern to code to exclude #[arg(long, value_delimiter = ',')] pub per_file_ignores: Vec, diff --git a/src/flake8_bugbear/plugins/assert_false.rs b/src/flake8_bugbear/plugins/assert_false.rs index 87293c948f..4daa507db9 100644 --- a/src/flake8_bugbear/plugins/assert_false.rs +++ b/src/flake8_bugbear/plugins/assert_false.rs @@ -36,6 +36,7 @@ fn assertion_error(msg: Option<&Expr>) -> Stmt { ) } +/// B011 pub fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: Option<&Expr>) { if let ExprKind::Constant { value: Constant::Bool(false), @@ -43,7 +44,7 @@ pub fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: Option } = &test.node { let mut check = Check::new(CheckKind::DoNotAssertFalse, Range::from_located(test)); - if checker.patch() { + if checker.patch(check.kind.code()) { let mut generator = SourceGenerator::new(); if let Ok(()) = generator.unparse_stmt(&assertion_error(msg)) { if let Ok(content) = generator.generate() { diff --git a/src/flake8_bugbear/plugins/duplicate_exceptions.rs b/src/flake8_bugbear/plugins/duplicate_exceptions.rs index 3d7e14e816..ba71042e50 100644 --- a/src/flake8_bugbear/plugins/duplicate_exceptions.rs +++ b/src/flake8_bugbear/plugins/duplicate_exceptions.rs @@ -54,7 +54,7 @@ fn duplicate_handler_exceptions<'a>( ), Range::from_located(expr), ); - if checker.patch() { + if checker.patch(check.kind.code()) { // TODO(charlie): If we have a single element, remove the tuple. let mut generator = SourceGenerator::new(); if let Ok(()) = generator.unparse_expr(&type_pattern(unique_elts), 0) { diff --git a/src/flake8_bugbear/plugins/getattr_with_constant.rs b/src/flake8_bugbear/plugins/getattr_with_constant.rs index e5fce9accf..dd4ede21b0 100644 --- a/src/flake8_bugbear/plugins/getattr_with_constant.rs +++ b/src/flake8_bugbear/plugins/getattr_with_constant.rs @@ -20,6 +20,7 @@ fn attribute(value: &Expr, attr: &str) -> Expr { ) } +/// B009 pub fn getattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) { if let ExprKind::Name { id, .. } = &func.node { if id == "getattr" { @@ -32,7 +33,7 @@ pub fn getattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, ar if IDENTIFIER_REGEX.is_match(value) && !KWLIST.contains(&value.as_str()) { let mut check = Check::new(CheckKind::GetAttrWithConstant, Range::from_located(expr)); - if checker.patch() { + if checker.patch(check.kind.code()) { let mut generator = SourceGenerator::new(); if let Ok(()) = generator.unparse_expr(&attribute(obj, value), 0) { if let Ok(content) = generator.generate() { diff --git a/src/flake8_bugbear/plugins/unused_loop_control_variable.rs b/src/flake8_bugbear/plugins/unused_loop_control_variable.rs index 684af5ddfb..492c33d699 100644 --- a/src/flake8_bugbear/plugins/unused_loop_control_variable.rs +++ b/src/flake8_bugbear/plugins/unused_loop_control_variable.rs @@ -65,7 +65,7 @@ pub fn unused_loop_control_variable(checker: &mut Checker, target: &Expr, body: CheckKind::UnusedLoopControlVariable(name.to_string()), Range::from_located(expr), ); - if checker.patch() { + if checker.patch(check.kind.code()) { // Prefix the variable name with an underscore. check.amend(Fix::replacement( format!("_{name}"), diff --git a/src/flake8_print/plugins/print_call.rs b/src/flake8_print/plugins/print_call.rs index c6cabceb18..36f794eca4 100644 --- a/src/flake8_print/plugins/print_call.rs +++ b/src/flake8_print/plugins/print_call.rs @@ -16,7 +16,7 @@ pub fn print_call(checker: &mut Checker, expr: &Expr, func: &Expr) { checker.settings.enabled.contains(&CheckCode::T203), Range::from_located(expr), ) { - if checker.patch() { + if checker.patch(check.kind.code()) { let context = checker.binding_context(); if matches!( checker.parents[context.defined_by].node, diff --git a/src/isort/plugins.rs b/src/isort/plugins.rs index 2640bb49a1..e6f0867ac8 100644 --- a/src/isort/plugins.rs +++ b/src/isort/plugins.rs @@ -91,7 +91,7 @@ pub fn check_imports( if has_leading_content || has_trailing_content { let mut check = Check::new(CheckKind::UnsortedImports, range); - if autofix.patch() { + if autofix.patch() && settings.fixable.contains(check.kind.code()) { let mut content = String::new(); if has_leading_content { content.push('\n'); @@ -119,7 +119,7 @@ pub fn check_imports( let actual = dedent(&locator.slice_source_code_range(&range)); if actual != expected { let mut check = Check::new(CheckKind::UnsortedImports, range); - if autofix.patch() { + if autofix.patch() && settings.fixable.contains(check.kind.code()) { check.amend(Fix::replacement( indent(&expected, &indentation), range.location, diff --git a/src/main.rs b/src/main.rs index 2e023bbc43..5c5e605c15 100644 --- a/src/main.rs +++ b/src/main.rs @@ -251,6 +251,12 @@ fn inner_main() -> Result { if !cli.extend_ignore.is_empty() { configuration.extend_ignore = cli.extend_ignore; } + if !cli.fixable.is_empty() { + configuration.fixable = cli.fixable; + } + if !cli.unfixable.is_empty() { + configuration.unfixable = cli.unfixable; + } if let Some(line_length) = cli.line_length { configuration.line_length = line_length; } diff --git a/src/pycodestyle/plugins.rs b/src/pycodestyle/plugins.rs index b6a6bd3d93..fd6b96109e 100644 --- a/src/pycodestyle/plugins.rs +++ b/src/pycodestyle/plugins.rs @@ -62,7 +62,7 @@ pub fn literal_comparisons( CheckKind::NoneComparison(RejectedCmpop::Eq), Range::from_located(comparator), ); - if checker.patch() { + if checker.patch(check.kind.code()) { // Dummy replacement check.amend(Fix::dummy(expr.location)); bad_ops.insert(0, Cmpop::Is); @@ -74,7 +74,7 @@ pub fn literal_comparisons( CheckKind::NoneComparison(RejectedCmpop::NotEq), Range::from_located(comparator), ); - if checker.patch() { + if checker.patch(check.kind.code()) { check.amend(Fix::dummy(expr.location)); bad_ops.insert(0, Cmpop::IsNot); } @@ -93,7 +93,7 @@ pub fn literal_comparisons( CheckKind::TrueFalseComparison(value, RejectedCmpop::Eq), Range::from_located(comparator), ); - if checker.patch() { + if checker.patch(check.kind.code()) { check.amend(Fix::dummy(expr.location)); bad_ops.insert(0, Cmpop::Is); } @@ -104,7 +104,7 @@ pub fn literal_comparisons( CheckKind::TrueFalseComparison(value, RejectedCmpop::NotEq), Range::from_located(comparator), ); - if checker.patch() { + if checker.patch(check.kind.code()) { check.amend(Fix::dummy(expr.location)); bad_ops.insert(0, Cmpop::IsNot); } @@ -129,7 +129,7 @@ pub fn literal_comparisons( CheckKind::NoneComparison(RejectedCmpop::Eq), Range::from_located(comparator), ); - if checker.patch() { + if checker.patch(check.kind.code()) { check.amend(Fix::dummy(expr.location)); bad_ops.insert(idx, Cmpop::Is); } @@ -140,7 +140,7 @@ pub fn literal_comparisons( CheckKind::NoneComparison(RejectedCmpop::NotEq), Range::from_located(comparator), ); - if checker.patch() { + if checker.patch(check.kind.code()) { check.amend(Fix::dummy(expr.location)); bad_ops.insert(idx, Cmpop::IsNot); } @@ -159,7 +159,7 @@ pub fn literal_comparisons( CheckKind::TrueFalseComparison(value, RejectedCmpop::Eq), Range::from_located(comparator), ); - if checker.patch() { + if checker.patch(check.kind.code()) { check.amend(Fix::dummy(expr.location)); bad_ops.insert(idx, Cmpop::Is); } @@ -170,7 +170,7 @@ pub fn literal_comparisons( CheckKind::TrueFalseComparison(value, RejectedCmpop::NotEq), Range::from_located(comparator), ); - if checker.patch() { + if checker.patch(check.kind.code()) { check.amend(Fix::dummy(expr.location)); bad_ops.insert(idx, Cmpop::IsNot); } @@ -226,7 +226,7 @@ pub fn not_tests( if check_not_in { let mut check = Check::new(CheckKind::NotInTest, Range::from_located(operand)); - if checker.patch() && should_fix { + if checker.patch(check.kind.code()) && should_fix { if let Some(content) = compare(left, &[Cmpop::NotIn], comparators) { check.amend(Fix::replacement( content, @@ -242,7 +242,7 @@ pub fn not_tests( if check_not_is { let mut check = Check::new(CheckKind::NotIsTest, Range::from_located(operand)); - if checker.patch() && should_fix { + if checker.patch(check.kind.code()) && should_fix { if let Some(content) = compare(left, &[Cmpop::IsNot], comparators) { check.amend(Fix::replacement( content, diff --git a/src/pydocstyle/plugins.rs b/src/pydocstyle/plugins.rs index d66bb4e212..78db55e391 100644 --- a/src/pydocstyle/plugins.rs +++ b/src/pydocstyle/plugins.rs @@ -179,7 +179,7 @@ pub fn blank_before_after_function(checker: &mut Checker, definition: &Definitio CheckKind::NoBlankLineBeforeFunction(blank_lines_before), Range::from_located(docstring), ); - if checker.patch() { + if checker.patch(check.kind.code()) { // Delete the blank line before the docstring. check.amend(Fix::deletion( Location::new(docstring.location.row() - blank_lines_before, 0), @@ -220,7 +220,7 @@ pub fn blank_before_after_function(checker: &mut Checker, definition: &Definitio CheckKind::NoBlankLineAfterFunction(blank_lines_after), Range::from_located(docstring), ); - if checker.patch() { + if checker.patch(check.kind.code()) { // Delete the blank line after the docstring. check.amend(Fix::deletion( Location::new(docstring.end_location.unwrap().row() + 1, 0), @@ -269,7 +269,7 @@ pub fn blank_before_after_class(checker: &mut Checker, definition: &Definition) CheckKind::NoBlankLineBeforeClass(blank_lines_before), Range::from_located(docstring), ); - if checker.patch() { + if checker.patch(check.kind.code()) { // Delete the blank line before the class. check.amend(Fix::deletion( Location::new(docstring.location.row() - blank_lines_before, 0), @@ -285,7 +285,7 @@ pub fn blank_before_after_class(checker: &mut Checker, definition: &Definition) CheckKind::OneBlankLineBeforeClass(blank_lines_before), Range::from_located(docstring), ); - if checker.patch() { + if checker.patch(check.kind.code()) { // Insert one blank line before the class. check.amend(Fix::replacement( "\n".to_string(), @@ -322,7 +322,7 @@ pub fn blank_before_after_class(checker: &mut Checker, definition: &Definition) CheckKind::OneBlankLineAfterClass(blank_lines_after), Range::from_located(docstring), ); - if checker.patch() { + if checker.patch(check.kind.code()) { // Insert a blank line before the class (replacing any existing lines). check.amend(Fix::replacement( "\n".to_string(), @@ -364,7 +364,7 @@ pub fn blank_after_summary(checker: &mut Checker, definition: &Definition) { CheckKind::BlankLineAfterSummary, Range::from_located(docstring), ); - if checker.patch() { + if checker.patch(check.kind.code()) { // Insert one blank line after the summary (replacing any existing lines). check.amend(Fix::replacement( "\n".to_string(), @@ -425,7 +425,7 @@ pub fn indent(checker: &mut Checker, definition: &Definition) { end_location: Location::new(docstring.location.row() + i, 0), }, ); - if checker.patch() { + if checker.patch(check.kind.code()) { check.amend(Fix::replacement( helpers::clean(&docstring_indent), Location::new(docstring.location.row() + i, 0), @@ -475,7 +475,7 @@ pub fn indent(checker: &mut Checker, definition: &Definition) { end_location: Location::new(docstring.location.row() + i, 0), }, ); - if checker.patch() { + if checker.patch(check.kind.code()) { check.amend(Fix::replacement( helpers::clean(&docstring_indent), Location::new(docstring.location.row() + i, 0), @@ -499,7 +499,7 @@ pub fn indent(checker: &mut Checker, definition: &Definition) { end_location: Location::new(docstring.location.row() + i, 0), }, ); - if checker.patch() { + if checker.patch(check.kind.code()) { check.amend(Fix::replacement( helpers::clean(&docstring_indent), Location::new(docstring.location.row() + i, 0), @@ -537,7 +537,7 @@ pub fn newline_after_last_paragraph(checker: &mut Checker, definition: &Definiti CheckKind::NewLineAfterLastParagraph, Range::from_located(docstring), ); - if checker.patch() { + if checker.patch(check.kind.code()) { // Insert a newline just before the end-quote(s). let content = format!( "\n{}", @@ -580,7 +580,7 @@ pub fn no_surrounding_whitespace(checker: &mut Checker, definition: &Definition) CheckKind::NoSurroundingWhitespace, Range::from_located(docstring), ); - if checker.patch() { + if checker.patch(check.kind.code()) { if let Some(first_line) = checker .locator .slice_source_code_range(&Range::from_located(docstring)) @@ -916,7 +916,7 @@ fn blanks_and_section_underline( CheckKind::DashedUnderlineAfterSection(context.section_name.to_string()), Range::from_located(docstring), ); - if checker.patch() { + if checker.patch(check.kind.code()) { // Add a dashed line (of the appropriate length) under the section header. let content = format!( "{}{}\n", @@ -950,7 +950,7 @@ fn blanks_and_section_underline( CheckKind::DashedUnderlineAfterSection(context.section_name.to_string()), Range::from_located(docstring), ); - if checker.patch() { + if checker.patch(check.kind.code()) { // Add a dashed line (of the appropriate length) under the section header. let content = format!( "{}{}\n", @@ -972,7 +972,7 @@ fn blanks_and_section_underline( ), Range::from_located(docstring), ); - if checker.patch() { + if checker.patch(check.kind.code()) { // Delete any blank lines between the header and content. check.amend(Fix::deletion( Location::new(docstring.location.row() + context.original_index + 1, 0), @@ -995,7 +995,7 @@ fn blanks_and_section_underline( CheckKind::SectionUnderlineAfterName(context.section_name.to_string()), Range::from_located(docstring), ); - if checker.patch() { + if checker.patch(check.kind.code()) { // Delete any blank lines between the header and the underline. check.amend(Fix::deletion( Location::new(docstring.location.row() + context.original_index + 1, 0), @@ -1026,7 +1026,7 @@ fn blanks_and_section_underline( ), Range::from_located(docstring), ); - if checker.patch() { + if checker.patch(check.kind.code()) { // Replace the existing underline with a line of the appropriate length. let content = format!( "{}{}\n", @@ -1064,7 +1064,7 @@ fn blanks_and_section_underline( CheckKind::SectionUnderlineNotOverIndented(context.section_name.to_string()), Range::from_located(docstring), ); - if checker.patch() { + if checker.patch(check.kind.code()) { // Replace the existing indentation with whitespace of the appropriate length. check.amend(Fix::replacement( helpers::clean(&indentation), @@ -1113,7 +1113,7 @@ fn blanks_and_section_underline( ), Range::from_located(docstring), ); - if checker.patch() { + if checker.patch(check.kind.code()) { // Delete any blank lines between the header and content. check.amend(Fix::deletion( Location::new( @@ -1172,7 +1172,7 @@ fn common_section( CheckKind::CapitalizeSectionName(context.section_name.to_string()), Range::from_located(docstring), ); - if checker.patch() { + if checker.patch(check.kind.code()) { // Replace the section title with the capitalized variant. This requires // locating the start and end of the section name. if let Some(index) = context.line.find(&context.section_name) { @@ -1205,7 +1205,7 @@ fn common_section( CheckKind::SectionNotOverIndented(context.section_name.to_string()), Range::from_located(docstring), ); - if checker.patch() { + if checker.patch(check.kind.code()) { // Replace the existing indentation with whitespace of the appropriate length. check.amend(Fix::replacement( helpers::clean(&indentation), @@ -1232,7 +1232,7 @@ fn common_section( CheckKind::BlankLineAfterLastSection(context.section_name.to_string()), Range::from_located(docstring), ); - if checker.patch() { + if checker.patch(check.kind.code()) { // Add a newline after the section. check.amend(Fix::insertion( "\n".to_string(), @@ -1253,7 +1253,7 @@ fn common_section( CheckKind::BlankLineAfterSection(context.section_name.to_string()), Range::from_located(docstring), ); - if checker.patch() { + if checker.patch(check.kind.code()) { // Add a newline after the section. check.amend(Fix::insertion( "\n".to_string(), @@ -1277,7 +1277,7 @@ fn common_section( CheckKind::BlankLineBeforeSection(context.section_name.to_string()), Range::from_located(docstring), ); - if checker.patch() { + if checker.patch(check.kind.code()) { // Add a blank line before the section. check.amend(Fix::insertion( "\n".to_string(), @@ -1444,7 +1444,7 @@ fn numpy_section(checker: &mut Checker, definition: &Definition, context: &Secti CheckKind::NewLineAfterSectionName(context.section_name.to_string()), Range::from_located(docstring), ); - if checker.patch() { + if checker.patch(check.kind.code()) { // Delete the suffix. This requires locating the end of the section name. if let Some(index) = context.line.find(&context.section_name) { // Map from bytes to characters. @@ -1493,7 +1493,7 @@ fn google_section(checker: &mut Checker, definition: &Definition, context: &Sect CheckKind::SectionNameEndsInColon(context.section_name.to_string()), Range::from_located(docstring), ); - if checker.patch() { + if checker.patch(check.kind.code()) { // Replace the suffix. This requires locating the end of the section name. if let Some(index) = context.line.find(&context.section_name) { // Map from bytes to characters. diff --git a/src/pyflakes/plugins/invalid_literal_comparisons.rs b/src/pyflakes/plugins/invalid_literal_comparisons.rs index f8b09363ca..4e85f55406 100644 --- a/src/pyflakes/plugins/invalid_literal_comparisons.rs +++ b/src/pyflakes/plugins/invalid_literal_comparisons.rs @@ -43,7 +43,7 @@ pub fn invalid_literal_comparison( && (is_constant_non_singleton(left) || is_constant_non_singleton(right)) { let mut check = Check::new(CheckKind::IsLiteral, location); - if checker.patch() { + if checker.patch(check.kind.code()) { match fix_invalid_literal_comparison( checker.locator, Range { diff --git a/src/pyflakes/plugins/raise_not_implemented.rs b/src/pyflakes/plugins/raise_not_implemented.rs index 0c329e17b9..c367f307c8 100644 --- a/src/pyflakes/plugins/raise_not_implemented.rs +++ b/src/pyflakes/plugins/raise_not_implemented.rs @@ -28,7 +28,7 @@ fn match_not_implemented(expr: &Expr) -> Option<&Expr> { pub fn raise_not_implemented(checker: &mut Checker, expr: &Expr) { if let Some(expr) = match_not_implemented(expr) { let mut check = Check::new(CheckKind::RaiseNotImplemented, Range::from_located(expr)); - if checker.patch() { + if checker.patch(check.kind.code()) { check.amend(Fix::replacement( "NotImplementedError".to_string(), expr.location, diff --git a/src/pyupgrade/plugins/convert_typed_dict_functional_to_class.rs b/src/pyupgrade/plugins/convert_typed_dict_functional_to_class.rs index cce623c5da..5625d244ee 100644 --- a/src/pyupgrade/plugins/convert_typed_dict_functional_to_class.rs +++ b/src/pyupgrade/plugins/convert_typed_dict_functional_to_class.rs @@ -220,7 +220,7 @@ pub fn convert_typed_dict_functional_to_class( CheckKind::ConvertTypedDictFunctionalToClass, Range::from_located(stmt), ); - if checker.patch() { + if checker.patch(check.kind.code()) { match convert_to_functional_class(stmt, class_name, body, total_keyword) { Ok(fix) => check.amend(fix), Err(err) => error!("Failed to convert TypedDict: {}", err), diff --git a/src/pyupgrade/plugins/deprecated_unittest_alias.rs b/src/pyupgrade/plugins/deprecated_unittest_alias.rs index 7c4fe3a684..5d5ee6316f 100644 --- a/src/pyupgrade/plugins/deprecated_unittest_alias.rs +++ b/src/pyupgrade/plugins/deprecated_unittest_alias.rs @@ -37,7 +37,7 @@ pub fn deprecated_unittest_alias(checker: &mut Checker, expr: &Expr) { CheckKind::DeprecatedUnittestAlias(attr.to_string(), target.to_string()), Range::from_located(expr), ); - if checker.patch() { + if checker.patch(check.kind.code()) { check.amend(Fix::replacement( format!("self.{}", target), expr.location, diff --git a/src/pyupgrade/plugins/super_call_with_parameters.rs b/src/pyupgrade/plugins/super_call_with_parameters.rs index b743477566..c6490d3104 100644 --- a/src/pyupgrade/plugins/super_call_with_parameters.rs +++ b/src/pyupgrade/plugins/super_call_with_parameters.rs @@ -17,7 +17,7 @@ pub fn super_call_with_parameters(checker: &mut Checker, expr: &Expr, func: &Exp .map(|index| checker.parents[*index]) .collect(); if let Some(mut check) = checks::super_args(scope, &parents, expr, func, args) { - if checker.patch() { + if checker.patch(check.kind.code()) { if let Some(fix) = pyupgrade::fixes::remove_super_arguments(checker.locator, expr) { check.amend(fix); } diff --git a/src/pyupgrade/plugins/type_of_primitive.rs b/src/pyupgrade/plugins/type_of_primitive.rs index 6a7575428b..d2dede8e03 100644 --- a/src/pyupgrade/plugins/type_of_primitive.rs +++ b/src/pyupgrade/plugins/type_of_primitive.rs @@ -9,7 +9,7 @@ use crate::pyupgrade::checks; /// U003 pub fn type_of_primitive(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) { if let Some(mut check) = checks::type_of_primitive(func, args, Range::from_located(expr)) { - if checker.patch() { + if checker.patch(check.kind.code()) { if let CheckKind::TypeOfPrimitive(primitive) = &check.kind { check.amend(Fix::replacement( primitive.builtin(), diff --git a/src/pyupgrade/plugins/unnecessary_encode_utf8.rs b/src/pyupgrade/plugins/unnecessary_encode_utf8.rs index c38dccc618..cd1e6f57c1 100644 --- a/src/pyupgrade/plugins/unnecessary_encode_utf8.rs +++ b/src/pyupgrade/plugins/unnecessary_encode_utf8.rs @@ -3,7 +3,7 @@ use rustpython_ast::{Constant, Expr, ExprKind, Keyword}; use crate::ast::types::Range; use crate::autofix::Fix; use crate::check_ast::Checker; -use crate::checks::{Check, CheckKind}; +use crate::checks::{Check, CheckCode, CheckKind}; use crate::source_code_locator::SourceCodeLocator; const UTF8_LITERALS: &[&str] = &["utf-8", "utf8", "utf_8", "u8", "utf", "cp65001"]; @@ -124,13 +124,16 @@ pub fn unnecessary_encode_utf8( expr, variable, checker.locator, - checker.patch(), + checker.patch(&CheckCode::U012), )); } else { // "unicode text©".encode("utf-8") - if let Some(check) = - delete_default_encode_arg_or_kwarg(expr, args, kwargs, checker.patch()) - { + if let Some(check) = delete_default_encode_arg_or_kwarg( + expr, + args, + kwargs, + checker.patch(&CheckCode::U012), + ) { checker.add_check(check); } } @@ -139,9 +142,12 @@ pub fn unnecessary_encode_utf8( // f"foo{bar}".encode(*args, **kwargs) ExprKind::JoinedStr { .. } => { if is_default_encode(args, kwargs) { - if let Some(check) = - delete_default_encode_arg_or_kwarg(expr, args, kwargs, checker.patch()) - { + if let Some(check) = delete_default_encode_arg_or_kwarg( + expr, + args, + kwargs, + checker.patch(&CheckCode::U012), + ) { checker.add_check(check); } } diff --git a/src/pyupgrade/plugins/unnecessary_future_import.rs b/src/pyupgrade/plugins/unnecessary_future_import.rs index 0e807be99d..9e21e8b2a3 100644 --- a/src/pyupgrade/plugins/unnecessary_future_import.rs +++ b/src/pyupgrade/plugins/unnecessary_future_import.rs @@ -56,7 +56,7 @@ pub fn unnecessary_future_import(checker: &mut Checker, stmt: &Stmt, names: &[Lo ), Range::from_located(stmt), ); - if checker.patch() { + if checker.patch(check.kind.code()) { let context = checker.binding_context(); let deleted: Vec<&Stmt> = checker .deletions diff --git a/src/pyupgrade/plugins/unnecessary_lru_cache_params.rs b/src/pyupgrade/plugins/unnecessary_lru_cache_params.rs index 79e1533860..5e1ecca7e9 100644 --- a/src/pyupgrade/plugins/unnecessary_lru_cache_params.rs +++ b/src/pyupgrade/plugins/unnecessary_lru_cache_params.rs @@ -11,7 +11,7 @@ pub fn unnecessary_lru_cache_params(checker: &mut Checker, decorator_list: &[Exp &checker.from_imports, &checker.import_aliases, ) { - if checker.patch() { + if checker.patch(check.kind.code()) { if let Some(fix) = fixes::remove_unnecessary_lru_cache_params(checker.locator, &check.location) { diff --git a/src/pyupgrade/plugins/use_pep585_annotation.rs b/src/pyupgrade/plugins/use_pep585_annotation.rs index 1654a09a35..f56e0c3585 100644 --- a/src/pyupgrade/plugins/use_pep585_annotation.rs +++ b/src/pyupgrade/plugins/use_pep585_annotation.rs @@ -12,7 +12,7 @@ pub fn use_pep585_annotation(checker: &mut Checker, expr: &Expr, id: &str) { CheckKind::UsePEP585Annotation(replacement.to_string()), Range::from_located(expr), ); - if checker.patch() { + if checker.patch(check.kind.code()) { check.amend(Fix::replacement( replacement.to_lowercase(), expr.location, diff --git a/src/pyupgrade/plugins/use_pep604_annotation.rs b/src/pyupgrade/plugins/use_pep604_annotation.rs index 0ecba45541..af216a26fd 100644 --- a/src/pyupgrade/plugins/use_pep604_annotation.rs +++ b/src/pyupgrade/plugins/use_pep604_annotation.rs @@ -47,7 +47,7 @@ pub fn use_pep604_annotation(checker: &mut Checker, expr: &Expr, value: &Expr, s let call_path = dealias_call_path(collect_call_paths(value), &checker.import_aliases); if checker.match_typing_call_path(&call_path, "Optional") { let mut check = Check::new(CheckKind::UsePEP604Annotation, Range::from_located(expr)); - if checker.patch() { + if checker.patch(check.kind.code()) { let mut generator = SourceGenerator::new(); if let Ok(()) = generator.unparse_expr(&optional(slice), 0) { if let Ok(content) = generator.generate() { @@ -62,7 +62,7 @@ pub fn use_pep604_annotation(checker: &mut Checker, expr: &Expr, value: &Expr, s checker.add_check(check); } else if checker.match_typing_call_path(&call_path, "Union") { let mut check = Check::new(CheckKind::UsePEP604Annotation, Range::from_located(expr)); - if checker.patch() { + if checker.patch(check.kind.code()) { match &slice.node { ExprKind::Slice { .. } => { // Invalid type annotation. diff --git a/src/pyupgrade/plugins/useless_metaclass_type.rs b/src/pyupgrade/plugins/useless_metaclass_type.rs index 70b2bb2785..b751a78c95 100644 --- a/src/pyupgrade/plugins/useless_metaclass_type.rs +++ b/src/pyupgrade/plugins/useless_metaclass_type.rs @@ -11,7 +11,7 @@ pub fn useless_metaclass_type(checker: &mut Checker, stmt: &Stmt, value: &Expr, if let Some(mut check) = checks::useless_metaclass_type(targets, value, Range::from_located(stmt)) { - if checker.patch() { + if checker.patch(check.kind.code()) { let context = checker.binding_context(); let deleted: Vec<&Stmt> = checker .deletions diff --git a/src/pyupgrade/plugins/useless_object_inheritance.rs b/src/pyupgrade/plugins/useless_object_inheritance.rs index 4cb6789daf..3371c78a41 100644 --- a/src/pyupgrade/plugins/useless_object_inheritance.rs +++ b/src/pyupgrade/plugins/useless_object_inheritance.rs @@ -14,7 +14,7 @@ pub fn useless_object_inheritance( ) { let scope = checker.current_scope(); if let Some(mut check) = checks::useless_object_inheritance(name, bases, scope) { - if checker.patch() { + if checker.patch(check.kind.code()) { if let Some(fix) = pyupgrade::fixes::remove_class_def_base( checker.locator, &stmt.location, diff --git a/src/rules/checks.rs b/src/rules/checks.rs index 47ae55932e..42bf956aa5 100644 --- a/src/rules/checks.rs +++ b/src/rules/checks.rs @@ -3,10 +3,10 @@ use once_cell::sync::Lazy; use rustpython_ast::Location; use crate::ast::types::Range; -use crate::autofix::Fix; +use crate::autofix::{fixer, Fix}; use crate::checks::CheckKind; use crate::source_code_locator::SourceCodeLocator; -use crate::Check; +use crate::{Check, Settings}; /// See: https://github.com/microsoft/vscode/blob/095ddabc52b82498ee7f718a34f9dd11d59099a8/src/vs/base/common/strings.ts#L1094 static CONFUSABLES: Lazy> = Lazy::new(|| { @@ -1606,7 +1606,8 @@ pub fn ambiguous_unicode_character( start: &Location, end: &Location, context: Context, - fix: bool, + settings: &Settings, + autofix: &fixer::Mode, ) -> Vec { let mut checks = vec![]; @@ -1645,14 +1646,16 @@ pub fn ambiguous_unicode_character( end_location, }, ); - if fix { - check.amend(Fix::replacement( - representant.to_string(), - location, - end_location, - )); + if settings.enabled.contains(check.kind.code()) { + if autofix.patch() && settings.fixable.contains(check.kind.code()) { + check.amend(Fix::replacement( + representant.to_string(), + location, + end_location, + )); + } + checks.push(check); } - checks.push(check); } } diff --git a/src/settings/configuration.rs b/src/settings/configuration.rs index 8a22db7850..3ec31d3599 100644 --- a/src/settings/configuration.rs +++ b/src/settings/configuration.rs @@ -25,13 +25,15 @@ pub struct Configuration { pub extend_ignore: Vec, pub extend_select: Vec, pub fix: bool, + pub fixable: Vec, pub ignore: Vec, pub line_length: usize, pub per_file_ignores: Vec, pub select: Vec, + pub show_source: bool, pub src: Vec, pub target_version: PythonVersion, - pub show_source: bool, + pub unfixable: Vec, // Plugins pub flake8_annotations: flake8_annotations::settings::Settings, pub flake8_bugbear: flake8_bugbear::settings::Settings, @@ -122,6 +124,28 @@ impl Configuration { .unwrap_or_else(|| vec![CheckCodePrefix::E, CheckCodePrefix::F]), extend_select: options.extend_select.unwrap_or_default(), fix: options.fix.unwrap_or_default(), + fixable: options.fixable.unwrap_or_else(|| { + // TODO(charlie): Autogenerate this list. + vec![ + CheckCodePrefix::A, + CheckCodePrefix::B, + CheckCodePrefix::BLE, + CheckCodePrefix::C, + CheckCodePrefix::D, + CheckCodePrefix::E, + CheckCodePrefix::F, + CheckCodePrefix::I, + CheckCodePrefix::M, + CheckCodePrefix::N, + CheckCodePrefix::Q, + CheckCodePrefix::S, + CheckCodePrefix::T, + CheckCodePrefix::U, + CheckCodePrefix::W, + CheckCodePrefix::YTT, + ] + }), + unfixable: options.unfixable.unwrap_or_default(), ignore: options.ignore.unwrap_or_default(), line_length: options.line_length.unwrap_or(88), per_file_ignores: options diff --git a/src/settings/mod.rs b/src/settings/mod.rs index 2f923d6f4d..fd42d27860 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -30,11 +30,12 @@ pub struct Settings { pub enabled: FnvHashSet, pub exclude: Vec, pub extend_exclude: Vec, + pub fixable: FnvHashSet, pub line_length: usize, pub per_file_ignores: Vec, + pub show_source: bool, pub src: Vec, pub target_version: PythonVersion, - pub show_source: bool, // Plugins pub flake8_annotations: flake8_annotations::settings::Settings, pub flake8_bugbear: flake8_bugbear::settings::Settings, @@ -50,13 +51,20 @@ impl Settings { Self { dummy_variable_rgx: config.dummy_variable_rgx, enabled: resolve_codes( - &config.select, - &config.extend_select, - &config.ignore, - &config.extend_ignore, + &config + .select + .into_iter() + .chain(config.extend_select.into_iter()) + .collect::>(), + &config + .ignore + .into_iter() + .chain(config.extend_ignore.into_iter()) + .collect::>(), ), exclude: config.exclude, extend_exclude: config.extend_exclude, + fixable: resolve_codes(&config.fixable, &config.unfixable), flake8_annotations: config.flake8_annotations, flake8_bugbear: config.flake8_bugbear, flake8_quotes: config.flake8_quotes, @@ -75,7 +83,8 @@ impl Settings { pub fn for_rule(check_code: CheckCode) -> Self { Self { dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(), - enabled: FnvHashSet::from_iter([check_code]), + enabled: FnvHashSet::from_iter([check_code.clone()]), + fixable: FnvHashSet::from_iter([check_code]), exclude: Default::default(), extend_exclude: Default::default(), line_length: 88, @@ -96,7 +105,8 @@ impl Settings { pub fn for_rules(check_codes: Vec) -> Self { Self { dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(), - enabled: FnvHashSet::from_iter(check_codes), + enabled: FnvHashSet::from_iter(check_codes.clone()), + fixable: FnvHashSet::from_iter(check_codes), exclude: Default::default(), extend_exclude: Default::default(), line_length: 88, @@ -122,6 +132,9 @@ impl Hash for Settings { for value in self.enabled.iter() { value.hash(state); } + for value in self.fixable.iter() { + value.hash(state); + } self.line_length.hash(state); for value in self.per_file_ignores.iter() { value.hash(state); @@ -141,12 +154,7 @@ impl Hash for 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], -) -> FnvHashSet { +fn resolve_codes(select: &[CheckCodePrefix], ignore: &[CheckCodePrefix]) -> FnvHashSet { let mut codes: FnvHashSet = FnvHashSet::default(); for specificity in [ PrefixSpecificity::Category, @@ -159,11 +167,6 @@ fn resolve_codes( 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() { @@ -171,13 +174,6 @@ fn resolve_codes( } } } - for prefix in extend_ignore { - if prefix.specificity() == specificity { - for code in prefix.codes() { - codes.remove(&code); - } - } - } } codes } @@ -192,19 +188,19 @@ mod tests { #[test] fn resolver() { - let actual = resolve_codes(&[CheckCodePrefix::W], &[], &[], &[]); + let actual = resolve_codes(&[CheckCodePrefix::W], &[]); let expected = FnvHashSet::from_iter([CheckCode::W292, CheckCode::W605]); assert_eq!(actual, expected); - let actual = resolve_codes(&[CheckCodePrefix::W6], &[], &[], &[]); + let actual = resolve_codes(&[CheckCodePrefix::W6], &[]); let expected = FnvHashSet::from_iter([CheckCode::W605]); assert_eq!(actual, expected); - let actual = resolve_codes(&[CheckCodePrefix::W], &[], &[CheckCodePrefix::W292], &[]); + let actual = resolve_codes(&[CheckCodePrefix::W], &[CheckCodePrefix::W292]); let expected = FnvHashSet::from_iter([CheckCode::W605]); assert_eq!(actual, expected); - let actual = resolve_codes(&[CheckCodePrefix::W605], &[], &[CheckCodePrefix::W605], &[]); + let actual = resolve_codes(&[CheckCodePrefix::W605], &[CheckCodePrefix::W605]); let expected = FnvHashSet::from_iter([]); assert_eq!(actual, expected); } diff --git a/src/settings/options.rs b/src/settings/options.rs index 4521edfae5..b1f7ab306c 100644 --- a/src/settings/options.rs +++ b/src/settings/options.rs @@ -19,12 +19,14 @@ pub struct Options { pub extend_ignore: Option>, pub extend_select: Option>, pub fix: Option, + pub fixable: Option>, pub ignore: Option>, pub line_length: Option, pub select: Option>, + pub show_source: Option, pub src: Option>, pub target_version: Option, - pub show_source: Option, + pub unfixable: Option>, // Plugins pub flake8_annotations: Option, pub flake8_bugbear: Option, diff --git a/src/settings/pyproject.rs b/src/settings/pyproject.rs index b5abb16f7f..f76a74087f 100644 --- a/src/settings/pyproject.rs +++ b/src/settings/pyproject.rs @@ -134,19 +134,21 @@ mod tests { pyproject.tool, Some(Tools { ruff: Some(Options { - line_length: None, - fix: None, + dummy_variable_rgx: None, exclude: None, extend_exclude: None, - select: None, - extend_select: None, - ignore: None, extend_ignore: None, + extend_select: None, + fix: None, + fixable: None, + ignore: None, + line_length: None, per_file_ignores: None, - dummy_variable_rgx: None, + select: None, + show_source: None, src: None, target_version: None, - show_source: None, + unfixable: None, flake8_annotations: None, flake8_bugbear: None, flake8_quotes: None, @@ -169,19 +171,21 @@ line-length = 79 pyproject.tool, Some(Tools { ruff: Some(Options { - line_length: Some(79), - fix: None, + dummy_variable_rgx: None, exclude: None, extend_exclude: None, - select: None, - extend_select: None, - ignore: None, extend_ignore: None, + extend_select: None, + fix: None, + fixable: None, + ignore: None, + line_length: Some(79), per_file_ignores: None, - dummy_variable_rgx: None, + select: None, + show_source: None, src: None, target_version: None, - show_source: None, + unfixable: None, flake8_annotations: None, flake8_bugbear: None, flake8_quotes: None, @@ -212,6 +216,8 @@ exclude = ["foo.py"] extend_select: None, ignore: None, extend_ignore: None, + fixable: None, + unfixable: None, per_file_ignores: None, dummy_variable_rgx: None, src: None, @@ -239,19 +245,21 @@ select = ["E501"] pyproject.tool, Some(Tools { ruff: Some(Options { - line_length: None, - fix: None, + dummy_variable_rgx: None, exclude: None, extend_exclude: None, - select: Some(vec![CheckCodePrefix::E501]), - extend_select: None, - ignore: None, extend_ignore: None, + extend_select: None, + fix: None, + fixable: None, + ignore: None, + line_length: None, per_file_ignores: None, - dummy_variable_rgx: None, + select: Some(vec![CheckCodePrefix::E501]), + show_source: None, src: None, target_version: None, - show_source: None, + unfixable: None, flake8_annotations: None, flake8_bugbear: None, flake8_quotes: None, @@ -275,19 +283,21 @@ ignore = ["E501"] pyproject.tool, Some(Tools { ruff: Some(Options { - line_length: None, - fix: None, + dummy_variable_rgx: None, exclude: None, extend_exclude: None, - select: None, - extend_select: Some(vec![CheckCodePrefix::M001]), - ignore: Some(vec![CheckCodePrefix::E501]), extend_ignore: None, + extend_select: Some(vec![CheckCodePrefix::M001]), + fix: None, + fixable: None, + ignore: Some(vec![CheckCodePrefix::E501]), + line_length: None, per_file_ignores: None, - dummy_variable_rgx: None, + select: None, + show_source: None, src: None, target_version: None, - show_source: None, + unfixable: None, flake8_annotations: None, flake8_bugbear: None, flake8_quotes: None, @@ -362,6 +372,8 @@ other-attribute = 1 extend_select: None, ignore: None, extend_ignore: None, + fixable: None, + unfixable: None, per_file_ignores: Some(FnvHashMap::from_iter([( "__init__.py".to_string(), vec![CheckCodePrefix::F401] diff --git a/src/settings/user.rs b/src/settings/user.rs index bb2c53abc9..7cf3446626 100644 --- a/src/settings/user.rs +++ b/src/settings/user.rs @@ -43,13 +43,15 @@ pub struct UserConfiguration { pub extend_ignore: Vec, pub extend_select: Vec, pub fix: bool, + pub fixable: Vec, pub ignore: Vec, pub line_length: usize, pub per_file_ignores: Vec<(Exclusion, Vec)>, pub select: Vec, + pub show_source: bool, pub src: Vec, pub target_version: PythonVersion, - pub show_source: bool, + pub unfixable: Vec, // Plugins pub flake8_annotations: flake8_annotations::settings::Settings, pub flake8_quotes: flake8_quotes::settings::Settings, @@ -82,6 +84,8 @@ impl UserConfiguration { extend_ignore: configuration.extend_ignore, extend_select: configuration.extend_select, fix: configuration.fix, + fixable: configuration.fixable, + unfixable: configuration.unfixable, ignore: configuration.ignore, line_length: configuration.line_length, per_file_ignores: configuration