diff --git a/Cargo.lock b/Cargo.lock index 65324c994f..2a013b4ebb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,6 +37,12 @@ dependencies = [ "libc", ] +[[package]] +name = "annotate-snippets" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7021ce4924a3f25f802b2cccd1af585e39ea1a363a1aa2e72afe54b67a3a7a7" + [[package]] name = "anyhow" version = "1.0.60" @@ -359,6 +365,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chic" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5b5db619f3556839cb2223ae86ff3f9a09da2c5013be42bc9af08c9589bf70c" +dependencies = [ + "annotate-snippets", +] + [[package]] name = "chrono" version = "0.4.21" @@ -1062,9 +1077,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] @@ -1153,6 +1168,30 @@ version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b" +[[package]] +name = "libcst" +version = "0.1.0" +source = "git+https://github.com/charliermarsh/LibCST?rev=32a044c127668df44582f85699358e67803b0d73#32a044c127668df44582f85699358e67803b0d73" +dependencies = [ + "chic", + "itertools", + "libcst_derive", + "once_cell", + "paste", + "peg", + "regex", + "thiserror", +] + +[[package]] +name = "libcst_derive" +version = "0.1.0" +source = "git+https://github.com/charliermarsh/LibCST?rev=32a044c127668df44582f85699358e67803b0d73#32a044c127668df44582f85699358e67803b0d73" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -1379,9 +1418,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "opaque-debug" @@ -1430,6 +1469,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "paste" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" + [[package]] name = "path-absolutize" version = "3.0.13" @@ -1448,6 +1493,33 @@ dependencies = [ "once_cell", ] +[[package]] +name = "peg" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a07f2cafdc3babeebc087e499118343442b742cc7c31b4d054682cc598508554" +dependencies = [ + "peg-macros", + "peg-runtime", +] + +[[package]] +name = "peg-macros" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a90084dc05cf0428428e3d12399f39faad19b0909f64fb9170c9fdd6d9cd49b" +dependencies = [ + "peg-runtime", + "proc-macro2", + "quote", +] + +[[package]] +name = "peg-runtime" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa00462b37ead6d11a82c9d568b26682d78e0477dc02d1966c013af80969739" + [[package]] name = "percent-encoding" version = "2.1.0" @@ -1815,6 +1887,7 @@ dependencies = [ "glob", "insta", "itertools", + "libcst", "log", "notify", "once_cell", @@ -2187,18 +2260,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.32" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.32" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 0c9d0d0869..93104e2e36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,8 @@ dirs = { version = "4.0.0" } fern = { version = "0.6.1" } filetime = { version = "0.2.17" } glob = { version = "0.3.0" } -itertools = { version = "0.10.3" } +itertools = { version = "0.10.5" } +libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "32a044c127668df44582f85699358e67803b0d73" } log = { version = "0.4.17" } notify = { version = "4.0.17" } once_cell = { version = "1.13.1" } diff --git a/src/ast/checks.rs b/src/ast/checks.rs index 1f11f1c739..2ca686b9fa 100644 --- a/src/ast/checks.rs +++ b/src/ast/checks.rs @@ -698,15 +698,26 @@ pub fn check_builtin_shadowing( // flake8-super /// Check that `super()` has no args -pub fn check_super_args(expr: &Expr, args: &Vec) -> Option { - if let ExprKind::Name { id, .. } = &expr.node { +pub fn check_super_args( + expr: &Expr, + func: &Expr, + args: &Vec, + locator: &mut SourceCodeLocator, + autofix: &fixer::Mode, +) -> Option { + if let ExprKind::Name { id, .. } = &func.node { if id == "super" && !args.is_empty() { - return Some(Check::new( + let mut check = Check::new( CheckKind::SuperCallWithParameters, Range::from_located(expr), - )); + ); + if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) { + if let Some(fix) = fixes::remove_super_arguments(locator, expr) { + check.amend(fix); + } + } + return Some(check); } } - None } diff --git a/src/ast/operations.rs b/src/ast/operations.rs index fac672a98d..e462cf9eb4 100644 --- a/src/ast/operations.rs +++ b/src/ast/operations.rs @@ -134,7 +134,7 @@ impl<'a> SourceCodeLocator<'a> { } } - pub fn slice_source_code(&mut self, location: &Location) -> &'a str { + pub fn slice_source_code_at(&mut self, location: &Location) -> &'a str { if !self.initialized { let mut offset = 0; for i in self.content.lines() { @@ -147,4 +147,19 @@ impl<'a> SourceCodeLocator<'a> { let offset = self.offsets[location.row() - 1] + location.column() - 1; &self.content[offset..] } + + pub fn slice_source_code_range(&mut self, start: &Location, end: &Location) -> &'a str { + if !self.initialized { + let mut offset = 0; + for i in self.content.lines() { + self.offsets.push(offset); + offset += i.len(); + offset += 1; + } + self.initialized = true; + } + let start = self.offsets[start.row() - 1] + start.column() - 1; + let end = self.offsets[end.row() - 1] + end.column() - 1; + &self.content[start..end] + } } diff --git a/src/autofix/fixes.rs b/src/autofix/fixes.rs index 8bf9ce5515..5a297e3204 100644 --- a/src/autofix/fixes.rs +++ b/src/autofix/fixes.rs @@ -1,3 +1,4 @@ +use libcst_native::{Codegen, Expression, SmallStatement, Statement}; use rustpython_parser::ast::{Expr, Keyword, Location}; use rustpython_parser::lexer; use rustpython_parser::token::Tok; @@ -25,7 +26,7 @@ pub fn remove_class_def_base( bases: &[Expr], keywords: &[Keyword], ) -> Option { - let content = locator.slice_source_code(stmt_at); + let content = locator.slice_source_code_at(stmt_at); // Case 1: `object` is the only base. if bases.len() == 1 && keywords.is_empty() { @@ -124,3 +125,34 @@ pub fn remove_class_def_base( } } } + +pub fn remove_super_arguments(locator: &mut SourceCodeLocator, expr: &Expr) -> Option { + let contents = locator.slice_source_code_range(&expr.location, &expr.end_location); + + let mut tree = match libcst_native::parse_module(contents, None) { + Ok(m) => m, + Err(_) => return None, + }; + + if let Some(Statement::Simple(body)) = tree.body.first_mut() { + if let Some(SmallStatement::Expr(body)) = body.body.first_mut() { + if let Expression::Call(body) = &mut body.value { + body.args = vec![]; + body.whitespace_before_args = Default::default(); + body.whitespace_after_func = Default::default(); + + let mut state = Default::default(); + tree.codegen(&mut state); + + return Some(Fix { + content: state.to_string(), + location: expr.location, + end_location: expr.end_location, + applied: false, + }); + } + } + } + + None +} diff --git a/src/check_ast.rs b/src/check_ast.rs index ced4a3af46..8af2c868b7 100644 --- a/src/check_ast.rs +++ b/src/check_ast.rs @@ -742,7 +742,9 @@ where // flake8-super if self.settings.select.contains(&CheckCode::SPR001) { - if let Some(check) = checks::check_super_args(func, args) { + if let Some(check) = + checks::check_super_args(expr, func, args, &mut self.locator, self.autofix) + { self.checks.push(check) } } diff --git a/src/checks.rs b/src/checks.rs index fb0d2f6b13..9d503645bd 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -712,6 +712,7 @@ impl CheckKind { CheckKind::NoAssertEquals | CheckKind::UselessObjectInheritance(_) | CheckKind::UnusedNOQA(_) + | CheckKind::SuperCallWithParameters ) } } diff --git a/src/snapshots/ruff__linter__tests__spr001.snap b/src/snapshots/ruff__linter__tests__spr001.snap index 6eb6820df2..8f0a29bf54 100644 --- a/src/snapshots/ruff__linter__tests__spr001.snap +++ b/src/snapshots/ruff__linter__tests__spr001.snap @@ -8,22 +8,46 @@ expression: checks column: 18 end_location: row: 17 - column: 23 - fix: ~ + column: 36 + fix: + content: super() + location: + row: 17 + column: 18 + end_location: + row: 17 + column: 36 + applied: false - kind: SuperCallWithParameters location: row: 18 column: 9 end_location: row: 18 - column: 14 - fix: ~ + column: 27 + fix: + content: super() + location: + row: 18 + column: 9 + end_location: + row: 18 + column: 27 + applied: false - kind: SuperCallWithParameters location: row: 19 column: 9 end_location: - row: 19 - column: 14 - fix: ~ + row: 22 + column: 10 + fix: + content: super() + location: + row: 19 + column: 9 + end_location: + row: 22 + column: 10 + applied: false