Compare commits

...

3 Commits

Author SHA1 Message Date
Charlie Marsh
6f317fc3e7 Try out rust-python-parser (nom-based) 2022-10-05 15:57:04 -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
16 changed files with 423 additions and 241 deletions

104
Cargo.lock generated
View File

@@ -16,7 +16,7 @@ checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom 0.2.7",
"once_cell",
"version_check",
"version_check 0.9.4",
]
[[package]]
@@ -319,6 +319,12 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
[[package]]
name = "bytecount"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f861d9ce359f56dbcb6e0c2a1cb84e52ad732cadb57b806adeb3c7668caccbd8"
[[package]]
name = "byteorder"
version = "1.4.3"
@@ -907,7 +913,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
dependencies = [
"typenum",
"version_check",
"version_check 0.9.4",
]
[[package]]
@@ -1137,7 +1143,7 @@ dependencies = [
"string_cache",
"term",
"tiny-keccak",
"unicode-xid",
"unicode-xid 0.2.4",
]
[[package]]
@@ -1366,6 +1372,16 @@ dependencies = [
"libc",
]
[[package]]
name = "nom"
version = "4.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
dependencies = [
"memchr",
"version_check 0.1.5",
]
[[package]]
name = "nom"
version = "5.1.2"
@@ -1373,7 +1389,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
dependencies = [
"memchr",
"version_check",
"version_check 0.9.4",
]
[[package]]
name = "nom_locate"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a47c112b3861d81f7fbf73892b9271af933af32bd5dee6889aa3c3fa9caed7e"
dependencies = [
"bytecount",
"memchr",
"nom 4.2.3",
]
[[package]]
@@ -1394,6 +1421,17 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "num-bigint"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.4.3"
@@ -1695,7 +1733,7 @@ dependencies = [
"proc-macro2",
"quote",
"syn",
"version_check",
"version_check 0.9.4",
]
[[package]]
@@ -1706,7 +1744,7 @@ checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
"version_check 0.9.4",
]
[[package]]
@@ -1718,6 +1756,21 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "python-parser"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683594d14f9c4db9e899643108918ad9db99aedad77121b5179aa205343c10de"
dependencies = [
"nom 4.2.3",
"nom_locate",
"num-bigint 0.2.6",
"num-traits",
"unicode-xid 0.1.0",
"unicode_names2 0.2.2",
"wtf8",
]
[[package]]
name = "quote"
version = "1.0.21"
@@ -1928,6 +1981,7 @@ dependencies = [
"notify",
"once_cell",
"path-absolutize",
"python-parser",
"rayon",
"regex",
"rustpython-ast",
@@ -1957,7 +2011,7 @@ name = "rustpython-ast"
version = "0.1.0"
source = "git+https://github.com/charliermarsh/RustPython.git?rev=4f457893efc381ad5c432576b24bcc7e4a08c641#4f457893efc381ad5c432576b24bcc7e4a08c641"
dependencies = [
"num-bigint",
"num-bigint 0.4.3",
"rustpython-common",
"rustpython-compiler-core",
]
@@ -1973,7 +2027,7 @@ dependencies = [
"lexical-parse-float",
"libc",
"lock_api",
"num-bigint",
"num-bigint 0.4.3",
"num-complex",
"num-traits",
"once_cell",
@@ -1995,7 +2049,7 @@ dependencies = [
"bstr",
"itertools",
"lz4_flex",
"num-bigint",
"num-bigint 0.4.3",
"num-complex",
"serde",
"static_assertions",
@@ -2013,7 +2067,7 @@ dependencies = [
"lalrpop",
"lalrpop-util",
"log",
"num-bigint",
"num-bigint 0.4.3",
"num-traits",
"phf 0.10.1",
"phf_codegen 0.10.0",
@@ -2023,7 +2077,7 @@ dependencies = [
"tiny-keccak",
"unic-emoji-char",
"unic-ucd-ident",
"unicode_names2",
"unicode_names2 0.5.1",
]
[[package]]
@@ -2315,7 +2369,7 @@ checksum = "76971977e6121664ec1b960d1313aacfa75642adc93b9d4d53b247bd4cb1747e"
dependencies = [
"dirs 2.0.2",
"fnv",
"nom",
"nom 5.1.2",
"phf 0.8.0",
"phf_codegen 0.8.0",
]
@@ -2485,12 +2539,24 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-xid"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
[[package]]
name = "unicode-xid"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "unicode_names2"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dc6c5da0c8d7200f9488cc346bd30ba62bcd9f79ef937ea6573132e3d507df9"
[[package]]
name = "unicode_names2"
version = "0.5.1"
@@ -2553,9 +2619,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55"
dependencies = [
"ctor",
"version_check",
"version_check 0.9.4",
]
[[package]]
name = "version_check"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
[[package]]
name = "version_check"
version = "0.9.4"
@@ -2820,6 +2892,12 @@ dependencies = [
"winapi-build",
]
[[package]]
name = "wtf8"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6b9309a86639c488a8eb2b5331cb5127cc9feb0a94a0db4b5d1ab5b84977956"
[[package]]
name = "yaml-rust"
version = "0.4.5"

View File

@@ -35,6 +35,7 @@ serde_json = { version = "1.0.83" }
toml = { version = "0.5.9" }
update-informer = { version = "0.5.0", default_features = false, features = ["pypi"], optional = true }
walkdir = { version = "2.3.2" }
python-parser = "0.2.0"
[dev-dependencies]
insta = { version = "1.19.1", features = ["yaml"] }

23
examples/parse_python.rs Normal file
View File

@@ -0,0 +1,23 @@
/// Print the AST for a given Python file.
use std::path::PathBuf;
use anyhow::Result;
use clap::Parser;
use ruff::fs;
#[derive(Debug, Parser)]
struct Cli {
#[arg(required = true)]
file: PathBuf,
}
fn main() -> Result<()> {
let cli = Cli::parse();
let contents = fs::read_file(&cli.file)?;
let ast = python_parser::file_input(python_parser::make_strspan(&contents))?.1;
println!("{:?}", ast);
Ok(())
}

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> {
@@ -216,8 +219,7 @@ where
StmtKind::Global { names } | StmtKind::Nonlocal { names } => {
let global_scope_id = self.scopes[GLOBAL_SCOPE_INDEX].id;
let current_scope =
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
let current_scope = self.current_scope();
let current_scope_id = current_scope.id;
if current_scope_id != global_scope_id {
for name in names {
@@ -366,19 +368,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 +587,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 +612,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 +723,25 @@ where
self.check_builtin_shadowing(id, Range::from_located(expr), true);
let parent =
self.parents[*(self.parent_stack.last().expect("No parent found."))];
self.handle_node_store(expr, parent);
self.handle_node_store(expr, self.current_parent());
}
ExprContext::Del => self.handle_node_delete(expr),
},
ExprKind::Call { func, args, .. } => {
if self.settings.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 +772,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 +808,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 } => {
@@ -1191,11 +1068,7 @@ where
false,
);
let scope = &self.scopes
[*(self.scope_stack.last().expect("No current scope found."))];
if scope.values.contains_key(name) {
let parent = self.parents
[*(self.parent_stack.last().expect("No parent found."))];
if self.current_scope().values.contains_key(name) {
self.handle_node_store(
&Expr::new(
excepthandler.location,
@@ -1205,15 +1078,11 @@ where
ctx: ExprContext::Store,
},
),
parent,
self.current_parent(),
);
}
let parent =
self.parents[*(self.parent_stack.last().expect("No parent found."))];
let scope = &self.scopes
[*(self.scope_stack.last().expect("No current scope found."))];
let definition = scope.values.get(name).cloned();
let definition = self.current_scope().values.get(name).cloned();
self.handle_node_store(
&Expr::new(
excepthandler.location,
@@ -1223,7 +1092,7 @@ where
ctx: ExprContext::Store,
},
),
parent,
self.current_parent(),
);
walk_excepthandler(self, excepthandler);
@@ -1306,6 +1175,10 @@ impl CheckLocator for Checker<'_> {
}
impl<'a> Checker<'a> {
pub fn add_check(&mut self, check: Check) {
self.checks.push(check);
}
fn push_parent(&mut self, parent: &'a Stmt) {
self.parent_stack.push(self.parents.len());
self.parents.push(parent);
@@ -1355,7 +1228,15 @@ impl<'a> Checker<'a> {
}
}
fn binding_context(&self) -> BindingContext {
pub fn current_scope(&self) -> &Scope {
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))]
}
pub fn current_parent(&self) -> &'a Stmt {
self.parents[*(self.parent_stack.last().expect("No parent found."))]
}
pub fn binding_context(&self) -> BindingContext {
let mut rev = self.parent_stack.iter().rev().fuse();
let defined_by = *rev.next().expect("Expected to bind within a statement.");
let defined_in = rev.next().cloned();
@@ -1793,7 +1674,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

@@ -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;

View File

@@ -8,7 +8,6 @@ use rustpython_parser::{lexer, parser};
use crate::ast::types::Range;
use crate::autofix::fixer;
use crate::autofix::fixer::fix_file;
use crate::check_ast::check_ast;
use crate::check_lines::check_lines;
use crate::checks::{Check, CheckCode, CheckKind, LintSource};
@@ -101,33 +100,19 @@ pub fn lint_path(
let contents = fs::read_file(path)?;
// Tokenize once.
let tokens: Vec<LexResult> = tokenize(&contents);
// let tokens: Vec<LexResult> = tokenize(&contents);
// Determine the noqa line for every line in the source.
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
// let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
// Generate checks.
let mut checks = check_path(path, &contents, tokens, &noqa_line_for, settings, autofix)?;
match python_parser::file_input(python_parser::make_strspan(&contents)) {
Ok(x) => {}
Err(e) => return Err(anyhow::anyhow!("Err: failed")),
}
// Apply autofix.
if matches!(autofix, fixer::Mode::Apply) {
fix_file(&mut checks, &contents, path)?;
};
// let _ = parser::parse_program_tokens(tokens, "<filename>")?;
// Convert to messages.
let messages: Vec<Message> = checks
.into_iter()
.map(|check| Message {
kind: check.kind,
fixed: check.fix.map(|fix| fix.applied).unwrap_or_default(),
location: check.location,
end_location: check.end_location,
filename: path.to_string_lossy().to_string(),
})
.collect();
cache::set(path, &metadata, settings, autofix, &messages, mode);
Ok(messages)
Ok(vec![])
}
pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {

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);
}
}