Compare commits

..

7 Commits

Author SHA1 Message Date
Charlie Marsh
82cc139d2d Bump version to 0.0.57 2022-10-06 09:16:56 -04:00
Adrian Garcia Badaracco
df438ba051 Support PEP 593 annotations (#333) 2022-10-06 09:16:07 -04:00
Adrian Garcia Badaracco
a70624cd47 add instructions for setting up cargo insta (#334) 2022-10-06 08:01:11 -04:00
Charlie Marsh
b307afc00c Move some accesses behind a shared function 2022-10-05 14:54:31 -04:00
Charlie Marsh
3d5bc1f51f Migrate Checker logic to independent plugins (#331) 2022-10-05 14:08:40 -04:00
Charlie Marsh
aba01745f5 Bump version to 0.0.56 2022-10-05 11:58:54 -04:00
Charlie Marsh
1eeeffab66 Add T201 and T203 to string conversion match (#332) 2022-10-05 11:58:33 -04:00
19 changed files with 436 additions and 221 deletions

View File

@@ -14,6 +14,12 @@ your proposed change.
ruff is written in Rust (1.63.0). You'll need to install the
[Rust toolchain](https://www.rust-lang.org/tools/install) for development.
You'll also need [Insta](https://insta.rs/docs/) to update snapshot tests:
```shell
cargo install cargo-insta
```
### Development
After cloning the repository, run ruff locally with:
@@ -58,7 +64,7 @@ similar rules are implemented.
To add a test fixture, create a file under `resources/test/fixtures`, named to match the `CheckCode`
you defined earlier (e.g., `E402.py`). This file should contain a variety of violations and
non-violations designed to evaluate and demonstrate the behavior of your lint rule. Run ruff locally
with (e.g.) `cargo run resources/test/fixtures/E402.py`. Once you're satisified with the output,
with (e.g.) `cargo run resources/test/fixtures/E402.py`. Once you're satisfied with the output,
codify the behavior as a snapshot test by adding a new function to the `mod tests` section of
`src/linter.rs`, like so:

2
Cargo.lock generated
View File

@@ -1907,7 +1907,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.55"
version = "0.0.57"
dependencies = [
"anyhow",
"bincode",

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.55"
version = "0.0.57"
edition = "2021"
[lib]

View File

@@ -89,3 +89,36 @@ A = (
f'B'
f'{B}'
)
from typing import Annotated, Literal
def arbitrary_callable() -> None:
...
class PEP593Test:
field: Annotated[
int,
"base64",
arbitrary_callable(),
123,
(1, 2, 3),
]
field_with_stringified_type: Annotated[
"PEP593Test",
123,
]
field_with_undefined_stringified_type: Annotated[
"PEP593Test123",
123,
]
field_with_nested_subscript: Annotated[
dict[Literal["foo"], str],
123,
]
field_with_undefined_nested_subscript: Annotated[
dict["foo", "bar"], # Expected to fail as undefined.
123,
]

View File

@@ -4,15 +4,13 @@ use itertools::izip;
use regex::Regex;
use rustpython_parser::ast::{
Arg, ArgData, Arguments, Cmpop, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind,
Keyword, Location, Stmt, StmtKind, Unaryop,
Stmt, StmtKind, Unaryop,
};
use crate::ast::operations::SourceCodeLocator;
use crate::ast::types::{
Binding, BindingKind, CheckLocator, FunctionScope, Range, Scope, ScopeKind,
};
use crate::autofix::{fixer, fixes};
use crate::checks::{Check, CheckKind, Fix, RejectedCmpop};
use crate::checks::{Check, CheckKind, RejectedCmpop};
use crate::python::builtins::BUILTINS;
/// Check IfTuple compliance.
@@ -178,13 +176,9 @@ pub fn check_ambiguous_function_name(name: &str, location: Range) -> Option<Chec
/// Check UselessObjectInheritance compliance.
pub fn check_useless_object_inheritance(
stmt: &Stmt,
name: &str,
bases: &[Expr],
keywords: &[Keyword],
scope: &Scope,
locator: &mut SourceCodeLocator,
autofix: &fixer::Mode,
) -> Option<Check> {
for expr in bases {
if let ExprKind::Name { id, .. } = &expr.node {
@@ -195,22 +189,10 @@ pub fn check_useless_object_inheritance(
kind: BindingKind::Builtin,
..
}) => {
let mut check = Check::new(
return Some(Check::new(
CheckKind::UselessObjectInheritance(name.to_string()),
Range::from_located(expr),
);
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if let Some(fix) = fixes::remove_class_def_base(
locator,
&stmt.location,
expr.location,
bases,
keywords,
) {
check.amend(fix);
}
}
return Some(check);
));
}
_ => {}
}
@@ -298,28 +280,15 @@ pub fn check_duplicate_arguments(arguments: &Arguments) -> Vec<Check> {
}
/// Check AssertEquals compliance.
pub fn check_assert_equals(expr: &Expr, autofix: &fixer::Mode) -> Option<Check> {
pub fn check_assert_equals(expr: &Expr) -> Option<Check> {
if let ExprKind::Attribute { value, attr, .. } = &expr.node {
if attr == "assertEquals" {
if let ExprKind::Name { id, .. } = &value.node {
if id == "self" {
let mut check =
Check::new(CheckKind::NoAssertEquals, Range::from_located(expr));
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
check.amend(Fix {
content: "assertEqual".to_string(),
location: Location::new(
expr.location.row(),
expr.location.column() + 1,
),
end_location: Location::new(
expr.location.row(),
expr.location.column() + 1 + "assertEquals".len(),
),
applied: false,
});
}
return Some(check);
return Some(Check::new(
CheckKind::NoAssertEquals,
Range::from_located(expr),
));
}
}
}
@@ -791,11 +760,16 @@ pub fn check_super_args(
// flake8-print
/// Check whether a function call is a `print` or `pprint` invocation
pub fn check_print_call(expr: &Expr, func: &Expr) -> Option<Check> {
pub fn check_print_call(
expr: &Expr,
func: &Expr,
check_print: bool,
check_pprint: bool,
) -> Option<Check> {
if let ExprKind::Name { id, .. } = &func.node {
if id == "print" {
if check_print && id == "print" {
return Some(Check::new(CheckKind::PrintFound, Range::from_located(expr)));
} else if id == "pprint" {
} else if check_pprint && id == "pprint" {
return Some(Check::new(
CheckKind::PPrintFound,
Range::from_located(expr),
@@ -805,7 +779,7 @@ pub fn check_print_call(expr: &Expr, func: &Expr) -> Option<Check> {
if let ExprKind::Attribute { value, attr, .. } = &func.node {
if let ExprKind::Name { id, .. } = &value.node {
if id == "pprint" && attr == "pprint" {
if check_pprint && id == "pprint" && attr == "pprint" {
return Some(Check::new(
CheckKind::PPrintFound,
Range::from_located(expr),

View File

@@ -21,6 +21,7 @@ use crate::ast::visitor::{walk_excepthandler, Visitor};
use crate::ast::{checks, operations, visitor};
use crate::autofix::{fixer, fixes};
use crate::checks::{Check, CheckCode, CheckKind};
use crate::plugins;
use crate::python::builtins::{BUILTINS, MAGIC_GLOBALS};
use crate::python::future::ALL_FEATURE_NAMES;
use crate::python::typing;
@@ -30,18 +31,22 @@ pub const GLOBAL_SCOPE_INDEX: usize = 0;
static DUNDER_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"__[^\s]+__").unwrap());
struct Checker<'a> {
pub struct Checker<'a> {
// Input data.
locator: SourceCodeLocator<'a>,
settings: &'a Settings,
autofix: &'a fixer::Mode,
path: &'a Path,
// TODO(charlie): Separate immutable from mutable state. (None of these should ever change.)
pub(crate) locator: SourceCodeLocator<'a>,
pub(crate) settings: &'a Settings,
pub(crate) autofix: &'a fixer::Mode,
// Computed checks.
checks: Vec<Check>,
// Edit tracking.
// TODO(charlie): Instead of exposing deletions, wrap in a public API.
pub(crate) deletions: BTreeSet<usize>,
// Retain all scopes and parent nodes, along with a stack of indexes to track which are active
// at various points in time.
parents: Vec<&'a Stmt>,
parent_stack: Vec<usize>,
pub(crate) parents: Vec<&'a Stmt>,
pub(crate) parent_stack: Vec<usize>,
scopes: Vec<Scope>,
scope_stack: Vec<usize>,
dead_scopes: Vec<usize>,
@@ -50,7 +55,7 @@ struct Checker<'a> {
deferred_functions: Vec<(&'a Stmt, Vec<usize>, Vec<usize>)>,
deferred_lambdas: Vec<(&'a Expr, Vec<usize>, Vec<usize>)>,
deferred_assignments: Vec<usize>,
// Derivative state.
// Internal, derivative state.
in_f_string: Option<Range>,
in_annotation: bool,
in_literal: bool,
@@ -58,8 +63,6 @@ struct Checker<'a> {
seen_docstring: bool,
futures_allowed: bool,
annotations_future_enabled: bool,
// Edit tracking.
deletions: BTreeSet<usize>,
}
impl<'a> Checker<'a> {
@@ -105,11 +108,32 @@ fn match_name_or_attr(expr: &Expr, target: &str) -> bool {
}
}
fn is_annotated_subscript(expr: &Expr) -> bool {
enum SubscriptKind {
AnnotatedSubscript,
PEP593AnnotatedSubscript,
}
fn match_annotated_subscript(expr: &Expr) -> Option<SubscriptKind> {
match &expr.node {
ExprKind::Attribute { attr, .. } => typing::is_annotated_subscript(attr),
ExprKind::Name { id, .. } => typing::is_annotated_subscript(id),
_ => false,
ExprKind::Attribute { attr, .. } => {
if typing::is_annotated_subscript(attr) {
Some(SubscriptKind::AnnotatedSubscript)
} else if typing::is_pep593_annotated_subscript(attr) {
Some(SubscriptKind::PEP593AnnotatedSubscript)
} else {
None
}
}
ExprKind::Name { id, .. } => {
if typing::is_annotated_subscript(id) {
Some(SubscriptKind::AnnotatedSubscript)
} else if typing::is_pep593_annotated_subscript(id) {
Some(SubscriptKind::PEP593AnnotatedSubscript)
} else {
None
}
}
_ => None,
}
}
@@ -216,8 +240,7 @@ where
StmtKind::Global { names } | StmtKind::Nonlocal { names } => {
let global_scope_id = self.scopes[GLOBAL_SCOPE_INDEX].id;
let current_scope =
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
let current_scope = self.current_scope();
let current_scope_id = current_scope.id;
if current_scope_id != global_scope_id {
for name in names {
@@ -366,19 +389,7 @@ where
..
} => {
if self.settings.enabled.contains(&CheckCode::R001) {
let scope =
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
if let Some(check) = checks::check_useless_object_inheritance(
stmt,
name,
bases,
keywords,
scope,
&mut self.locator,
self.autofix,
) {
self.checks.push(check);
}
plugins::useless_object_inheritance(self, stmt, name, bases, keywords);
}
if self.settings.enabled.contains(&CheckCode::E742) {
@@ -597,25 +608,12 @@ where
}
StmtKind::If { test, .. } => {
if self.settings.enabled.contains(&CheckCode::F634) {
if let Some(check) =
checks::check_if_tuple(test, self.locate_check(Range::from_located(stmt)))
{
self.checks.push(check);
}
plugins::if_tuple(self, stmt, test);
}
}
StmtKind::Assert { test, .. } => {
if self
.settings
.enabled
.contains(CheckKind::AssertTuple.code())
{
if let Some(check) = checks::check_assert_tuple(
test,
self.locate_check(Range::from_located(stmt)),
) {
self.checks.push(check);
}
if self.settings.enabled.contains(&CheckCode::F631) {
plugins::assert_tuple(self, stmt, test);
}
}
StmtKind::Try { handlers, .. } => {
@@ -635,35 +633,7 @@ where
}
}
if self.settings.enabled.contains(&CheckCode::U001) {
if let Some(mut check) = checks::check_useless_metaclass_type(
targets,
value,
self.locate_check(Range::from_located(stmt)),
) {
if matches!(self.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
let context = self.binding_context();
let deleted: Vec<&Stmt> = self
.deletions
.iter()
.map(|index| self.parents[*index])
.collect();
match fixes::remove_stmt(
self.parents[context.defined_by],
context.defined_in.map(|index| self.parents[index]),
&deleted,
) {
Ok(fix) => {
if fix.content.is_empty() || fix.content == "pass" {
self.deletions.insert(context.defined_by);
}
check.amend(fix)
}
Err(e) => error!("Failed to fix unused imports: {}", e),
}
}
self.checks.push(check);
}
plugins::useless_metaclass_type(self, stmt, value, targets);
}
}
StmtKind::AnnAssign { value, .. } => {
@@ -774,81 +744,25 @@ where
self.check_builtin_shadowing(id, Range::from_located(expr), true);
let parent =
self.parents[*(self.parent_stack.last().expect("No parent found."))];
self.handle_node_store(expr, parent);
self.handle_node_store(expr, self.current_parent());
}
ExprContext::Del => self.handle_node_delete(expr),
},
ExprKind::Call { func, args, .. } => {
if self.settings.enabled.contains(&CheckCode::R002) {
if let Some(check) = checks::check_assert_equals(func, self.autofix) {
self.checks.push(check)
}
plugins::assert_equals(self, func);
}
// flake8-super
if self.settings.enabled.contains(&CheckCode::SPR001) {
// Only bother going through the super check at all if we're in a `super` call.
// (We check this in `check_super_args` too, so this is just an optimization.)
if checks::is_super_call_with_arguments(func, args) {
let scope = &mut self.scopes
[*(self.scope_stack.last().expect("No current scope found."))];
let parents: Vec<&Stmt> = self
.parent_stack
.iter()
.map(|index| self.parents[*index])
.collect();
if let Some(mut check) =
checks::check_super_args(scope, &parents, expr, func, args)
{
if matches!(self.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if let Some(fix) =
fixes::remove_super_arguments(&mut self.locator, expr)
{
check.amend(fix);
}
}
self.checks.push(check)
}
}
plugins::super_call_with_parameters(self, expr, func, args);
}
// flake8-print
if self.settings.enabled.contains(&CheckCode::T201)
|| self.settings.enabled.contains(&CheckCode::T203)
{
if let Some(mut check) = checks::check_print_call(expr, func) {
if matches!(self.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
let context = self.binding_context();
if matches!(
self.parents[context.defined_by].node,
StmtKind::Expr { .. }
) {
let deleted: Vec<&Stmt> = self
.deletions
.iter()
.map(|index| self.parents[*index])
.collect();
match fixes::remove_stmt(
self.parents[context.defined_by],
context.defined_in.map(|index| self.parents[index]),
&deleted,
) {
Ok(fix) => {
if fix.content.is_empty() || fix.content == "pass" {
self.deletions.insert(context.defined_by);
}
check.amend(fix)
}
Err(e) => error!("Failed to fix unused imports: {}", e),
}
}
}
self.checks.push(check)
}
plugins::print_call(self, expr, func);
}
if let ExprKind::Name { id, ctx } = &func.node {
@@ -879,8 +793,7 @@ where
}
}
ExprKind::Yield { .. } | ExprKind::YieldFrom { .. } | ExprKind::Await { .. } => {
let scope =
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
let scope = self.current_scope();
if self
.settings
.enabled
@@ -916,22 +829,7 @@ where
..
} => {
if self.settings.enabled.contains(&CheckCode::F633) {
if let ExprKind::Name { id, .. } = &left.node {
if id == "print" {
let scope = &self.scopes
[*(self.scope_stack.last().expect("No current scope found."))];
if let Some(Binding {
kind: BindingKind::Builtin,
..
}) = scope.values.get("print")
{
self.checks.push(Check::new(
CheckKind::InvalidPrintSyntax,
Range::from_located(left),
));
}
}
}
plugins::invalid_print_syntax(self, left);
}
}
ExprKind::UnaryOp { op, operand } => {
@@ -985,9 +883,11 @@ where
ExprKind::Constant {
value: Constant::Str(value),
..
} if self.in_annotation && !self.in_literal => {
self.deferred_string_annotations
.push((Range::from_located(expr), value));
} => {
if self.in_annotation && !self.in_literal {
self.deferred_string_annotations
.push((Range::from_located(expr), value));
}
}
ExprKind::Lambda { args, .. } => {
// Visit the arguments, but avoid the body, which will be deferred.
@@ -1138,12 +1038,35 @@ where
}
}
ExprKind::Subscript { value, slice, ctx } => {
if is_annotated_subscript(value) {
self.visit_expr(value);
self.visit_annotation(slice);
self.visit_expr_context(ctx);
} else {
visitor::walk_expr(self, expr);
match match_annotated_subscript(value) {
Some(subscript) => match subscript {
// Ex) Optional[int]
SubscriptKind::AnnotatedSubscript => {
self.visit_expr(value);
self.visit_annotation(slice);
self.visit_expr_context(ctx);
}
// Ex) Annotated[int, "Hello, world!"]
SubscriptKind::PEP593AnnotatedSubscript => {
// First argument is a type (including forward references); the rest are
// arbitrary Python objects.
self.visit_expr(value);
if let ExprKind::Tuple { elts, ctx } = &slice.node {
if let Some(expr) = elts.first() {
self.visit_expr(expr);
self.in_annotation = false;
for expr in elts.iter().skip(1) {
self.visit_expr(expr);
}
self.in_annotation = true;
self.visit_expr_context(ctx);
}
} else {
error!("Found non-ExprKind::Tuple argument to PEP 593 Annotation.")
}
}
},
None => visitor::walk_expr(self, expr),
}
}
_ => visitor::walk_expr(self, expr),
@@ -1191,11 +1114,7 @@ where
false,
);
let scope = &self.scopes
[*(self.scope_stack.last().expect("No current scope found."))];
if scope.values.contains_key(name) {
let parent = self.parents
[*(self.parent_stack.last().expect("No parent found."))];
if self.current_scope().values.contains_key(name) {
self.handle_node_store(
&Expr::new(
excepthandler.location,
@@ -1205,15 +1124,11 @@ where
ctx: ExprContext::Store,
},
),
parent,
self.current_parent(),
);
}
let parent =
self.parents[*(self.parent_stack.last().expect("No parent found."))];
let scope = &self.scopes
[*(self.scope_stack.last().expect("No current scope found."))];
let definition = scope.values.get(name).cloned();
let definition = self.current_scope().values.get(name).cloned();
self.handle_node_store(
&Expr::new(
excepthandler.location,
@@ -1223,7 +1138,7 @@ where
ctx: ExprContext::Store,
},
),
parent,
self.current_parent(),
);
walk_excepthandler(self, excepthandler);
@@ -1306,6 +1221,10 @@ impl CheckLocator for Checker<'_> {
}
impl<'a> Checker<'a> {
pub fn add_check(&mut self, check: Check) {
self.checks.push(check);
}
fn push_parent(&mut self, parent: &'a Stmt) {
self.parent_stack.push(self.parents.len());
self.parents.push(parent);
@@ -1355,7 +1274,15 @@ impl<'a> Checker<'a> {
}
}
fn binding_context(&self) -> BindingContext {
pub fn current_scope(&self) -> &Scope {
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))]
}
pub fn current_parent(&self) -> &'a Stmt {
self.parents[*(self.parent_stack.last().expect("No parent found."))]
}
pub fn binding_context(&self) -> BindingContext {
let mut rev = self.parent_stack.iter().rev().fuse();
let defined_by = *rev.next().expect("Expected to bind within a statement.");
let defined_in = rev.next().cloned();
@@ -1793,7 +1720,7 @@ impl<'a> Checker<'a> {
}
fn check_builtin_shadowing(&mut self, name: &str, location: Range, is_attribute: bool) {
let scope = &self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
let scope = self.current_scope();
// flake8-builtins
if is_attribute && matches!(scope.kind, ScopeKind::Class) {

View File

@@ -235,6 +235,9 @@ impl FromStr for CheckCode {
"A003" => Ok(CheckCode::A003),
// flake8-super
"SPR001" => Ok(CheckCode::SPR001),
// flake8-print
"T201" => Ok(CheckCode::T201),
"T203" => Ok(CheckCode::T203),
// pyupgrade
"U001" => Ok(CheckCode::U001),
// Refactor

View File

@@ -21,6 +21,7 @@ pub mod linter;
pub mod logging;
pub mod message;
mod noqa;
mod plugins;
pub mod printer;
pub mod pyproject;
mod python;

17
src/plugins.rs Normal file
View File

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

View File

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

View File

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

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

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

View File

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

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

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

View File

@@ -0,0 +1,31 @@
use rustpython_ast::{Expr, Stmt};
use crate::ast::checks;
use crate::autofix::{fixer, fixes};
use crate::check_ast::Checker;
pub fn super_call_with_parameters(
checker: &mut Checker,
expr: &Expr,
func: &Expr,
args: &Vec<Expr>,
) {
// Only bother going through the super check at all if we're in a `super` call.
// (We check this in `check_super_args` too, so this is just an optimization.)
if checks::is_super_call_with_arguments(func, args) {
let scope = checker.current_scope();
let parents: Vec<&Stmt> = checker
.parent_stack
.iter()
.map(|index| checker.parents[*index])
.collect();
if let Some(mut check) = checks::check_super_args(scope, &parents, expr, func, args) {
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if let Some(fix) = fixes::remove_super_arguments(&mut checker.locator, expr) {
check.amend(fix);
}
}
checker.add_check(check)
}
}
}

View File

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

View File

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

View File

@@ -7,6 +7,7 @@ static ANNOTATED_SUBSCRIPTS: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
"AbstractAsyncContextManager",
"AbstractContextManager",
"AbstractSet",
// "Annotated",
"AsyncContextManager",
"AsyncGenerator",
"AsyncIterable",
@@ -87,3 +88,7 @@ static ANNOTATED_SUBSCRIPTS: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
pub fn is_annotated_subscript(name: &str) -> bool {
ANNOTATED_SUBSCRIPTS.contains(name)
}
pub fn is_pep593_annotated_subscript(name: &str) -> bool {
name == "Annotated"
}

View File

@@ -74,4 +74,31 @@ expression: checks
row: 89
column: 9
fix: ~
- kind:
UndefinedName: PEP593Test123
location:
row: 114
column: 10
end_location:
row: 114
column: 24
fix: ~
- kind:
UndefinedName: foo
location:
row: 122
column: 15
end_location:
row: 122
column: 19
fix: ~
- kind:
UndefinedName: bar
location:
row: 122
column: 22
end_location:
row: 122
column: 26
fix: ~