diff --git a/README.md b/README.md index a05db69ee6..afd954d23e 100644 --- a/README.md +++ b/README.md @@ -222,59 +222,63 @@ Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis F ## Rules -| Code | Name | Message | -| ---- | ----- | ------- | -| E402 | ModuleImportNotAtTopOfFile | Module level import not at top of file | -| E501 | LineTooLong | Line too long (89 > 88 characters) | -| E711 | NoneComparison | Comparison to `None` should be `cond is None` | -| E712 | TrueFalseComparison | Comparison to `True` should be `cond is True` | -| E713 | NotInTest | Test for membership should be `not in` | -| E714 | NotIsTest | Test for object identity should be `is not` | -| E721 | TypeComparison | do not compare types, use `isinstance()` | -| E722 | DoNotUseBareExcept | Do not use bare `except` | -| E731 | DoNotAssignLambda | Do not assign a lambda expression, use a def | -| E741 | AmbiguousVariableName | ambiguous variable name '...' | -| E742 | AmbiguousClassName | ambiguous class name '...' | -| E743 | AmbiguousFunctionName | ambiguous function name '...' | -| E902 | IOError | ... | -| E999 | SyntaxError | SyntaxError: ... | -| F401 | UnusedImport | `...` imported but unused | -| F402 | ImportShadowedByLoopVar | import '...' from line 1 shadowed by loop variable | -| F403 | ImportStarUsed | `from ... import *` used; unable to detect undefined names | -| F404 | LateFutureImport | from __future__ imports must occur at the beginning of the file | -| F405 | ImportStarUsage | '...' may be undefined, or defined from star imports: ... | -| F406 | ImportStarNotPermitted | `from ... import *` only allowed at module level | -| F407 | FutureFeatureNotDefined | future feature '...' is not defined | -| F541 | FStringMissingPlaceholders | f-string without any placeholders | -| F601 | MultiValueRepeatedKeyLiteral | Dictionary key literal repeated | -| F602 | MultiValueRepeatedKeyVariable | Dictionary key `...` repeated | -| F621 | TooManyExpressionsInStarredAssignment | too many expressions in star-unpacking assignment | -| F622 | TwoStarredExpressions | two starred expressions in assignment | -| F631 | AssertTuple | Assert test is a non-empty tuple, which is always `True` | -| F632 | IsLiteral | use ==/!= to compare constant literals | -| F633 | InvalidPrintSyntax | use of >> is invalid with print function | -| F634 | IfTuple | If test is a tuple, which is always `True` | -| F701 | BreakOutsideLoop | `break` outside loop | -| F702 | ContinueOutsideLoop | `continue` not properly in loop | -| F704 | YieldOutsideFunction | a `yield` or `yield from` statement outside of a function/method | -| F706 | ReturnOutsideFunction | a `return` statement outside of a function/method | -| F707 | DefaultExceptNotLast | an `except:` block as not the last exception handler | -| F722 | ForwardAnnotationSyntaxError | syntax error in forward annotation '...' | -| F821 | UndefinedName | Undefined name `...` | -| F822 | UndefinedExport | Undefined name `...` in `__all__` | -| F823 | UndefinedLocal | Local variable `...` referenced before assignment | -| F831 | DuplicateArgumentName | Duplicate argument name in function definition | -| F841 | UnusedVariable | Local variable `...` is assigned to but never used | -| F901 | RaiseNotImplemented | `raise NotImplemented` should be `raise NotImplementedError` | -| A001 | BuiltinVariableShadowing | Variable `...` is shadowing a python builtin | -| A002 | BuiltinArgumentShadowing | Argument `...` is shadowing a python builtin | -| A003 | BuiltinAttributeShadowing | class attribute `...` is shadowing a python builtin | -| SPR001 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` | -| T201 | PrintFound | `print` found | -| T203 | PPrintFound | `pprint` found` | -| R001 | UselessObjectInheritance | Class `...` inherits from object | -| R002 | NoAssertEquals | `assertEquals` is deprecated, use `assertEqual` instead | -| M001 | UnusedNOQA | Unused `noqa` directive | +The ✅ emoji indicates a rule is enabled by default. + +The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` command-line option. + +| Code | Name | Message | | | +| ---- | ---- | ------- | --- | --- | +| E402 | ModuleImportNotAtTopOfFile | Module level import not at top of file | ✅ | | +| E501 | LineTooLong | Line too long (89 > 88 characters) | ✅ | | +| E711 | NoneComparison | Comparison to `None` should be `cond is None` | ✅ | | +| E712 | TrueFalseComparison | Comparison to `True` should be `cond is True` | ✅ | | +| E713 | NotInTest | Test for membership should be `not in` | ✅ | | +| E714 | NotIsTest | Test for object identity should be `is not` | ✅ | | +| E721 | TypeComparison | Do not compare types, use `isinstance()` | ✅ | | +| E722 | DoNotUseBareExcept | Do not use bare `except` | ✅ | | +| E731 | DoNotAssignLambda | Do not assign a lambda expression, use a def | ✅ | | +| E741 | AmbiguousVariableName | Ambiguous variable name: `...` | ✅ | | +| E742 | AmbiguousClassName | Ambiguous class name: `...` | ✅ | | +| E743 | AmbiguousFunctionName | Ambiguous function name: `...` | ✅ | | +| E902 | IOError | IOError: `...` | ✅ | | +| E999 | SyntaxError | SyntaxError: `...` | ✅ | | +| F401 | UnusedImport | `...` imported but unused | ✅ | 🛠 | +| F402 | ImportShadowedByLoopVar | Import `...` from line 1 shadowed by loop variable | ✅ | | +| F403 | ImportStarUsed | `from ... import *` used; unable to detect undefined names | ✅ | | +| F404 | LateFutureImport | `from __future__` imports must occur at the beginning of the file | ✅ | | +| F405 | ImportStarUsage | `...` may be undefined, or defined from star imports: `...` | ✅ | | +| F406 | ImportStarNotPermitted | `from ... import *` only allowed at module level | ✅ | | +| F407 | FutureFeatureNotDefined | Future feature `...` is not defined | ✅ | | +| F541 | FStringMissingPlaceholders | f-string without any placeholders | ✅ | | +| F601 | MultiValueRepeatedKeyLiteral | Dictionary key literal repeated | ✅ | | +| F602 | MultiValueRepeatedKeyVariable | Dictionary key `...` repeated | ✅ | | +| F621 | ExpressionsInStarAssignment | Too many expressions in star-unpacking assignment | ✅ | | +| F622 | TwoStarredExpressions | Two starred expressions in assignment | ✅ | | +| F631 | AssertTuple | Assert test is a non-empty tuple, which is always `True` | ✅ | | +| F632 | IsLiteral | Use `==` and `!=` to compare constant literals | ✅ | | +| F633 | InvalidPrintSyntax | Use of `>>` is invalid with `print` function | ✅ | | +| F634 | IfTuple | If test is a tuple, which is always `True` | ✅ | | +| F701 | BreakOutsideLoop | `break` outside loop | ✅ | | +| F702 | ContinueOutsideLoop | `continue` not properly in loop | ✅ | | +| F704 | YieldOutsideFunction | `yield` or `yield from` statement outside of a function/method | ✅ | | +| F706 | ReturnOutsideFunction | `return` statement outside of a function/method | ✅ | | +| F707 | DefaultExceptNotLast | An `except:` block as not the last exception handler | ✅ | | +| F722 | ForwardAnnotationSyntaxError | Syntax error in forward annotation: `...` | ✅ | | +| F821 | UndefinedName | Undefined name `...` | ✅ | | +| F822 | UndefinedExport | Undefined name `...` in `__all__` | ✅ | | +| F823 | UndefinedLocal | Local variable `...` referenced before assignment | ✅ | | +| F831 | DuplicateArgumentName | Duplicate argument name in function definition | ✅ | | +| F841 | UnusedVariable | Local variable `...` is assigned to but never used | ✅ | | +| F901 | RaiseNotImplemented | `raise NotImplemented` should be `raise NotImplementedError` | ✅ | | +| A001 | BuiltinVariableShadowing | Variable `...` is shadowing a python builtin | | | +| A002 | BuiltinArgumentShadowing | Argument `...` is shadowing a python builtin | | | +| A003 | BuiltinAttributeShadowing | Class attribute `...` is shadowing a python builtin | | | +| SPR001 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` | | 🛠 | +| T201 | PrintFound | `print` found | | 🛠 | +| T203 | PPrintFound | `pprint` found | | 🛠 | +| R001 | UselessObjectInheritance | Class `...` inherits from object | | 🛠 | +| R002 | NoAssertEquals | `assertEquals` is deprecated, use `assertEqual` instead | | 🛠 | +| M001 | UnusedNOQA | Unused `noqa` directive | | 🛠 | ## Integrations diff --git a/examples/generate_rules_table.rs b/examples/generate_rules_table.rs index 54d5394114..e9f622d88c 100644 --- a/examples/generate_rules_table.rs +++ b/examples/generate_rules_table.rs @@ -1,19 +1,27 @@ /// Generate a Markdown-compatible table of supported lint rules. -use ruff::checks::{CheckCode, ALL_CHECK_CODES}; +use ruff::checks::{CheckCode, ALL_CHECK_CODES, DEFAULT_CHECK_CODES}; fn main() { let mut check_codes: Vec = ALL_CHECK_CODES.to_vec(); check_codes.sort(); - println!("| Code | Name | Message |"); - println!("| ---- | ----- | ------- |"); + println!("| Code | Name | Message | | |"); + println!("| ---- | ---- | ------- | --- | --- |"); for check_code in check_codes { let check_kind = check_code.kind(); + let default_token = if DEFAULT_CHECK_CODES.contains(&check_code) { + "✅" + } else { + "" + }; + let fix_token = if check_kind.fixable() { "🛠" } else { "" }; println!( - "| {} | {} | {} |", + "| {} | {} | {} | {} | {} |", check_kind.code().as_str(), check_kind.name(), - check_kind.body() + check_kind.body(), + default_token, + fix_token ); } } diff --git a/src/ast/checks.rs b/src/ast/checks.rs index aaa5614e4f..df424becaa 100644 --- a/src/ast/checks.rs +++ b/src/ast/checks.rs @@ -576,10 +576,7 @@ pub fn check_starred_expressions( if check_too_many_expressions { if let Some(starred_index) = starred_index { if starred_index >= 1 << 8 || elts.len() - starred_index > 1 << 24 { - return Some(Check::new( - CheckKind::TooManyExpressionsInStarredAssignment, - location, - )); + return Some(Check::new(CheckKind::ExpressionsInStarAssignment, location)); } } } diff --git a/src/check_ast.rs b/src/check_ast.rs index ec4120b7c3..9a70ed140e 100644 --- a/src/check_ast.rs +++ b/src/check_ast.rs @@ -1380,14 +1380,14 @@ impl<'a> Checker<'a> { let scope = &self.scopes[*scope_index]; for (name, binding) in scope.values.iter() { if matches!(binding.kind, BindingKind::StarImportation) { - from_list.push(name.as_str()); + from_list.push(name.to_string()); } } } from_list.sort(); self.checks.push(Check::new( - CheckKind::ImportStarUsage(id.clone(), from_list.join(", ")), + CheckKind::ImportStarUsage(id.clone(), from_list), self.locate_check(Range::from_located(expr)), )); } @@ -1645,7 +1645,7 @@ impl<'a> Checker<'a> { let mut from_list = vec![]; for (name, binding) in scope.values.iter() { if matches!(binding.kind, BindingKind::StarImportation) { - from_list.push(name.as_str()); + from_list.push(name.to_string()); } } from_list.sort(); @@ -1653,7 +1653,7 @@ impl<'a> Checker<'a> { for name in names { if !scope.values.contains_key(name) { self.checks.push(Check::new( - CheckKind::ImportStarUsage(name.clone(), from_list.join(", ")), + CheckKind::ImportStarUsage(name.clone(), from_list.clone()), self.locate_check(all_binding.range), )); } diff --git a/src/checks.rs b/src/checks.rs index 728103b8c0..4e7fc606d9 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -2,6 +2,7 @@ use std::str::FromStr; use crate::ast::types::Range; use anyhow::Result; +use itertools::Itertools; use rustpython_parser::ast::Location; use serde::{Deserialize, Serialize}; @@ -329,20 +330,22 @@ impl CheckCode { CheckCode::E741 => CheckKind::AmbiguousVariableName("...".to_string()), CheckCode::E742 => CheckKind::AmbiguousClassName("...".to_string()), CheckCode::E743 => CheckKind::AmbiguousFunctionName("...".to_string()), - CheckCode::E902 => CheckKind::IOError("...".to_string()), - CheckCode::E999 => CheckKind::SyntaxError("...".to_string()), + CheckCode::E902 => CheckKind::IOError("IOError: `...`".to_string()), + CheckCode::E999 => CheckKind::SyntaxError("`...`".to_string()), // pyflakes CheckCode::F401 => CheckKind::UnusedImport("...".to_string()), CheckCode::F402 => CheckKind::ImportShadowedByLoopVar("...".to_string(), 1), CheckCode::F403 => CheckKind::ImportStarUsed("...".to_string()), CheckCode::F404 => CheckKind::LateFutureImport, - CheckCode::F405 => CheckKind::ImportStarUsage("...".to_string(), "...".to_string()), + CheckCode::F405 => { + CheckKind::ImportStarUsage("...".to_string(), vec!["...".to_string()]) + } CheckCode::F406 => CheckKind::ImportStarNotPermitted("...".to_string()), CheckCode::F407 => CheckKind::FutureFeatureNotDefined("...".to_string()), CheckCode::F541 => CheckKind::FStringMissingPlaceholders, CheckCode::F601 => CheckKind::MultiValueRepeatedKeyLiteral, CheckCode::F602 => CheckKind::MultiValueRepeatedKeyVariable("...".to_string()), - CheckCode::F621 => CheckKind::TooManyExpressionsInStarredAssignment, + CheckCode::F621 => CheckKind::ExpressionsInStarAssignment, CheckCode::F622 => CheckKind::TwoStarredExpressions, CheckCode::F631 => CheckKind::AssertTuple, CheckCode::F632 => CheckKind::IsLiteral, @@ -393,7 +396,6 @@ pub enum RejectedCmpop { #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum CheckKind { - UnusedNOQA(Option), AmbiguousClassName(String), AmbiguousFunctionName(String), AmbiguousVariableName(String), @@ -404,14 +406,15 @@ pub enum CheckKind { DoNotAssignLambda, DoNotUseBareExcept, DuplicateArgumentName, - ForwardAnnotationSyntaxError(String), + ExpressionsInStarAssignment, FStringMissingPlaceholders, + ForwardAnnotationSyntaxError(String), FutureFeatureNotDefined(String), IOError(String), IfTuple, ImportShadowedByLoopVar(String, usize), ImportStarNotPermitted(String), - ImportStarUsage(String, String), + ImportStarUsage(String, Vec), ImportStarUsed(String), InvalidPrintSyntax, IsLiteral, @@ -427,7 +430,6 @@ pub enum CheckKind { RaiseNotImplemented, ReturnOutsideFunction, SyntaxError(String), - TooManyExpressionsInStarredAssignment, TrueFalseComparison(bool, RejectedCmpop), TwoStarredExpressions, TypeComparison, @@ -435,6 +437,7 @@ pub enum CheckKind { UndefinedLocal(String), UndefinedName(String), UnusedImport(String), + UnusedNOQA(Option), UnusedVariable(String), UselessObjectInheritance(String), YieldOutsideFunction, @@ -463,6 +466,7 @@ impl CheckKind { CheckKind::DoNotAssignLambda => "DoNotAssignLambda", CheckKind::DoNotUseBareExcept => "DoNotUseBareExcept", CheckKind::DuplicateArgumentName => "DuplicateArgumentName", + CheckKind::ExpressionsInStarAssignment => "ExpressionsInStarAssignment", CheckKind::FStringMissingPlaceholders => "FStringMissingPlaceholders", CheckKind::ForwardAnnotationSyntaxError(_) => "ForwardAnnotationSyntaxError", CheckKind::FutureFeatureNotDefined(_) => "FutureFeatureNotDefined", @@ -486,9 +490,6 @@ impl CheckKind { CheckKind::RaiseNotImplemented => "RaiseNotImplemented", CheckKind::ReturnOutsideFunction => "ReturnOutsideFunction", CheckKind::SyntaxError(_) => "SyntaxError", - CheckKind::TooManyExpressionsInStarredAssignment => { - "TooManyExpressionsInStarredAssignment" - } CheckKind::TrueFalseComparison(_, _) => "TrueFalseComparison", CheckKind::TwoStarredExpressions => "TwoStarredExpressions", CheckKind::TypeComparison => "TypeComparison", @@ -496,10 +497,10 @@ impl CheckKind { CheckKind::UndefinedLocal(_) => "UndefinedLocal", CheckKind::UndefinedName(_) => "UndefinedName", CheckKind::UnusedImport(_) => "UnusedImport", + CheckKind::UnusedNOQA(_) => "UnusedNOQA", CheckKind::UnusedVariable(_) => "UnusedVariable", CheckKind::UselessObjectInheritance(_) => "UselessObjectInheritance", CheckKind::YieldOutsideFunction => "YieldOutsideFunction", - CheckKind::UnusedNOQA(_) => "UnusedNOQA", // flake8-builtins CheckKind::BuiltinVariableShadowing(_) => "BuiltinVariableShadowing", CheckKind::BuiltinArgumentShadowing(_) => "BuiltinArgumentShadowing", @@ -548,7 +549,7 @@ impl CheckKind { CheckKind::RaiseNotImplemented => &CheckCode::F901, CheckKind::ReturnOutsideFunction => &CheckCode::F706, CheckKind::SyntaxError(_) => &CheckCode::E999, - CheckKind::TooManyExpressionsInStarredAssignment => &CheckCode::F621, + CheckKind::ExpressionsInStarAssignment => &CheckCode::F621, CheckKind::TrueFalseComparison(_, _) => &CheckCode::E712, CheckKind::TwoStarredExpressions => &CheckCode::F622, CheckKind::TypeComparison => &CheckCode::E721, @@ -576,13 +577,13 @@ impl CheckKind { pub fn body(&self) -> String { match self { CheckKind::AmbiguousClassName(name) => { - format!("ambiguous class name '{}'", name) + format!("Ambiguous class name: `{}`", name) } CheckKind::AmbiguousFunctionName(name) => { - format!("ambiguous function name '{}'", name) + format!("Ambiguous function name: `{}`", name) } CheckKind::AmbiguousVariableName(name) => { - format!("ambiguous variable name '{}'", name) + format!("Ambiguous variable name: `{}`", name) } CheckKind::AssertTuple => { "Assert test is a non-empty tuple, which is always `True`".to_string() @@ -590,7 +591,7 @@ impl CheckKind { CheckKind::BreakOutsideLoop => "`break` outside loop".to_string(), CheckKind::ContinueOutsideLoop => "`continue` not properly in loop".to_string(), CheckKind::DefaultExceptNotLast => { - "an `except:` block as not the last exception handler".to_string() + "An `except:` block as not the last exception handler".to_string() } CheckKind::DoNotAssignLambda => { "Do not assign a lambda expression, use a def".to_string() @@ -600,19 +601,21 @@ impl CheckKind { "Duplicate argument name in function definition".to_string() } CheckKind::ForwardAnnotationSyntaxError(body) => { - format!("syntax error in forward annotation '{body}'") + format!("Syntax error in forward annotation: `{body}`") } CheckKind::FStringMissingPlaceholders => { "f-string without any placeholders".to_string() } CheckKind::FutureFeatureNotDefined(name) => { - format!("future feature '{name}' is not defined") + format!("Future feature `{name}` is not defined") } CheckKind::IOError(message) => message.clone(), CheckKind::IfTuple => "If test is a tuple, which is always `True`".to_string(), - CheckKind::InvalidPrintSyntax => "use of >> is invalid with print function".to_string(), + CheckKind::InvalidPrintSyntax => { + "Use of `>>` is invalid with `print` function".to_string() + } CheckKind::ImportShadowedByLoopVar(name, line) => { - format!("import '{name}' from line {line} shadowed by loop variable") + format!("Import `{name}` from line {line} shadowed by loop variable") } CheckKind::ImportStarNotPermitted(name) => { format!("`from {name} import *` only allowed at module level") @@ -621,11 +624,15 @@ impl CheckKind { format!("`from {name} import *` used; unable to detect undefined names") } CheckKind::ImportStarUsage(name, sources) => { - format!("'{name}' may be undefined, or defined from star imports: {sources}") + let sources = sources + .iter() + .map(|source| format!("`{}`", source)) + .join(", "); + format!("`{name}` may be undefined, or defined from star imports: {sources}") } - CheckKind::IsLiteral => "use ==/!= to compare constant literals".to_string(), + CheckKind::IsLiteral => "Use `==` and `!=` to compare constant literals".to_string(), CheckKind::LateFutureImport => { - "from __future__ imports must occur at the beginning of the file".to_string() + "`from __future__` imports must occur at the beginning of the file".to_string() } CheckKind::LineTooLong(length, limit) => { format!("Line too long ({length} > {limit} characters)") @@ -654,11 +661,11 @@ impl CheckKind { "`raise NotImplemented` should be `raise NotImplementedError`".to_string() } CheckKind::ReturnOutsideFunction => { - "a `return` statement outside of a function/method".to_string() + "`return` statement outside of a function/method".to_string() } CheckKind::SyntaxError(message) => format!("SyntaxError: {message}"), - CheckKind::TooManyExpressionsInStarredAssignment => { - "too many expressions in star-unpacking assignment".to_string() + CheckKind::ExpressionsInStarAssignment => { + "Too many expressions in star-unpacking assignment".to_string() } CheckKind::TrueFalseComparison(value, op) => match *value { true => match op { @@ -678,8 +685,8 @@ impl CheckKind { } }, }, - CheckKind::TwoStarredExpressions => "two starred expressions in assignment".to_string(), - CheckKind::TypeComparison => "do not compare types, use `isinstance()`".to_string(), + CheckKind::TwoStarredExpressions => "Two starred expressions in assignment".to_string(), + CheckKind::TypeComparison => "Do not compare types, use `isinstance()`".to_string(), CheckKind::UndefinedExport(name) => { format!("Undefined name `{name}` in `__all__`") } @@ -697,7 +704,7 @@ impl CheckKind { format!("Class `{name}` inherits from object") } CheckKind::YieldOutsideFunction => { - "a `yield` or `yield from` statement outside of a function/method".to_string() + "`yield` or `yield from` statement outside of a function/method".to_string() } CheckKind::UnusedNOQA(code) => match code { None => "Unused `noqa` directive".to_string(), @@ -711,7 +718,7 @@ impl CheckKind { format!("Argument `{name}` is shadowing a python builtin") } CheckKind::BuiltinAttributeShadowing(name) => { - format!("class attribute `{name}` is shadowing a python builtin") + format!("Class attribute `{name}` is shadowing a python builtin") } // flake8-super CheckKind::SuperCallWithParameters => { @@ -719,7 +726,7 @@ impl CheckKind { } // flake8-print CheckKind::PrintFound => "`print` found".to_string(), - CheckKind::PPrintFound => "`pprint` found`".to_string(), + CheckKind::PPrintFound => "`pprint` found".to_string(), } } diff --git a/src/snapshots/ruff__linter__tests__f405.snap b/src/snapshots/ruff__linter__tests__f405.snap index ba4c4a80b1..a629a5d809 100644 --- a/src/snapshots/ruff__linter__tests__f405.snap +++ b/src/snapshots/ruff__linter__tests__f405.snap @@ -5,7 +5,7 @@ expression: checks - kind: ImportStarUsage: - name - - mymodule + - - mymodule location: row: 5 column: 11 @@ -16,7 +16,7 @@ expression: checks - kind: ImportStarUsage: - a - - mymodule + - - mymodule location: row: 11 column: 1