Compare commits

...

15 Commits

Author SHA1 Message Date
Charlie Marsh
6f317fc3e7 Try out rust-python-parser (nom-based) 2022-10-05 15:57:04 -04:00
Charlie Marsh
b307afc00c Move some accesses behind a shared function 2022-10-05 14:54:31 -04:00
Charlie Marsh
3d5bc1f51f Migrate Checker logic to independent plugins (#331) 2022-10-05 14:08:40 -04:00
Charlie Marsh
aba01745f5 Bump version to 0.0.56 2022-10-05 11:58:54 -04:00
Charlie Marsh
1eeeffab66 Add T201 and T203 to string conversion match (#332) 2022-10-05 11:58:33 -04:00
Charlie Marsh
9b564c9cf4 Bump version to 0.0.55 2022-10-04 20:07:31 -04:00
Charlie Marsh
5bf8b13644 Properly combine CLI and pyproject.toml ignores and selects (#329) 2022-10-04 20:07:17 -04:00
Anders Kaseorg
f80d5e70dd Support extend-select in pyproject.toml (#327) 2022-10-04 17:24:30 -04:00
Charlie Marsh
44897b2a5b Enable AST-to-source code generation (#292) 2022-10-04 16:27:57 -04:00
Anders Kaseorg
d1bcc919a2 Remove unnecessary Option wrapper from some pyproject::Config fields (#326) 2022-10-04 16:27:40 -04:00
Charlie Marsh
03e1397427 Bump version to 0.0.54 2022-10-04 14:32:06 -04:00
Charlie Marsh
fdb32330a9 Implement __metaclass__ = type removal (#324) 2022-10-04 14:31:52 -04:00
Charlie Marsh
4e6ae33a3a Only flag super calls in class-function scopes (#323) 2022-10-04 13:55:32 -04:00
Charlie Marsh
295ff8eb1a Add autofix and default status to README (#322) 2022-10-04 12:30:35 -04:00
Parth Shandilya
2449771d2f Fix the broken link to contribution guidelines (#321) 2022-10-04 11:10:10 -04:00
31 changed files with 2441 additions and 674 deletions

481
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.53"
version = "0.0.56"
edition = "2021"
[lib]
@@ -27,12 +27,15 @@ once_cell = { version = "1.13.1" }
path-absolutize = { version = "3.0.13", features = ["once_cell_cache"] }
rayon = { version = "1.5.3" }
regex = { version = "1.6.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/charliermarsh/RustPython.git", rev = "4f457893efc381ad5c432576b24bcc7e4a08c641" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "4f457893efc381ad5c432576b24bcc7e4a08c641" }
rustpython-common = { git = "https://github.com/charliermarsh/RustPython.git", rev = "4f457893efc381ad5c432576b24bcc7e4a08c641" }
serde = { version = "1.0.143", features = ["derive"] }
serde_json = { version = "1.0.83" }
toml = { version = "0.5.9" }
update-informer = { version = "0.5.0", default_features = false, features = ["pypi"], optional = true }
walkdir = { version = "2.3.2" }
python-parser = "0.2.0"
[dev-dependencies]
insta = { version = "1.19.1", features = ["yaml"] }

114
README.md
View File

@@ -213,6 +213,7 @@ ruff also implements some of the most popular Flake8 plugins natively, including
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (partial)
Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis Flake8:
@@ -222,59 +223,64 @@ 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 | | 🛠 |
| U001 | UselessMetaclassType | `__metaclass__ = type` is implied | | 🛠 |
| R001 | UselessObjectInheritance | Class `...` inherits from object | | 🛠 |
| R002 | NoAssertEquals | `assertEquals` is deprecated, use `assertEqual` instead | | 🛠 |
| M001 | UnusedNOQA | Unused `noqa` directive | | 🛠 |
## Integrations
@@ -477,4 +483,4 @@ MIT
## Contributing
Contributions are welcome and hugely appreciated. To get started, check out the
[contributing guidelines](https://github.com/charliermarsh/ruff/blob/main/CONTRIBUTORS.md).
[contributing guidelines](https://github.com/charliermarsh/ruff/blob/main/CONTRIBUTING.md).

View File

@@ -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<CheckCode> = 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
);
}
}

View File

@@ -0,0 +1,26 @@
use std::path::PathBuf;
use anyhow::Result;
use clap::Parser;
use rustpython_parser::parser;
use ruff::code_gen::SourceGenerator;
use ruff::fs;
#[derive(Debug, Parser)]
struct Cli {
#[arg(required = true)]
file: PathBuf,
}
fn main() -> Result<()> {
let cli = Cli::parse();
let contents = fs::read_file(&cli.file)?;
let python_ast = parser::parse_program(&contents, &cli.file.to_string_lossy())?;
let mut generator = SourceGenerator::new();
generator.unparse_suite(&python_ast)?;
println!("{}", generator.generate()?);
Ok(())
}

23
examples/parse_python.rs Normal file
View File

@@ -0,0 +1,23 @@
/// Print the AST for a given Python file.
use std::path::PathBuf;
use anyhow::Result;
use clap::Parser;
use ruff::fs;
#[derive(Debug, Parser)]
struct Cli {
#[arg(required = true)]
file: PathBuf,
}
fn main() -> Result<()> {
let cli = Cli::parse();
let contents = fs::read_file(&cli.file)?;
let ast = python_parser::file_input(python_parser::make_strspan(&contents))?.1;
println!("{:?}", ast);
Ok(())
}

37
output.py Normal file
View File

@@ -0,0 +1,37 @@
from collections import Counter
def f() -> None:
"""Docstring goes here."""
for x in range(5):
print(x)
else:
print("Nope!")
a = {
"a",
"b",
"c",
"a",
"b",
"c",
"a",
"b",
"c",
"a",
"b",
"c",
"a",
"b",
"c",
"a",
"b",
"c",
"a",
"b",
"c",
}
cls(title=title, before_text=before_text, after_text=after_text, before_description=before_description, after_description=after_description, transform_type=transform_type)

View File

@@ -20,3 +20,46 @@ class Child(Parent):
Child,
self,
).method() # wrong
class BaseClass:
def f(self):
print("f")
def defined_outside(self):
super(MyClass, self).f() # CANNOT use super()
class MyClass(BaseClass):
def normal(self):
super(MyClass, self).f() # can use super()
super().f()
def different_argument(self, other):
super(MyClass, other).f() # CANNOT use super()
def comprehension_scope(self):
[super(MyClass, self).f() for x in [1]] # CANNOT use super()
def inner_functions(self):
def outer_argument():
super(MyClass, self).f() # CANNOT use super()
def inner_argument(self):
super(MyClass, self).f() # can use super()
super().f()
outer_argument()
inner_argument(self)
def inner_class(self):
class InnerClass:
super(MyClass, self).f() # CANNOT use super()
def method(inner_self):
super(MyClass, self).f() # CANNOT use super()
InnerClass().method()
defined_outside = defined_outside

13
resources/test/fixtures/U001.py vendored Normal file
View File

@@ -0,0 +1,13 @@
class A:
__metaclass__ = type
class B:
__metaclass__ = type
def __init__(self) -> None:
pass
class C(metaclass=type):
pass

View File

@@ -3,16 +3,14 @@ use std::collections::BTreeSet;
use itertools::izip;
use regex::Regex;
use rustpython_parser::ast::{
Arg, Arguments, Cmpop, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Keyword,
Location, Stmt, StmtKind, Unaryop,
Arg, ArgData, Arguments, Cmpop, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind,
Stmt, StmtKind, Unaryop,
};
use crate::ast::operations::SourceCodeLocator;
use crate::ast::types::{
Binding, BindingKind, CheckLocator, FunctionScope, Range, Scope, ScopeKind,
};
use crate::autofix::{fixer, fixes};
use crate::checks::{Check, CheckKind, Fix, RejectedCmpop};
use crate::checks::{Check, CheckKind, RejectedCmpop};
use crate::python::builtins::BUILTINS;
/// Check IfTuple compliance.
@@ -116,6 +114,26 @@ pub fn check_do_not_assign_lambda(value: &Expr, location: Range) -> Option<Check
}
}
/// Check UselessMetaclassType compliance.
pub fn check_useless_metaclass_type(
targets: &Vec<Expr>,
value: &Expr,
location: Range,
) -> Option<Check> {
if targets.len() == 1 {
if let ExprKind::Name { id, .. } = targets.first().map(|expr| &expr.node).unwrap() {
if id == "__metaclass__" {
if let ExprKind::Name { id, .. } = &value.node {
if id == "type" {
return Some(Check::new(CheckKind::UselessMetaclassType, location));
}
}
}
}
}
None
}
fn is_ambiguous_name(name: &str) -> bool {
name == "l" || name == "I" || name == "O"
}
@@ -158,13 +176,9 @@ pub fn check_ambiguous_function_name(name: &str, location: Range) -> Option<Chec
/// Check UselessObjectInheritance compliance.
pub fn check_useless_object_inheritance(
stmt: &Stmt,
name: &str,
bases: &[Expr],
keywords: &[Keyword],
scope: &Scope,
locator: &mut SourceCodeLocator,
autofix: &fixer::Mode,
) -> Option<Check> {
for expr in bases {
if let ExprKind::Name { id, .. } = &expr.node {
@@ -175,22 +189,10 @@ pub fn check_useless_object_inheritance(
kind: BindingKind::Builtin,
..
}) => {
let mut check = Check::new(
return Some(Check::new(
CheckKind::UselessObjectInheritance(name.to_string()),
Range::from_located(expr),
);
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if let Some(fix) = fixes::remove_class_def_base(
locator,
&stmt.location,
expr.location,
bases,
keywords,
) {
check.amend(fix);
}
}
return Some(check);
));
}
_ => {}
}
@@ -278,28 +280,15 @@ pub fn check_duplicate_arguments(arguments: &Arguments) -> Vec<Check> {
}
/// Check AssertEquals compliance.
pub fn check_assert_equals(expr: &Expr, autofix: &fixer::Mode) -> Option<Check> {
pub fn check_assert_equals(expr: &Expr) -> Option<Check> {
if let ExprKind::Attribute { value, attr, .. } = &expr.node {
if attr == "assertEquals" {
if let ExprKind::Name { id, .. } = &value.node {
if id == "self" {
let mut check =
Check::new(CheckKind::NoAssertEquals, Range::from_located(expr));
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
check.amend(Fix {
content: "assertEqual".to_string(),
location: Location::new(
expr.location.row(),
expr.location.column() + 1,
),
end_location: Location::new(
expr.location.row(),
expr.location.column() + 1 + "assertEquals".len(),
),
applied: false,
});
}
return Some(check);
return Some(Check::new(
CheckKind::NoAssertEquals,
Range::from_located(expr),
));
}
}
}
@@ -576,10 +565,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));
}
}
}
@@ -696,39 +682,94 @@ pub fn check_builtin_shadowing(
}
}
/// Returns `true` if a call is an argumented `super` invocation.
pub fn is_super_call_with_arguments(func: &Expr, args: &Vec<Expr>) -> bool {
// Check: is this a `super` call?
if let ExprKind::Name { id, .. } = &func.node {
id == "super" && !args.is_empty()
} else {
false
}
}
// flake8-super
/// Check that `super()` has no args
pub fn check_super_args(
scope: &Scope,
parents: &[&Stmt],
expr: &Expr,
func: &Expr,
args: &Vec<Expr>,
locator: &mut SourceCodeLocator,
autofix: &fixer::Mode,
) -> Option<Check> {
if let ExprKind::Name { id, .. } = &func.node {
if id == "super" && !args.is_empty() {
let mut check = Check::new(
CheckKind::SuperCallWithParameters,
Range::from_located(expr),
);
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if let Some(fix) = fixes::remove_super_arguments(locator, expr) {
check.amend(fix);
if !is_super_call_with_arguments(func, args) {
return None;
}
// Check: are we in a Function scope?
if !matches!(scope.kind, ScopeKind::Function { .. }) {
return None;
}
let mut parents = parents.iter().rev();
// For a `super` invocation to be unnecessary, the first argument needs to match the enclosing
// class, and the second argument needs to match the first argument to the enclosing function.
if let [first_arg, second_arg] = args.as_slice() {
// Find the enclosing function definition (if any).
if let Some(StmtKind::FunctionDef {
args: parent_args, ..
}) = parents
.find(|stmt| matches!(stmt.node, StmtKind::FunctionDef { .. }))
.map(|stmt| &stmt.node)
{
// Extract the name of the first argument to the enclosing function.
if let Some(ArgData {
arg: parent_arg, ..
}) = parent_args.args.first().map(|expr| &expr.node)
{
// Find the enclosing class definition (if any).
if let Some(StmtKind::ClassDef {
name: parent_name, ..
}) = parents
.find(|stmt| matches!(stmt.node, StmtKind::ClassDef { .. }))
.map(|stmt| &stmt.node)
{
if let (
ExprKind::Name {
id: first_arg_id, ..
},
ExprKind::Name {
id: second_arg_id, ..
},
) = (&first_arg.node, &second_arg.node)
{
if first_arg_id == parent_name && second_arg_id == parent_arg {
return Some(Check::new(
CheckKind::SuperCallWithParameters,
Range::from_located(expr),
));
}
}
}
}
return Some(check);
}
}
None
}
// flake8-print
/// Check whether a function call is a `print` or `pprint` invocation
pub fn check_print_call(expr: &Expr, func: &Expr) -> Option<Check> {
pub fn check_print_call(
expr: &Expr,
func: &Expr,
check_print: bool,
check_pprint: bool,
) -> Option<Check> {
if let ExprKind::Name { id, .. } = &func.node {
if id == "print" {
if check_print && id == "print" {
return Some(Check::new(CheckKind::PrintFound, Range::from_located(expr)));
} else if id == "pprint" {
} else if check_pprint && id == "pprint" {
return Some(Check::new(
CheckKind::PPrintFound,
Range::from_located(expr),
@@ -738,7 +779,7 @@ pub fn check_print_call(expr: &Expr, func: &Expr) -> Option<Check> {
if let ExprKind::Attribute { value, attr, .. } = &func.node {
if let ExprKind::Name { id, .. } = &value.node {
if id == "pprint" && attr == "pprint" {
if check_pprint && id == "pprint" && attr == "pprint" {
return Some(Check::new(
CheckKind::PPrintFound,
Range::from_located(expr),

View File

@@ -19,9 +19,9 @@ use crate::ast::types::{
};
use crate::ast::visitor::{walk_excepthandler, Visitor};
use crate::ast::{checks, operations, visitor};
use crate::autofix::fixes::remove_stmt;
use crate::autofix::{fixer, fixes};
use crate::checks::{Check, CheckCode, CheckKind};
use crate::plugins;
use crate::python::builtins::{BUILTINS, MAGIC_GLOBALS};
use crate::python::future::ALL_FEATURE_NAMES;
use crate::python::typing;
@@ -31,18 +31,22 @@ pub const GLOBAL_SCOPE_INDEX: usize = 0;
static DUNDER_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"__[^\s]+__").unwrap());
struct Checker<'a> {
pub struct Checker<'a> {
// Input data.
locator: SourceCodeLocator<'a>,
settings: &'a Settings,
autofix: &'a fixer::Mode,
path: &'a Path,
// TODO(charlie): Separate immutable from mutable state. (None of these should ever change.)
pub(crate) locator: SourceCodeLocator<'a>,
pub(crate) settings: &'a Settings,
pub(crate) autofix: &'a fixer::Mode,
// Computed checks.
checks: Vec<Check>,
// Edit tracking.
// TODO(charlie): Instead of exposing deletions, wrap in a public API.
pub(crate) deletions: BTreeSet<usize>,
// Retain all scopes and parent nodes, along with a stack of indexes to track which are active
// at various points in time.
parents: Vec<&'a Stmt>,
parent_stack: Vec<usize>,
pub(crate) parents: Vec<&'a Stmt>,
pub(crate) parent_stack: Vec<usize>,
scopes: Vec<Scope>,
scope_stack: Vec<usize>,
dead_scopes: Vec<usize>,
@@ -51,7 +55,7 @@ struct Checker<'a> {
deferred_functions: Vec<(&'a Stmt, Vec<usize>, Vec<usize>)>,
deferred_lambdas: Vec<(&'a Expr, Vec<usize>, Vec<usize>)>,
deferred_assignments: Vec<usize>,
// Derivative state.
// Internal, derivative state.
in_f_string: Option<Range>,
in_annotation: bool,
in_literal: bool,
@@ -59,8 +63,6 @@ struct Checker<'a> {
seen_docstring: bool,
futures_allowed: bool,
annotations_future_enabled: bool,
// Edit tracking.
deletions: BTreeSet<usize>,
}
impl<'a> Checker<'a> {
@@ -217,8 +219,7 @@ where
StmtKind::Global { names } | StmtKind::Nonlocal { names } => {
let global_scope_id = self.scopes[GLOBAL_SCOPE_INDEX].id;
let current_scope =
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
let current_scope = self.current_scope();
let current_scope_id = current_scope.id;
if current_scope_id != global_scope_id {
for name in names {
@@ -235,7 +236,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| {
@@ -245,7 +246,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,
@@ -257,7 +258,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,
@@ -282,7 +283,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)),
@@ -343,7 +344,7 @@ where
StmtKind::Return { .. } => {
if self
.settings
.select
.enabled
.contains(CheckKind::ReturnOutsideFunction.code())
{
if let Some(scope_index) = self.scope_stack.last().cloned() {
@@ -366,23 +367,11 @@ where
decorator_list,
..
} => {
if self.settings.select.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(
stmt,
name,
bases,
keywords,
scope,
&mut self.locator,
self.autofix,
) {
self.checks.push(check);
}
if self.settings.enabled.contains(&CheckCode::R001) {
plugins::useless_object_inheritance(self, stmt, name, bases, keywords);
}
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)),
@@ -411,7 +400,7 @@ where
StmtKind::Import { names } => {
if self
.settings
.select
.enabled
.contains(CheckKind::ModuleImportNotAtTopOfFile.code())
&& self.seen_non_import
&& stmt.location.column() == 1
@@ -471,7 +460,7 @@ where
} => {
if self
.settings
.select
.enabled
.contains(CheckKind::ModuleImportNotAtTopOfFile.code())
&& self.seen_non_import
&& stmt.location.column() == 1
@@ -509,7 +498,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(
@@ -518,7 +507,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,
@@ -541,7 +530,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) {
@@ -552,7 +541,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)),
@@ -585,7 +574,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);
@@ -597,33 +586,24 @@ where
self.handle_node_load(target);
}
StmtKind::If { test, .. } => {
if self.settings.select.contains(&CheckCode::F634) {
if let Some(check) =
checks::check_if_tuple(test, self.locate_check(Range::from_located(stmt)))
{
self.checks.push(check);
}
if self.settings.enabled.contains(&CheckCode::F634) {
plugins::if_tuple(self, stmt, test);
}
}
StmtKind::Assert { test, .. } => {
if self.settings.select.contains(CheckKind::AssertTuple.code()) {
if let Some(check) = checks::check_assert_tuple(
test,
self.locate_check(Range::from_located(stmt)),
) {
self.checks.push(check);
}
if self.settings.enabled.contains(&CheckCode::F631) {
plugins::assert_tuple(self, stmt, test);
}
}
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 { value, .. } => {
if self.settings.select.contains(&CheckCode::E731) {
StmtKind::Assign { targets, value, .. } => {
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)),
@@ -631,9 +611,12 @@ where
self.checks.push(check);
}
}
if self.settings.enabled.contains(&CheckCode::U001) {
plugins::useless_metaclass_type(self, stmt, value, targets);
}
}
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,
@@ -713,9 +696,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,
@@ -729,7 +712,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)),
@@ -740,63 +723,25 @@ where
self.check_builtin_shadowing(id, Range::from_located(expr), true);
let parent =
self.parents[*(self.parent_stack.last().expect("No parent found."))];
self.handle_node_store(expr, parent);
self.handle_node_store(expr, self.current_parent());
}
ExprContext::Del => self.handle_node_delete(expr),
},
ExprKind::Call { func, args, .. } => {
if self.settings.select.contains(&CheckCode::R002) {
if let Some(check) = checks::check_assert_equals(func, self.autofix) {
self.checks.push(check)
}
if self.settings.enabled.contains(&CheckCode::R002) {
plugins::assert_equals(self, func);
}
// flake8-super
if self.settings.select.contains(&CheckCode::SPR001) {
if let Some(check) =
checks::check_super_args(expr, func, args, &mut self.locator, self.autofix)
{
self.checks.push(check)
}
if self.settings.enabled.contains(&CheckCode::SPR001) {
plugins::super_call_with_parameters(self, expr, func, args);
}
// 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) {
let context = self.binding_context();
if matches!(
self.parents[context.defined_by].node,
StmtKind::Expr { .. }
) {
let deleted: Vec<&Stmt> = self
.deletions
.iter()
.map(|index| self.parents[*index])
.collect();
match remove_stmt(
self.parents[context.defined_by],
context.defined_in.map(|index| self.parents[index]),
&deleted,
) {
Ok(fix) => {
if fix.content.is_empty() || fix.content == "pass" {
self.deletions.insert(context.defined_by);
}
check.amend(fix)
}
Err(e) => error!("Failed to fix unused imports: {}", e),
}
}
}
self.checks.push(check)
}
plugins::print_call(self, expr, func);
}
if let ExprKind::Name { id, ctx } = &func.node {
@@ -815,8 +760,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,
@@ -827,11 +772,10 @@ where
}
}
ExprKind::Yield { .. } | ExprKind::YieldFrom { .. } | ExprKind::Await { .. } => {
let scope =
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
let scope = self.current_scope();
if self
.settings
.select
.enabled
.contains(CheckKind::YieldOutsideFunction.code())
&& matches!(scope.kind, ScopeKind::Class | ScopeKind::Module)
{
@@ -845,7 +789,7 @@ where
if self.in_f_string.is_none()
&& self
.settings
.select
.enabled
.contains(CheckKind::FStringMissingPlaceholders.code())
&& !values
.iter()
@@ -863,28 +807,13 @@ where
op: Operator::RShift,
..
} => {
if self.settings.select.contains(&CheckCode::F633) {
if let ExprKind::Name { id, .. } = &left.node {
if id == "print" {
let scope = &self.scopes
[*(self.scope_stack.last().expect("No current scope found."))];
if let Some(Binding {
kind: BindingKind::Builtin,
..
}) = scope.values.get("print")
{
self.checks.push(Check::new(
CheckKind::InvalidPrintSyntax,
Range::from_located(left),
));
}
}
}
if self.settings.enabled.contains(&CheckCode::F633) {
plugins::invalid_print_syntax(self, left);
}
}
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,
@@ -900,8 +829,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,
@@ -913,7 +842,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,
@@ -922,7 +851,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,
@@ -1116,7 +1045,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),
@@ -1124,7 +1053,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)),
@@ -1139,11 +1068,7 @@ where
false,
);
let scope = &self.scopes
[*(self.scope_stack.last().expect("No current scope found."))];
if scope.values.contains_key(name) {
let parent = self.parents
[*(self.parent_stack.last().expect("No parent found."))];
if self.current_scope().values.contains_key(name) {
self.handle_node_store(
&Expr::new(
excepthandler.location,
@@ -1153,15 +1078,11 @@ where
ctx: ExprContext::Store,
},
),
parent,
self.current_parent(),
);
}
let parent =
self.parents[*(self.parent_stack.last().expect("No parent found."))];
let scope = &self.scopes
[*(self.scope_stack.last().expect("No current scope found."))];
let definition = scope.values.get(name).cloned();
let definition = self.current_scope().values.get(name).cloned();
self.handle_node_store(
&Expr::new(
excepthandler.location,
@@ -1171,7 +1092,7 @@ where
ctx: ExprContext::Store,
},
),
parent,
self.current_parent(),
);
walk_excepthandler(self, excepthandler);
@@ -1179,7 +1100,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(
@@ -1200,7 +1121,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));
}
@@ -1234,7 +1155,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)),
@@ -1254,6 +1175,10 @@ impl CheckLocator for Checker<'_> {
}
impl<'a> Checker<'a> {
pub fn add_check(&mut self, check: Check) {
self.checks.push(check);
}
fn push_parent(&mut self, parent: &'a Stmt) {
self.parent_stack.push(self.parents.len());
self.parents.push(parent);
@@ -1303,7 +1228,15 @@ impl<'a> Checker<'a> {
}
}
fn binding_context(&self) -> BindingContext {
pub fn current_scope(&self) -> &Scope {
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))]
}
pub fn current_parent(&self) -> &'a Stmt {
self.parents[*(self.parent_stack.last().expect("No parent found."))]
}
pub fn binding_context(&self) -> BindingContext {
let mut rev = self.parent_stack.iter().rev().fuse();
let defined_by = *rev.next().expect("Expected to bind within a statement.");
let defined_in = rev.next().cloned();
@@ -1320,7 +1253,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,
@@ -1374,27 +1307,27 @@ 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];
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)),
));
}
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;
@@ -1412,7 +1345,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)
{
@@ -1512,7 +1445,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()),
@@ -1538,7 +1471,7 @@ impl<'a> Checker<'a> {
if let Ok(mut expr) = parser::parse_expression(expression, "<filename>") {
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),
@@ -1593,7 +1526,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],
@@ -1605,9 +1538,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;
}
@@ -1621,7 +1554,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")
{
@@ -1639,13 +1572,13 @@ 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![];
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 +1586,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),
));
}
@@ -1662,7 +1595,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<usize>), Vec<&str>> =
@@ -1741,11 +1674,11 @@ impl<'a> Checker<'a> {
}
fn check_builtin_shadowing(&mut self, name: &str, location: Range, is_attribute: bool) {
let scope = &self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
let scope = self.current_scope();
// 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),
@@ -1754,7 +1687,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),
@@ -1766,7 +1699,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),

View File

@@ -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<usize, (Directive, Vec<&str>)> = BTreeMap::new();

View File

@@ -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};
@@ -52,7 +53,7 @@ pub const DEFAULT_CHECK_CODES: [CheckCode; 42] = [
CheckCode::F901,
];
pub const ALL_CHECK_CODES: [CheckCode; 51] = [
pub const ALL_CHECK_CODES: [CheckCode; 52] = [
// pycodestyle
CheckCode::E402,
CheckCode::E501,
@@ -106,11 +107,13 @@ pub const ALL_CHECK_CODES: [CheckCode; 51] = [
// flake8-print
CheckCode::T201,
CheckCode::T203,
// Meta
CheckCode::M001,
// pyupgrade
CheckCode::U001,
// Refactor
CheckCode::R001,
CheckCode::R002,
// Meta
CheckCode::M001,
];
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Hash, PartialOrd, Ord)]
@@ -168,6 +171,8 @@ pub enum CheckCode {
// flake8-print
T201,
T203,
// pyupgrade
U001,
// Refactor
R001,
R002,
@@ -230,6 +235,11 @@ impl FromStr for CheckCode {
"A003" => Ok(CheckCode::A003),
// flake8-super
"SPR001" => Ok(CheckCode::SPR001),
// flake8-print
"T201" => Ok(CheckCode::T201),
"T203" => Ok(CheckCode::T203),
// pyupgrade
"U001" => Ok(CheckCode::U001),
// Refactor
"R001" => Ok(CheckCode::R001),
"R002" => Ok(CheckCode::R002),
@@ -296,6 +306,8 @@ impl CheckCode {
// flake8-print
CheckCode::T201 => "T201",
CheckCode::T203 => "T203",
// pyupgrade
CheckCode::U001 => "U001",
// Refactor
CheckCode::R001 => "R001",
CheckCode::R002 => "R002",
@@ -329,20 +341,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,
@@ -369,6 +383,8 @@ impl CheckCode {
// flake8-print
CheckCode::T201 => CheckKind::PrintFound,
CheckCode::T203 => CheckKind::PPrintFound,
// pyupgrade
CheckCode::U001 => CheckKind::UselessMetaclassType,
// Refactor
CheckCode::R001 => CheckKind::UselessObjectInheritance("...".to_string()),
CheckCode::R002 => CheckKind::NoAssertEquals,
@@ -393,7 +409,6 @@ pub enum RejectedCmpop {
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum CheckKind {
UnusedNOQA(Option<String>),
AmbiguousClassName(String),
AmbiguousFunctionName(String),
AmbiguousVariableName(String),
@@ -404,14 +419,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<String>),
ImportStarUsed(String),
InvalidPrintSyntax,
IsLiteral,
@@ -427,7 +443,6 @@ pub enum CheckKind {
RaiseNotImplemented,
ReturnOutsideFunction,
SyntaxError(String),
TooManyExpressionsInStarredAssignment,
TrueFalseComparison(bool, RejectedCmpop),
TwoStarredExpressions,
TypeComparison,
@@ -435,7 +450,9 @@ pub enum CheckKind {
UndefinedLocal(String),
UndefinedName(String),
UnusedImport(String),
UnusedNOQA(Option<String>),
UnusedVariable(String),
UselessMetaclassType,
UselessObjectInheritance(String),
YieldOutsideFunction,
// flake8-builtin
@@ -463,6 +480,7 @@ impl CheckKind {
CheckKind::DoNotAssignLambda => "DoNotAssignLambda",
CheckKind::DoNotUseBareExcept => "DoNotUseBareExcept",
CheckKind::DuplicateArgumentName => "DuplicateArgumentName",
CheckKind::ExpressionsInStarAssignment => "ExpressionsInStarAssignment",
CheckKind::FStringMissingPlaceholders => "FStringMissingPlaceholders",
CheckKind::ForwardAnnotationSyntaxError(_) => "ForwardAnnotationSyntaxError",
CheckKind::FutureFeatureNotDefined(_) => "FutureFeatureNotDefined",
@@ -479,16 +497,12 @@ impl CheckKind {
CheckKind::ModuleImportNotAtTopOfFile => "ModuleImportNotAtTopOfFile",
CheckKind::MultiValueRepeatedKeyLiteral => "MultiValueRepeatedKeyLiteral",
CheckKind::MultiValueRepeatedKeyVariable(_) => "MultiValueRepeatedKeyVariable",
CheckKind::NoAssertEquals => "NoAssertEquals",
CheckKind::NoneComparison(_) => "NoneComparison",
CheckKind::NotInTest => "NotInTest",
CheckKind::NotIsTest => "NotIsTest",
CheckKind::RaiseNotImplemented => "RaiseNotImplemented",
CheckKind::ReturnOutsideFunction => "ReturnOutsideFunction",
CheckKind::SyntaxError(_) => "SyntaxError",
CheckKind::TooManyExpressionsInStarredAssignment => {
"TooManyExpressionsInStarredAssignment"
}
CheckKind::TrueFalseComparison(_, _) => "TrueFalseComparison",
CheckKind::TwoStarredExpressions => "TwoStarredExpressions",
CheckKind::TypeComparison => "TypeComparison",
@@ -497,9 +511,7 @@ impl CheckKind {
CheckKind::UndefinedName(_) => "UndefinedName",
CheckKind::UnusedImport(_) => "UnusedImport",
CheckKind::UnusedVariable(_) => "UnusedVariable",
CheckKind::UselessObjectInheritance(_) => "UselessObjectInheritance",
CheckKind::YieldOutsideFunction => "YieldOutsideFunction",
CheckKind::UnusedNOQA(_) => "UnusedNOQA",
// flake8-builtins
CheckKind::BuiltinVariableShadowing(_) => "BuiltinVariableShadowing",
CheckKind::BuiltinArgumentShadowing(_) => "BuiltinArgumentShadowing",
@@ -509,6 +521,13 @@ impl CheckKind {
// flake8-print
CheckKind::PrintFound => "PrintFound",
CheckKind::PPrintFound => "PPrintFound",
// pyupgrade
CheckKind::UselessMetaclassType => "UselessMetaclassType",
// Refactor
CheckKind::NoAssertEquals => "NoAssertEquals",
CheckKind::UselessObjectInheritance(_) => "UselessObjectInheritance",
// Meta
CheckKind::UnusedNOQA(_) => "UnusedNOQA",
}
}
@@ -541,14 +560,13 @@ impl CheckKind {
CheckKind::ModuleImportNotAtTopOfFile => &CheckCode::E402,
CheckKind::MultiValueRepeatedKeyLiteral => &CheckCode::F601,
CheckKind::MultiValueRepeatedKeyVariable(_) => &CheckCode::F602,
CheckKind::NoAssertEquals => &CheckCode::R002,
CheckKind::NoneComparison(_) => &CheckCode::E711,
CheckKind::NotInTest => &CheckCode::E713,
CheckKind::NotIsTest => &CheckCode::E714,
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,
@@ -556,9 +574,7 @@ impl CheckKind {
CheckKind::UndefinedLocal(_) => &CheckCode::F823,
CheckKind::UndefinedName(_) => &CheckCode::F821,
CheckKind::UnusedImport(_) => &CheckCode::F401,
CheckKind::UnusedNOQA(_) => &CheckCode::M001,
CheckKind::UnusedVariable(_) => &CheckCode::F841,
CheckKind::UselessObjectInheritance(_) => &CheckCode::R001,
CheckKind::YieldOutsideFunction => &CheckCode::F704,
// flake8-builtins
CheckKind::BuiltinVariableShadowing(_) => &CheckCode::A001,
@@ -569,6 +585,13 @@ impl CheckKind {
// flake8-print
CheckKind::PrintFound => &CheckCode::T201,
CheckKind::PPrintFound => &CheckCode::T203,
// pyupgrade
CheckKind::UselessMetaclassType => &CheckCode::U001,
// Refactor
CheckKind::NoAssertEquals => &CheckCode::R002,
CheckKind::UselessObjectInheritance(_) => &CheckCode::R001,
// Meta
CheckKind::UnusedNOQA(_) => &CheckCode::M001,
}
}
@@ -576,13 +599,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 +613,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 +623,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 +646,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)")
@@ -639,9 +668,6 @@ impl CheckKind {
CheckKind::MultiValueRepeatedKeyVariable(name) => {
format!("Dictionary key `{name}` repeated")
}
CheckKind::NoAssertEquals => {
"`assertEquals` is deprecated, use `assertEqual` instead".to_string()
}
CheckKind::NoneComparison(op) => match op {
RejectedCmpop::Eq => "Comparison to `None` should be `cond is None`".to_string(),
RejectedCmpop::NotEq => {
@@ -654,11 +680,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 +704,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__`")
}
@@ -693,16 +719,10 @@ impl CheckKind {
CheckKind::UnusedVariable(name) => {
format!("Local variable `{name}` is assigned to but never used")
}
CheckKind::UselessObjectInheritance(name) => {
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(),
Some(code) => format!("Unused `noqa` directive for: {code}"),
},
// flake8-builtins
CheckKind::BuiltinVariableShadowing(name) => {
format!("Variable `{name}` is shadowing a python builtin")
@@ -711,7 +731,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 +739,21 @@ impl CheckKind {
}
// flake8-print
CheckKind::PrintFound => "`print` found".to_string(),
CheckKind::PPrintFound => "`pprint` found`".to_string(),
CheckKind::PPrintFound => "`pprint` found".to_string(),
// pyupgrade
CheckKind::UselessMetaclassType => "`__metaclass__ = type` is implied".to_string(),
// Refactor
CheckKind::NoAssertEquals => {
"`assertEquals` is deprecated, use `assertEqual` instead".to_string()
}
CheckKind::UselessObjectInheritance(name) => {
format!("Class `{name}` inherits from object")
}
// Meta
CheckKind::UnusedNOQA(code) => match code {
None => "Unused `noqa` directive".to_string(),
Some(code) => format!("Unused `noqa` directive for: {code}"),
},
}
}
@@ -728,12 +762,13 @@ impl CheckKind {
matches!(
self,
CheckKind::NoAssertEquals
| CheckKind::UselessObjectInheritance(_)
| CheckKind::UnusedNOQA(_)
| CheckKind::PPrintFound
| CheckKind::PrintFound
| CheckKind::SuperCallWithParameters
| CheckKind::UnusedImport(_)
| CheckKind::PrintFound
| CheckKind::PPrintFound
| CheckKind::UnusedNOQA(_)
| CheckKind::UselessMetaclassType
| CheckKind::UselessObjectInheritance(_)
)
}
}

1039
src/code_gen.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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;
@@ -15,11 +15,13 @@ pub mod cache;
pub mod check_ast;
mod check_lines;
pub mod checks;
pub mod code_gen;
pub mod fs;
pub mod linter;
pub mod logging;
pub mod message;
mod noqa;
mod plugins;
pub mod printer;
pub mod pyproject;
mod python;
@@ -39,7 +41,7 @@ pub fn check(path: &Path, contents: &str) -> Result<Vec<Message>> {
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<LexResult> = tokenize(contents);

View File

@@ -1,3 +1,4 @@
use std::fs::write;
use std::path::Path;
use anyhow::Result;
@@ -7,10 +8,10 @@ use rustpython_parser::{lexer, parser};
use crate::ast::types::Range;
use crate::autofix::fixer;
use crate::autofix::fixer::fix_file;
use crate::check_ast::check_ast;
use crate::check_lines::check_lines;
use crate::checks::{Check, CheckCode, CheckKind, LintSource};
use crate::code_gen::SourceGenerator;
use crate::message::Message;
use crate::noqa::add_noqa;
use crate::settings::Settings;
@@ -42,7 +43,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))
{
@@ -51,7 +52,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 {
@@ -99,33 +100,19 @@ pub fn lint_path(
let contents = fs::read_file(path)?;
// Tokenize once.
let tokens: Vec<LexResult> = tokenize(&contents);
// let tokens: Vec<LexResult> = tokenize(&contents);
// Determine the noqa line for every line in the source.
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
// let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
// Generate checks.
let mut checks = check_path(path, &contents, tokens, &noqa_line_for, settings, autofix)?;
match python_parser::file_input(python_parser::make_strspan(&contents)) {
Ok(x) => {}
Err(e) => return Err(anyhow::anyhow!("Err: failed")),
}
// Apply autofix.
if matches!(autofix, fixer::Mode::Apply) {
fix_file(&mut checks, &contents, path)?;
};
// let _ = parser::parse_program_tokens(tokens, "<filename>")?;
// Convert to messages.
let messages: Vec<Message> = checks
.into_iter()
.map(|check| Message {
kind: check.kind,
fixed: check.fix.map(|fix| fix.applied).unwrap_or_default(),
location: check.location,
end_location: check.end_location,
filename: path.to_string_lossy().to_string(),
})
.collect();
cache::set(path, &metadata, settings, autofix, &messages, mode);
Ok(messages)
Ok(vec![])
}
pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
@@ -151,6 +138,22 @@ pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
add_noqa(&checks, &contents, &noqa_line_for, path)
}
pub fn autoformat_path(path: &Path) -> Result<()> {
// Read the file from disk.
let contents = fs::read_file(path)?;
// Tokenize once.
let tokens: Vec<LexResult> = tokenize(&contents);
// Generate the AST.
let python_ast = parser::parse_program_tokens(tokens, "<filename>")?;
let mut generator: SourceGenerator = Default::default();
generator.unparse_suite(&python_ast)?;
write(path, generator.generate()?)?;
Ok(())
}
#[cfg(test)]
mod tests {
use std::path::Path;
@@ -803,4 +806,16 @@ mod tests {
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn u001() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/U001.py"),
&settings::Settings::for_rule(CheckCode::U001),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
}

View File

@@ -26,6 +26,8 @@ use ::ruff::pyproject::{self, StrCheckCodePair};
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");
@@ -87,6 +89,9 @@ struct Cli {
/// Enable automatic additions of noqa directives to failing lines.
#[arg(long)]
add_noqa: bool,
/// Enable automatic formatting.
#[arg(long)]
autoformat: bool,
/// Regular expression matching the name of dummy variables.
#[arg(long)]
dummy_variable_rgx: Option<Regex>,
@@ -116,7 +121,7 @@ fn check_for_updates() {
}
}
fn show_settings(settings: Settings) {
fn show_settings(settings: RawSettings) {
println!("{:#?}", CurrentSettings::from_settings(settings));
}
@@ -165,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,
@@ -222,6 +227,33 @@ fn add_noqa(files: &[PathBuf], settings: &Settings) -> Result<usize> {
Ok(modifications)
}
fn autoformat(files: &[PathBuf], settings: &Settings) -> Result<usize> {
// Collect all the files to format.
let start = Instant::now();
let paths: Vec<DirEntry> = files
.iter()
.flat_map(|path| iter_python_files(path, &settings.exclude, &settings.extend_exclude))
.flatten()
.collect();
let duration = start.elapsed();
debug!("Identified files to lint in: {:?}", duration);
let start = Instant::now();
let modifications = paths
.par_iter()
.map(|entry| {
let path = entry.path();
autoformat_path(path)
})
.flatten()
.count();
let duration = start.elapsed();
debug!("Auto-formatted files in: {:?}", duration);
Ok(modifications)
}
fn inner_main() -> Result<ExitCode> {
let cli = Cli::parse();
@@ -256,7 +288,7 @@ fn inner_main() -> Result<ExitCode> {
.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;
}
@@ -267,17 +299,16 @@ fn inner_main() -> Result<ExitCode> {
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;
@@ -287,15 +318,18 @@ fn inner_main() -> Result<ExitCode> {
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);
@@ -308,6 +342,10 @@ fn inner_main() -> Result<ExitCode> {
eprintln!("Warning: --no-qa is not enabled in watch mode.");
}
if cli.autoformat {
eprintln!("Warning: --autoformat is not enabled in watch mode.");
}
if cli.format != SerializationFormat::Text {
eprintln!("Warning: --format 'text' is used in watch mode.");
}
@@ -351,6 +389,11 @@ fn inner_main() -> Result<ExitCode> {
if modifications > 0 {
println!("Added {modifications} noqa directives.");
}
} else if cli.autoformat {
let modifications = autoformat(&cli.files, &settings)?;
if modifications > 0 {
println!("Formatted {modifications} files.");
}
} else {
let messages = run_once(&cli.files, &settings, !cli.no_cache, cli.fix)?;
if !cli.quiet {

17
src/plugins.rs Normal file
View File

@@ -0,0 +1,17 @@
mod assert_equals;
mod assert_tuple;
mod if_tuple;
mod invalid_print_syntax;
mod print_call;
mod super_call_with_parameters;
mod useless_metaclass_type;
mod useless_object_inheritance;
pub use assert_equals::assert_equals;
pub use assert_tuple::assert_tuple;
pub use if_tuple::if_tuple;
pub use invalid_print_syntax::invalid_print_syntax;
pub use print_call::print_call;
pub use super_call_with_parameters::super_call_with_parameters;
pub use useless_metaclass_type::useless_metaclass_type;
pub use useless_object_inheritance::useless_object_inheritance;

View File

@@ -0,0 +1,23 @@
use rustpython_ast::{Expr, Location};
use crate::ast::checks;
use crate::autofix::fixer;
use crate::check_ast::Checker;
use crate::checks::Fix;
pub fn assert_equals(checker: &mut Checker, expr: &Expr) {
if let Some(mut check) = checks::check_assert_equals(expr) {
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
check.amend(Fix {
content: "assertEqual".to_string(),
location: Location::new(expr.location.row(), expr.location.column() + 1),
end_location: Location::new(
expr.location.row(),
expr.location.column() + 1 + "assertEquals".len(),
),
applied: false,
});
}
checker.add_check(check);
}
}

View File

@@ -0,0 +1,13 @@
use rustpython_ast::{Expr, Stmt};
use crate::ast::checks;
use crate::ast::types::{CheckLocator, Range};
use crate::check_ast::Checker;
pub fn assert_tuple(checker: &mut Checker, stmt: &Stmt, test: &Expr) {
if let Some(check) =
checks::check_assert_tuple(test, checker.locate_check(Range::from_located(stmt)))
{
checker.add_check(check);
}
}

13
src/plugins/if_tuple.rs Normal file
View File

@@ -0,0 +1,13 @@
use rustpython_ast::{Expr, Stmt};
use crate::ast::checks;
use crate::ast::types::{CheckLocator, Range};
use crate::check_ast::Checker;
pub fn if_tuple(checker: &mut Checker, stmt: &Stmt, test: &Expr) {
if let Some(check) =
checks::check_if_tuple(test, checker.locate_check(Range::from_located(stmt)))
{
checker.add_check(check);
}
}

View File

@@ -0,0 +1,23 @@
use rustpython_ast::{Expr, ExprKind};
use crate::ast::types::{Binding, BindingKind, Range};
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
pub fn invalid_print_syntax(checker: &mut Checker, left: &Expr) {
if let ExprKind::Name { id, .. } = &left.node {
if id == "print" {
let scope = checker.current_scope();
if let Some(Binding {
kind: BindingKind::Builtin,
..
}) = scope.values.get("print")
{
checker.add_check(Check::new(
CheckKind::InvalidPrintSyntax,
Range::from_located(left),
));
}
}
}
}

46
src/plugins/print_call.rs Normal file
View File

@@ -0,0 +1,46 @@
use log::error;
use rustpython_ast::{Expr, Stmt, StmtKind};
use crate::ast::checks;
use crate::autofix::{fixer, fixes};
use crate::check_ast::Checker;
use crate::checks::CheckCode;
pub fn print_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
if let Some(mut check) = checks::check_print_call(
expr,
func,
checker.settings.enabled.contains(&CheckCode::T201),
checker.settings.enabled.contains(&CheckCode::T203),
) {
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
let context = checker.binding_context();
if matches!(
checker.parents[context.defined_by].node,
StmtKind::Expr { .. }
) {
let deleted: Vec<&Stmt> = checker
.deletions
.iter()
.map(|index| checker.parents[*index])
.collect();
match fixes::remove_stmt(
checker.parents[context.defined_by],
context.defined_in.map(|index| checker.parents[index]),
&deleted,
) {
Ok(fix) => {
if fix.content.is_empty() || fix.content == "pass" {
checker.deletions.insert(context.defined_by);
}
check.amend(fix)
}
Err(e) => error!("Failed to fix unused imports: {}", e),
}
}
}
checker.add_check(check);
}
}

View File

@@ -0,0 +1,31 @@
use rustpython_ast::{Expr, Stmt};
use crate::ast::checks;
use crate::autofix::{fixer, fixes};
use crate::check_ast::Checker;
pub fn super_call_with_parameters(
checker: &mut Checker,
expr: &Expr,
func: &Expr,
args: &Vec<Expr>,
) {
// 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) {
let scope = checker.current_scope();
let parents: Vec<&Stmt> = checker
.parent_stack
.iter()
.map(|index| checker.parents[*index])
.collect();
if let Some(mut check) = checks::check_super_args(scope, &parents, expr, func, args) {
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if let Some(fix) = fixes::remove_super_arguments(&mut checker.locator, expr) {
check.amend(fix);
}
}
checker.add_check(check)
}
}
}

View File

@@ -0,0 +1,44 @@
use log::error;
use rustpython_ast::{Expr, Stmt};
use crate::ast::checks;
use crate::ast::types::{CheckLocator, Range};
use crate::autofix::{fixer, fixes};
use crate::check_ast::Checker;
pub fn useless_metaclass_type(
checker: &mut Checker,
stmt: &Stmt,
value: &Expr,
targets: &Vec<Expr>,
) {
if let Some(mut check) = checks::check_useless_metaclass_type(
targets,
value,
checker.locate_check(Range::from_located(stmt)),
) {
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
let context = checker.binding_context();
let deleted: Vec<&Stmt> = checker
.deletions
.iter()
.map(|index| checker.parents[*index])
.collect();
match fixes::remove_stmt(
checker.parents[context.defined_by],
context.defined_in.map(|index| checker.parents[index]),
&deleted,
) {
Ok(fix) => {
if fix.content.is_empty() || fix.content == "pass" {
checker.deletions.insert(context.defined_by);
}
check.amend(fix)
}
Err(e) => error!("Failed to fix unused imports: {}", e),
}
}
checker.add_check(check);
}
}

View File

@@ -0,0 +1,29 @@
use rustpython_ast::{Expr, Keyword, Stmt};
use crate::ast::checks;
use crate::autofix::{fixer, fixes};
use crate::check_ast::Checker;
pub fn useless_object_inheritance(
checker: &mut Checker,
stmt: &Stmt,
name: &str,
bases: &[Expr],
keywords: &[Keyword],
) {
let scope = checker.current_scope();
if let Some(mut check) = checks::check_useless_object_inheritance(name, bases, scope) {
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if let Some(fix) = fixes::remove_class_def_base(
&mut checker.locator,
&stmt.location,
check.location,
bases,
keywords,
) {
check.amend(fix);
}
}
checker.add_check(check);
}
}

View File

@@ -29,10 +29,17 @@ pub fn load_config(pyproject: &Option<PathBuf>) -> Result<Config> {
pub struct Config {
pub line_length: Option<usize>,
pub exclude: Option<Vec<String>>,
pub extend_exclude: Option<Vec<String>>,
#[serde(default)]
pub extend_exclude: Vec<String>,
pub select: Option<Vec<CheckCode>>,
pub ignore: Option<Vec<CheckCode>>,
pub per_file_ignores: Option<Vec<StrCheckCodePair>>,
#[serde(default)]
pub extend_select: Vec<CheckCode>,
#[serde(default)]
pub ignore: Vec<CheckCode>,
#[serde(default)]
pub extend_ignore: Vec<CheckCode>,
#[serde(default)]
pub per_file_ignores: Vec<StrCheckCodePair>,
pub dummy_variable_rgx: Option<String>,
}
@@ -175,10 +182,12 @@ mod tests {
ruff: Some(Config {
line_length: None,
exclude: None,
extend_exclude: None,
extend_exclude: vec![],
select: None,
ignore: None,
per_file_ignores: None,
extend_select: vec![],
ignore: vec![],
extend_ignore: vec![],
per_file_ignores: vec![],
dummy_variable_rgx: None,
})
})
@@ -197,10 +206,12 @@ line-length = 79
ruff: Some(Config {
line_length: Some(79),
exclude: None,
extend_exclude: None,
extend_exclude: vec![],
select: None,
ignore: None,
per_file_ignores: None,
extend_select: vec![],
ignore: vec![],
extend_ignore: vec![],
per_file_ignores: vec![],
dummy_variable_rgx: None,
})
})
@@ -219,10 +230,12 @@ exclude = ["foo.py"]
ruff: Some(Config {
line_length: None,
exclude: Some(vec!["foo.py".to_string()]),
extend_exclude: None,
extend_exclude: vec![],
select: None,
ignore: None,
per_file_ignores: None,
extend_select: vec![],
ignore: vec![],
extend_ignore: vec![],
per_file_ignores: vec![],
dummy_variable_rgx: None,
})
})
@@ -241,10 +254,12 @@ select = ["E501"]
ruff: Some(Config {
line_length: None,
exclude: None,
extend_exclude: None,
extend_exclude: vec![],
select: Some(vec![CheckCode::E501]),
ignore: None,
per_file_ignores: None,
extend_select: vec![],
ignore: vec![],
extend_ignore: vec![],
per_file_ignores: vec![],
dummy_variable_rgx: None,
})
})
@@ -254,6 +269,7 @@ select = ["E501"]
r#"
[tool.black]
[tool.ruff]
extend-select = ["M001"]
ignore = ["E501"]
"#,
)?;
@@ -263,10 +279,12 @@ ignore = ["E501"]
ruff: Some(Config {
line_length: None,
exclude: None,
extend_exclude: None,
extend_exclude: vec![],
select: None,
ignore: Some(vec![CheckCode::E501]),
per_file_ignores: None,
extend_select: vec![CheckCode::M001],
ignore: vec![CheckCode::E501],
extend_ignore: vec![],
per_file_ignores: vec![],
dummy_variable_rgx: None,
})
})
@@ -325,14 +343,16 @@ other-attribute = 1
Config {
line_length: Some(88),
exclude: None,
extend_exclude: Some(vec![
extend_exclude: vec![
"excluded.py".to_string(),
"migrations".to_string(),
"directory/also_excluded.py".to_string(),
]),
],
select: None,
ignore: None,
per_file_ignores: None,
extend_select: vec![],
ignore: vec![],
extend_ignore: vec![],
per_file_ignores: vec![],
dummy_variable_rgx: None,
}
);

View File

@@ -51,56 +51,18 @@ impl PerFileIgnore {
}
#[derive(Debug)]
pub struct Settings {
pub pyproject: Option<PathBuf>,
pub project_root: Option<PathBuf>,
pub line_length: usize,
pub struct RawSettings {
pub dummy_variable_rgx: Regex,
pub exclude: Vec<FilePattern>,
pub extend_exclude: Vec<FilePattern>,
pub select: BTreeSet<CheckCode>,
pub extend_ignore: Vec<CheckCode>,
pub extend_select: Vec<CheckCode>,
pub ignore: Vec<CheckCode>,
pub line_length: usize,
pub per_file_ignores: Vec<PerFileIgnore>,
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<CheckCode>) -> 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<H: Hasher>(&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<PathBuf>,
pub pyproject: Option<PathBuf>,
pub select: Vec<CheckCode>,
}
static DEFAULT_EXCLUDE: Lazy<Vec<FilePattern>> = Lazy::new(|| {
@@ -130,14 +92,18 @@ static DEFAULT_EXCLUDE: Lazy<Vec<FilePattern>> = Lazy::new(|| {
static DEFAULT_DUMMY_VARIABLE_RGX: Lazy<Regex> =
Lazy::new(|| Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap());
impl Settings {
impl RawSettings {
pub fn from_pyproject(
pyproject: Option<PathBuf>,
project_root: Option<PathBuf>,
) -> Result<Self> {
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| {
@@ -149,54 +115,91 @@ impl Settings {
.unwrap_or_else(|| DEFAULT_EXCLUDE.clone()),
extend_exclude: config
.extend_exclude
.map(|paths| {
paths
.iter()
.map(|path| FilePattern::from_user(path, &project_root))
.collect()
})
.unwrap_or_default(),
select: if let Some(select) = config.select {
BTreeSet::from_iter(select)
} else {
BTreeSet::from_iter(DEFAULT_CHECK_CODES)
},
.iter()
.map(|path| FilePattern::from_user(path, &project_root))
.collect(),
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
.map(|ignore_strings| {
ignore_strings
.into_iter()
.map(|pair| PerFileIgnore::new(pair, &project_root))
.collect()
})
.unwrap_or_default(),
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,
.into_iter()
.map(|pair| PerFileIgnore::new(pair, &project_root))
.collect(),
project_root,
};
if let Some(ignore) = &config.ignore {
settings.ignore(ignore);
pyproject,
select: config
.select
.unwrap_or_else(|| DEFAULT_CHECK_CODES.to_vec()),
})
}
}
#[derive(Debug)]
pub struct Settings {
pub dummy_variable_rgx: Regex,
pub enabled: BTreeSet<CheckCode>,
pub exclude: Vec<FilePattern>,
pub extend_exclude: Vec<FilePattern>,
pub line_length: usize,
pub per_file_ignores: Vec<PerFileIgnore>,
}
impl Settings {
pub fn from_raw(settings: RawSettings) -> Self {
// Materialize the set of enabled CheckCodes.
let mut enabled: BTreeSet<CheckCode> = BTreeSet::new();
enabled.extend(settings.select);
enabled.extend(settings.extend_select);
for code in &settings.ignore {
enabled.remove(code);
}
Ok(settings)
}
pub fn clear(&mut self) {
self.select.clear();
}
pub fn select(&mut self, codes: Vec<CheckCode>) {
for code in codes {
self.select.insert(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<CheckCode>) -> 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<H: Hasher>(&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);
}
}
}
@@ -227,22 +230,23 @@ impl Exclusion {
/// Struct to render user-facing Settings.
#[derive(Debug)]
pub struct CurrentSettings {
pub pyproject: Option<PathBuf>,
pub project_root: Option<PathBuf>,
pub line_length: usize,
pub dummy_variable_rgx: Regex,
pub exclude: Vec<Exclusion>,
pub extend_exclude: Vec<Exclusion>,
pub select: BTreeSet<CheckCode>,
pub extend_ignore: Vec<CheckCode>,
pub extend_select: Vec<CheckCode>,
pub ignore: Vec<CheckCode>,
pub line_length: usize,
pub per_file_ignores: Vec<PerFileIgnore>,
pub dummy_variable_rgx: Regex,
pub project_root: Option<PathBuf>,
pub pyproject: Option<PathBuf>,
pub select: Vec<CheckCode>,
}
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()
@@ -253,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,
}
}
}

View File

@@ -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

View File

@@ -50,4 +50,36 @@ expression: checks
row: 22
column: 10
applied: false
- kind: SuperCallWithParameters
location:
row: 36
column: 9
end_location:
row: 36
column: 29
fix:
content: super()
location:
row: 36
column: 9
end_location:
row: 36
column: 29
applied: false
- kind: SuperCallWithParameters
location:
row: 50
column: 13
end_location:
row: 50
column: 33
fix:
content: super()
location:
row: 50
column: 13
end_location:
row: 50
column: 33
applied: false

View File

@@ -0,0 +1,37 @@
---
source: src/linter.rs
expression: checks
---
- kind: UselessMetaclassType
location:
row: 2
column: 5
end_location:
row: 2
column: 25
fix:
content: pass
location:
row: 2
column: 5
end_location:
row: 2
column: 25
applied: false
- kind: UselessMetaclassType
location:
row: 6
column: 5
end_location:
row: 6
column: 25
fix:
content: ""
location:
row: 6
column: 1
end_location:
row: 7
column: 1
applied: false