Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64df4eb311 | ||
|
|
2bac3027a5 | ||
|
|
c28ac75591 | ||
|
|
59f009b52d | ||
|
|
b5edcee9f2 | ||
|
|
3f739214b4 | ||
|
|
0b9e3f8b47 | ||
|
|
556ae00078 | ||
|
|
adad214619 | ||
|
|
0ebed13e67 | ||
|
|
3afedcd48b | ||
|
|
875e812188 | ||
|
|
80f3cd0ef7 | ||
|
|
9e3c35e6dc |
1
.github/workflows/release.yaml
vendored
1
.github/workflows/release.yaml
vendored
@@ -1,7 +1,6 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
create:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
15
Cargo.lock
generated
15
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"
|
||||
@@ -1635,7 +1641,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.20"
|
||||
version = "0.0.22"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@@ -1648,6 +1654,7 @@ dependencies = [
|
||||
"dirs 4.0.0",
|
||||
"fern",
|
||||
"filetime",
|
||||
"glob",
|
||||
"log",
|
||||
"notify",
|
||||
"rayon",
|
||||
@@ -1662,7 +1669,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=97a9d9de687227595932d6c8a04014dcfc892ff4#97a9d9de687227595932d6c8a04014dcfc892ff4"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=1613f6c6990011a4bc559e79aaf28d715f9f729b#1613f6c6990011a4bc559e79aaf28d715f9f729b"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"rustpython-compiler-core",
|
||||
@@ -1671,7 +1678,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-compiler-core"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=97a9d9de687227595932d6c8a04014dcfc892ff4#97a9d9de687227595932d6c8a04014dcfc892ff4"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=1613f6c6990011a4bc559e79aaf28d715f9f729b#1613f6c6990011a4bc559e79aaf28d715f9f729b"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bitflags",
|
||||
@@ -1688,7 +1695,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=97a9d9de687227595932d6c8a04014dcfc892ff4#97a9d9de687227595932d6c8a04014dcfc892ff4"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=1613f6c6990011a4bc559e79aaf28d715f9f729b#1613f6c6990011a4bc559e79aaf28d715f9f729b"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.20"
|
||||
version = "0.0.22"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
@@ -18,11 +18,12 @@ 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" }
|
||||
rayon = { version = "1.5.3" }
|
||||
regex = { version = "1.6.0" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "97a9d9de687227595932d6c8a04014dcfc892ff4" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "1613f6c6990011a4bc559e79aaf28d715f9f729b" }
|
||||
serde = { version = "1.0.143", features = ["derive"] }
|
||||
serde_json = { version = "1.0.83" }
|
||||
toml = { version = "0.5.9" }
|
||||
|
||||
@@ -5,10 +5,14 @@ from collections import (
|
||||
OrderedDict,
|
||||
namedtuple,
|
||||
)
|
||||
import multiprocessing.pool
|
||||
import multiprocessing.process
|
||||
import logging.config
|
||||
import logging.handlers
|
||||
|
||||
|
||||
class X:
|
||||
def a(self) -> "namedtuple":
|
||||
x = os.environ["1"]
|
||||
y = Counter()
|
||||
return X
|
||||
z = multiprocessing.pool.ThreadPool()
|
||||
|
||||
10
resources/test/src/F704.py
Normal file
10
resources/test/src/F704.py
Normal file
@@ -0,0 +1,10 @@
|
||||
def f() -> int:
|
||||
yield 1
|
||||
|
||||
|
||||
class Foo:
|
||||
yield 2
|
||||
|
||||
|
||||
yield 3
|
||||
yield from 3
|
||||
@@ -13,4 +13,34 @@ def get_name():
|
||||
def get_name(self):
|
||||
return self.name
|
||||
|
||||
|
||||
x = list()
|
||||
|
||||
|
||||
def randdec(maxprec, maxexp):
|
||||
return numeric_string(maxprec, maxexp)
|
||||
|
||||
|
||||
def ternary_optarg(prec, exp_range, itr):
|
||||
for _ in range(100):
|
||||
a = randdec(prec, 2 * exp_range)
|
||||
b = randdec(prec, 2 * exp_range)
|
||||
c = randdec(prec, 2 * exp_range)
|
||||
yield a, b, c, None
|
||||
yield a, b, c, None, None
|
||||
|
||||
|
||||
class Foo:
|
||||
CLASS_VAR = 1
|
||||
REFERENCES_CLASS_VAR = {"CLASS_VAR": CLASS_VAR}
|
||||
|
||||
|
||||
class Class:
|
||||
def __init__(self):
|
||||
# TODO(charlie): This should be recognized as a defined variable.
|
||||
Class # noqa: F821
|
||||
|
||||
try:
|
||||
x = 1 / 0
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
16
resources/test/src/F841.py
Normal file
16
resources/test/src/F841.py
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
|
||||
0
resources/test/src/bar/__init__.py
Normal file
0
resources/test/src/bar/__init__.py
Normal file
0
resources/test/src/bar/migrations/__init__.py
Normal file
0
resources/test/src/bar/migrations/__init__.py
Normal file
9
resources/test/src/bar/migrations/migration.py
Normal file
9
resources/test/src/bar/migrations/migration.py
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/src/foo/__init__.py
Normal file
0
resources/test/src/foo/__init__.py
Normal file
0
resources/test/src/foo/migrations/__init__.py
Normal file
0
resources/test/src/foo/migrations/__init__.py
Normal file
9
resources/test/src/foo/migrations/migration.py
Normal file
9
resources/test/src/foo/migrations/migration.py
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,17 @@
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
exclude = ["excluded.py"]
|
||||
exclude = ["excluded.py", "**/migrations"]
|
||||
select = [
|
||||
"E501",
|
||||
"F401",
|
||||
"F403",
|
||||
"F541",
|
||||
"F634",
|
||||
"F704",
|
||||
"F706",
|
||||
"F821",
|
||||
"F831",
|
||||
"F832",
|
||||
"F841",
|
||||
"F901",
|
||||
]
|
||||
|
||||
337
src/check_ast.rs
337
src/check_ast.rs
@@ -1,17 +1,18 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
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,
|
||||
Location, Stmt, StmtKind, Suite,
|
||||
};
|
||||
use rustpython_parser::parser;
|
||||
|
||||
use crate::builtins::{BUILTINS, MAGIC_GLOBALS};
|
||||
use crate::check_ast::ScopeKind::{Class, Function, Generator, Module};
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
use crate::settings::Settings;
|
||||
use crate::visitor;
|
||||
use crate::visitor::Visitor;
|
||||
use crate::visitor::{walk_excepthandler, Visitor};
|
||||
|
||||
fn id() -> usize {
|
||||
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
||||
@@ -41,21 +42,22 @@ impl Scope {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum BindingKind {
|
||||
Argument,
|
||||
Assignment,
|
||||
ClassDefinition,
|
||||
Definition,
|
||||
ClassDefinition,
|
||||
Builtin,
|
||||
FutureImportation,
|
||||
Importation(String),
|
||||
StarImportation,
|
||||
SubmoduleImportation,
|
||||
SubmoduleImportation(String),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Binding {
|
||||
kind: BindingKind,
|
||||
name: String,
|
||||
location: Location,
|
||||
used: Option<usize>,
|
||||
}
|
||||
@@ -99,7 +101,6 @@ impl Visitor for Checker<'_> {
|
||||
name.to_string(),
|
||||
Binding {
|
||||
kind: BindingKind::Assignment,
|
||||
name: name.clone(),
|
||||
used: Some(global_scope_id),
|
||||
location: stmt.location,
|
||||
},
|
||||
@@ -109,22 +110,26 @@ 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));
|
||||
self.add_binding(Binding {
|
||||
kind: BindingKind::ClassDefinition,
|
||||
name: name.clone(),
|
||||
used: None,
|
||||
location: stmt.location,
|
||||
})
|
||||
}
|
||||
StmtKind::AsyncFunctionDef { name, .. } => {
|
||||
self.add_binding(
|
||||
name.to_string(),
|
||||
Binding {
|
||||
kind: BindingKind::Definition,
|
||||
used: None,
|
||||
location: stmt.location,
|
||||
},
|
||||
);
|
||||
self.push_scope(Scope::new(Function));
|
||||
self.add_binding(Binding {
|
||||
kind: BindingKind::ClassDefinition,
|
||||
name: name.clone(),
|
||||
used: None,
|
||||
location: stmt.location,
|
||||
})
|
||||
}
|
||||
StmtKind::Return { .. } => {
|
||||
if self
|
||||
@@ -149,29 +154,37 @@ impl Visitor for Checker<'_> {
|
||||
StmtKind::Import { names } => {
|
||||
for alias in names {
|
||||
if alias.node.name.contains('.') && alias.node.asname.is_none() {
|
||||
self.add_binding(Binding {
|
||||
kind: BindingKind::SubmoduleImportation,
|
||||
name: alias.node.name.clone(),
|
||||
used: None,
|
||||
location: stmt.location,
|
||||
})
|
||||
// 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 {
|
||||
kind: BindingKind::SubmoduleImportation(
|
||||
alias.node.name.to_string(),
|
||||
),
|
||||
used: None,
|
||||
location: stmt.location,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
self.add_binding(Binding {
|
||||
kind: BindingKind::Importation(
|
||||
alias
|
||||
.node
|
||||
.asname
|
||||
.clone()
|
||||
.unwrap_or_else(|| alias.node.name.clone()),
|
||||
),
|
||||
name: alias
|
||||
self.add_binding(
|
||||
alias
|
||||
.node
|
||||
.asname
|
||||
.clone()
|
||||
.unwrap_or_else(|| alias.node.name.clone()),
|
||||
used: None,
|
||||
location: stmt.location,
|
||||
})
|
||||
Binding {
|
||||
kind: BindingKind::Importation(
|
||||
alias
|
||||
.node
|
||||
.asname
|
||||
.clone()
|
||||
.unwrap_or_else(|| alias.node.name.clone()),
|
||||
),
|
||||
used: None,
|
||||
location: stmt.location,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,19 +196,25 @@ impl Visitor for Checker<'_> {
|
||||
.clone()
|
||||
.unwrap_or_else(|| alias.node.name.clone());
|
||||
if let Some("future") = module.as_deref() {
|
||||
self.add_binding(Binding {
|
||||
kind: BindingKind::FutureImportation,
|
||||
self.add_binding(
|
||||
name,
|
||||
used: Some(self.scopes.last().expect("No current scope found.").id),
|
||||
location: stmt.location,
|
||||
});
|
||||
Binding {
|
||||
kind: BindingKind::FutureImportation,
|
||||
|
||||
used: Some(self.scopes.last().expect("No current scope found.").id),
|
||||
location: stmt.location,
|
||||
},
|
||||
);
|
||||
} else if alias.node.name == "*" {
|
||||
self.add_binding(Binding {
|
||||
kind: BindingKind::StarImportation,
|
||||
self.add_binding(
|
||||
name,
|
||||
used: None,
|
||||
location: stmt.location,
|
||||
});
|
||||
Binding {
|
||||
kind: BindingKind::StarImportation,
|
||||
|
||||
used: None,
|
||||
location: stmt.location,
|
||||
},
|
||||
);
|
||||
|
||||
if self
|
||||
.settings
|
||||
@@ -208,15 +227,15 @@ impl Visitor for Checker<'_> {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
self.add_binding(Binding {
|
||||
let binding = Binding {
|
||||
kind: BindingKind::Importation(match module {
|
||||
None => name.clone(),
|
||||
Some(parent) => format!("{}.{}", parent, name),
|
||||
Some(parent) => format!("{}.{}", parent, name.clone()),
|
||||
}),
|
||||
name,
|
||||
used: None,
|
||||
location: stmt.location,
|
||||
})
|
||||
};
|
||||
self.add_binding(name, binding)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -261,33 +280,55 @@ 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);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
if let StmtKind::ClassDef { name, .. } = &stmt.node {
|
||||
self.add_binding(Binding {
|
||||
kind: BindingKind::Definition,
|
||||
name: name.clone(),
|
||||
used: None,
|
||||
location: stmt.location,
|
||||
});
|
||||
self.add_binding(
|
||||
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;
|
||||
@@ -308,6 +349,21 @@ impl Visitor for Checker<'_> {
|
||||
| ExprKind::DictComp { .. }
|
||||
| ExprKind::SetComp { .. } => self.push_scope(Scope::new(Generator)),
|
||||
ExprKind::Lambda { .. } => self.push_scope(Scope::new(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
|
||||
@@ -351,6 +407,53 @@ 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,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
));
|
||||
|
||||
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
|
||||
@@ -372,17 +475,17 @@ impl Visitor for Checker<'_> {
|
||||
}
|
||||
|
||||
// Search for duplicates.
|
||||
let mut idents: BTreeSet<String> = BTreeSet::new();
|
||||
let mut idents: BTreeSet<&str> = BTreeSet::new();
|
||||
for arg in all_arguments {
|
||||
let ident = &arg.node.arg;
|
||||
if idents.contains(ident) {
|
||||
if idents.contains(ident.as_str()) {
|
||||
self.checks.push(Check {
|
||||
kind: CheckKind::DuplicateArgumentName,
|
||||
location: arg.location,
|
||||
});
|
||||
break;
|
||||
}
|
||||
idents.insert(ident.clone());
|
||||
idents.insert(ident);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,12 +493,14 @@ impl Visitor for Checker<'_> {
|
||||
}
|
||||
|
||||
fn visit_arg(&mut self, arg: &Arg) {
|
||||
self.add_binding(Binding {
|
||||
kind: BindingKind::Argument,
|
||||
name: arg.node.arg.clone(),
|
||||
used: None,
|
||||
location: arg.location,
|
||||
});
|
||||
self.add_binding(
|
||||
arg.node.arg.to_string(),
|
||||
Binding {
|
||||
kind: BindingKind::Argument,
|
||||
used: None,
|
||||
location: arg.location,
|
||||
},
|
||||
);
|
||||
visitor::walk_arg(self, arg);
|
||||
}
|
||||
}
|
||||
@@ -412,49 +517,52 @@ impl Checker<'_> {
|
||||
|
||||
fn bind_builtins(&mut self) {
|
||||
for builtin in BUILTINS {
|
||||
self.add_binding(Binding {
|
||||
kind: BindingKind::Builtin,
|
||||
name: builtin.to_string(),
|
||||
location: Default::default(),
|
||||
used: None,
|
||||
})
|
||||
self.add_binding(
|
||||
builtin.to_string(),
|
||||
Binding {
|
||||
kind: BindingKind::Builtin,
|
||||
location: Default::default(),
|
||||
used: None,
|
||||
},
|
||||
)
|
||||
}
|
||||
for builtin in MAGIC_GLOBALS {
|
||||
self.add_binding(Binding {
|
||||
kind: BindingKind::Builtin,
|
||||
name: builtin.to_string(),
|
||||
location: Default::default(),
|
||||
used: None,
|
||||
})
|
||||
self.add_binding(
|
||||
builtin.to_string(),
|
||||
Binding {
|
||||
kind: BindingKind::Builtin,
|
||||
location: Default::default(),
|
||||
used: None,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn add_binding(&mut self, binding: Binding) {
|
||||
fn add_binding(&mut self, name: String, binding: Binding) {
|
||||
let scope = self.scopes.last_mut().expect("No current scope found.");
|
||||
|
||||
// TODO(charlie): Don't treat annotations as assignments if there is an existing value.
|
||||
scope.values.insert(
|
||||
binding.name.clone(),
|
||||
match scope.values.get(&binding.name) {
|
||||
None => binding,
|
||||
Some(existing) => Binding {
|
||||
kind: binding.kind,
|
||||
name: binding.name,
|
||||
location: binding.location,
|
||||
used: existing.used,
|
||||
},
|
||||
let binding = match scope.values.get(&name) {
|
||||
None => binding,
|
||||
Some(existing) => Binding {
|
||||
kind: binding.kind,
|
||||
location: binding.location,
|
||||
used: existing.used,
|
||||
},
|
||||
);
|
||||
};
|
||||
scope.values.insert(name, binding);
|
||||
}
|
||||
|
||||
fn handle_node_load(&mut self, expr: &Expr) {
|
||||
if let ExprKind::Name { id, .. } = &expr.node {
|
||||
let scope_id = self.scopes.last_mut().expect("No current scope found.").id;
|
||||
let mut first_iter = true;
|
||||
let mut in_generators = false;
|
||||
for scope in self.scopes.iter_mut().rev() {
|
||||
if matches!(scope.kind, Class) {
|
||||
if id == "__class__" {
|
||||
return;
|
||||
} else {
|
||||
} else if !first_iter && !in_generators {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -462,6 +570,9 @@ impl Checker<'_> {
|
||||
binding.used = Some(scope_id);
|
||||
return;
|
||||
}
|
||||
|
||||
first_iter = false;
|
||||
in_generators = matches!(scope.kind, Generator);
|
||||
}
|
||||
|
||||
if self.settings.select.contains(&CheckCode::F821) {
|
||||
@@ -500,12 +611,14 @@ impl Checker<'_> {
|
||||
}
|
||||
|
||||
// TODO(charlie): Handle alternate binding types (like `Annotation`).
|
||||
self.add_binding(Binding {
|
||||
kind: BindingKind::Assignment,
|
||||
name: id.to_string(),
|
||||
used: None,
|
||||
location: expr.location,
|
||||
});
|
||||
self.add_binding(
|
||||
id.to_string(),
|
||||
Binding {
|
||||
kind: BindingKind::Assignment,
|
||||
used: None,
|
||||
location: expr.location,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -537,11 +650,15 @@ impl Checker<'_> {
|
||||
for scope in &self.dead_scopes {
|
||||
for (_, binding) in scope.values.iter().rev() {
|
||||
if binding.used.is_none() {
|
||||
if let BindingKind::Importation(name) = &binding.kind {
|
||||
self.checks.push(Check {
|
||||
kind: CheckKind::UnusedImport(name.clone()),
|
||||
location: binding.location,
|
||||
});
|
||||
match &binding.kind {
|
||||
BindingKind::Importation(full_name)
|
||||
| BindingKind::SubmoduleImportation(full_name) => {
|
||||
self.checks.push(Check {
|
||||
kind: CheckKind::UnusedImport(full_name.to_string()),
|
||||
location: binding.location,
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,9 @@ pub fn check_lines(checks: &mut Vec<Check>, contents: &str, settings: &Settings)
|
||||
}
|
||||
}
|
||||
}
|
||||
ignored.sort();
|
||||
for index in ignored.iter().rev() {
|
||||
checks.remove(*index);
|
||||
checks.swap_remove(*index);
|
||||
}
|
||||
checks.extend(line_checks);
|
||||
}
|
||||
|
||||
@@ -12,10 +12,12 @@ pub enum CheckCode {
|
||||
F403,
|
||||
F541,
|
||||
F634,
|
||||
F704,
|
||||
F706,
|
||||
F821,
|
||||
F831,
|
||||
F832,
|
||||
F841,
|
||||
F901,
|
||||
}
|
||||
|
||||
@@ -29,10 +31,12 @@ 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),
|
||||
"F831" => Ok(CheckCode::F831),
|
||||
"F832" => Ok(CheckCode::F832),
|
||||
"F841" => Ok(CheckCode::F841),
|
||||
"F901" => Ok(CheckCode::F901),
|
||||
_ => Err(anyhow::anyhow!("Unknown check code: {s}")),
|
||||
}
|
||||
@@ -47,10 +51,12 @@ impl CheckCode {
|
||||
CheckCode::F403 => "F403",
|
||||
CheckCode::F541 => "F541",
|
||||
CheckCode::F634 => "F634",
|
||||
CheckCode::F704 => "F704",
|
||||
CheckCode::F706 => "F706",
|
||||
CheckCode::F821 => "F821",
|
||||
CheckCode::F831 => "F831",
|
||||
CheckCode::F832 => "F832",
|
||||
CheckCode::F841 => "F841",
|
||||
CheckCode::F901 => "F901",
|
||||
}
|
||||
}
|
||||
@@ -63,10 +69,12 @@ 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::F831 => &LintSource::AST,
|
||||
CheckCode::F832 => &LintSource::AST,
|
||||
CheckCode::F841 => &LintSource::AST,
|
||||
CheckCode::F901 => &LintSource::AST,
|
||||
}
|
||||
}
|
||||
@@ -86,9 +94,11 @@ pub enum CheckKind {
|
||||
ImportStarUsage,
|
||||
LineTooLong,
|
||||
RaiseNotImplemented,
|
||||
YieldOutsideFunction,
|
||||
ReturnOutsideFunction,
|
||||
UndefinedName(String),
|
||||
UndefinedLocal(String),
|
||||
UnusedVariable(String),
|
||||
UnusedImport(String),
|
||||
}
|
||||
|
||||
@@ -102,9 +112,11 @@ impl CheckKind {
|
||||
CheckKind::ImportStarUsage => &CheckCode::F403,
|
||||
CheckKind::LineTooLong => &CheckCode::E501,
|
||||
CheckKind::RaiseNotImplemented => &CheckCode::F901,
|
||||
CheckKind::YieldOutsideFunction => &CheckCode::F704,
|
||||
CheckKind::ReturnOutsideFunction => &CheckCode::F706,
|
||||
CheckKind::UndefinedName(_) => &CheckCode::F821,
|
||||
CheckKind::UndefinedLocal(_) => &CheckCode::F832,
|
||||
CheckKind::UnusedVariable(_) => &CheckCode::F841,
|
||||
CheckKind::UnusedImport(_) => &CheckCode::F401,
|
||||
}
|
||||
}
|
||||
@@ -126,12 +138,18 @@ impl CheckKind {
|
||||
CheckKind::RaiseNotImplemented => {
|
||||
"'raise NotImplemented' should be 'raise NotImplementedError".to_string()
|
||||
}
|
||||
CheckKind::YieldOutsideFunction => {
|
||||
"a `yield` or `yield from` statement outside of a function/method".to_string()
|
||||
}
|
||||
CheckKind::ReturnOutsideFunction => {
|
||||
"a `return` statement outside of a function/method".to_string()
|
||||
}
|
||||
CheckKind::UndefinedName(name) => {
|
||||
format!("Undefined name `{name}`")
|
||||
}
|
||||
CheckKind::UnusedVariable(name) => {
|
||||
format!("Local variable `{name}` is assigned to but never used")
|
||||
}
|
||||
CheckKind::UndefinedLocal(name) => {
|
||||
format!("Local variable `{name}` referenced before assignment")
|
||||
}
|
||||
|
||||
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"))
|
||||
}
|
||||
|
||||
@@ -101,6 +101,11 @@ mod tests {
|
||||
&cache::Mode::None,
|
||||
)?;
|
||||
let expected = vec![
|
||||
Message {
|
||||
kind: CheckKind::UnusedImport("logging.handlers".to_string()),
|
||||
location: Location::new(11, 1),
|
||||
filename: "./resources/test/src/F401.py".to_string(),
|
||||
},
|
||||
Message {
|
||||
kind: CheckKind::UnusedImport("functools".to_string()),
|
||||
location: Location::new(2, 1),
|
||||
@@ -217,6 +222,42 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f704() -> Result<()> {
|
||||
let actual = check_path(
|
||||
&Path::new("./resources/test/src/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/src/F704.py".to_string(),
|
||||
},
|
||||
Message {
|
||||
kind: CheckKind::YieldOutsideFunction,
|
||||
location: Location::new(9, 1),
|
||||
filename: "./resources/test/src/F704.py".to_string(),
|
||||
},
|
||||
Message {
|
||||
kind: CheckKind::YieldOutsideFunction,
|
||||
location: Location::new(10, 1),
|
||||
filename: "./resources/test/src/F704.py".to_string(),
|
||||
},
|
||||
];
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
for i in 0..actual.len() {
|
||||
assert_eq!(actual[i], expected[i]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f706() -> Result<()> {
|
||||
let actual = check_path(
|
||||
@@ -275,6 +316,11 @@ mod tests {
|
||||
location: Location::new(10, 9),
|
||||
filename: "./resources/test/src/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(),
|
||||
},
|
||||
];
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
for i in 0..actual.len() {
|
||||
@@ -344,6 +390,37 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f841() -> Result<()> {
|
||||
let actual = check_path(
|
||||
&Path::new("./resources/test/src/F841.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F841]),
|
||||
},
|
||||
&cache::Mode::None,
|
||||
)?;
|
||||
let expected = vec![
|
||||
Message {
|
||||
kind: CheckKind::UnusedVariable("e".to_string()),
|
||||
location: Location::new(3, 1),
|
||||
filename: "./resources/test/src/F841.py".to_string(),
|
||||
},
|
||||
Message {
|
||||
kind: CheckKind::UnusedVariable("z".to_string()),
|
||||
location: Location::new(16, 5),
|
||||
filename: "./resources/test/src/F841.py".to_string(),
|
||||
},
|
||||
];
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
for i in 0..actual.len() {
|
||||
assert_eq!(actual[i], expected[i]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f901() -> Result<()> {
|
||||
let actual = check_path(
|
||||
|
||||
14
src/main.rs
14
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 messages: Vec<Message> = files
|
||||
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());
|
||||
@@ -72,6 +69,7 @@ fn run_once(files: &[PathBuf], settings: &Settings, cache: bool) -> Result<Vec<M
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
messages.sort_unstable();
|
||||
let duration = start.elapsed();
|
||||
debug!("Checked files in: {:?}", duration);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt;
|
||||
|
||||
use colored::Colorize;
|
||||
@@ -29,6 +30,22 @@ pub struct Message {
|
||||
pub filename: String,
|
||||
}
|
||||
|
||||
impl Ord for Message {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
(&self.filename, self.location.row(), self.location.column()).cmp(&(
|
||||
&other.filename,
|
||||
other.location.row(),
|
||||
self.location.column(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Message {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Message {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
|
||||
@@ -225,17 +225,22 @@ other-attribute = 1
|
||||
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::F831,
|
||||
CheckCode::F832,
|
||||
CheckCode::F841,
|
||||
CheckCode::F901,
|
||||
])),
|
||||
}
|
||||
|
||||
@@ -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([
|
||||
@@ -48,7 +50,6 @@ impl Settings {
|
||||
CheckCode::F541,
|
||||
CheckCode::F634,
|
||||
CheckCode::F706,
|
||||
CheckCode::F821,
|
||||
CheckCode::F831,
|
||||
CheckCode::F832,
|
||||
CheckCode::F901,
|
||||
|
||||
@@ -14,9 +14,6 @@ pub trait Visitor {
|
||||
fn visit_expr(&mut self, expr: &Expr) {
|
||||
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);
|
||||
}
|
||||
@@ -67,53 +64,48 @@ 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);
|
||||
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);
|
||||
}
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt)
|
||||
}
|
||||
}
|
||||
StmtKind::AsyncFunctionDef {
|
||||
name,
|
||||
args,
|
||||
body,
|
||||
decorator_list,
|
||||
returns,
|
||||
..
|
||||
} => {
|
||||
visitor.visit_ident(name);
|
||||
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);
|
||||
}
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt)
|
||||
}
|
||||
}
|
||||
StmtKind::ClassDef {
|
||||
name,
|
||||
bases,
|
||||
keywords,
|
||||
body,
|
||||
decorator_list,
|
||||
..
|
||||
} => {
|
||||
visitor.visit_ident(name);
|
||||
for expr in bases {
|
||||
visitor.visit_expr(expr)
|
||||
}
|
||||
@@ -271,24 +263,13 @@ 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::Global { .. } => {}
|
||||
StmtKind::Nonlocal { .. } => {}
|
||||
StmtKind::Expr { value } => visitor.visit_expr(value),
|
||||
StmtKind::Pass => {}
|
||||
StmtKind::Break => {}
|
||||
@@ -340,33 +321,33 @@ pub fn walk_expr<V: Visitor + ?Sized>(visitor: &mut V, expr: &Expr) {
|
||||
}
|
||||
}
|
||||
ExprKind::ListComp { elt, generators } => {
|
||||
visitor.visit_expr(elt);
|
||||
for comprehension in generators {
|
||||
visitor.visit_comprehension(comprehension)
|
||||
}
|
||||
visitor.visit_expr(elt);
|
||||
}
|
||||
ExprKind::SetComp { elt, generators } => {
|
||||
visitor.visit_expr(elt);
|
||||
for comprehension in generators {
|
||||
visitor.visit_comprehension(comprehension)
|
||||
}
|
||||
visitor.visit_expr(elt);
|
||||
}
|
||||
ExprKind::DictComp {
|
||||
key,
|
||||
value,
|
||||
generators,
|
||||
} => {
|
||||
for comprehension in generators {
|
||||
visitor.visit_comprehension(comprehension)
|
||||
}
|
||||
visitor.visit_expr(key);
|
||||
visitor.visit_expr(value);
|
||||
for comprehension in generators {
|
||||
visitor.visit_comprehension(comprehension)
|
||||
}
|
||||
}
|
||||
ExprKind::GeneratorExp { elt, generators } => {
|
||||
visitor.visit_expr(elt);
|
||||
for comprehension in generators {
|
||||
visitor.visit_comprehension(comprehension)
|
||||
}
|
||||
visitor.visit_expr(elt);
|
||||
}
|
||||
ExprKind::Await { value } => visitor.visit_expr(value),
|
||||
ExprKind::Yield { value } => {
|
||||
@@ -428,8 +409,7 @@ pub fn walk_expr<V: Visitor + ?Sized>(visitor: &mut V, expr: &Expr) {
|
||||
visitor.visit_expr(value);
|
||||
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 } => {
|
||||
@@ -476,13 +456,10 @@ pub fn walk_comprehension<V: Visitor + ?Sized>(visitor: &mut V, comprehension: &
|
||||
|
||||
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);
|
||||
}
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt);
|
||||
}
|
||||
@@ -550,50 +527,34 @@ pub fn walk_pattern<V: Visitor + ?Sized>(visitor: &mut V, pattern: &Pattern) {
|
||||
visitor.visit_pattern(pattern)
|
||||
}
|
||||
}
|
||||
PatternKind::MatchMapping {
|
||||
keys,
|
||||
patterns,
|
||||
rest,
|
||||
} => {
|
||||
PatternKind::MatchMapping { keys, patterns, .. } => {
|
||||
for expr in keys {
|
||||
visitor.visit_expr(expr);
|
||||
}
|
||||
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);
|
||||
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 +565,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