Compare commits

..

18 Commits

Author SHA1 Message Date
Charlie Marsh
7c17785eac Bump version to 0.0.30 2022-09-08 11:42:45 -04:00
Charlie Marsh
f1acd28f08 Skip slice error for invalid TypeVar calls 2022-09-08 11:40:55 -04:00
Charlie Marsh
c61ff9a947 Adjust location when parsing deferred type annotations (#133) 2022-09-08 11:37:19 -04:00
Charlie Marsh
2c64cf3149 Add support for Literal, Type, and TypeVar (#131) 2022-09-08 11:07:45 -04:00
Charlie Marsh
fc5f34c76f Use scope-tracking logic for parents (#130) 2022-09-08 09:14:58 -04:00
Charlie Marsh
a8f4faa6e4 Fix crash on missing parent 2022-09-07 22:40:25 -04:00
Charlie Marsh
2ac5c830c1 Parse assignment annotations prior to targets (#127) 2022-09-07 22:35:39 -04:00
Charlie Marsh
994f12050d Support 'ignore' in pyproject.toml (#126) 2022-09-07 22:34:51 -04:00
Charlie Marsh
fad4e4c51d Defer checking of function bodies (#125) 2022-09-07 22:34:43 -04:00
Charlie Marsh
c0042a3ca4 Use Mode::None for --no-cache 2022-09-07 21:47:07 -04:00
Charlie Marsh
5deb63a05f Implement F601 and F602 (#122) 2022-09-07 12:57:50 -04:00
Charlie Marsh
5e9ea8bda2 Add documentation on parity with Flake8 2022-09-07 10:32:28 -04:00
Charlie Marsh
55d1f34bae Bump version to 0.0.29 2022-09-06 22:14:12 -04:00
Charlie Marsh
59b518a54a Upgrade RustPython to handle AnnAssign (#117) 2022-09-06 20:53:51 -04:00
Colin J. Fuller
74ecdc73ac Handle E731 in type-annotated assignment (#116) 2022-09-06 20:18:46 -04:00
Colin J. Fuller
1ad6be7196 Add fixture examples for #114 (#115) 2022-09-06 20:18:05 -04:00
Charlie Marsh
b44d6c2c44 Bump version to 0.0.28 2022-09-06 14:20:02 -04:00
Charlie Marsh
2749660b1f Disable update-informer on linux-cross (#113) 2022-09-06 14:19:38 -04:00
24 changed files with 1028 additions and 331 deletions

View File

@@ -1,6 +1,8 @@
name: Release
on:
pull_request:
branches: [main]
create:
tags:
- v*
@@ -127,7 +129,7 @@ jobs:
with:
target: ${{ matrix.target }}
manylinux: auto
args: --release --out dist
args: --no-default-features --release --out dist
maturin-version: "v0.13.0"
- uses: uraimo/run-on-arch-action@v2.0.5
if: matrix.target != 'ppc64'

View File

@@ -1,5 +1,5 @@
repos:
- repo: https://github.com/charliermarsh/ruff
rev: v0.0.27
rev: v0.0.30
hooks:
- id: lint

8
Cargo.lock generated
View File

@@ -1744,7 +1744,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.27"
version = "0.0.30"
dependencies = [
"anyhow",
"bincode",
@@ -1787,7 +1787,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.1.0"
source = "git+https://github.com/charliermarsh/RustPython.git?rev=ff6112d7c70729be76eaad8c9e5b8ef24d13de99#ff6112d7c70729be76eaad8c9e5b8ef24d13de99"
source = "git+https://github.com/charliermarsh/RustPython.git?rev=8cc3d23ddaa6339bf56608adaeefabff3ad329e7#8cc3d23ddaa6339bf56608adaeefabff3ad329e7"
dependencies = [
"num-bigint",
"rustpython-compiler-core",
@@ -1796,7 +1796,7 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.1.2"
source = "git+https://github.com/charliermarsh/RustPython.git?rev=ff6112d7c70729be76eaad8c9e5b8ef24d13de99#ff6112d7c70729be76eaad8c9e5b8ef24d13de99"
source = "git+https://github.com/charliermarsh/RustPython.git?rev=8cc3d23ddaa6339bf56608adaeefabff3ad329e7#8cc3d23ddaa6339bf56608adaeefabff3ad329e7"
dependencies = [
"bincode",
"bitflags",
@@ -1813,7 +1813,7 @@ dependencies = [
[[package]]
name = "rustpython-parser"
version = "0.1.2"
source = "git+https://github.com/charliermarsh/RustPython.git?rev=ff6112d7c70729be76eaad8c9e5b8ef24d13de99#ff6112d7c70729be76eaad8c9e5b8ef24d13de99"
source = "git+https://github.com/charliermarsh/RustPython.git?rev=8cc3d23ddaa6339bf56608adaeefabff3ad329e7#8cc3d23ddaa6339bf56608adaeefabff3ad329e7"
dependencies = [
"ahash",
"anyhow",

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.27"
version = "0.0.30"
edition = "2021"
[lib]
@@ -25,13 +25,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 = "ff6112d7c70729be76eaad8c9e5b8ef24d13de99" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "8cc3d23ddaa6339bf56608adaeefabff3ad329e7" }
serde = { version = "1.0.143", features = ["derive"] }
serde_json = { version = "1.0.83" }
toml = { version = "0.5.9" }
update-informer = { version = "0.5.0", default_features = false, features = ["pypi"] }
update-informer = { version = "0.5.0", default_features = false, features = ["pypi"], optional = true }
walkdir = { version = "2.3.2" }
[features]
default = ["update-informer"]
update-informer = ["dep:update-informer"]
[profile.release]
panic = "abort"
lto = "thin"

View File

@@ -57,7 +57,7 @@ ruff also works with [Pre-Commit](https://pre-commit.com) (requires Cargo on sys
```yaml
repos:
- repo: https://github.com/charliermarsh/ruff
rev: v0.0.27
rev: v0.0.30
hooks:
- id: lint
```
@@ -86,7 +86,7 @@ ruff path/to/code/ --select F401 F403
See `ruff --help` for more:
```shell
ruff (v0.0.27)
ruff (v0.0.30)
An extremely fast Python linter.
USAGE:
@@ -116,6 +116,31 @@ compatible out-of-the-box as long as the `line-length` setting is consistent bet
As a project, ruff is designed to be used alongside Black and, as such, will defer implementing
lint rules that are obviated by Black (e.g., stylistic rules).
### Parity with Flake8
ruff's goal is to achieve feature-parity with Flake8 when used (1) without any plugins,
(2) alongside Black, and (3) on Python 3 code. (Using Black obviates the need for many of Flake8's
stylistic checks; limiting to Python 3 obviates the need for certain compatibility checks.)
Under those conditions, Flake8 implements about 58 rules, give or take. At time of writing, ruff
implements 24 rules. (Note that these 24 rules likely cover a disproportionate share of errors:
unused imports, undefined variables, etc.)
Of the unimplemented rules, ruff is missing:
- 15 rules related to string `.format` calls.
- 6 rules related to misplaced `yield`, `break`, and `return` statements.
- 3 rules related to syntax errors in doctests and annotations.
- 2 rules related to redefining unused names.
...along with a variety of others that don't fit neatly into any specific category.
Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis Flake8:
1. Flake8 supports a wider range of `noqa` patterns, such as per-file ignores defined in `.flake8`.
2. Flake8 has a plugin architecture and supports writing custom lint rules.
3. ruff does not yet support parenthesized context managers.
## Rules
| Code | Name | Message |
@@ -131,6 +156,8 @@ lint rules that are obviated by Black (e.g., stylistic rules).
| F401 | UnusedImport | `...` imported but unused |
| F403 | ImportStarUsage | Unable to detect undefined names |
| F541 | FStringMissingPlaceholders | f-string without any placeholders |
| F601 | MultiValueRepeatedKeyLiteral | Dictionary key literal repeated |
| F602 | MultiValueRepeatedKeyVariable | Dictionary key `...` repeated |
| F631 | AssertTuple | Assert test is a non-empty tuple, which is always `True` |
| F634 | IfTuple | If test is a tuple, which is always `True` |
| F704 | YieldOutsideFunction | a `yield` or `yield from` statement outside of a function/method |

View File

@@ -13,6 +13,8 @@ fn main() {
CheckKind::ImportStarUsage,
CheckKind::LineTooLong,
CheckKind::ModuleImportNotAtTopOfFile,
CheckKind::MultiValueRepeatedKeyLiteral,
CheckKind::MultiValueRepeatedKeyVariable("...".to_string()),
CheckKind::NoAssertEquals,
CheckKind::NoneComparison(RejectedCmpop::Eq),
CheckKind::NotInTest,

View File

@@ -1,2 +1,6 @@
from typing import Callable, Iterable
a = lambda x: x**2
b = map(lambda x: 2 * x, range(3))
c: Callable = lambda x: x**2
d: Iterable = map(lambda x: 2 * x, range(3))

View File

@@ -1,6 +1,7 @@
from __future__ import all_feature_names
import os
import functools
from datetime import datetime
from collections import (
Counter,
OrderedDict,
@@ -10,11 +11,15 @@ import multiprocessing.pool
import multiprocessing.process
import logging.config
import logging.handlers
from typing import NamedTuple, Dict, Type, TypeVar, List, Set
from blah import ClassA, ClassB, ClassC
class X:
datetime: datetime
foo: Type["NamedTuple"]
def a(self) -> "namedtuple":
x = os.environ["1"]
y = Counter()
@@ -23,3 +28,7 @@ class X:
__all__ = ["ClassA"] + ["ClassB"]
__all__ += ["ClassC"]
X = TypeVar("X")
Y = TypeVar("Y", bound="Dict")
Z = TypeVar("Z", "List", "Set")

12
resources/test/fixtures/F601.py vendored Normal file
View File

@@ -0,0 +1,12 @@
x = {
"a": 1,
"a": 2,
"b": 3,
("a", "b"): 3,
("a", "b"): 4,
1.0: 2,
1: 0,
1: 3,
b"123": 1,
b"123": 4,
}

7
resources/test/fixtures/F602.py vendored Normal file
View File

@@ -0,0 +1,7 @@
a = 1
b = 2
x = {
a: 1,
a: 2,
b: 3,
}

View File

@@ -33,14 +33,26 @@ def ternary_optarg(prec, exp_range, itr):
class Foo:
CLASS_VAR = 1
REFERENCES_CLASS_VAR = {"CLASS_VAR": CLASS_VAR}
ANNOTATED_CLASS_VAR: int = 2
from typing import Literal
class Class:
copy_on_model_validation: Literal["none", "deep", "shallow"]
post_init_call: Literal["before_validation", "after_validation"]
def __init__(self):
# TODO(charlie): This should be recognized as a defined variable.
Class # noqa: F821
Class
try:
x = 1 / 0
except Exception as e:
print(e)
y: int = 1
x: "Bar" = 1

View File

@@ -13,6 +13,8 @@ select = [
"F401",
"F403",
"F541",
"F601",
"F602",
"F631",
"F634",
"F704",

View File

@@ -8,6 +8,7 @@ fn id() -> usize {
COUNTER.fetch_add(1, Ordering::Relaxed)
}
#[derive(Clone, Debug)]
pub enum ScopeKind {
Class,
Function,
@@ -15,6 +16,7 @@ pub enum ScopeKind {
Module,
}
#[derive(Clone, Debug)]
pub struct Scope {
pub id: usize,
pub kind: ScopeKind,

View File

@@ -3,12 +3,12 @@ use std::fs::Metadata;
use std::hash::{Hash, Hasher};
use std::path::Path;
use crate::autofix;
use cacache::Error::EntryNotFound;
use filetime::FileTime;
use log::error;
use serde::{Deserialize, Serialize};
use crate::autofix;
use crate::message::Message;
use crate::settings::Settings;
@@ -62,7 +62,7 @@ impl From<bool> for Mode {
fn from(value: bool) -> Self {
match value {
true => Mode::ReadWrite,
false => Mode::WriteOnly,
false => Mode::None,
}
}
}

View File

@@ -4,7 +4,7 @@ use std::path::Path;
use itertools::izip;
use rustpython_parser::ast::{
Arg, Arguments, Cmpop, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind,
Location, Stmt, StmtKind, Suite, Unaryop,
KeywordData, Location, Stmt, StmtKind, Suite, Unaryop,
};
use rustpython_parser::parser;
@@ -13,27 +13,41 @@ use crate::ast_ops::{
};
use crate::builtins::{BUILTINS, MAGIC_GLOBALS};
use crate::checks::{Check, CheckCode, CheckKind, Fix, RejectedCmpop};
use crate::relocator::relocate_expr;
use crate::settings::Settings;
use crate::visitor::{walk_excepthandler, Visitor};
use crate::{autofix, fixer, visitor};
pub const GLOBAL_SCOPE_INDEX: usize = 0;
struct Checker<'a> {
// Input data.
locator: SourceCodeLocator<'a>,
settings: &'a Settings,
autofix: &'a autofix::Mode,
path: &'a str,
// Computed checks.
checks: Vec<Check>,
// Retain all scopes and parent nodes, along with a stack of indexes to track which are active
// at various points in time.
parents: Vec<&'a Stmt>,
parent_stack: Vec<usize>,
scopes: Vec<Scope>,
dead_scopes: Vec<Scope>,
deferred: Vec<String>,
scope_stack: Vec<usize>,
dead_scopes: Vec<usize>,
deferred_annotations: Vec<(Location, &'a str)>,
deferred_functions: Vec<(&'a Stmt, Vec<usize>, Vec<usize>)>,
deferred_lambdas: Vec<(&'a Expr, Vec<usize>, Vec<usize>)>,
// Derivative state.
in_f_string: bool,
in_annotation: bool,
in_literal: bool,
seen_non_import: bool,
seen_docstring: bool,
}
impl Checker<'_> {
pub fn new<'a>(
impl<'a> Checker<'a> {
pub fn new(
settings: &'a Settings,
autofix: &'a autofix::Mode,
path: &'a str,
@@ -45,28 +59,64 @@ impl Checker<'_> {
path,
locator: SourceCodeLocator::new(content),
checks: vec![],
parents: vec![],
parent_stack: vec![],
scopes: vec![],
scope_stack: vec![],
dead_scopes: vec![],
deferred: vec![],
deferred_annotations: vec![],
deferred_functions: vec![],
deferred_lambdas: vec![],
in_f_string: false,
in_annotation: false,
in_literal: false,
seen_non_import: false,
seen_docstring: false,
}
}
}
impl Visitor for Checker<'_> {
fn visit_stmt(&mut self, stmt: &Stmt) {
#[derive(Debug, PartialEq)]
enum DictionaryKey<'a> {
Constant(&'a Constant),
Variable(&'a String),
}
fn convert_to_value(expr: &Expr) -> Option<DictionaryKey> {
match &expr.node {
ExprKind::Constant { value, .. } => Some(DictionaryKey::Constant(value)),
ExprKind::Name { id, .. } => Some(DictionaryKey::Variable(id)),
_ => None,
}
}
fn match_name_or_attr(expr: &Expr, target: &str) -> bool {
match &expr.node {
ExprKind::Attribute { attr, .. } => target == attr,
ExprKind::Name { id, .. } => target == id,
_ => false,
}
}
impl<'a, 'b> Visitor<'b> for Checker<'a>
where
'b: 'a,
{
fn visit_stmt(&mut self, stmt: &'b Stmt) {
self.push_parent(stmt);
// Pre-visit.
match &stmt.node {
StmtKind::Global { names } | StmtKind::Nonlocal { names } => {
// TODO(charlie): Handle doctests.
let global_scope_index = 0;
let global_scope_id = self.scopes[global_scope_index].id;
let current_scope_id = self.scopes.last().expect("No current scope found.").id;
let global_scope_id = self.scopes[GLOBAL_SCOPE_INDEX].id;
let current_scope =
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
let current_scope_id = current_scope.id;
if current_scope_id != global_scope_id {
for name in names {
for scope in self.scopes.iter_mut().skip(global_scope_index + 1) {
for scope in self.scopes.iter_mut().skip(GLOBAL_SCOPE_INDEX + 1) {
scope.values.insert(
name.to_string(),
Binding {
@@ -92,7 +142,7 @@ impl Visitor for Checker<'_> {
..
} => {
for expr in decorator_list {
self.visit_expr(expr, Some(stmt));
self.visit_expr(expr);
}
for expr in returns {
self.visit_annotation(expr);
@@ -105,7 +155,6 @@ impl Visitor for Checker<'_> {
location: stmt.location,
},
);
self.push_scope(Scope::new(ScopeKind::Function));
}
StmtKind::Return { .. } => {
if self
@@ -113,8 +162,8 @@ impl Visitor for Checker<'_> {
.select
.contains(CheckKind::ReturnOutsideFunction.code())
{
if let Some(scope) = self.scopes.last() {
match scope.kind {
if let Some(scope_index) = self.scope_stack.last().cloned() {
match self.scopes[scope_index].kind {
ScopeKind::Class | ScopeKind::Module => {
self.checks.push(Check::new(
CheckKind::ReturnOutsideFunction,
@@ -137,7 +186,8 @@ impl Visitor for Checker<'_> {
for expr in bases {
if let ExprKind::Name { id, .. } = &expr.node {
if id == "object" {
let scope = self.scopes.last().expect("No current scope found.");
let scope = &self.scopes
[*(self.scope_stack.last().expect("No current scope found."))];
match scope.values.get(id) {
None
| Some(Binding {
@@ -172,13 +222,13 @@ impl Visitor for Checker<'_> {
}
for expr in bases {
self.visit_expr(expr, Some(stmt))
self.visit_expr(expr)
}
for keyword in keywords {
self.visit_keyword(keyword)
}
for expr in decorator_list {
self.visit_expr(expr, Some(stmt))
self.visit_expr(expr)
}
self.push_scope(Scope::new(ScopeKind::Class))
}
@@ -257,7 +307,13 @@ impl Visitor for Checker<'_> {
name,
Binding {
kind: BindingKind::FutureImportation,
used: Some(self.scopes.last().expect("No current scope found.").id),
used: Some(
self.scopes[*(self
.scope_stack
.last()
.expect("No current scope found."))]
.id,
),
location: stmt.location,
},
);
@@ -391,47 +447,47 @@ impl Visitor for Checker<'_> {
}
}
}
StmtKind::Delete { .. } | StmtKind::AnnAssign { .. } => {
StmtKind::AnnAssign { value, .. } => {
self.seen_non_import = true;
if self
.settings
.select
.contains(CheckKind::DoNotAssignLambda.code())
{
if let Some(v) = value {
if let ExprKind::Lambda { .. } = v.node {
self.checks
.push(Check::new(CheckKind::DoNotAssignLambda, stmt.location));
}
}
}
}
StmtKind::Delete { .. } => {
self.seen_non_import = true;
}
_ => {}
}
visitor::walk_stmt(self, stmt);
// Recurse.
match &stmt.node {
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::new(
CheckKind::UnusedVariable(name.to_string()),
binding.location,
));
}
}
if let Some(scope) = self.scopes.pop() {
self.dead_scopes.push(scope);
self.deferred_functions.push((
stmt,
self.scope_stack.clone(),
self.parent_stack.clone(),
));
}
StmtKind::ClassDef { body, .. } => {
for stmt in body {
self.visit_stmt(stmt);
}
}
_ => {}
_ => visitor::walk_stmt(self, stmt),
};
// Post-visit.
if let StmtKind::ClassDef { name, .. } = &stmt.node {
self.pop_scope();
self.add_binding(
name.to_string(),
Binding {
@@ -440,21 +496,36 @@ impl Visitor for Checker<'_> {
location: stmt.location,
},
);
}
}
fn visit_annotation(&mut self, expr: &Expr) {
let initial = self.in_annotation;
self.in_annotation = true;
self.visit_expr(expr, None);
self.in_annotation = initial;
};
self.pop_parent();
}
fn visit_expr(&mut self, expr: &Expr, parent: Option<&Stmt>) {
let initial = self.in_f_string;
fn visit_annotation(&mut self, expr: &'b Expr) {
let prev_in_annotation = self.in_annotation;
self.in_annotation = true;
self.visit_expr(expr);
self.in_annotation = prev_in_annotation;
}
fn visit_expr(&mut self, expr: &'b Expr) {
let prev_in_f_string = self.in_f_string;
let prev_in_literal = self.in_literal;
// Pre-visit.
match &expr.node {
ExprKind::Subscript { value, .. } => {
if match_name_or_attr(value, "Literal") {
self.in_literal = true;
}
}
ExprKind::Name { ctx, .. } => match ctx {
ExprContext::Load => self.handle_node_load(expr),
ExprContext::Store => self.handle_node_store(expr, parent),
ExprContext::Store => {
let parent =
self.parents[*(self.parent_stack.last().expect("No parent found."))];
self.handle_node_store(expr, Some(parent));
}
ExprContext::Del => self.handle_node_delete(expr),
},
ExprKind::Call { func, .. } => {
@@ -488,13 +559,54 @@ impl Visitor for Checker<'_> {
}
}
}
ExprKind::Dict { keys, .. } => {
if self.settings.select.contains(&CheckCode::F601)
|| self.settings.select.contains(&CheckCode::F602)
{
let num_keys = keys.len();
for i in 0..num_keys {
let k1 = &keys[i];
let v1 = convert_to_value(k1);
for k2 in keys.iter().take(num_keys).skip(i + 1) {
let v2 = convert_to_value(k2);
match (&v1, &v2) {
(
Some(DictionaryKey::Constant(v1)),
Some(DictionaryKey::Constant(v2)),
) => {
if self.settings.select.contains(&CheckCode::F601) && v1 == v2 {
self.checks.push(Check::new(
CheckKind::MultiValueRepeatedKeyLiteral,
k2.location,
))
}
}
(
Some(DictionaryKey::Variable(v1)),
Some(DictionaryKey::Variable(v2)),
) => {
if self.settings.select.contains(&CheckCode::F602) && v1 == v2 {
self.checks.push(Check::new(
CheckKind::MultiValueRepeatedKeyVariable(
v2.to_string(),
),
k2.location,
))
}
}
_ => {}
}
}
}
}
}
ExprKind::GeneratorExp { .. }
| ExprKind::ListComp { .. }
| ExprKind::DictComp { .. }
| 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.");
let scope =
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
if self
.settings
.select
@@ -647,35 +759,80 @@ impl Visitor for Checker<'_> {
ExprKind::Constant {
value: Constant::Str(value),
..
} if self.in_annotation => self.deferred.push(value.to_string()),
} if self.in_annotation && !self.in_literal => {
self.deferred_annotations.push((expr.location, value));
}
_ => {}
};
visitor::walk_expr(self, expr);
// Recurse.
match &expr.node {
ExprKind::Lambda { .. } => {
self.deferred_lambdas.push((
expr,
self.scope_stack.clone(),
self.parent_stack.clone(),
));
}
ExprKind::Call {
func,
args,
keywords,
} => {
if match_name_or_attr(func, "TypeVar") {
self.visit_expr(func);
for expr in args.iter().skip(1) {
self.visit_annotation(expr);
}
for keyword in keywords {
let KeywordData { arg, value } = &keyword.node;
if let Some(id) = arg {
if id == "bound" {
self.visit_annotation(value);
} else {
self.visit_expr(value);
}
}
}
} else {
visitor::walk_expr(self, expr);
}
}
ExprKind::Subscript { value, slice, ctx } => {
if match_name_or_attr(value, "Type") {
self.visit_expr(value);
self.visit_annotation(slice);
self.visit_expr_context(ctx);
} else {
visitor::walk_expr(self, expr);
}
}
_ => visitor::walk_expr(self, expr),
}
// Post-visit.
match &expr.node {
ExprKind::GeneratorExp { .. }
| ExprKind::ListComp { .. }
| ExprKind::DictComp { .. }
| ExprKind::SetComp { .. }
| ExprKind::Lambda { .. } => {
if let Some(scope) = self.scopes.pop() {
self.dead_scopes.push(scope);
}
}
ExprKind::JoinedStr { .. } => {
self.in_f_string = initial;
| ExprKind::SetComp { .. } => {
self.pop_scope();
}
_ => {}
};
self.in_literal = prev_in_literal;
self.in_f_string = prev_in_f_string;
}
fn visit_excepthandler(&mut self, excepthandler: &Excepthandler) {
fn visit_excepthandler(&mut self, excepthandler: &'b Excepthandler) {
match &excepthandler.node {
ExcepthandlerKind::ExceptHandler { name, .. } => match name {
Some(name) => {
let scope = self.scopes.last().expect("No current scope found.");
let scope =
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
if scope.values.contains_key(name) {
let parent =
self.parents[*(self.parent_stack.last().expect("No parent found."))];
self.handle_node_store(
&Expr::new(
excepthandler.location,
@@ -684,12 +841,16 @@ impl Visitor for Checker<'_> {
ctx: ExprContext::Store,
},
),
None,
Some(parent),
);
self.parents.push(parent);
}
let scope = self.scopes.last().expect("No current scope found.");
let prev_definition = scope.values.get(name).cloned();
let parent =
self.parents[*(self.parent_stack.last().expect("No parent found."))];
let scope =
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
let definition = scope.values.get(name).cloned();
self.handle_node_store(
&Expr::new(
excepthandler.location,
@@ -698,13 +859,15 @@ impl Visitor for Checker<'_> {
ctx: ExprContext::Store,
},
),
None,
Some(parent),
);
self.parents.push(parent);
walk_excepthandler(self, excepthandler);
let scope = self.scopes.last_mut().expect("No current scope found.");
if let Some(binding) = scope.values.remove(name) {
let scope = &mut self.scopes
[*(self.scope_stack.last().expect("No current scope found."))];
if let Some(binding) = &scope.values.remove(name) {
if self.settings.select.contains(&CheckCode::F841) && binding.used.is_none()
{
self.checks.push(Check::new(
@@ -714,7 +877,7 @@ impl Visitor for Checker<'_> {
}
}
if let Some(binding) = prev_definition {
if let Some(binding) = definition {
scope.values.insert(name.to_string(), binding);
}
}
@@ -723,7 +886,7 @@ impl Visitor for Checker<'_> {
}
}
fn visit_arguments(&mut self, arguments: &Arguments) {
fn visit_arguments(&mut self, arguments: &'b Arguments) {
if self
.settings
.select
@@ -759,7 +922,7 @@ impl Visitor for Checker<'_> {
visitor::walk_arguments(self, arguments);
}
fn visit_arg(&mut self, arg: &Arg) {
fn visit_arg(&mut self, arg: &'b Arg) {
self.add_binding(
arg.node.arg.to_string(),
Binding {
@@ -772,41 +935,58 @@ impl Visitor for Checker<'_> {
}
}
impl Checker<'_> {
impl<'a> Checker<'a> {
fn push_parent(&mut self, parent: &'a Stmt) {
self.parent_stack.push(self.parents.len());
self.parents.push(parent);
}
fn pop_parent(&mut self) {
self.parent_stack
.pop()
.expect("Attempted to pop without scope.");
}
fn push_scope(&mut self, scope: Scope) {
self.scope_stack.push(self.scopes.len());
self.scopes.push(scope);
}
fn pop_scope(&mut self) {
self.dead_scopes
.push(self.scopes.pop().expect("Attempted to pop without scope."));
self.dead_scopes.push(
self.scope_stack
.pop()
.expect("Attempted to pop without scope."),
);
}
fn bind_builtins(&mut self) {
let scope = &mut self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
for builtin in BUILTINS {
self.add_binding(
scope.values.insert(
builtin.to_string(),
Binding {
kind: BindingKind::Builtin,
location: Default::default(),
used: None,
},
)
);
}
for builtin in MAGIC_GLOBALS {
self.add_binding(
scope.values.insert(
builtin.to_string(),
Binding {
kind: BindingKind::Builtin,
location: Default::default(),
used: None,
},
)
);
}
}
fn add_binding(&mut self, name: String, binding: Binding) {
let scope = self.scopes.last_mut().expect("No current scope found.");
let scope = &mut self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
// TODO(charlie): Don't treat annotations as assignments if there is an existing value.
let binding = match scope.values.get(&name) {
@@ -822,14 +1002,17 @@ impl Checker<'_> {
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 scope_id =
self.scopes[*(self.scope_stack.last().expect("No current scope found."))].id;
let mut first_iter = true;
let mut in_generators = false;
for scope in self.scopes.iter_mut().rev() {
let mut in_generator = false;
for scope_index in self.scope_stack.iter().rev() {
let scope = &mut self.scopes[*scope_index];
if matches!(scope.kind, ScopeKind::Class) {
if id == "__class__" {
return;
} else if !first_iter && !in_generators {
} else if !first_iter && !in_generator {
continue;
}
}
@@ -839,7 +1022,7 @@ impl Checker<'_> {
}
first_iter = false;
in_generators = matches!(scope.kind, ScopeKind::Generator);
in_generator = matches!(scope.kind, ScopeKind::Generator);
}
if self.settings.select.contains(&CheckCode::F821) {
@@ -853,7 +1036,8 @@ impl Checker<'_> {
fn handle_node_store(&mut self, expr: &Expr, parent: Option<&Stmt>) {
if let ExprKind::Name { id, .. } = &expr.node {
let current = self.scopes.last().expect("No current scope found.");
let current =
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
if self.settings.select.contains(&CheckCode::F823)
&& matches!(current.kind, ScopeKind::Function)
@@ -883,16 +1067,14 @@ impl Checker<'_> {
// TODO(charlie): Handle alternate binding types (like `Annotation`).
if id == "__all__"
&& matches!(current.kind, ScopeKind::Module)
&& match parent {
None => false,
Some(stmt) => {
&& parent
.map(|stmt| {
matches!(stmt.node, StmtKind::Assign { .. })
|| matches!(stmt.node, StmtKind::AugAssign { .. })
|| matches!(stmt.node, StmtKind::AnnAssign { .. })
}
}
})
.unwrap_or_default()
{
// Really need parent here.
self.add_binding(
id.to_string(),
Binding {
@@ -916,9 +1098,9 @@ impl Checker<'_> {
fn handle_node_delete(&mut self, expr: &Expr) {
if let ExprKind::Name { id, .. } = &expr.node {
let current = self.scopes.last_mut().expect("No current scope found.");
if current.values.remove(id).is_none()
&& self.settings.select.contains(&CheckCode::F821)
let scope =
&mut self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
if scope.values.remove(id).is_none() && self.settings.select.contains(&CheckCode::F821)
{
self.checks.push(Check::new(
CheckKind::UndefinedName(id.clone()),
@@ -928,12 +1110,96 @@ 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, None);
fn check_deferred_annotations<'b>(&mut self, path: &str, allocator: &'b mut Vec<Expr>)
where
'b: 'a,
{
while !self.deferred_annotations.is_empty() {
let (location, expression) = self.deferred_annotations.pop().unwrap();
if let Ok(mut expr) = parser::parse_expression(expression, path) {
relocate_expr(&mut expr, location);
allocator.push(expr);
}
}
for expr in allocator {
self.visit_expr(expr);
}
}
fn check_deferred_functions(&mut self) {
while !self.deferred_functions.is_empty() {
let (stmt, scopes, parents) = self.deferred_functions.pop().unwrap();
self.parent_stack = parents;
self.scope_stack = scopes;
self.push_scope(Scope::new(ScopeKind::Function));
match &stmt.node {
StmtKind::FunctionDef { body, args, .. }
| StmtKind::AsyncFunctionDef { body, args, .. } => {
self.visit_arguments(args);
for stmt in body {
self.visit_stmt(stmt);
}
}
_ => {}
}
let scope = &self.scopes[*(self.scope_stack.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::new(
CheckKind::UnusedVariable(name.to_string()),
binding.location,
));
}
}
self.pop_scope();
}
}
fn check_deferred_lambdas(&mut self) {
while !self.deferred_lambdas.is_empty() {
let (expr, scopes, parents) = self.deferred_lambdas.pop().unwrap();
self.parent_stack = parents;
self.scope_stack = scopes;
self.push_scope(Scope::new(ScopeKind::Function));
if let ExprKind::Lambda { args, body } = &expr.node {
self.visit_arguments(args);
self.visit_expr(body);
}
let scope = &self.scopes[*(self.scope_stack.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::new(
CheckKind::UnusedVariable(name.to_string()),
binding.location,
));
}
}
self.pop_scope();
}
}
fn check_dead_scopes(&mut self) {
@@ -943,7 +1209,9 @@ impl Checker<'_> {
return;
}
for scope in &self.dead_scopes {
for index in self.dead_scopes.clone() {
let scope = &self.scopes[index];
let all_binding = scope.values.get("__all__");
let all_names = all_binding.and_then(|binding| match &binding.kind {
BindingKind::Export(names) => Some(names),
@@ -1003,12 +1271,21 @@ pub fn check_ast(
checker.push_scope(Scope::new(ScopeKind::Module));
checker.bind_builtins();
// Iterate over the AST.
for stmt in python_ast {
checker.visit_stmt(stmt);
}
checker.check_deferred(path);
// Check any deferred statements.
checker.check_deferred_functions();
checker.check_deferred_lambdas();
let mut allocator = vec![];
checker.check_deferred_annotations(path, &mut allocator);
// Reset the scope to module-level, and check all consumed scopes.
checker.scope_stack = vec![GLOBAL_SCOPE_INDEX];
checker.pop_scope();
checker.check_dead_scopes();
checker.checks
}

View File

@@ -19,6 +19,8 @@ pub enum CheckCode {
F401,
F403,
F541,
F601,
F602,
F631,
F634,
F704,
@@ -50,6 +52,8 @@ impl FromStr for CheckCode {
"F401" => Ok(CheckCode::F401),
"F403" => Ok(CheckCode::F403),
"F541" => Ok(CheckCode::F541),
"F601" => Ok(CheckCode::F601),
"F602" => Ok(CheckCode::F602),
"F631" => Ok(CheckCode::F631),
"F634" => Ok(CheckCode::F634),
"F704" => Ok(CheckCode::F704),
@@ -82,6 +86,8 @@ impl CheckCode {
CheckCode::F401 => "F401",
CheckCode::F403 => "F403",
CheckCode::F541 => "F541",
CheckCode::F601 => "F601",
CheckCode::F602 => "F602",
CheckCode::F631 => "F631",
CheckCode::F634 => "F634",
CheckCode::F704 => "F704",
@@ -112,6 +118,8 @@ impl CheckCode {
CheckCode::F401 => &LintSource::AST,
CheckCode::F403 => &LintSource::AST,
CheckCode::F541 => &LintSource::AST,
CheckCode::F601 => &LintSource::AST,
CheckCode::F602 => &LintSource::AST,
CheckCode::F631 => &LintSource::AST,
CheckCode::F634 => &LintSource::AST,
CheckCode::F704 => &LintSource::AST,
@@ -154,6 +162,8 @@ pub enum CheckKind {
ImportStarUsage,
LineTooLong,
ModuleImportNotAtTopOfFile,
MultiValueRepeatedKeyLiteral,
MultiValueRepeatedKeyVariable(String),
NoAssertEquals,
NoneComparison(RejectedCmpop),
NotInTest,
@@ -184,6 +194,8 @@ impl CheckKind {
CheckKind::LineTooLong => "LineTooLong",
CheckKind::DoNotAssignLambda => "DoNotAssignLambda",
CheckKind::ModuleImportNotAtTopOfFile => "ModuleImportNotAtTopOfFile",
CheckKind::MultiValueRepeatedKeyLiteral => "MultiValueRepeatedKeyLiteral",
CheckKind::MultiValueRepeatedKeyVariable(_) => "MultiValueRepeatedKeyVariable",
CheckKind::NoAssertEquals => "NoAssertEquals",
CheckKind::NoneComparison(_) => "NoneComparison",
CheckKind::NotInTest => "NotInTest",
@@ -214,6 +226,8 @@ impl CheckKind {
CheckKind::LineTooLong => &CheckCode::E501,
CheckKind::DoNotAssignLambda => &CheckCode::E731,
CheckKind::ModuleImportNotAtTopOfFile => &CheckCode::E402,
CheckKind::MultiValueRepeatedKeyLiteral => &CheckCode::F601,
CheckKind::MultiValueRepeatedKeyVariable(_) => &CheckCode::F602,
CheckKind::NoAssertEquals => &CheckCode::R002,
CheckKind::NoneComparison(_) => &CheckCode::E711,
CheckKind::NotInTest => &CheckCode::E713,
@@ -258,6 +272,12 @@ impl CheckKind {
CheckKind::ModuleImportNotAtTopOfFile => {
"Module level import not at top of file".to_string()
}
CheckKind::MultiValueRepeatedKeyLiteral => {
"Dictionary key literal repeated".to_string()
}
CheckKind::MultiValueRepeatedKeyVariable(name) => {
format!("Dictionary key `{name}` repeated")
}
CheckKind::NoAssertEquals => {
"`assertEquals` is deprecated, use `assertEqual` instead".to_string()
}
@@ -328,6 +348,8 @@ impl CheckKind {
CheckKind::DoNotAssignLambda => false,
CheckKind::LineTooLong => false,
CheckKind::ModuleImportNotAtTopOfFile => false,
CheckKind::MultiValueRepeatedKeyLiteral => false,
CheckKind::MultiValueRepeatedKeyVariable(_) => false,
CheckKind::NoAssertEquals => true,
CheckKind::NotInTest => false,
CheckKind::NotIsTest => false,

View File

@@ -1,8 +1,8 @@
use crate::ast_ops::SourceCodeLocator;
use rustpython_parser::ast::{Expr, Keyword, Location};
use rustpython_parser::lexer;
use rustpython_parser::token::Tok;
use crate::ast_ops::SourceCodeLocator;
use crate::checks::Fix;
/// Convert a location within a file (relative to `base`) to an absolute position.

View File

@@ -13,5 +13,6 @@ pub mod linter;
pub mod logging;
pub mod message;
mod pyproject;
mod relocator;
pub mod settings;
mod visitor;

View File

@@ -90,7 +90,7 @@ mod tests {
#[test]
fn e402() -> Result<()> {
let actual = check_path(
let mut actual = check_path(
Path::new("./resources/test/fixtures/E402.py"),
&settings::Settings {
line_length: 88,
@@ -99,6 +99,7 @@ mod tests {
},
&autofix::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![Check {
kind: CheckKind::ModuleImportNotAtTopOfFile,
location: Location::new(20, 1),
@@ -114,7 +115,7 @@ mod tests {
#[test]
fn e501() -> Result<()> {
let actual = check_path(
let mut actual = check_path(
Path::new("./resources/test/fixtures/E501.py"),
&settings::Settings {
line_length: 88,
@@ -123,6 +124,7 @@ mod tests {
},
&autofix::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![Check {
kind: CheckKind::LineTooLong,
location: Location::new(5, 89),
@@ -138,7 +140,7 @@ mod tests {
#[test]
fn e711() -> Result<()> {
let actual = check_path(
let mut actual = check_path(
Path::new("./resources/test/fixtures/E711.py"),
&settings::Settings {
line_length: 88,
@@ -147,6 +149,7 @@ mod tests {
},
&autofix::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![
Check {
kind: CheckKind::NoneComparison(RejectedCmpop::Eq),
@@ -169,7 +172,7 @@ mod tests {
#[test]
fn e712() -> Result<()> {
let actual = check_path(
let mut actual = check_path(
Path::new("./resources/test/fixtures/E712.py"),
&settings::Settings {
line_length: 88,
@@ -178,6 +181,7 @@ mod tests {
},
&autofix::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![
Check {
kind: CheckKind::TrueFalseComparison(true, RejectedCmpop::Eq),
@@ -211,7 +215,7 @@ mod tests {
#[test]
fn e713() -> Result<()> {
let actual = check_path(
let mut actual = check_path(
Path::new("./resources/test/fixtures/E713.py"),
&settings::Settings {
line_length: 88,
@@ -220,6 +224,7 @@ mod tests {
},
&autofix::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![Check {
kind: CheckKind::NotInTest,
location: Location::new(2, 12),
@@ -235,7 +240,7 @@ mod tests {
#[test]
fn e714() -> Result<()> {
let actual = check_path(
let mut actual = check_path(
Path::new("./resources/test/fixtures/E714.py"),
&settings::Settings {
line_length: 88,
@@ -244,6 +249,7 @@ mod tests {
},
&autofix::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![Check {
kind: CheckKind::NotIsTest,
location: Location::new(1, 13),
@@ -259,7 +265,7 @@ mod tests {
#[test]
fn e731() -> Result<()> {
let actual = check_path(
let mut actual = check_path(
Path::new("./resources/test/fixtures/E731.py"),
&settings::Settings {
line_length: 88,
@@ -268,11 +274,19 @@ mod tests {
},
&autofix::Mode::Generate,
)?;
let expected = vec![Check {
kind: CheckKind::DoNotAssignLambda,
location: Location::new(1, 1),
fix: None,
}];
actual.sort_by_key(|check| check.location);
let expected = vec![
Check {
kind: CheckKind::DoNotAssignLambda,
location: Location::new(3, 1),
fix: None,
},
Check {
kind: CheckKind::DoNotAssignLambda,
location: Location::new(5, 1),
fix: None,
},
];
assert_eq!(actual.len(), expected.len());
for i in 0..actual.len() {
@@ -284,7 +298,7 @@ mod tests {
#[test]
fn f401() -> Result<()> {
let actual = check_path(
let mut actual = check_path(
Path::new("./resources/test/fixtures/F401.py"),
&settings::Settings {
line_length: 88,
@@ -293,12 +307,8 @@ mod tests {
},
&autofix::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![
Check {
kind: CheckKind::UnusedImport("logging.handlers".to_string()),
location: Location::new(12, 1),
fix: None,
},
Check {
kind: CheckKind::UnusedImport("functools".to_string()),
location: Location::new(3, 1),
@@ -306,7 +316,12 @@ mod tests {
},
Check {
kind: CheckKind::UnusedImport("collections.OrderedDict".to_string()),
location: Location::new(4, 1),
location: Location::new(5, 1),
fix: None,
},
Check {
kind: CheckKind::UnusedImport("logging.handlers".to_string()),
location: Location::new(13, 1),
fix: None,
},
];
@@ -320,7 +335,7 @@ mod tests {
#[test]
fn f403() -> Result<()> {
let actual = check_path(
let mut actual = check_path(
Path::new("./resources/test/fixtures/F403.py"),
&settings::Settings {
line_length: 88,
@@ -329,6 +344,7 @@ mod tests {
},
&autofix::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![
Check {
kind: CheckKind::ImportStarUsage,
@@ -350,7 +366,7 @@ mod tests {
}
#[test]
fn f541() -> Result<()> {
let actual = check_path(
let mut actual = check_path(
Path::new("./resources/test/fixtures/F541.py"),
&settings::Settings {
line_length: 88,
@@ -359,6 +375,7 @@ mod tests {
},
&autofix::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![
Check {
kind: CheckKind::FStringMissingPlaceholders,
@@ -385,8 +402,68 @@ mod tests {
}
#[test]
fn f631() -> Result<()> {
fn f601() -> Result<()> {
let actual = check_path(
Path::new("./resources/test/fixtures/F601.py"),
&settings::Settings {
line_length: 88,
exclude: vec![],
select: BTreeSet::from([CheckCode::F601]),
},
&autofix::Mode::Generate,
)?;
let expected = vec![
Check {
kind: CheckKind::MultiValueRepeatedKeyLiteral,
location: Location::new(3, 6),
fix: None,
},
Check {
kind: CheckKind::MultiValueRepeatedKeyLiteral,
location: Location::new(9, 5),
fix: None,
},
Check {
kind: CheckKind::MultiValueRepeatedKeyLiteral,
location: Location::new(11, 7),
fix: None,
},
];
assert_eq!(actual.len(), expected.len());
for i in 0..actual.len() {
assert_eq!(actual[i], expected[i]);
}
Ok(())
}
#[test]
fn f602() -> Result<()> {
let actual = check_path(
Path::new("./resources/test/fixtures/F602.py"),
&settings::Settings {
line_length: 88,
exclude: vec![],
select: BTreeSet::from([CheckCode::F602]),
},
&autofix::Mode::Generate,
)?;
let expected = vec![Check {
kind: CheckKind::MultiValueRepeatedKeyVariable("a".to_string()),
location: Location::new(5, 5),
fix: None,
}];
assert_eq!(actual.len(), expected.len());
for i in 0..actual.len() {
assert_eq!(actual[i], expected[i]);
}
Ok(())
}
#[test]
fn f631() -> Result<()> {
let mut actual = check_path(
Path::new("./resources/test/fixtures/F631.py"),
&settings::Settings {
line_length: 88,
@@ -395,6 +472,7 @@ mod tests {
},
&autofix::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![
Check {
kind: CheckKind::AssertTuple,
@@ -417,7 +495,7 @@ mod tests {
#[test]
fn f634() -> Result<()> {
let actual = check_path(
let mut actual = check_path(
Path::new("./resources/test/fixtures/F634.py"),
&settings::Settings {
line_length: 88,
@@ -426,6 +504,7 @@ mod tests {
},
&autofix::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![
Check {
kind: CheckKind::IfTuple,
@@ -448,7 +527,7 @@ mod tests {
#[test]
fn f704() -> Result<()> {
let actual = check_path(
let mut actual = check_path(
Path::new("./resources/test/fixtures/F704.py"),
&settings::Settings {
line_length: 88,
@@ -457,6 +536,7 @@ mod tests {
},
&autofix::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![
Check {
kind: CheckKind::YieldOutsideFunction,
@@ -484,7 +564,7 @@ mod tests {
#[test]
fn f706() -> Result<()> {
let actual = check_path(
let mut actual = check_path(
Path::new("./resources/test/fixtures/F706.py"),
&settings::Settings {
line_length: 88,
@@ -493,6 +573,7 @@ mod tests {
},
&autofix::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![
Check {
kind: CheckKind::ReturnOutsideFunction,
@@ -515,7 +596,7 @@ mod tests {
#[test]
fn f707() -> Result<()> {
let actual = check_path(
let mut actual = check_path(
Path::new("./resources/test/fixtures/F707.py"),
&settings::Settings {
line_length: 88,
@@ -524,6 +605,7 @@ mod tests {
},
&autofix::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![
Check {
kind: CheckKind::DefaultExceptNotLast,
@@ -551,7 +633,7 @@ mod tests {
#[test]
fn f821() -> Result<()> {
let actual = check_path(
let mut actual = check_path(
Path::new("./resources/test/fixtures/F821.py"),
&settings::Settings {
line_length: 88,
@@ -560,6 +642,7 @@ mod tests {
},
&autofix::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![
Check {
kind: CheckKind::UndefinedName("self".to_string()),
@@ -581,6 +664,11 @@ mod tests {
location: Location::new(21, 12),
fix: None,
},
Check {
kind: CheckKind::UndefinedName("Bar".to_string()),
location: Location::new(58, 5),
fix: None,
},
];
assert_eq!(actual.len(), expected.len());
for i in 0..actual.len() {
@@ -592,7 +680,7 @@ mod tests {
#[test]
fn f822() -> Result<()> {
let actual = check_path(
let mut actual = check_path(
Path::new("./resources/test/fixtures/F822.py"),
&settings::Settings {
line_length: 88,
@@ -601,6 +689,7 @@ mod tests {
},
&autofix::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![Check {
kind: CheckKind::UndefinedExport("b".to_string()),
location: Location::new(3, 1),
@@ -616,7 +705,7 @@ mod tests {
#[test]
fn f823() -> Result<()> {
let actual = check_path(
let mut actual = check_path(
Path::new("./resources/test/fixtures/F823.py"),
&settings::Settings {
line_length: 88,
@@ -625,6 +714,7 @@ mod tests {
},
&autofix::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![Check {
kind: CheckKind::UndefinedLocal("my_var".to_string()),
location: Location::new(6, 5),
@@ -640,7 +730,7 @@ mod tests {
#[test]
fn f831() -> Result<()> {
let actual = check_path(
let mut actual = check_path(
Path::new("./resources/test/fixtures/F831.py"),
&settings::Settings {
line_length: 88,
@@ -649,6 +739,7 @@ mod tests {
},
&autofix::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![
Check {
kind: CheckKind::DuplicateArgumentName,
@@ -676,7 +767,7 @@ mod tests {
#[test]
fn f841() -> Result<()> {
let actual = check_path(
let mut actual = check_path(
Path::new("./resources/test/fixtures/F841.py"),
&settings::Settings {
line_length: 88,
@@ -685,6 +776,7 @@ mod tests {
},
&autofix::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![
Check {
kind: CheckKind::UnusedVariable("e".to_string()),
@@ -707,7 +799,7 @@ mod tests {
#[test]
fn f901() -> Result<()> {
let actual = check_path(
let mut actual = check_path(
Path::new("./resources/test/fixtures/F901.py"),
&settings::Settings {
line_length: 88,
@@ -716,6 +808,7 @@ mod tests {
},
&autofix::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![
Check {
kind: CheckKind::RaiseNotImplemented,
@@ -738,7 +831,7 @@ mod tests {
#[test]
fn r001() -> Result<()> {
let actual = check_path(
let mut actual = check_path(
Path::new("./resources/test/fixtures/R001.py"),
&settings::Settings {
line_length: 88,
@@ -747,6 +840,7 @@ mod tests {
},
&autofix::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![
Check {
kind: CheckKind::UselessObjectInheritance("A".to_string()),
@@ -959,7 +1053,7 @@ mod tests {
#[test]
fn r002() -> Result<()> {
let actual = check_path(
let mut actual = check_path(
Path::new("./resources/test/fixtures/R002.py"),
&settings::Settings {
line_length: 88,
@@ -968,6 +1062,7 @@ mod tests {
},
&autofix::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![
Check {
kind: CheckKind::NoAssertEquals,

View File

@@ -12,13 +12,13 @@ use rayon::prelude::*;
use walkdir::DirEntry;
use ::ruff::checks::CheckCode;
use ::ruff::checks::CheckKind;
use ::ruff::fs::iter_python_files;
use ::ruff::linter::lint_path;
use ::ruff::logging::set_up_logging;
use ::ruff::message::Message;
use ::ruff::settings::Settings;
use ::ruff::tell_user;
use ruff::checks::CheckKind;
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -55,6 +55,30 @@ struct Cli {
ignore: Vec<CheckCode>,
}
#[cfg(feature = "update-informer")]
fn check_for_updates() {
use update_informer::{registry, Check};
let informer = update_informer::new(registry::PyPI, CARGO_PKG_NAME, CARGO_PKG_VERSION);
if let Some(new_version) = informer.check_version().ok().flatten() {
let msg = format!(
"A new version of {pkg_name} is available: v{pkg_version} -> {new_version}",
pkg_name = CARGO_PKG_NAME.italic().cyan(),
pkg_version = CARGO_PKG_VERSION,
new_version = new_version.to_string().green()
);
let cmd = format!(
"Run to update: {cmd} {pkg_name}",
cmd = "pip3 install --upgrade".green(),
pkg_name = CARGO_PKG_NAME.green()
);
println!("\n{msg}\n{cmd}");
}
}
fn run_once(
files: &[PathBuf],
settings: &Settings,
@@ -210,6 +234,7 @@ fn inner_main() -> Result<ExitCode> {
report_once(&messages)?;
}
#[cfg(feature = "update-informer")]
check_for_updates();
if !messages.is_empty() && !cli.exit_zero {
@@ -220,29 +245,6 @@ fn inner_main() -> Result<ExitCode> {
Ok(ExitCode::SUCCESS)
}
fn check_for_updates() {
use update_informer::{registry, Check};
let informer = update_informer::new(registry::PyPI, CARGO_PKG_NAME, CARGO_PKG_VERSION);
if let Some(new_version) = informer.check_version().ok().flatten() {
let msg = format!(
"A new version of {pkg_name} is available: v{pkg_version} -> {new_version}",
pkg_name = CARGO_PKG_NAME.italic().cyan(),
pkg_version = CARGO_PKG_VERSION,
new_version = new_version.to_string().green()
);
let cmd = format!(
"Run to update: {cmd} {pkg_name}",
cmd = "pip3 install --upgrade".green(),
pkg_name = CARGO_PKG_NAME.green()
);
println!("\n{msg}\n{cmd}");
}
}
fn main() -> ExitCode {
match inner_main() {
Ok(code) => code,

View File

@@ -1,4 +1,3 @@
use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
use anyhow::Result;
@@ -40,7 +39,8 @@ pub fn load_config<'a>(paths: impl IntoIterator<Item = &'a Path>) -> Result<(Pat
pub struct Config {
pub line_length: Option<usize>,
pub exclude: Option<Vec<PathBuf>>,
pub select: Option<BTreeSet<CheckCode>>,
pub select: Option<Vec<CheckCode>>,
pub ignore: Option<Vec<CheckCode>>,
}
#[derive(Debug, PartialEq, Eq, Deserialize)]
@@ -90,7 +90,6 @@ fn find_project_root<'a>(sources: impl IntoIterator<Item = &'a Path>) -> Option<
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use std::path::Path;
use anyhow::Result;
@@ -125,6 +124,7 @@ mod tests {
line_length: None,
exclude: None,
select: None,
ignore: None,
})
})
);
@@ -143,6 +143,7 @@ line-length = 79
line_length: Some(79),
exclude: None,
select: None,
ignore: None,
})
})
);
@@ -161,6 +162,7 @@ exclude = ["foo.py"]
line_length: None,
exclude: Some(vec![Path::new("foo.py").to_path_buf()]),
select: None,
ignore: None,
})
})
);
@@ -178,7 +180,27 @@ select = ["E501"]
ruff: Some(Config {
line_length: None,
exclude: None,
select: Some(BTreeSet::from([CheckCode::E501])),
select: Some(vec![CheckCode::E501]),
ignore: None,
})
})
);
let pyproject: PyProject = toml::from_str(
r#"
[tool.black]
[tool.ruff]
ignore = ["E501"]
"#,
)?;
assert_eq!(
pyproject.tool,
Some(Tools {
ruff: Some(Config {
line_length: None,
exclude: None,
select: None,
ignore: Some(vec![CheckCode::E501]),
})
})
);
@@ -236,7 +258,7 @@ other-attribute = 1
Path::new("excluded.py").to_path_buf(),
Path::new("**/migrations").to_path_buf()
]),
select: Some(BTreeSet::from([
select: Some(vec![
CheckCode::E402,
CheckCode::E501,
CheckCode::E711,
@@ -248,6 +270,8 @@ other-attribute = 1
CheckCode::F401,
CheckCode::F403,
CheckCode::F541,
CheckCode::F601,
CheckCode::F602,
CheckCode::F631,
CheckCode::F634,
CheckCode::F704,
@@ -261,7 +285,8 @@ other-attribute = 1
CheckCode::F901,
CheckCode::R001,
CheckCode::R002,
])),
]),
ignore: None,
}
);

137
src/relocator.rs Normal file
View File

@@ -0,0 +1,137 @@
use rustpython_parser::ast::{Expr, ExprKind, Keyword, Location};
fn relocate_keyword(keyword: &mut Keyword, location: Location) {
keyword.location = location;
relocate_expr(&mut keyword.node.value, location);
}
/// Change an expression's location (recursively) to match a desired, fixed location.
pub fn relocate_expr(expr: &mut Expr, location: Location) {
expr.location = location;
match &mut expr.node {
ExprKind::BoolOp { values, .. } => {
for expr in values {
relocate_expr(expr, location);
}
}
ExprKind::NamedExpr { target, value } => {
relocate_expr(target, location);
relocate_expr(value, location);
}
ExprKind::BinOp { left, right, .. } => {
relocate_expr(left, location);
relocate_expr(right, location);
}
ExprKind::UnaryOp { operand, .. } => {
relocate_expr(operand, location);
}
ExprKind::Lambda { body, .. } => {
relocate_expr(body, location);
}
ExprKind::IfExp { test, body, orelse } => {
relocate_expr(test, location);
relocate_expr(body, location);
relocate_expr(orelse, location);
}
ExprKind::Dict { keys, values } => {
for expr in keys {
relocate_expr(expr, location);
}
for expr in values {
relocate_expr(expr, location);
}
}
ExprKind::Set { elts } => {
for expr in elts {
relocate_expr(expr, location);
}
}
ExprKind::ListComp { elt, .. } => {
relocate_expr(elt, location);
}
ExprKind::SetComp { elt, .. } => {
relocate_expr(elt, location);
}
ExprKind::DictComp { key, value, .. } => {
relocate_expr(key, location);
relocate_expr(value, location);
}
ExprKind::GeneratorExp { elt, .. } => {
relocate_expr(elt, location);
}
ExprKind::Await { value } => relocate_expr(value, location),
ExprKind::Yield { value } => {
if let Some(expr) = value {
relocate_expr(expr, location);
}
}
ExprKind::YieldFrom { value } => relocate_expr(value, location),
ExprKind::Compare {
left, comparators, ..
} => {
relocate_expr(left, location);
for expr in comparators {
relocate_expr(expr, location);
}
}
ExprKind::Call {
func,
args,
keywords,
} => {
relocate_expr(func, location);
for expr in args {
relocate_expr(expr, location);
}
for keyword in keywords {
relocate_keyword(keyword, location);
}
}
ExprKind::FormattedValue {
value, format_spec, ..
} => {
relocate_expr(value, location);
if let Some(expr) = format_spec {
relocate_expr(expr, location);
}
}
ExprKind::JoinedStr { values } => {
for expr in values {
relocate_expr(expr, location);
}
}
ExprKind::Constant { .. } => {}
ExprKind::Attribute { value, .. } => {
relocate_expr(value, location);
}
ExprKind::Subscript { value, slice, .. } => {
relocate_expr(value, location);
relocate_expr(slice, location);
}
ExprKind::Starred { value, .. } => {
relocate_expr(value, location);
}
ExprKind::Name { .. } => {}
ExprKind::List { elts, .. } => {
for expr in elts {
relocate_expr(expr, location);
}
}
ExprKind::Tuple { elts, .. } => {
for expr in elts {
relocate_expr(expr, location);
}
}
ExprKind::Slice { lower, upper, step } => {
if let Some(expr) = lower {
relocate_expr(expr, location);
}
if let Some(expr) = upper {
relocate_expr(expr, location);
}
if let Some(expr) = step {
relocate_expr(expr, location);
}
}
}
}

View File

@@ -27,7 +27,7 @@ impl Hash for Settings {
impl Settings {
pub fn from_paths<'a>(paths: impl IntoIterator<Item = &'a Path>) -> Result<Self> {
let (project_root, config) = load_config(paths)?;
Ok(Settings {
let mut settings = Settings {
line_length: config.line_length.unwrap_or(88),
exclude: config
.exclude
@@ -42,8 +42,8 @@ impl Settings {
})
.map(|path| Pattern::new(&path.to_string_lossy()).expect("Invalid pattern."))
.collect(),
select: config.select.unwrap_or_else(|| {
BTreeSet::from([
select: BTreeSet::from_iter(config.select.unwrap_or_else(|| {
vec![
CheckCode::E402,
CheckCode::E501,
CheckCode::E711,
@@ -55,6 +55,8 @@ impl Settings {
CheckCode::F401,
CheckCode::F403,
CheckCode::F541,
CheckCode::F601,
CheckCode::F602,
CheckCode::F631,
CheckCode::F634,
CheckCode::F704,
@@ -69,9 +71,13 @@ impl Settings {
// Disable refactoring codes by default.
// CheckCode::R001,
// CheckCode::R002,
])
}),
})
]
})),
};
if let Some(ignore) = &config.ignore {
settings.ignore(ignore);
}
Ok(settings)
}
pub fn select(&mut self, codes: Vec<CheckCode>) {

View File

@@ -4,102 +4,141 @@ use rustpython_parser::ast::{
PatternKind, Stmt, StmtKind, Unaryop, Withitem,
};
pub trait Visitor {
fn visit_stmt(&mut self, stmt: &Stmt) {
pub trait Visitor<'a> {
fn visit_stmt(&mut self, stmt: &'a Stmt) {
walk_stmt(self, stmt);
}
fn visit_annotation(&mut self, expr: &Expr) {
fn visit_annotation(&mut self, expr: &'a Expr) {
walk_expr(self, expr);
}
fn visit_expr(&mut self, expr: &Expr, _parent: Option<&Stmt>) {
fn visit_expr(&mut self, expr: &'a Expr) {
walk_expr(self, expr);
}
fn visit_constant(&mut self, constant: &Constant) {
fn visit_constant(&mut self, constant: &'a Constant) {
walk_constant(self, constant);
}
fn visit_expr_context(&mut self, expr_content: &ExprContext) {
fn visit_expr_context(&mut self, expr_content: &'a ExprContext) {
walk_expr_context(self, expr_content);
}
fn visit_boolop(&mut self, boolop: &Boolop) {
fn visit_boolop(&mut self, boolop: &'a Boolop) {
walk_boolop(self, boolop);
}
fn visit_operator(&mut self, operator: &Operator) {
fn visit_operator(&mut self, operator: &'a Operator) {
walk_operator(self, operator);
}
fn visit_unaryop(&mut self, unaryop: &Unaryop) {
fn visit_unaryop(&mut self, unaryop: &'a Unaryop) {
walk_unaryop(self, unaryop);
}
fn visit_cmpop(&mut self, cmpop: &Cmpop) {
fn visit_cmpop(&mut self, cmpop: &'a Cmpop) {
walk_cmpop(self, cmpop);
}
fn visit_comprehension(&mut self, comprehension: &Comprehension) {
fn visit_comprehension(&mut self, comprehension: &'a Comprehension) {
walk_comprehension(self, comprehension);
}
fn visit_excepthandler(&mut self, excepthandler: &Excepthandler) {
fn visit_excepthandler(&mut self, excepthandler: &'a Excepthandler) {
walk_excepthandler(self, excepthandler);
}
fn visit_arguments(&mut self, arguments: &Arguments) {
fn visit_arguments(&mut self, arguments: &'a Arguments) {
walk_arguments(self, arguments);
}
fn visit_arg(&mut self, arg: &Arg) {
fn visit_arg(&mut self, arg: &'a Arg) {
walk_arg(self, arg);
}
fn visit_keyword(&mut self, keyword: &Keyword) {
fn visit_keyword(&mut self, keyword: &'a Keyword) {
walk_keyword(self, keyword);
}
fn visit_alias(&mut self, alias: &Alias) {
fn visit_alias(&mut self, alias: &'a Alias) {
walk_alias(self, alias);
}
fn visit_withitem(&mut self, withitem: &Withitem) {
fn visit_withitem(&mut self, withitem: &'a Withitem) {
walk_withitem(self, withitem);
}
fn visit_match_case(&mut self, match_case: &MatchCase) {
fn visit_match_case(&mut self, match_case: &'a MatchCase) {
walk_match_case(self, match_case);
}
fn visit_pattern(&mut self, pattern: &Pattern) {
fn visit_pattern(&mut self, pattern: &'a Pattern) {
walk_pattern(self, pattern);
}
}
pub fn walk_stmt<V: Visitor + ?Sized>(visitor: &mut V, stmt: &Stmt) {
pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
match &stmt.node {
StmtKind::FunctionDef { args, body, .. } => {
StmtKind::FunctionDef {
args,
body,
decorator_list,
returns,
..
} => {
visitor.visit_arguments(args);
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)
visitor.visit_stmt(stmt);
}
}
StmtKind::AsyncFunctionDef { args, body, .. } => {
StmtKind::AsyncFunctionDef {
args,
body,
decorator_list,
returns,
..
} => {
visitor.visit_arguments(args);
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)
visitor.visit_stmt(stmt);
}
}
StmtKind::ClassDef { body, .. } => {
StmtKind::ClassDef {
bases,
keywords,
body,
decorator_list,
..
} => {
for expr in bases {
visitor.visit_expr(expr);
}
for keyword in keywords {
visitor.visit_keyword(keyword);
}
for expr in decorator_list {
visitor.visit_expr(expr);
}
for stmt in body {
visitor.visit_stmt(stmt)
visitor.visit_stmt(stmt);
}
}
StmtKind::Return { value } => {
if let Some(expr) = value {
visitor.visit_expr(expr, Some(stmt))
visitor.visit_expr(expr);
}
}
StmtKind::Delete { targets } => {
for expr in targets {
visitor.visit_expr(expr, Some(stmt))
visitor.visit_expr(expr);
}
}
StmtKind::Assign { targets, value, .. } => {
visitor.visit_expr(value);
for expr in targets {
visitor.visit_expr(expr, Some(stmt))
visitor.visit_expr(expr);
}
visitor.visit_expr(value, Some(stmt))
}
StmtKind::AugAssign { target, op, value } => {
visitor.visit_expr(target, Some(stmt));
visitor.visit_expr(target);
visitor.visit_operator(op);
visitor.visit_expr(value, Some(stmt));
visitor.visit_expr(value);
}
StmtKind::AnnAssign {
target,
@@ -107,11 +146,11 @@ pub fn walk_stmt<V: Visitor + ?Sized>(visitor: &mut V, stmt: &Stmt) {
value,
..
} => {
visitor.visit_expr(target, Some(stmt));
visitor.visit_annotation(annotation);
if let Some(expr) = value {
visitor.visit_expr(expr, Some(stmt))
visitor.visit_expr(expr);
}
visitor.visit_expr(target);
}
StmtKind::For {
target,
@@ -120,13 +159,13 @@ pub fn walk_stmt<V: Visitor + ?Sized>(visitor: &mut V, stmt: &Stmt) {
orelse,
..
} => {
visitor.visit_expr(target, Some(stmt));
visitor.visit_expr(iter, Some(stmt));
visitor.visit_expr(target);
visitor.visit_expr(iter);
for stmt in body {
visitor.visit_stmt(stmt)
visitor.visit_stmt(stmt);
}
for stmt in orelse {
visitor.visit_stmt(stmt)
visitor.visit_stmt(stmt);
}
}
StmtKind::AsyncFor {
@@ -136,31 +175,31 @@ pub fn walk_stmt<V: Visitor + ?Sized>(visitor: &mut V, stmt: &Stmt) {
orelse,
..
} => {
visitor.visit_expr(target, Some(stmt));
visitor.visit_expr(iter, Some(stmt));
visitor.visit_expr(target);
visitor.visit_expr(iter);
for stmt in body {
visitor.visit_stmt(stmt)
visitor.visit_stmt(stmt);
}
for stmt in orelse {
visitor.visit_stmt(stmt)
visitor.visit_stmt(stmt);
}
}
StmtKind::While { test, body, orelse } => {
visitor.visit_expr(test, Some(stmt));
visitor.visit_expr(test);
for stmt in body {
visitor.visit_stmt(stmt)
visitor.visit_stmt(stmt);
}
for stmt in orelse {
visitor.visit_stmt(stmt)
visitor.visit_stmt(stmt);
}
}
StmtKind::If { test, body, orelse } => {
visitor.visit_expr(test, Some(stmt));
visitor.visit_expr(test);
for stmt in body {
visitor.visit_stmt(stmt)
visitor.visit_stmt(stmt);
}
for stmt in orelse {
visitor.visit_stmt(stmt)
visitor.visit_stmt(stmt);
}
}
StmtKind::With { items, body, .. } => {
@@ -168,7 +207,7 @@ pub fn walk_stmt<V: Visitor + ?Sized>(visitor: &mut V, stmt: &Stmt) {
visitor.visit_withitem(withitem);
}
for stmt in body {
visitor.visit_stmt(stmt)
visitor.visit_stmt(stmt);
}
}
StmtKind::AsyncWith { items, body, .. } => {
@@ -176,22 +215,22 @@ pub fn walk_stmt<V: Visitor + ?Sized>(visitor: &mut V, stmt: &Stmt) {
visitor.visit_withitem(withitem);
}
for stmt in body {
visitor.visit_stmt(stmt)
visitor.visit_stmt(stmt);
}
}
StmtKind::Match { subject, cases } => {
// TODO(charlie): Handle `cases`.
visitor.visit_expr(subject, Some(stmt));
visitor.visit_expr(subject);
for match_case in cases {
visitor.visit_match_case(match_case);
}
}
StmtKind::Raise { exc, cause } => {
if let Some(expr) = exc {
visitor.visit_expr(expr, Some(stmt))
visitor.visit_expr(expr);
};
if let Some(expr) = cause {
visitor.visit_expr(expr, Some(stmt))
visitor.visit_expr(expr);
};
}
StmtKind::Try {
@@ -201,22 +240,22 @@ pub fn walk_stmt<V: Visitor + ?Sized>(visitor: &mut V, stmt: &Stmt) {
finalbody,
} => {
for stmt in body {
visitor.visit_stmt(stmt)
visitor.visit_stmt(stmt);
}
for excepthandler in handlers {
visitor.visit_excepthandler(excepthandler)
}
for stmt in orelse {
visitor.visit_stmt(stmt)
visitor.visit_stmt(stmt);
}
for stmt in finalbody {
visitor.visit_stmt(stmt)
visitor.visit_stmt(stmt);
}
}
StmtKind::Assert { test, msg } => {
visitor.visit_expr(test, None);
visitor.visit_expr(test);
if let Some(expr) = msg {
visitor.visit_expr(expr, Some(stmt))
visitor.visit_expr(expr);
}
}
StmtKind::Import { names } => {
@@ -231,67 +270,67 @@ pub fn walk_stmt<V: Visitor + ?Sized>(visitor: &mut V, stmt: &Stmt) {
}
StmtKind::Global { .. } => {}
StmtKind::Nonlocal { .. } => {}
StmtKind::Expr { value } => visitor.visit_expr(value, Some(stmt)),
StmtKind::Expr { value } => visitor.visit_expr(value),
StmtKind::Pass => {}
StmtKind::Break => {}
StmtKind::Continue => {}
}
}
pub fn walk_expr<V: Visitor + ?Sized>(visitor: &mut V, expr: &Expr) {
pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) {
match &expr.node {
ExprKind::BoolOp { op, values } => {
visitor.visit_boolop(op);
for expr in values {
visitor.visit_expr(expr, None)
visitor.visit_expr(expr);
}
}
ExprKind::NamedExpr { target, value } => {
visitor.visit_expr(target, None);
visitor.visit_expr(value, None);
visitor.visit_expr(target);
visitor.visit_expr(value);
}
ExprKind::BinOp { left, op, right } => {
visitor.visit_expr(left, None);
visitor.visit_expr(left);
visitor.visit_operator(op);
visitor.visit_expr(right, None);
visitor.visit_expr(right);
}
ExprKind::UnaryOp { op, operand } => {
visitor.visit_unaryop(op);
visitor.visit_expr(operand, None);
visitor.visit_expr(operand);
}
ExprKind::Lambda { args, body } => {
visitor.visit_arguments(args);
visitor.visit_expr(body, None);
visitor.visit_expr(body);
}
ExprKind::IfExp { test, body, orelse } => {
visitor.visit_expr(test, None);
visitor.visit_expr(body, None);
visitor.visit_expr(orelse, None);
visitor.visit_expr(test);
visitor.visit_expr(body);
visitor.visit_expr(orelse);
}
ExprKind::Dict { keys, values } => {
for expr in keys {
visitor.visit_expr(expr, None)
visitor.visit_expr(expr);
}
for expr in values {
visitor.visit_expr(expr, None)
visitor.visit_expr(expr);
}
}
ExprKind::Set { elts } => {
for expr in elts {
visitor.visit_expr(expr, None)
visitor.visit_expr(expr);
}
}
ExprKind::ListComp { elt, generators } => {
for comprehension in generators {
visitor.visit_comprehension(comprehension)
visitor.visit_comprehension(comprehension);
}
visitor.visit_expr(elt, None);
visitor.visit_expr(elt);
}
ExprKind::SetComp { elt, generators } => {
for comprehension in generators {
visitor.visit_comprehension(comprehension)
visitor.visit_comprehension(comprehension);
}
visitor.visit_expr(elt, None);
visitor.visit_expr(elt);
}
ExprKind::DictComp {
key,
@@ -299,35 +338,35 @@ pub fn walk_expr<V: Visitor + ?Sized>(visitor: &mut V, expr: &Expr) {
generators,
} => {
for comprehension in generators {
visitor.visit_comprehension(comprehension)
visitor.visit_comprehension(comprehension);
}
visitor.visit_expr(key, None);
visitor.visit_expr(value, None);
visitor.visit_expr(key);
visitor.visit_expr(value);
}
ExprKind::GeneratorExp { elt, generators } => {
for comprehension in generators {
visitor.visit_comprehension(comprehension)
visitor.visit_comprehension(comprehension);
}
visitor.visit_expr(elt, None);
visitor.visit_expr(elt);
}
ExprKind::Await { value } => visitor.visit_expr(value, None),
ExprKind::Await { value } => visitor.visit_expr(value),
ExprKind::Yield { value } => {
if let Some(expr) = value {
visitor.visit_expr(expr, None)
visitor.visit_expr(expr);
}
}
ExprKind::YieldFrom { value } => visitor.visit_expr(value, None),
ExprKind::YieldFrom { value } => visitor.visit_expr(value),
ExprKind::Compare {
left,
ops,
comparators,
} => {
visitor.visit_expr(left, None);
visitor.visit_expr(left);
for cmpop in ops {
visitor.visit_cmpop(cmpop);
}
for expr in comparators {
visitor.visit_expr(expr, None)
visitor.visit_expr(expr);
}
}
ExprKind::Call {
@@ -335,9 +374,9 @@ pub fn walk_expr<V: Visitor + ?Sized>(visitor: &mut V, expr: &Expr) {
args,
keywords,
} => {
visitor.visit_expr(func, None);
visitor.visit_expr(func);
for expr in args {
visitor.visit_expr(expr, None);
visitor.visit_expr(expr);
}
for keyword in keywords {
visitor.visit_keyword(keyword);
@@ -346,28 +385,28 @@ pub fn walk_expr<V: Visitor + ?Sized>(visitor: &mut V, expr: &Expr) {
ExprKind::FormattedValue {
value, format_spec, ..
} => {
visitor.visit_expr(value, None);
visitor.visit_expr(value);
if let Some(expr) = format_spec {
visitor.visit_expr(expr, None)
visitor.visit_expr(expr);
}
}
ExprKind::JoinedStr { values } => {
for expr in values {
visitor.visit_expr(expr, None)
visitor.visit_expr(expr);
}
}
ExprKind::Constant { value, .. } => visitor.visit_constant(value),
ExprKind::Attribute { value, ctx, .. } => {
visitor.visit_expr(value, None);
visitor.visit_expr(value);
visitor.visit_expr_context(ctx);
}
ExprKind::Subscript { value, slice, ctx } => {
visitor.visit_expr(value, None);
visitor.visit_expr(slice, None);
visitor.visit_expr(value);
visitor.visit_expr(slice);
visitor.visit_expr_context(ctx);
}
ExprKind::Starred { value, ctx } => {
visitor.visit_expr(value, None);
visitor.visit_expr(value);
visitor.visit_expr_context(ctx);
}
ExprKind::Name { ctx, .. } => {
@@ -375,31 +414,31 @@ pub fn walk_expr<V: Visitor + ?Sized>(visitor: &mut V, expr: &Expr) {
}
ExprKind::List { elts, ctx } => {
for expr in elts {
visitor.visit_expr(expr, None);
visitor.visit_expr(expr);
}
visitor.visit_expr_context(ctx);
}
ExprKind::Tuple { elts, ctx } => {
for expr in elts {
visitor.visit_expr(expr, None);
visitor.visit_expr(expr);
}
visitor.visit_expr_context(ctx);
}
ExprKind::Slice { lower, upper, step } => {
if let Some(expr) = lower {
visitor.visit_expr(expr, None);
visitor.visit_expr(expr);
}
if let Some(expr) = upper {
visitor.visit_expr(expr, None);
visitor.visit_expr(expr);
}
if let Some(expr) = step {
visitor.visit_expr(expr, None);
visitor.visit_expr(expr);
}
}
}
}
pub fn walk_constant<V: Visitor + ?Sized>(visitor: &mut V, constant: &Constant) {
pub fn walk_constant<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, constant: &'a Constant) {
if let Constant::Tuple(constants) = constant {
for constant in constants {
visitor.visit_constant(constant)
@@ -407,19 +446,25 @@ 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, None);
visitor.visit_expr(&comprehension.iter, None);
pub fn walk_comprehension<'a, V: Visitor<'a> + ?Sized>(
visitor: &mut V,
comprehension: &'a Comprehension,
) {
visitor.visit_expr(&comprehension.target);
visitor.visit_expr(&comprehension.iter);
for expr in &comprehension.ifs {
visitor.visit_expr(expr, None);
visitor.visit_expr(expr);
}
}
pub fn walk_excepthandler<V: Visitor + ?Sized>(visitor: &mut V, excepthandler: &Excepthandler) {
pub fn walk_excepthandler<'a, V: Visitor<'a> + ?Sized>(
visitor: &mut V,
excepthandler: &'a Excepthandler,
) {
match &excepthandler.node {
ExcepthandlerKind::ExceptHandler { type_, body, .. } => {
if let Some(expr) = type_ {
visitor.visit_expr(expr, None);
visitor.visit_expr(expr);
}
for stmt in body {
visitor.visit_stmt(stmt);
@@ -428,7 +473,7 @@ pub fn walk_excepthandler<V: Visitor + ?Sized>(visitor: &mut V, excepthandler: &
}
}
pub fn walk_arguments<V: Visitor + ?Sized>(visitor: &mut V, arguments: &Arguments) {
pub fn walk_arguments<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, arguments: &'a Arguments) {
for arg in &arguments.posonlyargs {
visitor.visit_arg(arg);
}
@@ -436,61 +481,61 @@ pub fn walk_arguments<V: Visitor + ?Sized>(visitor: &mut V, arguments: &Argument
visitor.visit_arg(arg);
}
if let Some(arg) = &arguments.vararg {
visitor.visit_arg(arg)
visitor.visit_arg(arg);
}
for arg in &arguments.kwonlyargs {
visitor.visit_arg(arg);
}
for expr in &arguments.kw_defaults {
visitor.visit_expr(expr, None)
visitor.visit_expr(expr);
}
if let Some(arg) = &arguments.kwarg {
visitor.visit_arg(arg)
visitor.visit_arg(arg);
}
for expr in &arguments.defaults {
visitor.visit_expr(expr, None)
visitor.visit_expr(expr);
}
}
pub fn walk_arg<V: Visitor + ?Sized>(visitor: &mut V, arg: &Arg) {
pub fn walk_arg<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, arg: &'a Arg) {
if let Some(expr) = &arg.node.annotation {
visitor.visit_annotation(expr)
visitor.visit_annotation(expr);
}
}
pub fn walk_keyword<V: Visitor + ?Sized>(visitor: &mut V, keyword: &Keyword) {
visitor.visit_expr(&keyword.node.value, None);
pub fn walk_keyword<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, keyword: &'a Keyword) {
visitor.visit_expr(&keyword.node.value);
}
pub fn walk_withitem<V: Visitor + ?Sized>(visitor: &mut V, withitem: &Withitem) {
visitor.visit_expr(&withitem.context_expr, None);
pub fn walk_withitem<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, withitem: &'a Withitem) {
visitor.visit_expr(&withitem.context_expr);
if let Some(expr) = &withitem.optional_vars {
visitor.visit_expr(expr, None);
visitor.visit_expr(expr);
}
}
pub fn walk_match_case<V: Visitor + ?Sized>(visitor: &mut V, match_case: &MatchCase) {
pub fn walk_match_case<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, match_case: &'a MatchCase) {
visitor.visit_pattern(&match_case.pattern);
if let Some(expr) = &match_case.guard {
visitor.visit_expr(expr, None);
visitor.visit_expr(expr);
}
for stmt in &match_case.body {
visitor.visit_stmt(stmt);
}
}
pub fn walk_pattern<V: Visitor + ?Sized>(visitor: &mut V, pattern: &Pattern) {
pub fn walk_pattern<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, pattern: &'a Pattern) {
match &pattern.node {
PatternKind::MatchValue { value } => visitor.visit_expr(value, None),
PatternKind::MatchValue { value } => visitor.visit_expr(value),
PatternKind::MatchSingleton { value } => visitor.visit_constant(value),
PatternKind::MatchSequence { patterns } => {
for pattern in patterns {
visitor.visit_pattern(pattern)
visitor.visit_pattern(pattern);
}
}
PatternKind::MatchMapping { keys, patterns, .. } => {
for expr in keys {
visitor.visit_expr(expr, None);
visitor.visit_expr(expr);
}
for pattern in patterns {
visitor.visit_pattern(pattern);
@@ -502,7 +547,7 @@ pub fn walk_pattern<V: Visitor + ?Sized>(visitor: &mut V, pattern: &Pattern) {
kwd_patterns,
..
} => {
visitor.visit_expr(cls, None);
visitor.visit_expr(cls);
for pattern in patterns {
visitor.visit_pattern(pattern);
}
@@ -514,7 +559,7 @@ pub fn walk_pattern<V: Visitor + ?Sized>(visitor: &mut V, pattern: &Pattern) {
PatternKind::MatchStar { .. } => {}
PatternKind::MatchAs { pattern, .. } => {
if let Some(pattern) = pattern {
visitor.visit_pattern(pattern)
visitor.visit_pattern(pattern);
}
}
PatternKind::MatchOr { patterns } => {
@@ -527,24 +572,28 @@ pub fn walk_pattern<V: Visitor + ?Sized>(visitor: &mut V, pattern: &Pattern) {
#[allow(unused_variables)]
#[inline(always)]
pub fn walk_expr_context<V: Visitor + ?Sized>(visitor: &mut V, expr_context: &ExprContext) {}
pub fn walk_expr_context<'a, V: Visitor<'a> + ?Sized>(
visitor: &mut V,
expr_context: &'a ExprContext,
) {
}
#[allow(unused_variables)]
#[inline(always)]
pub fn walk_boolop<V: Visitor + ?Sized>(visitor: &mut V, boolop: &Boolop) {}
pub fn walk_boolop<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, boolop: &'a Boolop) {}
#[allow(unused_variables)]
#[inline(always)]
pub fn walk_operator<V: Visitor + ?Sized>(visitor: &mut V, operator: &Operator) {}
pub fn walk_operator<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, operator: &'a Operator) {}
#[allow(unused_variables)]
#[inline(always)]
pub fn walk_unaryop<V: Visitor + ?Sized>(visitor: &mut V, unaryop: &Unaryop) {}
pub fn walk_unaryop<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, unaryop: &'a Unaryop) {}
#[allow(unused_variables)]
#[inline(always)]
pub fn walk_cmpop<V: Visitor + ?Sized>(visitor: &mut V, cmpop: &Cmpop) {}
pub fn walk_cmpop<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, cmpop: &'a Cmpop) {}
#[allow(unused_variables)]
#[inline(always)]
pub fn walk_alias<V: Visitor + ?Sized>(visitor: &mut V, alias: &Alias) {}
pub fn walk_alias<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, alias: &'a Alias) {}