Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4319bd1755 | ||
|
|
6bb6cb1783 | ||
|
|
e9412c9452 | ||
|
|
94faa7f301 | ||
|
|
5041f6530c | ||
|
|
3c7716ef27 | ||
|
|
26e1f4b6df | ||
|
|
17c08523dc | ||
|
|
221f4304ad | ||
|
|
c0131e65e5 | ||
|
|
bf4722a62f | ||
|
|
994f5d452c | ||
|
|
741857cdf9 | ||
|
|
4f42f51bd2 | ||
|
|
5a3092e805 | ||
|
|
0318406535 | ||
|
|
0c99b5aac5 | ||
|
|
b442402b13 | ||
|
|
ba27e50164 | ||
|
|
d55651d470 | ||
|
|
0c110bcecf | ||
|
|
9bf8a0d0f1 | ||
|
|
888cfeba35 | ||
|
|
64df4eb311 | ||
|
|
2bac3027a5 | ||
|
|
c28ac75591 | ||
|
|
59f009b52d | ||
|
|
b5edcee9f2 | ||
|
|
3f739214b4 | ||
|
|
0b9e3f8b47 |
16
.github/workflows/ci.yaml
vendored
16
.github/workflows/ci.yaml
vendored
@@ -2,16 +2,16 @@ name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
cargo_build:
|
||||
name: "cargo build"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
name: "cargo fmt"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
name: "cargo clippy"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
name: "cargo test"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
@@ -99,13 +99,13 @@ jobs:
|
||||
name: "maturin build"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
python-version: "3.10"
|
||||
- run: pip install maturin
|
||||
- uses: actions/cache@v3
|
||||
env:
|
||||
|
||||
6
.github/workflows/release.yaml
vendored
6
.github/workflows/release.yaml
vendored
@@ -227,9 +227,9 @@ jobs:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
target: [x86_64, aarch64]
|
||||
python-version:
|
||||
- '3.7'
|
||||
- '3.8'
|
||||
- '3.9'
|
||||
- "3.7"
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
exclude:
|
||||
- os: macos-latest
|
||||
target: aarch64
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,5 @@
|
||||
# Local cache
|
||||
.cache
|
||||
.ruff_cache
|
||||
resources/test/cpython
|
||||
|
||||
###
|
||||
|
||||
5
.pre-commit-config.yaml
Normal file
5
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff
|
||||
rev: v0.0.25
|
||||
hooks:
|
||||
- id: lint
|
||||
7
.pre-commit-hooks.yaml
Normal file
7
.pre-commit-hooks.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
- id: lint
|
||||
name: ruff lint
|
||||
description: Run ruff to lint Python files.
|
||||
entry: ruff
|
||||
language: python
|
||||
types_or: [python]
|
||||
pass_filenames: true
|
||||
14
Cargo.lock
generated
14
Cargo.lock
generated
@@ -850,6 +850,12 @@ dependencies = [
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||
|
||||
[[package]]
|
||||
name = "gloo-timers"
|
||||
version = "0.2.4"
|
||||
@@ -1254,9 +1260,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.13.0"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
|
||||
checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
@@ -1635,7 +1641,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.21"
|
||||
version = "0.0.25"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@@ -1648,8 +1654,10 @@ dependencies = [
|
||||
"dirs 4.0.0",
|
||||
"fern",
|
||||
"filetime",
|
||||
"glob",
|
||||
"log",
|
||||
"notify",
|
||||
"once_cell",
|
||||
"rayon",
|
||||
"regex",
|
||||
"rustpython-parser",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.21"
|
||||
version = "0.0.25"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
@@ -18,8 +18,10 @@ common-path = { version = "1.0.0" }
|
||||
dirs = { version = "4.0.0" }
|
||||
fern = { version = "0.6.1" }
|
||||
filetime = { version = "0.2.17" }
|
||||
glob = { version = "0.3.0"}
|
||||
log = { version = "0.4.17" }
|
||||
notify = { version = "4.0.17" }
|
||||
once_cell = { version = "1.13.1" }
|
||||
rayon = { version = "1.5.3" }
|
||||
regex = { version = "1.6.0" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "1613f6c6990011a4bc559e79aaf28d715f9f729b" }
|
||||
|
||||
32
README.md
32
README.md
@@ -51,6 +51,16 @@ You can run ruff in `--watch` mode to automatically re-run on-change:
|
||||
ruff path/to/code/ --watch
|
||||
```
|
||||
|
||||
ruff also works with [Pre-Commit](https://pre-commit.com) (requires Cargo on system):
|
||||
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff
|
||||
rev: v0.0.25
|
||||
hooks:
|
||||
- id: lint
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
ruff is configurable both via `pyproject.toml` and the command line.
|
||||
@@ -96,6 +106,25 @@ OPTIONS:
|
||||
-w, --watch Run in watch mode by re-running whenever files change
|
||||
```
|
||||
|
||||
## Rules
|
||||
|
||||
| Code | Name | Message |
|
||||
| ---- | ----- | ------- |
|
||||
| E501 | LineTooLong | Line too long |
|
||||
| F401 | UnusedImport | `...` imported but unused |
|
||||
| F403 | ImportStarUsage | Unable to detect undefined names |
|
||||
| F541 | FStringMissingPlaceholders | f-string without any placeholders |
|
||||
| F634 | IfTuple | If test is a tuple, which is always `True` |
|
||||
| F704 | YieldOutsideFunction | a `yield` or `yield from` statement outside of a function/method |
|
||||
| F706 | ReturnOutsideFunction | a `return` statement outside of a function/method |
|
||||
| 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 |
|
||||
| R0205 | UselessObjectInheritance | Class ... inherits from object |
|
||||
|
||||
## Development
|
||||
|
||||
ruff is written in Rust (1.63.0). You'll need to install the [Rust toolchain](https://www.rust-lang.org/tools/install)
|
||||
@@ -104,7 +133,7 @@ for development.
|
||||
Assuming you have `cargo` installed, you can run:
|
||||
|
||||
```shell
|
||||
cargo run resources/test/src
|
||||
cargo run resources/test/fixtures
|
||||
cargo fmt
|
||||
cargo clippy
|
||||
cargo test
|
||||
@@ -215,6 +244,7 @@ hyperfine --ignore-failure --warmup 5 \
|
||||
```
|
||||
|
||||
In order, these evaluate:
|
||||
|
||||
- ruff
|
||||
- Pylint
|
||||
- PyFlakes
|
||||
|
||||
33
examples/generate_rules_table.rs
Normal file
33
examples/generate_rules_table.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
/// Generate a Markdown-compatible table of supported lint rules.
|
||||
use ruff::checks::CheckKind;
|
||||
|
||||
fn main() {
|
||||
let mut check_kinds: Vec<CheckKind> = vec![
|
||||
CheckKind::DuplicateArgumentName,
|
||||
CheckKind::FStringMissingPlaceholders,
|
||||
CheckKind::IfTuple,
|
||||
CheckKind::ImportStarUsage,
|
||||
CheckKind::LineTooLong,
|
||||
CheckKind::RaiseNotImplemented,
|
||||
CheckKind::ReturnOutsideFunction,
|
||||
CheckKind::UndefinedLocal("...".to_string()),
|
||||
CheckKind::UndefinedName("...".to_string()),
|
||||
CheckKind::UndefinedExport("...".to_string()),
|
||||
CheckKind::UnusedImport("...".to_string()),
|
||||
CheckKind::UnusedVariable("...".to_string()),
|
||||
CheckKind::UselessObjectInheritance("...".to_string()),
|
||||
CheckKind::YieldOutsideFunction,
|
||||
];
|
||||
check_kinds.sort_by_key(|check_kind| check_kind.code());
|
||||
|
||||
println!("| Code | Name | Message |");
|
||||
println!("| ---- | ----- | ------- |");
|
||||
for check_kind in check_kinds {
|
||||
println!(
|
||||
"| {} | {} | {} |",
|
||||
check_kind.code().as_str(),
|
||||
check_kind.name(),
|
||||
check_kind.body()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
from __future__ import all_feature_names
|
||||
import os
|
||||
import functools
|
||||
from collections import (
|
||||
@@ -10,9 +11,15 @@ import multiprocessing.process
|
||||
import logging.config
|
||||
import logging.handlers
|
||||
|
||||
from blah import ClassA, ClassB, ClassC
|
||||
|
||||
|
||||
class X:
|
||||
def a(self) -> "namedtuple":
|
||||
x = os.environ["1"]
|
||||
y = Counter()
|
||||
z = multiprocessing.pool.ThreadPool()
|
||||
|
||||
|
||||
__all__ = ["ClassA"] + ["ClassB"]
|
||||
__all__ += ["ClassC"]
|
||||
10
resources/test/fixtures/F704.py
vendored
Normal file
10
resources/test/fixtures/F704.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
def f() -> int:
|
||||
yield 1
|
||||
|
||||
|
||||
class Foo:
|
||||
yield 2
|
||||
|
||||
|
||||
yield 3
|
||||
yield from 3
|
||||
@@ -40,9 +40,7 @@ class Class:
|
||||
# TODO(charlie): This should be recognized as a defined variable.
|
||||
Class # noqa: F821
|
||||
|
||||
|
||||
try:
|
||||
x = 1 / 0
|
||||
except Exception as e:
|
||||
# TODO(charlie): This should be recognized as a defined variable.
|
||||
print(e) # noqa: F821
|
||||
print(e)
|
||||
3
resources/test/fixtures/F822.py
vendored
Normal file
3
resources/test/fixtures/F822.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
a = 1
|
||||
|
||||
__all__ = ["a", "b"]
|
||||
@@ -15,3 +15,13 @@ def baz():
|
||||
global my_var
|
||||
global my_dict
|
||||
my_dict[my_var] += 1
|
||||
|
||||
|
||||
def dec(x):
|
||||
return x
|
||||
|
||||
|
||||
@dec
|
||||
def f():
|
||||
dec = 1
|
||||
return dec
|
||||
16
resources/test/fixtures/F841.py
vendored
Normal file
16
resources/test/fixtures/F841.py
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
try:
|
||||
1 / 0
|
||||
except ValueError as e:
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
1 / 0
|
||||
except ValueError as e:
|
||||
print(e)
|
||||
|
||||
|
||||
def f():
|
||||
x = 1
|
||||
y = 2
|
||||
z = x + y
|
||||
22
resources/test/fixtures/R0205.py
vendored
Normal file
22
resources/test/fixtures/R0205.py
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
class A:
|
||||
...
|
||||
|
||||
|
||||
class B(object):
|
||||
...
|
||||
|
||||
|
||||
class C(B, object):
|
||||
...
|
||||
|
||||
|
||||
def f():
|
||||
class D(object):
|
||||
...
|
||||
|
||||
|
||||
object = A
|
||||
|
||||
|
||||
class E(object):
|
||||
...
|
||||
0
resources/test/fixtures/bar/__init__.py
vendored
Normal file
0
resources/test/fixtures/bar/__init__.py
vendored
Normal file
0
resources/test/fixtures/bar/migrations/__init__.py
vendored
Normal file
0
resources/test/fixtures/bar/migrations/__init__.py
vendored
Normal file
9
resources/test/fixtures/excluded.py
vendored
Normal file
9
resources/test/fixtures/excluded.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
a = "abc"
|
||||
b = f"ghi{'jkl'}"
|
||||
|
||||
c = f"def"
|
||||
d = f"def" + "ghi"
|
||||
e = (
|
||||
f"def" +
|
||||
"ghi"
|
||||
)
|
||||
0
resources/test/fixtures/foo/__init__.py
vendored
Normal file
0
resources/test/fixtures/foo/__init__.py
vendored
Normal file
0
resources/test/fixtures/foo/migrations/__init__.py
vendored
Normal file
0
resources/test/fixtures/foo/migrations/__init__.py
vendored
Normal file
9
resources/test/fixtures/foo/migrations/migration.py
vendored
Normal file
9
resources/test/fixtures/foo/migrations/migration.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
a = "abc"
|
||||
b = f"ghi{'jkl'}"
|
||||
|
||||
c = f"def"
|
||||
d = f"def" + "ghi"
|
||||
e = (
|
||||
f"def" +
|
||||
"ghi"
|
||||
)
|
||||
@@ -1,15 +1,19 @@
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
exclude = ["excluded.py"]
|
||||
exclude = ["excluded.py", "**/migrations"]
|
||||
select = [
|
||||
"E501",
|
||||
"F401",
|
||||
"F403",
|
||||
"F541",
|
||||
"F634",
|
||||
"F704",
|
||||
"F706",
|
||||
"F821",
|
||||
"F822",
|
||||
"F823",
|
||||
"F831",
|
||||
"F832",
|
||||
"F841",
|
||||
"F901",
|
||||
"R0205",
|
||||
]
|
||||
117
src/ast_ops.rs
Normal file
117
src/ast_ops.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprKind, Location, Stmt, StmtKind};
|
||||
|
||||
fn id() -> usize {
|
||||
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
||||
COUNTER.fetch_add(1, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub enum ScopeKind {
|
||||
Class,
|
||||
Function,
|
||||
Generator,
|
||||
Module,
|
||||
}
|
||||
|
||||
pub struct Scope {
|
||||
pub id: usize,
|
||||
pub kind: ScopeKind,
|
||||
pub values: BTreeMap<String, Binding>,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
pub fn new(kind: ScopeKind) -> Self {
|
||||
Scope {
|
||||
id: id(),
|
||||
kind,
|
||||
values: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum BindingKind {
|
||||
Argument,
|
||||
Assignment,
|
||||
Builtin,
|
||||
ClassDefinition,
|
||||
Definition,
|
||||
Export(Vec<String>),
|
||||
FutureImportation,
|
||||
Importation(String),
|
||||
StarImportation,
|
||||
SubmoduleImportation(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Binding {
|
||||
pub kind: BindingKind,
|
||||
pub location: Location,
|
||||
pub used: Option<usize>,
|
||||
}
|
||||
|
||||
/// Extract the names bound to a given __all__ assignment.
|
||||
pub fn extract_all_names(stmt: &Stmt, scope: &Scope) -> Vec<String> {
|
||||
let mut names: Vec<String> = vec![];
|
||||
|
||||
fn add_to_names(names: &mut Vec<String>, elts: &[Expr]) {
|
||||
for elt in elts {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
} = &elt.node
|
||||
{
|
||||
names.push(value.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Grab the existing bound __all__ values.
|
||||
if let StmtKind::AugAssign { .. } = &stmt.node {
|
||||
if let Some(binding) = scope.values.get("__all__") {
|
||||
if let BindingKind::Export(existing) = &binding.kind {
|
||||
names.extend(existing.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(value) = match &stmt.node {
|
||||
StmtKind::Assign { value, .. } => Some(value),
|
||||
StmtKind::AnnAssign { value, .. } => value.as_ref(),
|
||||
StmtKind::AugAssign { value, .. } => Some(value),
|
||||
_ => None,
|
||||
} {
|
||||
match &value.node {
|
||||
ExprKind::List { elts, .. } | ExprKind::Tuple { elts, .. } => {
|
||||
add_to_names(&mut names, elts)
|
||||
}
|
||||
ExprKind::BinOp { left, right, .. } => {
|
||||
let mut current_left = left;
|
||||
let mut current_right = right;
|
||||
while let Some(elts) = match ¤t_right.node {
|
||||
ExprKind::List { elts, .. } => Some(elts),
|
||||
ExprKind::Tuple { elts, .. } => Some(elts),
|
||||
_ => None,
|
||||
} {
|
||||
add_to_names(&mut names, elts);
|
||||
match ¤t_left.node {
|
||||
ExprKind::BinOp { left, right, .. } => {
|
||||
current_left = left;
|
||||
current_right = right;
|
||||
}
|
||||
ExprKind::List { elts, .. } | ExprKind::Tuple { elts, .. } => {
|
||||
add_to_names(&mut names, elts);
|
||||
break;
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
names
|
||||
}
|
||||
@@ -66,7 +66,7 @@ impl From<bool> for Mode {
|
||||
}
|
||||
|
||||
fn cache_dir() -> &'static str {
|
||||
"./.cache"
|
||||
"./.ruff_cache"
|
||||
}
|
||||
|
||||
fn cache_key(path: &Path, settings: &Settings) -> String {
|
||||
|
||||
394
src/check_ast.rs
394
src/check_ast.rs
@@ -1,66 +1,22 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::collections::BTreeSet;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::builtins::{BUILTINS, MAGIC_GLOBALS};
|
||||
use rustpython_parser::ast::{
|
||||
Arg, Arguments, Constant, Expr, ExprContext, ExprKind, Location, Stmt, StmtKind, Suite,
|
||||
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind, Stmt,
|
||||
StmtKind, Suite,
|
||||
};
|
||||
use rustpython_parser::parser;
|
||||
|
||||
use crate::check_ast::ScopeKind::{Class, Function, Generator, Module};
|
||||
use crate::ast_ops::{extract_all_names, Binding, BindingKind, Scope, ScopeKind};
|
||||
use crate::builtins::{BUILTINS, MAGIC_GLOBALS};
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
use crate::settings::Settings;
|
||||
use crate::visitor;
|
||||
use crate::visitor::Visitor;
|
||||
|
||||
fn id() -> usize {
|
||||
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
||||
COUNTER.fetch_add(1, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
enum ScopeKind {
|
||||
Class,
|
||||
Function,
|
||||
Generator,
|
||||
Module,
|
||||
}
|
||||
|
||||
struct Scope {
|
||||
id: usize,
|
||||
kind: ScopeKind,
|
||||
values: BTreeMap<String, Binding>,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
fn new(kind: ScopeKind) -> Self {
|
||||
Scope {
|
||||
id: id(),
|
||||
kind,
|
||||
values: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum BindingKind {
|
||||
Argument,
|
||||
Assignment,
|
||||
Definition,
|
||||
ClassDefinition,
|
||||
Builtin,
|
||||
FutureImportation,
|
||||
Importation(String),
|
||||
StarImportation,
|
||||
SubmoduleImportation(String),
|
||||
}
|
||||
|
||||
struct Binding {
|
||||
kind: BindingKind,
|
||||
location: Location,
|
||||
used: Option<usize>,
|
||||
}
|
||||
use crate::visitor::{walk_excepthandler, Visitor};
|
||||
|
||||
struct Checker<'a> {
|
||||
settings: &'a Settings,
|
||||
path: &'a str,
|
||||
checks: Vec<Check>,
|
||||
scopes: Vec<Scope>,
|
||||
dead_scopes: Vec<Scope>,
|
||||
@@ -70,9 +26,10 @@ struct Checker<'a> {
|
||||
}
|
||||
|
||||
impl Checker<'_> {
|
||||
pub fn new(settings: &Settings) -> Checker {
|
||||
pub fn new<'a>(settings: &'a Settings, path: &'a str) -> Checker<'a> {
|
||||
Checker {
|
||||
settings,
|
||||
path,
|
||||
checks: vec![],
|
||||
scopes: vec![],
|
||||
dead_scopes: vec![],
|
||||
@@ -106,29 +63,33 @@ impl Visitor for Checker<'_> {
|
||||
}
|
||||
}
|
||||
}
|
||||
StmtKind::FunctionDef { name, .. } => {
|
||||
self.add_binding(
|
||||
name.to_string(),
|
||||
Binding {
|
||||
kind: BindingKind::Definition,
|
||||
|
||||
used: None,
|
||||
location: stmt.location,
|
||||
},
|
||||
);
|
||||
self.push_scope(Scope::new(Function));
|
||||
StmtKind::FunctionDef {
|
||||
name,
|
||||
decorator_list,
|
||||
returns,
|
||||
..
|
||||
}
|
||||
StmtKind::AsyncFunctionDef { name, .. } => {
|
||||
| StmtKind::AsyncFunctionDef {
|
||||
name,
|
||||
decorator_list,
|
||||
returns,
|
||||
..
|
||||
} => {
|
||||
for expr in decorator_list {
|
||||
self.visit_expr(expr, Some(stmt));
|
||||
}
|
||||
for expr in returns {
|
||||
self.visit_annotation(expr);
|
||||
}
|
||||
self.add_binding(
|
||||
name.to_string(),
|
||||
Binding {
|
||||
kind: BindingKind::Definition,
|
||||
|
||||
used: None,
|
||||
location: stmt.location,
|
||||
},
|
||||
);
|
||||
self.push_scope(Scope::new(Function));
|
||||
self.push_scope(Scope::new(ScopeKind::Function));
|
||||
}
|
||||
StmtKind::Return { .. } => {
|
||||
if self
|
||||
@@ -138,7 +99,7 @@ impl Visitor for Checker<'_> {
|
||||
{
|
||||
if let Some(scope) = self.scopes.last() {
|
||||
match scope.kind {
|
||||
Class | Module => {
|
||||
ScopeKind::Class | ScopeKind::Module => {
|
||||
self.checks.push(Check {
|
||||
kind: CheckKind::ReturnOutsideFunction,
|
||||
location: stmt.location,
|
||||
@@ -149,10 +110,54 @@ impl Visitor for Checker<'_> {
|
||||
}
|
||||
}
|
||||
}
|
||||
StmtKind::ClassDef { .. } => self.push_scope(Scope::new(Class)),
|
||||
StmtKind::ClassDef {
|
||||
name,
|
||||
bases,
|
||||
keywords,
|
||||
decorator_list,
|
||||
..
|
||||
} => {
|
||||
if self.settings.select.contains(&CheckCode::R0205) {
|
||||
for expr in bases {
|
||||
if let ExprKind::Name { id, .. } = &expr.node {
|
||||
if id == "object" {
|
||||
let scope = self.scopes.last().expect("No current scope found.");
|
||||
match scope.values.get(id) {
|
||||
None
|
||||
| Some(Binding {
|
||||
kind: BindingKind::Builtin,
|
||||
..
|
||||
}) => {
|
||||
self.checks.push(Check {
|
||||
kind: CheckKind::UselessObjectInheritance(
|
||||
name.to_string(),
|
||||
),
|
||||
location: stmt.location,
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for expr in bases {
|
||||
self.visit_expr(expr, Some(stmt))
|
||||
}
|
||||
for keyword in keywords {
|
||||
self.visit_keyword(keyword)
|
||||
}
|
||||
for expr in decorator_list {
|
||||
self.visit_expr(expr, Some(stmt))
|
||||
}
|
||||
self.push_scope(Scope::new(ScopeKind::Class))
|
||||
}
|
||||
StmtKind::Import { names } => {
|
||||
for alias in names {
|
||||
if alias.node.name.contains('.') && alias.node.asname.is_none() {
|
||||
// TODO(charlie): Multiple submodule imports with the same parent module
|
||||
// will be merged into a single binding.
|
||||
self.add_binding(
|
||||
alias.node.name.split('.').next().unwrap().to_string(),
|
||||
Binding {
|
||||
@@ -192,12 +197,11 @@ impl Visitor for Checker<'_> {
|
||||
.asname
|
||||
.clone()
|
||||
.unwrap_or_else(|| alias.node.name.clone());
|
||||
if let Some("future") = module.as_deref() {
|
||||
if let Some("__future__") = module.as_deref() {
|
||||
self.add_binding(
|
||||
name,
|
||||
Binding {
|
||||
kind: BindingKind::FutureImportation,
|
||||
|
||||
used: Some(self.scopes.last().expect("No current scope found.").id),
|
||||
location: stmt.location,
|
||||
},
|
||||
@@ -207,7 +211,6 @@ impl Visitor for Checker<'_> {
|
||||
name,
|
||||
Binding {
|
||||
kind: BindingKind::StarImportation,
|
||||
|
||||
used: None,
|
||||
location: stmt.location,
|
||||
},
|
||||
@@ -277,19 +280,40 @@ impl Visitor for Checker<'_> {
|
||||
}
|
||||
}
|
||||
}
|
||||
StmtKind::AugAssign { target, .. } => {
|
||||
self.handle_node_load(target);
|
||||
}
|
||||
StmtKind::AugAssign { target, .. } => self.handle_node_load(target),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
visitor::walk_stmt(self, stmt);
|
||||
|
||||
match &stmt.node {
|
||||
StmtKind::ClassDef { .. }
|
||||
| StmtKind::FunctionDef { .. }
|
||||
| StmtKind::AsyncFunctionDef { .. } => {
|
||||
self.pop_scope();
|
||||
StmtKind::ClassDef { .. } => {
|
||||
if let Some(scope) = self.scopes.pop() {
|
||||
self.dead_scopes.push(scope);
|
||||
}
|
||||
}
|
||||
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
|
||||
let scope = self.scopes.last().expect("No current scope found.");
|
||||
for (name, binding) in scope.values.iter() {
|
||||
// TODO(charlie): Ignore if using `locals`.
|
||||
if self.settings.select.contains(&CheckCode::F841)
|
||||
&& binding.used.is_none()
|
||||
&& name != "_"
|
||||
&& name != "__tracebackhide__"
|
||||
&& name != "__traceback_info__"
|
||||
&& name != "__traceback_supplement__"
|
||||
&& matches!(binding.kind, BindingKind::Assignment)
|
||||
{
|
||||
self.checks.push(Check {
|
||||
kind: CheckKind::UnusedVariable(name.to_string()),
|
||||
location: binding.location,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(scope) = self.scopes.pop() {
|
||||
self.dead_scopes.push(scope);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
@@ -299,34 +323,47 @@ impl Visitor for Checker<'_> {
|
||||
name.to_string(),
|
||||
Binding {
|
||||
kind: BindingKind::ClassDefinition,
|
||||
|
||||
used: None,
|
||||
location: stmt.location,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_annotation(&mut self, expr: &Expr) {
|
||||
let initial = self.in_annotation;
|
||||
self.in_annotation = true;
|
||||
self.visit_expr(expr);
|
||||
self.visit_expr(expr, None);
|
||||
self.in_annotation = initial;
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &Expr) {
|
||||
fn visit_expr(&mut self, expr: &Expr, parent: Option<&Stmt>) {
|
||||
let initial = self.in_f_string;
|
||||
match &expr.node {
|
||||
ExprKind::Name { ctx, .. } => match ctx {
|
||||
ExprContext::Load => self.handle_node_load(expr),
|
||||
ExprContext::Store => self.handle_node_store(expr),
|
||||
ExprContext::Store => self.handle_node_store(expr, parent),
|
||||
ExprContext::Del => self.handle_node_delete(expr),
|
||||
},
|
||||
ExprKind::GeneratorExp { .. }
|
||||
| ExprKind::ListComp { .. }
|
||||
| ExprKind::DictComp { .. }
|
||||
| ExprKind::SetComp { .. } => self.push_scope(Scope::new(Generator)),
|
||||
ExprKind::Lambda { .. } => self.push_scope(Scope::new(Function)),
|
||||
| ExprKind::SetComp { .. } => self.push_scope(Scope::new(ScopeKind::Generator)),
|
||||
ExprKind::Lambda { .. } => self.push_scope(Scope::new(ScopeKind::Function)),
|
||||
ExprKind::Yield { .. } | ExprKind::YieldFrom { .. } => {
|
||||
let scope = self.scopes.last().expect("No current scope found.");
|
||||
if self
|
||||
.settings
|
||||
.select
|
||||
.contains(CheckKind::YieldOutsideFunction.code())
|
||||
&& matches!(scope.kind, ScopeKind::Class)
|
||||
|| matches!(scope.kind, ScopeKind::Module)
|
||||
{
|
||||
self.checks.push(Check {
|
||||
kind: CheckKind::YieldOutsideFunction,
|
||||
location: expr.location,
|
||||
});
|
||||
}
|
||||
}
|
||||
ExprKind::JoinedStr { values } => {
|
||||
if !self.in_f_string
|
||||
&& self
|
||||
@@ -370,6 +407,59 @@ impl Visitor for Checker<'_> {
|
||||
};
|
||||
}
|
||||
|
||||
fn visit_excepthandler(&mut self, excepthandler: &Excepthandler) {
|
||||
match &excepthandler.node {
|
||||
ExcepthandlerKind::ExceptHandler { name, .. } => match name {
|
||||
Some(name) => {
|
||||
let scope = self.scopes.last().expect("No current scope found.");
|
||||
if scope.values.contains_key(name) {
|
||||
self.handle_node_store(
|
||||
&Expr::new(
|
||||
excepthandler.location,
|
||||
ExprKind::Name {
|
||||
id: name.to_string(),
|
||||
ctx: ExprContext::Store,
|
||||
},
|
||||
),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
let scope = self.scopes.last().expect("No current scope found.");
|
||||
let prev_definition = scope.values.get(name).cloned();
|
||||
self.handle_node_store(
|
||||
&Expr::new(
|
||||
excepthandler.location,
|
||||
ExprKind::Name {
|
||||
id: name.to_string(),
|
||||
ctx: ExprContext::Store,
|
||||
},
|
||||
),
|
||||
None,
|
||||
);
|
||||
|
||||
walk_excepthandler(self, excepthandler);
|
||||
|
||||
let scope = self.scopes.last_mut().expect("No current scope found.");
|
||||
if let Some(binding) = scope.values.remove(name) {
|
||||
if self.settings.select.contains(&CheckCode::F841) && binding.used.is_none()
|
||||
{
|
||||
self.checks.push(Check {
|
||||
kind: CheckKind::UnusedVariable(name.to_string()),
|
||||
location: excepthandler.location,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(binding) = prev_definition {
|
||||
scope.values.insert(name.to_string(), binding);
|
||||
}
|
||||
}
|
||||
None => walk_excepthandler(self, excepthandler),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_arguments(&mut self, arguments: &Arguments) {
|
||||
if self
|
||||
.settings
|
||||
@@ -475,7 +565,7 @@ impl Checker<'_> {
|
||||
let mut first_iter = true;
|
||||
let mut in_generators = false;
|
||||
for scope in self.scopes.iter_mut().rev() {
|
||||
if matches!(scope.kind, Class) {
|
||||
if matches!(scope.kind, ScopeKind::Class) {
|
||||
if id == "__class__" {
|
||||
return;
|
||||
} else if !first_iter && !in_generators {
|
||||
@@ -488,7 +578,7 @@ impl Checker<'_> {
|
||||
}
|
||||
|
||||
first_iter = false;
|
||||
in_generators = matches!(scope.kind, Generator);
|
||||
in_generators = matches!(scope.kind, ScopeKind::Generator);
|
||||
}
|
||||
|
||||
if self.settings.select.contains(&CheckCode::F821) {
|
||||
@@ -500,26 +590,29 @@ impl Checker<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_node_store(&mut self, expr: &Expr) {
|
||||
fn handle_node_store(&mut self, expr: &Expr, parent: Option<&Stmt>) {
|
||||
if let ExprKind::Name { id, .. } = &expr.node {
|
||||
if self.settings.select.contains(&CheckCode::F832) {
|
||||
let current = self.scopes.last().expect("No current scope found.");
|
||||
if matches!(current.kind, ScopeKind::Function) && !current.values.contains_key(id) {
|
||||
for scope in self.scopes.iter().rev().skip(1) {
|
||||
if matches!(scope.kind, ScopeKind::Function) || matches!(scope.kind, Module)
|
||||
{
|
||||
let used = scope
|
||||
.values
|
||||
.get(id)
|
||||
.map(|binding| binding.used)
|
||||
.unwrap_or_default();
|
||||
if let Some(scope_id) = used {
|
||||
if scope_id == current.id {
|
||||
self.checks.push(Check {
|
||||
kind: CheckKind::UndefinedLocal(id.clone()),
|
||||
location: expr.location,
|
||||
});
|
||||
}
|
||||
let current = self.scopes.last().expect("No current scope found.");
|
||||
|
||||
if self.settings.select.contains(&CheckCode::F823)
|
||||
&& matches!(current.kind, ScopeKind::Function)
|
||||
&& !current.values.contains_key(id)
|
||||
{
|
||||
for scope in self.scopes.iter().rev().skip(1) {
|
||||
if matches!(scope.kind, ScopeKind::Function)
|
||||
|| matches!(scope.kind, ScopeKind::Module)
|
||||
{
|
||||
let used = scope
|
||||
.values
|
||||
.get(id)
|
||||
.map(|binding| binding.used)
|
||||
.unwrap_or_default();
|
||||
if let Some(scope_id) = used {
|
||||
if scope_id == current.id {
|
||||
self.checks.push(Check {
|
||||
kind: CheckKind::UndefinedLocal(id.clone()),
|
||||
location: expr.location,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -527,14 +620,36 @@ impl Checker<'_> {
|
||||
}
|
||||
|
||||
// TODO(charlie): Handle alternate binding types (like `Annotation`).
|
||||
self.add_binding(
|
||||
id.to_string(),
|
||||
Binding {
|
||||
kind: BindingKind::Assignment,
|
||||
used: None,
|
||||
location: expr.location,
|
||||
},
|
||||
);
|
||||
if id == "__all__"
|
||||
&& matches!(current.kind, ScopeKind::Module)
|
||||
&& match parent {
|
||||
None => false,
|
||||
Some(stmt) => {
|
||||
matches!(stmt.node, StmtKind::Assign { .. })
|
||||
|| matches!(stmt.node, StmtKind::AugAssign { .. })
|
||||
|| matches!(stmt.node, StmtKind::AnnAssign { .. })
|
||||
}
|
||||
}
|
||||
{
|
||||
// Really need parent here.
|
||||
self.add_binding(
|
||||
id.to_string(),
|
||||
Binding {
|
||||
kind: BindingKind::Export(extract_all_names(parent.unwrap(), current)),
|
||||
used: None,
|
||||
location: expr.location,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
self.add_binding(
|
||||
id.to_string(),
|
||||
Binding {
|
||||
kind: BindingKind::Assignment,
|
||||
used: None,
|
||||
location: expr.location,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -555,17 +670,50 @@ impl Checker<'_> {
|
||||
fn check_deferred(&mut self, path: &str) {
|
||||
for value in self.deferred.clone() {
|
||||
if let Ok(expr) = &parser::parse_expression(&value, path) {
|
||||
self.visit_expr(expr);
|
||||
self.visit_expr(expr, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_dead_scopes(&mut self) {
|
||||
if self.settings.select.contains(&CheckCode::F401) {
|
||||
// TODO(charlie): Handle `__all__`.
|
||||
for scope in &self.dead_scopes {
|
||||
for (_, binding) in scope.values.iter().rev() {
|
||||
if binding.used.is_none() {
|
||||
if !self.settings.select.contains(&CheckCode::F822)
|
||||
&& !self.settings.select.contains(&CheckCode::F401)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for scope in &self.dead_scopes {
|
||||
let all_binding = scope.values.get("__all__");
|
||||
let all_names = all_binding.and_then(|binding| match &binding.kind {
|
||||
BindingKind::Export(names) => Some(names),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
if self.settings.select.contains(&CheckCode::F822)
|
||||
&& !Path::new(self.path).ends_with("__init__.py")
|
||||
{
|
||||
if let Some(binding) = all_binding {
|
||||
if let Some(names) = all_names {
|
||||
for name in names {
|
||||
if !scope.values.contains_key(name) {
|
||||
self.checks.push(Check {
|
||||
kind: CheckKind::UndefinedExport(name.to_string()),
|
||||
location: binding.location,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.select.contains(&CheckCode::F401) {
|
||||
for (name, binding) in scope.values.iter().rev() {
|
||||
let used = binding.used.is_some()
|
||||
|| all_names
|
||||
.map(|names| names.contains(name))
|
||||
.unwrap_or_default();
|
||||
|
||||
if !used {
|
||||
match &binding.kind {
|
||||
BindingKind::Importation(full_name)
|
||||
| BindingKind::SubmoduleImportation(full_name) => {
|
||||
@@ -584,8 +732,8 @@ impl Checker<'_> {
|
||||
}
|
||||
|
||||
pub fn check_ast(python_ast: &Suite, settings: &Settings, path: &str) -> Vec<Check> {
|
||||
let mut checker = Checker::new(settings);
|
||||
checker.push_scope(Scope::new(Module));
|
||||
let mut checker = Checker::new(settings, path);
|
||||
checker.push_scope(Scope::new(ScopeKind::Module));
|
||||
checker.bind_builtins();
|
||||
|
||||
for stmt in python_ast {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Result;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustpython_parser::ast::Location;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -12,11 +13,15 @@ pub enum CheckCode {
|
||||
F403,
|
||||
F541,
|
||||
F634,
|
||||
F704,
|
||||
F706,
|
||||
F821,
|
||||
F822,
|
||||
F823,
|
||||
F831,
|
||||
F832,
|
||||
F841,
|
||||
F901,
|
||||
R0205,
|
||||
}
|
||||
|
||||
impl FromStr for CheckCode {
|
||||
@@ -29,11 +34,15 @@ impl FromStr for CheckCode {
|
||||
"F403" => Ok(CheckCode::F403),
|
||||
"F541" => Ok(CheckCode::F541),
|
||||
"F634" => Ok(CheckCode::F634),
|
||||
"F704" => Ok(CheckCode::F704),
|
||||
"F706" => Ok(CheckCode::F706),
|
||||
"F821" => Ok(CheckCode::F821),
|
||||
"F822" => Ok(CheckCode::F822),
|
||||
"F823" => Ok(CheckCode::F823),
|
||||
"F831" => Ok(CheckCode::F831),
|
||||
"F832" => Ok(CheckCode::F832),
|
||||
"F841" => Ok(CheckCode::F841),
|
||||
"F901" => Ok(CheckCode::F901),
|
||||
"R0205" => Ok(CheckCode::R0205),
|
||||
_ => Err(anyhow::anyhow!("Unknown check code: {s}")),
|
||||
}
|
||||
}
|
||||
@@ -47,11 +56,15 @@ impl CheckCode {
|
||||
CheckCode::F403 => "F403",
|
||||
CheckCode::F541 => "F541",
|
||||
CheckCode::F634 => "F634",
|
||||
CheckCode::F704 => "F704",
|
||||
CheckCode::F706 => "F706",
|
||||
CheckCode::F821 => "F821",
|
||||
CheckCode::F823 => "F823",
|
||||
CheckCode::F822 => "F822",
|
||||
CheckCode::F831 => "F831",
|
||||
CheckCode::F832 => "F832",
|
||||
CheckCode::F841 => "F841",
|
||||
CheckCode::F901 => "F901",
|
||||
CheckCode::R0205 => "R0205",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,11 +76,15 @@ impl CheckCode {
|
||||
CheckCode::F403 => &LintSource::AST,
|
||||
CheckCode::F541 => &LintSource::AST,
|
||||
CheckCode::F634 => &LintSource::AST,
|
||||
CheckCode::F704 => &LintSource::AST,
|
||||
CheckCode::F706 => &LintSource::AST,
|
||||
CheckCode::F821 => &LintSource::AST,
|
||||
CheckCode::F822 => &LintSource::AST,
|
||||
CheckCode::F823 => &LintSource::AST,
|
||||
CheckCode::F831 => &LintSource::AST,
|
||||
CheckCode::F832 => &LintSource::AST,
|
||||
CheckCode::F841 => &LintSource::AST,
|
||||
CheckCode::F901 => &LintSource::AST,
|
||||
CheckCode::R0205 => &LintSource::AST,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,12 +104,36 @@ pub enum CheckKind {
|
||||
LineTooLong,
|
||||
RaiseNotImplemented,
|
||||
ReturnOutsideFunction,
|
||||
UndefinedName(String),
|
||||
UndefinedLocal(String),
|
||||
UndefinedExport(String),
|
||||
UndefinedName(String),
|
||||
UnusedImport(String),
|
||||
UnusedVariable(String),
|
||||
UselessObjectInheritance(String),
|
||||
YieldOutsideFunction,
|
||||
}
|
||||
|
||||
impl CheckKind {
|
||||
/// The name of the check.
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
CheckKind::DuplicateArgumentName => "DuplicateArgumentName",
|
||||
CheckKind::FStringMissingPlaceholders => "FStringMissingPlaceholders",
|
||||
CheckKind::IfTuple => "IfTuple",
|
||||
CheckKind::ImportStarUsage => "ImportStarUsage",
|
||||
CheckKind::LineTooLong => "LineTooLong",
|
||||
CheckKind::RaiseNotImplemented => "RaiseNotImplemented",
|
||||
CheckKind::ReturnOutsideFunction => "ReturnOutsideFunction",
|
||||
CheckKind::UndefinedLocal(_) => "UndefinedLocal",
|
||||
CheckKind::UndefinedExport(_) => "UndefinedExport",
|
||||
CheckKind::UndefinedName(_) => "UndefinedName",
|
||||
CheckKind::UnusedImport(_) => "UnusedImport",
|
||||
CheckKind::UnusedVariable(_) => "UnusedVariable",
|
||||
CheckKind::UselessObjectInheritance(_) => "UselessObjectInheritance",
|
||||
CheckKind::YieldOutsideFunction => "YieldOutsideFunction",
|
||||
}
|
||||
}
|
||||
|
||||
/// A four-letter shorthand code for the check.
|
||||
pub fn code(&self) -> &'static CheckCode {
|
||||
match self {
|
||||
@@ -103,9 +144,13 @@ impl CheckKind {
|
||||
CheckKind::LineTooLong => &CheckCode::E501,
|
||||
CheckKind::RaiseNotImplemented => &CheckCode::F901,
|
||||
CheckKind::ReturnOutsideFunction => &CheckCode::F706,
|
||||
CheckKind::UndefinedExport(_) => &CheckCode::F822,
|
||||
CheckKind::UndefinedLocal(_) => &CheckCode::F823,
|
||||
CheckKind::UndefinedName(_) => &CheckCode::F821,
|
||||
CheckKind::UndefinedLocal(_) => &CheckCode::F832,
|
||||
CheckKind::UnusedImport(_) => &CheckCode::F401,
|
||||
CheckKind::UnusedVariable(_) => &CheckCode::F841,
|
||||
CheckKind::UselessObjectInheritance(_) => &CheckCode::R0205,
|
||||
CheckKind::YieldOutsideFunction => &CheckCode::F704,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,9 +163,7 @@ impl CheckKind {
|
||||
CheckKind::FStringMissingPlaceholders => {
|
||||
"f-string without any placeholders".to_string()
|
||||
}
|
||||
CheckKind::IfTuple => {
|
||||
"If test is a tuple.to_string(), which is always `True`".to_string()
|
||||
}
|
||||
CheckKind::IfTuple => "If test is a tuple, which is always `True`".to_string(),
|
||||
CheckKind::ImportStarUsage => "Unable to detect undefined names".to_string(),
|
||||
CheckKind::LineTooLong => "Line too long".to_string(),
|
||||
CheckKind::RaiseNotImplemented => {
|
||||
@@ -129,6 +172,9 @@ impl CheckKind {
|
||||
CheckKind::ReturnOutsideFunction => {
|
||||
"a `return` statement outside of a function/method".to_string()
|
||||
}
|
||||
CheckKind::UndefinedExport(name) => {
|
||||
format!("Undefined name `{name}` in __all__")
|
||||
}
|
||||
CheckKind::UndefinedName(name) => {
|
||||
format!("Undefined name `{name}`")
|
||||
}
|
||||
@@ -136,6 +182,15 @@ impl CheckKind {
|
||||
format!("Local variable `{name}` referenced before assignment")
|
||||
}
|
||||
CheckKind::UnusedImport(name) => format!("`{name}` imported but unused"),
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,14 +201,17 @@ pub struct Check {
|
||||
pub location: Location,
|
||||
}
|
||||
|
||||
static NO_QA_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(r"(?i)# noqa(?::\s?(?P<codes>([A-Z]+[0-9]+(?:[,\s]+)?)+))?").expect("Invalid regex")
|
||||
});
|
||||
static SPLIT_COMMA_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"[,\s]").expect("Invalid regex"));
|
||||
|
||||
impl Check {
|
||||
pub fn is_inline_ignored(&self, line: &str) -> bool {
|
||||
let re = Regex::new(r"(?i)# noqa(?::\s?(?P<codes>([A-Z]+[0-9]+(?:[,\s]+)?)+))?").unwrap();
|
||||
match re.captures(line) {
|
||||
match NO_QA_REGEX.captures(line) {
|
||||
Some(caps) => match caps.name("codes") {
|
||||
Some(codes) => {
|
||||
let re = Regex::new(r"[,\s]").unwrap();
|
||||
for code in re
|
||||
for code in SPLIT_COMMA_REGEX
|
||||
.split(codes.as_str())
|
||||
.map(|code| code.trim())
|
||||
.filter(|code| !code.is_empty())
|
||||
|
||||
18
src/fs.rs
18
src/fs.rs
@@ -3,21 +3,33 @@ use std::io::{BufReader, Read};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use glob::Pattern;
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
fn is_not_hidden(entry: &DirEntry) -> bool {
|
||||
entry
|
||||
.file_name()
|
||||
.to_str()
|
||||
.map(|s| entry.depth() == 0 || !s.starts_with('.'))
|
||||
.map(|s| (entry.depth() == 0 || !s.starts_with('.')))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn iter_python_files(path: &PathBuf) -> impl Iterator<Item = DirEntry> {
|
||||
fn is_not_excluded(entry: &DirEntry, exclude: &[Pattern]) -> bool {
|
||||
entry
|
||||
.path()
|
||||
.to_str()
|
||||
.map(|s| !exclude.iter().any(|pattern| pattern.matches(s)))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn iter_python_files<'a>(
|
||||
path: &'a PathBuf,
|
||||
exclude: &'a [Pattern],
|
||||
) -> impl Iterator<Item = DirEntry> + 'a {
|
||||
WalkDir::new(path)
|
||||
.follow_links(true)
|
||||
.into_iter()
|
||||
.filter_entry(is_not_hidden)
|
||||
.filter_entry(|entry| is_not_hidden(entry) && is_not_excluded(entry, exclude))
|
||||
.filter_map(|entry| entry.ok())
|
||||
.filter(|entry| entry.path().to_string_lossy().ends_with(".py"))
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
mod ast_ops;
|
||||
mod builtins;
|
||||
mod cache;
|
||||
pub mod check_ast;
|
||||
|
||||
211
src/linter.rs
211
src/linter.rs
@@ -68,7 +68,7 @@ mod tests {
|
||||
#[test]
|
||||
fn e501() -> Result<()> {
|
||||
let actual = check_path(
|
||||
&Path::new("./resources/test/src/E501.py"),
|
||||
Path::new("./resources/test/fixtures/E501.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
@@ -79,7 +79,7 @@ mod tests {
|
||||
let expected = vec![Message {
|
||||
kind: CheckKind::LineTooLong,
|
||||
location: Location::new(5, 89),
|
||||
filename: "./resources/test/src/E501.py".to_string(),
|
||||
filename: "./resources/test/fixtures/E501.py".to_string(),
|
||||
}];
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
for i in 0..actual.len() {
|
||||
@@ -92,7 +92,7 @@ mod tests {
|
||||
#[test]
|
||||
fn f401() -> Result<()> {
|
||||
let actual = check_path(
|
||||
&Path::new("./resources/test/src/F401.py"),
|
||||
Path::new("./resources/test/fixtures/F401.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
@@ -103,18 +103,18 @@ mod tests {
|
||||
let expected = vec![
|
||||
Message {
|
||||
kind: CheckKind::UnusedImport("logging.handlers".to_string()),
|
||||
location: Location::new(11, 1),
|
||||
filename: "./resources/test/src/F401.py".to_string(),
|
||||
location: Location::new(12, 1),
|
||||
filename: "./resources/test/fixtures/F401.py".to_string(),
|
||||
},
|
||||
Message {
|
||||
kind: CheckKind::UnusedImport("functools".to_string()),
|
||||
location: Location::new(2, 1),
|
||||
filename: "./resources/test/src/F401.py".to_string(),
|
||||
location: Location::new(3, 1),
|
||||
filename: "./resources/test/fixtures/F401.py".to_string(),
|
||||
},
|
||||
Message {
|
||||
kind: CheckKind::UnusedImport("collections.OrderedDict".to_string()),
|
||||
location: Location::new(3, 1),
|
||||
filename: "./resources/test/src/F401.py".to_string(),
|
||||
location: Location::new(4, 1),
|
||||
filename: "./resources/test/fixtures/F401.py".to_string(),
|
||||
},
|
||||
];
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
@@ -128,7 +128,7 @@ mod tests {
|
||||
#[test]
|
||||
fn f403() -> Result<()> {
|
||||
let actual = check_path(
|
||||
&Path::new("./resources/test/src/F403.py"),
|
||||
Path::new("./resources/test/fixtures/F403.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
@@ -140,12 +140,12 @@ mod tests {
|
||||
Message {
|
||||
kind: CheckKind::ImportStarUsage,
|
||||
location: Location::new(1, 1),
|
||||
filename: "./resources/test/src/F403.py".to_string(),
|
||||
filename: "./resources/test/fixtures/F403.py".to_string(),
|
||||
},
|
||||
Message {
|
||||
kind: CheckKind::ImportStarUsage,
|
||||
location: Location::new(2, 1),
|
||||
filename: "./resources/test/src/F403.py".to_string(),
|
||||
filename: "./resources/test/fixtures/F403.py".to_string(),
|
||||
},
|
||||
];
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
@@ -158,7 +158,7 @@ mod tests {
|
||||
#[test]
|
||||
fn f541() -> Result<()> {
|
||||
let actual = check_path(
|
||||
&Path::new("./resources/test/src/F541.py"),
|
||||
Path::new("./resources/test/fixtures/F541.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
@@ -170,17 +170,17 @@ mod tests {
|
||||
Message {
|
||||
kind: CheckKind::FStringMissingPlaceholders,
|
||||
location: Location::new(4, 7),
|
||||
filename: "./resources/test/src/F541.py".to_string(),
|
||||
filename: "./resources/test/fixtures/F541.py".to_string(),
|
||||
},
|
||||
Message {
|
||||
kind: CheckKind::FStringMissingPlaceholders,
|
||||
location: Location::new(5, 7),
|
||||
filename: "./resources/test/src/F541.py".to_string(),
|
||||
filename: "./resources/test/fixtures/F541.py".to_string(),
|
||||
},
|
||||
Message {
|
||||
kind: CheckKind::FStringMissingPlaceholders,
|
||||
location: Location::new(7, 7),
|
||||
filename: "./resources/test/src/F541.py".to_string(),
|
||||
filename: "./resources/test/fixtures/F541.py".to_string(),
|
||||
},
|
||||
];
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
@@ -194,7 +194,7 @@ mod tests {
|
||||
#[test]
|
||||
fn f634() -> Result<()> {
|
||||
let actual = check_path(
|
||||
&Path::new("./resources/test/src/F634.py"),
|
||||
Path::new("./resources/test/fixtures/F634.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
@@ -206,12 +206,48 @@ mod tests {
|
||||
Message {
|
||||
kind: CheckKind::IfTuple,
|
||||
location: Location::new(1, 1),
|
||||
filename: "./resources/test/src/F634.py".to_string(),
|
||||
filename: "./resources/test/fixtures/F634.py".to_string(),
|
||||
},
|
||||
Message {
|
||||
kind: CheckKind::IfTuple,
|
||||
location: Location::new(7, 5),
|
||||
filename: "./resources/test/src/F634.py".to_string(),
|
||||
filename: "./resources/test/fixtures/F634.py".to_string(),
|
||||
},
|
||||
];
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
for i in 0..actual.len() {
|
||||
assert_eq!(actual[i], expected[i]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f704() -> Result<()> {
|
||||
let actual = check_path(
|
||||
Path::new("./resources/test/fixtures/F704.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F704]),
|
||||
},
|
||||
&cache::Mode::None,
|
||||
)?;
|
||||
let expected = vec![
|
||||
Message {
|
||||
kind: CheckKind::YieldOutsideFunction,
|
||||
location: Location::new(6, 5),
|
||||
filename: "./resources/test/fixtures/F704.py".to_string(),
|
||||
},
|
||||
Message {
|
||||
kind: CheckKind::YieldOutsideFunction,
|
||||
location: Location::new(9, 1),
|
||||
filename: "./resources/test/fixtures/F704.py".to_string(),
|
||||
},
|
||||
Message {
|
||||
kind: CheckKind::YieldOutsideFunction,
|
||||
location: Location::new(10, 1),
|
||||
filename: "./resources/test/fixtures/F704.py".to_string(),
|
||||
},
|
||||
];
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
@@ -225,7 +261,7 @@ mod tests {
|
||||
#[test]
|
||||
fn f706() -> Result<()> {
|
||||
let actual = check_path(
|
||||
&Path::new("./resources/test/src/F706.py"),
|
||||
Path::new("./resources/test/fixtures/F706.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
@@ -237,12 +273,12 @@ mod tests {
|
||||
Message {
|
||||
kind: CheckKind::ReturnOutsideFunction,
|
||||
location: Location::new(6, 5),
|
||||
filename: "./resources/test/src/F706.py".to_string(),
|
||||
filename: "./resources/test/fixtures/F706.py".to_string(),
|
||||
},
|
||||
Message {
|
||||
kind: CheckKind::ReturnOutsideFunction,
|
||||
location: Location::new(9, 1),
|
||||
filename: "./resources/test/src/F706.py".to_string(),
|
||||
filename: "./resources/test/fixtures/F706.py".to_string(),
|
||||
},
|
||||
];
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
@@ -256,7 +292,7 @@ mod tests {
|
||||
#[test]
|
||||
fn f821() -> Result<()> {
|
||||
let actual = check_path(
|
||||
&Path::new("./resources/test/src/F821.py"),
|
||||
Path::new("./resources/test/fixtures/F821.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
@@ -268,22 +304,22 @@ mod tests {
|
||||
Message {
|
||||
kind: CheckKind::UndefinedName("self".to_string()),
|
||||
location: Location::new(2, 12),
|
||||
filename: "./resources/test/src/F821.py".to_string(),
|
||||
filename: "./resources/test/fixtures/F821.py".to_string(),
|
||||
},
|
||||
Message {
|
||||
kind: CheckKind::UndefinedName("self".to_string()),
|
||||
location: Location::new(6, 13),
|
||||
filename: "./resources/test/src/F821.py".to_string(),
|
||||
filename: "./resources/test/fixtures/F821.py".to_string(),
|
||||
},
|
||||
Message {
|
||||
kind: CheckKind::UndefinedName("self".to_string()),
|
||||
location: Location::new(10, 9),
|
||||
filename: "./resources/test/src/F821.py".to_string(),
|
||||
filename: "./resources/test/fixtures/F821.py".to_string(),
|
||||
},
|
||||
Message {
|
||||
kind: CheckKind::UndefinedName("numeric_string".to_string()),
|
||||
location: Location::new(21, 12),
|
||||
filename: "./resources/test/src/F821.py".to_string(),
|
||||
filename: "./resources/test/fixtures/F821.py".to_string(),
|
||||
},
|
||||
];
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
@@ -294,10 +330,58 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f822() -> Result<()> {
|
||||
let actual = check_path(
|
||||
Path::new("./resources/test/fixtures/F822.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F822]),
|
||||
},
|
||||
&cache::Mode::None,
|
||||
)?;
|
||||
let expected = vec![Message {
|
||||
kind: CheckKind::UndefinedExport("b".to_string()),
|
||||
location: Location::new(3, 1),
|
||||
filename: "./resources/test/fixtures/F822.py".to_string(),
|
||||
}];
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
for i in 0..actual.len() {
|
||||
assert_eq!(actual[i], expected[i]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f823() -> Result<()> {
|
||||
let actual = check_path(
|
||||
Path::new("./resources/test/fixtures/F823.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F823]),
|
||||
},
|
||||
&cache::Mode::None,
|
||||
)?;
|
||||
let expected = vec![Message {
|
||||
kind: CheckKind::UndefinedLocal("my_var".to_string()),
|
||||
location: Location::new(6, 5),
|
||||
filename: "./resources/test/fixtures/F823.py".to_string(),
|
||||
}];
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
for i in 0..actual.len() {
|
||||
assert_eq!(actual[i], expected[i]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f831() -> Result<()> {
|
||||
let actual = check_path(
|
||||
&Path::new("./resources/test/src/F831.py"),
|
||||
Path::new("./resources/test/fixtures/F831.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
@@ -309,17 +393,17 @@ mod tests {
|
||||
Message {
|
||||
kind: CheckKind::DuplicateArgumentName,
|
||||
location: Location::new(1, 25),
|
||||
filename: "./resources/test/src/F831.py".to_string(),
|
||||
filename: "./resources/test/fixtures/F831.py".to_string(),
|
||||
},
|
||||
Message {
|
||||
kind: CheckKind::DuplicateArgumentName,
|
||||
location: Location::new(5, 28),
|
||||
filename: "./resources/test/src/F831.py".to_string(),
|
||||
filename: "./resources/test/fixtures/F831.py".to_string(),
|
||||
},
|
||||
Message {
|
||||
kind: CheckKind::DuplicateArgumentName,
|
||||
location: Location::new(9, 27),
|
||||
filename: "./resources/test/src/F831.py".to_string(),
|
||||
filename: "./resources/test/fixtures/F831.py".to_string(),
|
||||
},
|
||||
];
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
@@ -331,21 +415,28 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f832() -> Result<()> {
|
||||
fn f841() -> Result<()> {
|
||||
let actual = check_path(
|
||||
&Path::new("./resources/test/src/F832.py"),
|
||||
Path::new("./resources/test/fixtures/F841.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F832]),
|
||||
select: BTreeSet::from([CheckCode::F841]),
|
||||
},
|
||||
&cache::Mode::None,
|
||||
)?;
|
||||
let expected = vec![Message {
|
||||
kind: CheckKind::UndefinedLocal("my_var".to_string()),
|
||||
location: Location::new(6, 5),
|
||||
filename: "./resources/test/src/F832.py".to_string(),
|
||||
}];
|
||||
let expected = vec![
|
||||
Message {
|
||||
kind: CheckKind::UnusedVariable("e".to_string()),
|
||||
location: Location::new(3, 1),
|
||||
filename: "./resources/test/fixtures/F841.py".to_string(),
|
||||
},
|
||||
Message {
|
||||
kind: CheckKind::UnusedVariable("z".to_string()),
|
||||
location: Location::new(16, 5),
|
||||
filename: "./resources/test/fixtures/F841.py".to_string(),
|
||||
},
|
||||
];
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
for i in 0..actual.len() {
|
||||
assert_eq!(actual[i], expected[i]);
|
||||
@@ -357,7 +448,7 @@ mod tests {
|
||||
#[test]
|
||||
fn f901() -> Result<()> {
|
||||
let actual = check_path(
|
||||
&Path::new("./resources/test/src/F901.py"),
|
||||
Path::new("./resources/test/fixtures/F901.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
@@ -369,12 +460,48 @@ mod tests {
|
||||
Message {
|
||||
kind: CheckKind::RaiseNotImplemented,
|
||||
location: Location::new(2, 5),
|
||||
filename: "./resources/test/src/F901.py".to_string(),
|
||||
filename: "./resources/test/fixtures/F901.py".to_string(),
|
||||
},
|
||||
Message {
|
||||
kind: CheckKind::RaiseNotImplemented,
|
||||
location: Location::new(6, 5),
|
||||
filename: "./resources/test/src/F901.py".to_string(),
|
||||
filename: "./resources/test/fixtures/F901.py".to_string(),
|
||||
},
|
||||
];
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
for i in 0..actual.len() {
|
||||
assert_eq!(actual[i], expected[i]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn r0205() -> Result<()> {
|
||||
let actual = check_path(
|
||||
Path::new("./resources/test/fixtures/R0205.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::R0205]),
|
||||
},
|
||||
&cache::Mode::None,
|
||||
)?;
|
||||
let expected = vec![
|
||||
Message {
|
||||
kind: CheckKind::UselessObjectInheritance("B".to_string()),
|
||||
location: Location::new(5, 1),
|
||||
filename: "./resources/test/fixtures/R0205.py".to_string(),
|
||||
},
|
||||
Message {
|
||||
kind: CheckKind::UselessObjectInheritance("C".to_string()),
|
||||
location: Location::new(9, 1),
|
||||
filename: "./resources/test/fixtures/R0205.py".to_string(),
|
||||
},
|
||||
Message {
|
||||
kind: CheckKind::UselessObjectInheritance("D".to_string()),
|
||||
location: Location::new(14, 5),
|
||||
filename: "./resources/test/fixtures/R0205.py".to_string(),
|
||||
},
|
||||
];
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
|
||||
11
src/main.rs
11
src/main.rs
@@ -51,19 +51,16 @@ struct Cli {
|
||||
fn run_once(files: &[PathBuf], settings: &Settings, cache: bool) -> Result<Vec<Message>> {
|
||||
// Collect all the files to check.
|
||||
let start = Instant::now();
|
||||
let files: Vec<DirEntry> = files.iter().flat_map(iter_python_files).collect();
|
||||
let files: Vec<DirEntry> = files
|
||||
.iter()
|
||||
.flat_map(|path| iter_python_files(path, &settings.exclude))
|
||||
.collect();
|
||||
let duration = start.elapsed();
|
||||
debug!("Identified files to lint in: {:?}", duration);
|
||||
|
||||
let start = Instant::now();
|
||||
let mut messages: Vec<Message> = files
|
||||
.par_iter()
|
||||
.filter(|entry| {
|
||||
!settings
|
||||
.exclude
|
||||
.iter()
|
||||
.any(|exclusion| entry.path().starts_with(exclusion))
|
||||
})
|
||||
.map(|entry| {
|
||||
check_path(entry.path(), settings, &cache.into()).unwrap_or_else(|e| {
|
||||
error!("Failed to check {}: {e:?}", entry.path().to_string_lossy());
|
||||
|
||||
@@ -14,12 +14,20 @@ pub fn load_config<'a>(paths: impl IntoIterator<Item = &'a Path>) -> Result<(Pat
|
||||
Some(project_root) => match find_pyproject_toml(&project_root) {
|
||||
Some(path) => {
|
||||
debug!("Found pyproject.toml at: {}", path.to_string_lossy());
|
||||
let pyproject = parse_pyproject_toml(&path)?;
|
||||
let config = pyproject
|
||||
.tool
|
||||
.and_then(|tool| tool.ruff)
|
||||
.unwrap_or_default();
|
||||
Ok((project_root, config))
|
||||
match parse_pyproject_toml(&path) {
|
||||
Ok(pyproject) => {
|
||||
let config = pyproject
|
||||
.tool
|
||||
.and_then(|tool| tool.ruff)
|
||||
.unwrap_or_default();
|
||||
Ok((project_root, config))
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to load pyproject.toml: {:?}", e);
|
||||
println!("Falling back to default configuration...");
|
||||
Ok(Default::default())
|
||||
}
|
||||
}
|
||||
}
|
||||
None => Ok(Default::default()),
|
||||
},
|
||||
@@ -208,35 +216,41 @@ other-attribute = 1
|
||||
|
||||
#[test]
|
||||
fn find_and_parse_pyproject_toml() -> Result<()> {
|
||||
let project_root = find_project_root([Path::new("resources/test/src/__init__.py")])
|
||||
let project_root = find_project_root([Path::new("resources/test/fixtures/__init__.py")])
|
||||
.expect("Unable to find project root.");
|
||||
assert_eq!(project_root, Path::new("resources/test/src"));
|
||||
assert_eq!(project_root, Path::new("resources/test/fixtures"));
|
||||
|
||||
let path = find_pyproject_toml(&project_root).expect("Unable to find pyproject.toml.");
|
||||
assert_eq!(path, Path::new("resources/test/src/pyproject.toml"));
|
||||
assert_eq!(path, Path::new("resources/test/fixtures/pyproject.toml"));
|
||||
|
||||
let pyproject = parse_pyproject_toml(&path)?;
|
||||
let config = pyproject
|
||||
.tool
|
||||
.map(|tool| tool.ruff)
|
||||
.flatten()
|
||||
.and_then(|tool| tool.ruff)
|
||||
.expect("Unable to find tool.ruff.");
|
||||
assert_eq!(
|
||||
config,
|
||||
Config {
|
||||
line_length: Some(88),
|
||||
exclude: Some(vec![Path::new("excluded.py").to_path_buf()]),
|
||||
exclude: Some(vec![
|
||||
Path::new("excluded.py").to_path_buf(),
|
||||
Path::new("**/migrations").to_path_buf()
|
||||
]),
|
||||
select: Some(BTreeSet::from([
|
||||
CheckCode::E501,
|
||||
CheckCode::F401,
|
||||
CheckCode::F403,
|
||||
CheckCode::F541,
|
||||
CheckCode::F634,
|
||||
CheckCode::F704,
|
||||
CheckCode::F706,
|
||||
CheckCode::F821,
|
||||
CheckCode::F822,
|
||||
CheckCode::F823,
|
||||
CheckCode::F831,
|
||||
CheckCode::F832,
|
||||
CheckCode::F841,
|
||||
CheckCode::F901,
|
||||
CheckCode::R0205,
|
||||
])),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use glob::Pattern;
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::pyproject::load_config;
|
||||
@@ -10,7 +11,7 @@ use crate::pyproject::load_config;
|
||||
#[derive(Debug)]
|
||||
pub struct Settings {
|
||||
pub line_length: usize,
|
||||
pub exclude: Vec<PathBuf>,
|
||||
pub exclude: Vec<Pattern>,
|
||||
pub select: BTreeSet<CheckCode>,
|
||||
}
|
||||
|
||||
@@ -39,6 +40,7 @@ impl Settings {
|
||||
path
|
||||
}
|
||||
})
|
||||
.map(|path| Pattern::new(&path.to_string_lossy()).expect("Invalid pattern."))
|
||||
.collect(),
|
||||
select: config.select.unwrap_or_else(|| {
|
||||
BTreeSet::from([
|
||||
@@ -49,7 +51,7 @@ impl Settings {
|
||||
CheckCode::F634,
|
||||
CheckCode::F706,
|
||||
CheckCode::F831,
|
||||
CheckCode::F832,
|
||||
CheckCode::F823,
|
||||
CheckCode::F901,
|
||||
])
|
||||
}),
|
||||
|
||||
255
src/visitor.rs
255
src/visitor.rs
@@ -11,12 +11,9 @@ pub trait Visitor {
|
||||
fn visit_annotation(&mut self, expr: &Expr) {
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
fn visit_expr(&mut self, expr: &Expr) {
|
||||
fn visit_expr(&mut self, expr: &Expr, _parent: Option<&Stmt>) {
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
fn visit_ident(&mut self, ident: &str) {
|
||||
walk_ident(self, ident);
|
||||
}
|
||||
fn visit_constant(&mut self, constant: &Constant) {
|
||||
walk_constant(self, constant);
|
||||
}
|
||||
@@ -66,87 +63,43 @@ pub trait Visitor {
|
||||
|
||||
pub fn walk_stmt<V: Visitor + ?Sized>(visitor: &mut V, stmt: &Stmt) {
|
||||
match &stmt.node {
|
||||
StmtKind::FunctionDef {
|
||||
name,
|
||||
args,
|
||||
body,
|
||||
decorator_list,
|
||||
returns,
|
||||
..
|
||||
} => {
|
||||
visitor.visit_ident(name);
|
||||
StmtKind::FunctionDef { args, body, .. } => {
|
||||
visitor.visit_arguments(args);
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt)
|
||||
}
|
||||
for expr in decorator_list {
|
||||
visitor.visit_expr(expr)
|
||||
}
|
||||
for expr in returns {
|
||||
visitor.visit_annotation(expr);
|
||||
}
|
||||
}
|
||||
StmtKind::AsyncFunctionDef {
|
||||
name,
|
||||
args,
|
||||
body,
|
||||
decorator_list,
|
||||
returns,
|
||||
..
|
||||
} => {
|
||||
visitor.visit_ident(name);
|
||||
StmtKind::AsyncFunctionDef { args, body, .. } => {
|
||||
visitor.visit_arguments(args);
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt)
|
||||
}
|
||||
for expr in decorator_list {
|
||||
visitor.visit_expr(expr)
|
||||
}
|
||||
for expr in returns {
|
||||
visitor.visit_annotation(expr);
|
||||
}
|
||||
}
|
||||
StmtKind::ClassDef {
|
||||
name,
|
||||
bases,
|
||||
keywords,
|
||||
body,
|
||||
decorator_list,
|
||||
} => {
|
||||
visitor.visit_ident(name);
|
||||
for expr in bases {
|
||||
visitor.visit_expr(expr)
|
||||
}
|
||||
for keyword in keywords {
|
||||
visitor.visit_keyword(keyword)
|
||||
}
|
||||
StmtKind::ClassDef { body, .. } => {
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt)
|
||||
}
|
||||
for expr in decorator_list {
|
||||
visitor.visit_expr(expr)
|
||||
}
|
||||
}
|
||||
StmtKind::Return { value } => {
|
||||
if let Some(expr) = value {
|
||||
visitor.visit_expr(expr)
|
||||
visitor.visit_expr(expr, Some(stmt))
|
||||
}
|
||||
}
|
||||
StmtKind::Delete { targets } => {
|
||||
for expr in targets {
|
||||
visitor.visit_expr(expr)
|
||||
visitor.visit_expr(expr, Some(stmt))
|
||||
}
|
||||
}
|
||||
StmtKind::Assign { targets, value, .. } => {
|
||||
for expr in targets {
|
||||
visitor.visit_expr(expr)
|
||||
visitor.visit_expr(expr, Some(stmt))
|
||||
}
|
||||
visitor.visit_expr(value)
|
||||
visitor.visit_expr(value, Some(stmt))
|
||||
}
|
||||
StmtKind::AugAssign { target, op, value } => {
|
||||
visitor.visit_expr(target);
|
||||
visitor.visit_expr(target, Some(stmt));
|
||||
visitor.visit_operator(op);
|
||||
visitor.visit_expr(value);
|
||||
visitor.visit_expr(value, Some(stmt));
|
||||
}
|
||||
StmtKind::AnnAssign {
|
||||
target,
|
||||
@@ -154,10 +107,10 @@ pub fn walk_stmt<V: Visitor + ?Sized>(visitor: &mut V, stmt: &Stmt) {
|
||||
value,
|
||||
..
|
||||
} => {
|
||||
visitor.visit_expr(target);
|
||||
visitor.visit_expr(target, Some(stmt));
|
||||
visitor.visit_annotation(annotation);
|
||||
if let Some(expr) = value {
|
||||
visitor.visit_expr(expr)
|
||||
visitor.visit_expr(expr, Some(stmt))
|
||||
}
|
||||
}
|
||||
StmtKind::For {
|
||||
@@ -167,8 +120,8 @@ pub fn walk_stmt<V: Visitor + ?Sized>(visitor: &mut V, stmt: &Stmt) {
|
||||
orelse,
|
||||
..
|
||||
} => {
|
||||
visitor.visit_expr(target);
|
||||
visitor.visit_expr(iter);
|
||||
visitor.visit_expr(target, Some(stmt));
|
||||
visitor.visit_expr(iter, Some(stmt));
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt)
|
||||
}
|
||||
@@ -183,8 +136,8 @@ pub fn walk_stmt<V: Visitor + ?Sized>(visitor: &mut V, stmt: &Stmt) {
|
||||
orelse,
|
||||
..
|
||||
} => {
|
||||
visitor.visit_expr(target);
|
||||
visitor.visit_expr(iter);
|
||||
visitor.visit_expr(target, Some(stmt));
|
||||
visitor.visit_expr(iter, Some(stmt));
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt)
|
||||
}
|
||||
@@ -193,7 +146,7 @@ pub fn walk_stmt<V: Visitor + ?Sized>(visitor: &mut V, stmt: &Stmt) {
|
||||
}
|
||||
}
|
||||
StmtKind::While { test, body, orelse } => {
|
||||
visitor.visit_expr(test);
|
||||
visitor.visit_expr(test, Some(stmt));
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt)
|
||||
}
|
||||
@@ -202,7 +155,7 @@ pub fn walk_stmt<V: Visitor + ?Sized>(visitor: &mut V, stmt: &Stmt) {
|
||||
}
|
||||
}
|
||||
StmtKind::If { test, body, orelse } => {
|
||||
visitor.visit_expr(test);
|
||||
visitor.visit_expr(test, Some(stmt));
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt)
|
||||
}
|
||||
@@ -228,17 +181,17 @@ pub fn walk_stmt<V: Visitor + ?Sized>(visitor: &mut V, stmt: &Stmt) {
|
||||
}
|
||||
StmtKind::Match { subject, cases } => {
|
||||
// TODO(charlie): Handle `cases`.
|
||||
visitor.visit_expr(subject);
|
||||
visitor.visit_expr(subject, Some(stmt));
|
||||
for match_case in cases {
|
||||
visitor.visit_match_case(match_case);
|
||||
}
|
||||
}
|
||||
StmtKind::Raise { exc, cause } => {
|
||||
if let Some(expr) = exc {
|
||||
visitor.visit_expr(expr)
|
||||
visitor.visit_expr(expr, Some(stmt))
|
||||
};
|
||||
if let Some(expr) = cause {
|
||||
visitor.visit_expr(expr)
|
||||
visitor.visit_expr(expr, Some(stmt))
|
||||
};
|
||||
}
|
||||
StmtKind::Try {
|
||||
@@ -261,9 +214,9 @@ pub fn walk_stmt<V: Visitor + ?Sized>(visitor: &mut V, stmt: &Stmt) {
|
||||
}
|
||||
}
|
||||
StmtKind::Assert { test, msg } => {
|
||||
visitor.visit_expr(test);
|
||||
visitor.visit_expr(test, None);
|
||||
if let Some(expr) = msg {
|
||||
visitor.visit_expr(expr)
|
||||
visitor.visit_expr(expr, Some(stmt))
|
||||
}
|
||||
}
|
||||
StmtKind::Import { names } => {
|
||||
@@ -271,25 +224,14 @@ pub fn walk_stmt<V: Visitor + ?Sized>(visitor: &mut V, stmt: &Stmt) {
|
||||
visitor.visit_alias(alias);
|
||||
}
|
||||
}
|
||||
StmtKind::ImportFrom { module, names, .. } => {
|
||||
if let Some(ident) = module {
|
||||
visitor.visit_ident(ident);
|
||||
}
|
||||
StmtKind::ImportFrom { names, .. } => {
|
||||
for alias in names {
|
||||
visitor.visit_alias(alias);
|
||||
}
|
||||
}
|
||||
StmtKind::Global { names } => {
|
||||
for ident in names {
|
||||
visitor.visit_ident(ident)
|
||||
}
|
||||
}
|
||||
StmtKind::Nonlocal { names } => {
|
||||
for ident in names {
|
||||
visitor.visit_ident(ident)
|
||||
}
|
||||
}
|
||||
StmtKind::Expr { value } => visitor.visit_expr(value),
|
||||
StmtKind::Global { .. } => {}
|
||||
StmtKind::Nonlocal { .. } => {}
|
||||
StmtKind::Expr { value } => visitor.visit_expr(value, Some(stmt)),
|
||||
StmtKind::Pass => {}
|
||||
StmtKind::Break => {}
|
||||
StmtKind::Continue => {}
|
||||
@@ -301,55 +243,55 @@ pub fn walk_expr<V: Visitor + ?Sized>(visitor: &mut V, expr: &Expr) {
|
||||
ExprKind::BoolOp { op, values } => {
|
||||
visitor.visit_boolop(op);
|
||||
for expr in values {
|
||||
visitor.visit_expr(expr)
|
||||
visitor.visit_expr(expr, None)
|
||||
}
|
||||
}
|
||||
ExprKind::NamedExpr { target, value } => {
|
||||
visitor.visit_expr(target);
|
||||
visitor.visit_expr(value);
|
||||
visitor.visit_expr(target, None);
|
||||
visitor.visit_expr(value, None);
|
||||
}
|
||||
ExprKind::BinOp { left, op, right } => {
|
||||
visitor.visit_expr(left);
|
||||
visitor.visit_expr(left, None);
|
||||
visitor.visit_operator(op);
|
||||
visitor.visit_expr(right);
|
||||
visitor.visit_expr(right, None);
|
||||
}
|
||||
ExprKind::UnaryOp { op, operand } => {
|
||||
visitor.visit_unaryop(op);
|
||||
visitor.visit_expr(operand);
|
||||
visitor.visit_expr(operand, None);
|
||||
}
|
||||
ExprKind::Lambda { args, body } => {
|
||||
visitor.visit_arguments(args);
|
||||
visitor.visit_expr(body);
|
||||
visitor.visit_expr(body, None);
|
||||
}
|
||||
ExprKind::IfExp { test, body, orelse } => {
|
||||
visitor.visit_expr(test);
|
||||
visitor.visit_expr(body);
|
||||
visitor.visit_expr(orelse);
|
||||
visitor.visit_expr(test, None);
|
||||
visitor.visit_expr(body, None);
|
||||
visitor.visit_expr(orelse, None);
|
||||
}
|
||||
ExprKind::Dict { keys, values } => {
|
||||
for expr in keys {
|
||||
visitor.visit_expr(expr)
|
||||
visitor.visit_expr(expr, None)
|
||||
}
|
||||
for expr in values {
|
||||
visitor.visit_expr(expr)
|
||||
visitor.visit_expr(expr, None)
|
||||
}
|
||||
}
|
||||
ExprKind::Set { elts } => {
|
||||
for expr in elts {
|
||||
visitor.visit_expr(expr)
|
||||
visitor.visit_expr(expr, None)
|
||||
}
|
||||
}
|
||||
ExprKind::ListComp { elt, generators } => {
|
||||
for comprehension in generators {
|
||||
visitor.visit_comprehension(comprehension)
|
||||
}
|
||||
visitor.visit_expr(elt);
|
||||
visitor.visit_expr(elt, None);
|
||||
}
|
||||
ExprKind::SetComp { elt, generators } => {
|
||||
for comprehension in generators {
|
||||
visitor.visit_comprehension(comprehension)
|
||||
}
|
||||
visitor.visit_expr(elt);
|
||||
visitor.visit_expr(elt, None);
|
||||
}
|
||||
ExprKind::DictComp {
|
||||
key,
|
||||
@@ -359,33 +301,33 @@ pub fn walk_expr<V: Visitor + ?Sized>(visitor: &mut V, expr: &Expr) {
|
||||
for comprehension in generators {
|
||||
visitor.visit_comprehension(comprehension)
|
||||
}
|
||||
visitor.visit_expr(key);
|
||||
visitor.visit_expr(value);
|
||||
visitor.visit_expr(key, None);
|
||||
visitor.visit_expr(value, None);
|
||||
}
|
||||
ExprKind::GeneratorExp { elt, generators } => {
|
||||
for comprehension in generators {
|
||||
visitor.visit_comprehension(comprehension)
|
||||
}
|
||||
visitor.visit_expr(elt);
|
||||
visitor.visit_expr(elt, None);
|
||||
}
|
||||
ExprKind::Await { value } => visitor.visit_expr(value),
|
||||
ExprKind::Await { value } => visitor.visit_expr(value, None),
|
||||
ExprKind::Yield { value } => {
|
||||
if let Some(expr) = value {
|
||||
visitor.visit_expr(expr)
|
||||
visitor.visit_expr(expr, None)
|
||||
}
|
||||
}
|
||||
ExprKind::YieldFrom { value } => visitor.visit_expr(value),
|
||||
ExprKind::YieldFrom { value } => visitor.visit_expr(value, None),
|
||||
ExprKind::Compare {
|
||||
left,
|
||||
ops,
|
||||
comparators,
|
||||
} => {
|
||||
visitor.visit_expr(left);
|
||||
visitor.visit_expr(left, None);
|
||||
for cmpop in ops {
|
||||
visitor.visit_cmpop(cmpop);
|
||||
}
|
||||
for expr in comparators {
|
||||
visitor.visit_expr(expr)
|
||||
visitor.visit_expr(expr, None)
|
||||
}
|
||||
}
|
||||
ExprKind::Call {
|
||||
@@ -393,9 +335,9 @@ pub fn walk_expr<V: Visitor + ?Sized>(visitor: &mut V, expr: &Expr) {
|
||||
args,
|
||||
keywords,
|
||||
} => {
|
||||
visitor.visit_expr(func);
|
||||
visitor.visit_expr(func, None);
|
||||
for expr in args {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None);
|
||||
}
|
||||
for keyword in keywords {
|
||||
visitor.visit_keyword(keyword);
|
||||
@@ -404,55 +346,54 @@ pub fn walk_expr<V: Visitor + ?Sized>(visitor: &mut V, expr: &Expr) {
|
||||
ExprKind::FormattedValue {
|
||||
value, format_spec, ..
|
||||
} => {
|
||||
visitor.visit_expr(value);
|
||||
visitor.visit_expr(value, None);
|
||||
if let Some(expr) = format_spec {
|
||||
visitor.visit_expr(expr)
|
||||
visitor.visit_expr(expr, None)
|
||||
}
|
||||
}
|
||||
ExprKind::JoinedStr { values } => {
|
||||
for expr in values {
|
||||
visitor.visit_expr(expr)
|
||||
visitor.visit_expr(expr, None)
|
||||
}
|
||||
}
|
||||
ExprKind::Constant { value, .. } => visitor.visit_constant(value),
|
||||
ExprKind::Attribute { value, ctx, .. } => {
|
||||
visitor.visit_expr(value);
|
||||
visitor.visit_expr(value, None);
|
||||
visitor.visit_expr_context(ctx);
|
||||
}
|
||||
ExprKind::Subscript { value, slice, ctx } => {
|
||||
visitor.visit_expr(value);
|
||||
visitor.visit_expr(slice);
|
||||
visitor.visit_expr(value, None);
|
||||
visitor.visit_expr(slice, None);
|
||||
visitor.visit_expr_context(ctx);
|
||||
}
|
||||
ExprKind::Starred { value, ctx } => {
|
||||
visitor.visit_expr(value);
|
||||
visitor.visit_expr(value, None);
|
||||
visitor.visit_expr_context(ctx);
|
||||
}
|
||||
ExprKind::Name { id, ctx } => {
|
||||
visitor.visit_ident(id);
|
||||
ExprKind::Name { ctx, .. } => {
|
||||
visitor.visit_expr_context(ctx);
|
||||
}
|
||||
ExprKind::List { elts, ctx } => {
|
||||
for expr in elts {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None);
|
||||
}
|
||||
visitor.visit_expr_context(ctx);
|
||||
}
|
||||
ExprKind::Tuple { elts, ctx } => {
|
||||
for expr in elts {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None);
|
||||
}
|
||||
visitor.visit_expr_context(ctx);
|
||||
}
|
||||
ExprKind::Slice { lower, upper, step } => {
|
||||
if let Some(expr) = lower {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None);
|
||||
}
|
||||
if let Some(expr) = upper {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None);
|
||||
}
|
||||
if let Some(expr) = step {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -467,21 +408,18 @@ pub fn walk_constant<V: Visitor + ?Sized>(visitor: &mut V, constant: &Constant)
|
||||
}
|
||||
|
||||
pub fn walk_comprehension<V: Visitor + ?Sized>(visitor: &mut V, comprehension: &Comprehension) {
|
||||
visitor.visit_expr(&comprehension.target);
|
||||
visitor.visit_expr(&comprehension.iter);
|
||||
visitor.visit_expr(&comprehension.target, None);
|
||||
visitor.visit_expr(&comprehension.iter, None);
|
||||
for expr in &comprehension.ifs {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_excepthandler<V: Visitor + ?Sized>(visitor: &mut V, excepthandler: &Excepthandler) {
|
||||
match &excepthandler.node {
|
||||
ExcepthandlerKind::ExceptHandler { type_, name, body } => {
|
||||
ExcepthandlerKind::ExceptHandler { type_, body, .. } => {
|
||||
if let Some(expr) = type_ {
|
||||
visitor.visit_expr(expr);
|
||||
}
|
||||
if let Some(ident) = name {
|
||||
visitor.visit_ident(ident);
|
||||
visitor.visit_expr(expr, None);
|
||||
}
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt);
|
||||
@@ -504,13 +442,13 @@ pub fn walk_arguments<V: Visitor + ?Sized>(visitor: &mut V, arguments: &Argument
|
||||
visitor.visit_arg(arg);
|
||||
}
|
||||
for expr in &arguments.kw_defaults {
|
||||
visitor.visit_expr(expr)
|
||||
visitor.visit_expr(expr, None)
|
||||
}
|
||||
if let Some(arg) = &arguments.kwarg {
|
||||
visitor.visit_arg(arg)
|
||||
}
|
||||
for expr in &arguments.defaults {
|
||||
visitor.visit_expr(expr)
|
||||
visitor.visit_expr(expr, None)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,20 +459,20 @@ pub fn walk_arg<V: Visitor + ?Sized>(visitor: &mut V, arg: &Arg) {
|
||||
}
|
||||
|
||||
pub fn walk_keyword<V: Visitor + ?Sized>(visitor: &mut V, keyword: &Keyword) {
|
||||
visitor.visit_expr(&keyword.node.value);
|
||||
visitor.visit_expr(&keyword.node.value, None);
|
||||
}
|
||||
|
||||
pub fn walk_withitem<V: Visitor + ?Sized>(visitor: &mut V, withitem: &Withitem) {
|
||||
visitor.visit_expr(&withitem.context_expr);
|
||||
visitor.visit_expr(&withitem.context_expr, None);
|
||||
if let Some(expr) = &withitem.optional_vars {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_match_case<V: Visitor + ?Sized>(visitor: &mut V, match_case: &MatchCase) {
|
||||
visitor.visit_pattern(&match_case.pattern);
|
||||
if let Some(expr) = &match_case.guard {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None);
|
||||
}
|
||||
for stmt in &match_case.body {
|
||||
visitor.visit_stmt(stmt);
|
||||
@@ -543,57 +481,41 @@ pub fn walk_match_case<V: Visitor + ?Sized>(visitor: &mut V, match_case: &MatchC
|
||||
|
||||
pub fn walk_pattern<V: Visitor + ?Sized>(visitor: &mut V, pattern: &Pattern) {
|
||||
match &pattern.node {
|
||||
PatternKind::MatchValue { value } => visitor.visit_expr(value),
|
||||
PatternKind::MatchValue { value } => visitor.visit_expr(value, None),
|
||||
PatternKind::MatchSingleton { value } => visitor.visit_constant(value),
|
||||
PatternKind::MatchSequence { patterns } => {
|
||||
for pattern in patterns {
|
||||
visitor.visit_pattern(pattern)
|
||||
}
|
||||
}
|
||||
PatternKind::MatchMapping {
|
||||
keys,
|
||||
patterns,
|
||||
rest,
|
||||
} => {
|
||||
PatternKind::MatchMapping { keys, patterns, .. } => {
|
||||
for expr in keys {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_expr(expr, None);
|
||||
}
|
||||
for pattern in patterns {
|
||||
visitor.visit_pattern(pattern);
|
||||
}
|
||||
if let Some(ident) = rest {
|
||||
visitor.visit_ident(ident);
|
||||
}
|
||||
}
|
||||
PatternKind::MatchClass {
|
||||
cls,
|
||||
patterns,
|
||||
kwd_attrs,
|
||||
kwd_patterns,
|
||||
..
|
||||
} => {
|
||||
visitor.visit_expr(cls);
|
||||
visitor.visit_expr(cls, None);
|
||||
for pattern in patterns {
|
||||
visitor.visit_pattern(pattern);
|
||||
}
|
||||
for ident in kwd_attrs {
|
||||
visitor.visit_ident(ident);
|
||||
}
|
||||
|
||||
for pattern in kwd_patterns {
|
||||
visitor.visit_pattern(pattern);
|
||||
}
|
||||
}
|
||||
PatternKind::MatchStar { name } => {
|
||||
if let Some(ident) = name {
|
||||
visitor.visit_ident(ident)
|
||||
}
|
||||
}
|
||||
PatternKind::MatchAs { pattern, name } => {
|
||||
PatternKind::MatchStar { .. } => {}
|
||||
PatternKind::MatchAs { pattern, .. } => {
|
||||
if let Some(pattern) = pattern {
|
||||
visitor.visit_pattern(pattern)
|
||||
}
|
||||
if let Some(ident) = name {
|
||||
visitor.visit_ident(ident)
|
||||
}
|
||||
}
|
||||
PatternKind::MatchOr { patterns } => {
|
||||
for pattern in patterns {
|
||||
@@ -604,22 +526,25 @@ pub fn walk_pattern<V: Visitor + ?Sized>(visitor: &mut V, pattern: &Pattern) {
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
pub fn walk_ident<V: Visitor + ?Sized>(visitor: &mut V, ident: &str) {}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline(always)]
|
||||
pub fn walk_expr_context<V: Visitor + ?Sized>(visitor: &mut V, expr_context: &ExprContext) {}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline(always)]
|
||||
pub fn walk_boolop<V: Visitor + ?Sized>(visitor: &mut V, boolop: &Boolop) {}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline(always)]
|
||||
pub fn walk_operator<V: Visitor + ?Sized>(visitor: &mut V, operator: &Operator) {}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline(always)]
|
||||
pub fn walk_unaryop<V: Visitor + ?Sized>(visitor: &mut V, unaryop: &Unaryop) {}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline(always)]
|
||||
pub fn walk_cmpop<V: Visitor + ?Sized>(visitor: &mut V, cmpop: &Cmpop) {}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline(always)]
|
||||
pub fn walk_alias<V: Visitor + ?Sized>(visitor: &mut V, alias: &Alias) {}
|
||||
|
||||
Reference in New Issue
Block a user