Compare commits
15 Commits
v0.0.53
...
charlie/no
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f317fc3e7 | ||
|
|
b307afc00c | ||
|
|
3d5bc1f51f | ||
|
|
aba01745f5 | ||
|
|
1eeeffab66 | ||
|
|
9b564c9cf4 | ||
|
|
5bf8b13644 | ||
|
|
f80d5e70dd | ||
|
|
44897b2a5b | ||
|
|
d1bcc919a2 | ||
|
|
03e1397427 | ||
|
|
fdb32330a9 | ||
|
|
4e6ae33a3a | ||
|
|
295ff8eb1a | ||
|
|
2449771d2f |
481
Cargo.lock
generated
481
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -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
114
README.md
@@ -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).
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
26
examples/generate_source_code.rs
Normal file
26
examples/generate_source_code.rs
Normal 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
23
examples/parse_python.rs
Normal 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
37
output.py
Normal 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)
|
||||
|
||||
43
resources/test/fixtures/SPR001.py
vendored
43
resources/test/fixtures/SPR001.py
vendored
@@ -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
13
resources/test/fixtures/U001.py
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
class A:
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class B:
|
||||
__metaclass__ = type
|
||||
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class C(metaclass=type):
|
||||
pass
|
||||
@@ -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),
|
||||
|
||||
279
src/check_ast.rs
279
src/check_ast.rs
@@ -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),
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
143
src/checks.rs
143
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};
|
||||
|
||||
@@ -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
1039
src/code_gen.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
67
src/main.rs
67
src/main.rs
@@ -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
17
src/plugins.rs
Normal 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;
|
||||
23
src/plugins/assert_equals.rs
Normal file
23
src/plugins/assert_equals.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
13
src/plugins/assert_tuple.rs
Normal file
13
src/plugins/assert_tuple.rs
Normal 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
13
src/plugins/if_tuple.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
23
src/plugins/invalid_print_syntax.rs
Normal file
23
src/plugins/invalid_print_syntax.rs
Normal 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
46
src/plugins/print_call.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
31
src/plugins/super_call_with_parameters.rs
Normal file
31
src/plugins/super_call_with_parameters.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/plugins/useless_metaclass_type.rs
Normal file
44
src/plugins/useless_metaclass_type.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
29
src/plugins/useless_object_inheritance.rs
Normal file
29
src/plugins/useless_object_inheritance.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
);
|
||||
|
||||
213
src/settings.rs
213
src/settings.rs
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
37
src/snapshots/ruff__linter__tests__u001.snap
Normal file
37
src/snapshots/ruff__linter__tests__u001.snap
Normal 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
|
||||
|
||||
Reference in New Issue
Block a user