diff --git a/README.md b/README.md index 298cdd917b..af92f434d7 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,8 @@ Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis F | A002 | BuiltinArgumentShadowing | Argument `...` is shadowing a python builtin | | A003 | BuiltinAttributeShadowing | class attribute `...` is shadowing a python builtin | | SPR001 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` | +| T201 | PrintFound | `print` found | +| T203 | PPrintFound | `pprint` found` | | R001 | UselessObjectInheritance | Class `...` inherits from object | | R002 | NoAssertEquals | `assertEquals` is deprecated, use `assertEqual` instead | | M001 | UnusedNOQA | Unused `noqa` directive | diff --git a/resources/test/fixtures/T201.py b/resources/test/fixtures/T201.py new file mode 100644 index 0000000000..9a9d7da2c9 --- /dev/null +++ b/resources/test/fixtures/T201.py @@ -0,0 +1 @@ +print("Hello, world!") # T201 diff --git a/resources/test/fixtures/T203.py b/resources/test/fixtures/T203.py new file mode 100644 index 0000000000..e607298b04 --- /dev/null +++ b/resources/test/fixtures/T203.py @@ -0,0 +1,10 @@ +from pprint import pprint + +pprint("Hello, world!") # T203 + + +import pprint + +pprint.pprint("Hello, world!") # T203 + +pprint.pformat("Hello, world!") diff --git a/src/ast/checks.rs b/src/ast/checks.rs index dda798586a..aaa5614e4f 100644 --- a/src/ast/checks.rs +++ b/src/ast/checks.rs @@ -721,3 +721,31 @@ pub fn check_super_args( } None } + +// flake8-print +/// Check whether a function call is a `print` or `pprint` invocation +pub fn check_print_call(expr: &Expr, func: &Expr) -> Option { + if let ExprKind::Name { id, .. } = &func.node { + if id == "print" { + return Some(Check::new(CheckKind::PrintFound, Range::from_located(expr))); + } else if id == "pprint" { + return Some(Check::new( + CheckKind::PPrintFound, + Range::from_located(expr), + )); + } + } + + if let ExprKind::Attribute { value, attr, .. } = &func.node { + if let ExprKind::Name { id, .. } = &value.node { + if id == "pprint" && attr == "pprint" { + return Some(Check::new( + CheckKind::PPrintFound, + Range::from_located(expr), + )); + } + } + } + + None +} diff --git a/src/autofix/fixes.rs b/src/autofix/fixes.rs index 5c4b8ea617..7a3dae8c0b 100644 --- a/src/autofix/fixes.rs +++ b/src/autofix/fixes.rs @@ -224,7 +224,7 @@ fn is_lone_child(child: &Stmt, parent: &Stmt, deleted: &[&Stmt]) -> Result } } -fn remove_stmt(stmt: &Stmt, parent: Option<&Stmt>, deleted: &[&Stmt]) -> Result { +pub fn remove_stmt(stmt: &Stmt, parent: Option<&Stmt>, deleted: &[&Stmt]) -> Result { if parent .map(|parent| is_lone_child(stmt, parent, deleted)) .map_or(Ok(None), |v| v.map(Some))? diff --git a/src/check_ast.rs b/src/check_ast.rs index 13f0e08d7a..ec4120b7c3 100644 --- a/src/check_ast.rs +++ b/src/check_ast.rs @@ -19,6 +19,7 @@ use crate::ast::types::{ }; use crate::ast::visitor::{walk_excepthandler, Visitor}; use crate::ast::{checks, operations, visitor}; +use crate::autofix::fixes::remove_stmt; use crate::autofix::{fixer, fixes}; use crate::checks::{Check, CheckCode, CheckKind}; use crate::python::builtins::{BUILTINS, MAGIC_GLOBALS}; @@ -761,6 +762,43 @@ where } } + // flake8-print + if self.settings.select.contains(&CheckCode::T201) + || self.settings.select.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 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) + } + } + if let ExprKind::Name { id, ctx } = &func.node { if id == "locals" && matches!(ctx, ExprContext::Load) { let scope = &mut self.scopes[*(self diff --git a/src/checks.rs b/src/checks.rs index 64c3014fa8..32b3d8a0be 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -5,7 +5,7 @@ use anyhow::Result; use rustpython_parser::ast::Location; use serde::{Deserialize, Serialize}; -pub const DEFAULT_CHECK_CODES: [CheckCode; 46] = [ +pub const DEFAULT_CHECK_CODES: [CheckCode; 48] = [ // pycodestyle CheckCode::E402, CheckCode::E501, @@ -54,11 +54,14 @@ pub const DEFAULT_CHECK_CODES: [CheckCode; 46] = [ CheckCode::A001, CheckCode::A002, CheckCode::A003, + // flake8-print + CheckCode::T201, + CheckCode::T203, // flake8-super CheckCode::SPR001, ]; -pub const ALL_CHECK_CODES: [CheckCode; 49] = [ +pub const ALL_CHECK_CODES: [CheckCode; 51] = [ // pycodestyle CheckCode::E402, CheckCode::E501, @@ -109,6 +112,9 @@ pub const ALL_CHECK_CODES: [CheckCode; 49] = [ CheckCode::A003, // flake8-super CheckCode::SPR001, + // flake8-print + CheckCode::T201, + CheckCode::T203, // Meta CheckCode::M001, // Refactor @@ -168,6 +174,9 @@ pub enum CheckCode { A003, // flake8-super SPR001, + // flake8-print + T201, + T203, // Refactor R001, R002, @@ -293,6 +302,9 @@ impl CheckCode { CheckCode::A003 => "A003", // flake8-super CheckCode::SPR001 => "SPR001", + // flake8-print + CheckCode::T201 => "T201", + CheckCode::T203 => "T203", // Refactor CheckCode::R001 => "R001", CheckCode::R002 => "R002", @@ -363,6 +375,9 @@ impl CheckCode { CheckCode::A003 => CheckKind::BuiltinAttributeShadowing("...".to_string()), // flake8-super CheckCode::SPR001 => CheckKind::SuperCallWithParameters, + // flake8-print + CheckCode::T201 => CheckKind::PrintFound, + CheckCode::T203 => CheckKind::PPrintFound, // Refactor CheckCode::R001 => CheckKind::UselessObjectInheritance("...".to_string()), CheckCode::R002 => CheckKind::NoAssertEquals, @@ -438,6 +453,9 @@ pub enum CheckKind { BuiltinAttributeShadowing(String), // flake8-super SuperCallWithParameters, + // flake8-print + PrintFound, + PPrintFound, } impl CheckKind { @@ -497,6 +515,9 @@ impl CheckKind { CheckKind::BuiltinAttributeShadowing(_) => "BuiltinAttributeShadowing", // flake8-super CheckKind::SuperCallWithParameters => "SuperCallWithParameters", + // flake8-print + CheckKind::PrintFound => "PrintFound", + CheckKind::PPrintFound => "PPrintFound", } } @@ -554,6 +575,9 @@ impl CheckKind { CheckKind::BuiltinAttributeShadowing(_) => &CheckCode::A003, // flake8-super CheckKind::SuperCallWithParameters => &CheckCode::SPR001, + // flake8-print + CheckKind::PrintFound => &CheckCode::T201, + CheckKind::PPrintFound => &CheckCode::T203, } } @@ -702,6 +726,9 @@ impl CheckKind { CheckKind::SuperCallWithParameters => { "Use `super()` instead of `super(__class__, self)`".to_string() } + // flake8-print + CheckKind::PrintFound => "`print` found".to_string(), + CheckKind::PPrintFound => "`pprint` found`".to_string(), } } @@ -714,6 +741,8 @@ impl CheckKind { | CheckKind::UnusedNOQA(_) | CheckKind::SuperCallWithParameters | CheckKind::UnusedImport(_) + | CheckKind::PrintFound + | CheckKind::PPrintFound ) } } diff --git a/src/linter.rs b/src/linter.rs index 17a4291e56..f465bf30a0 100644 --- a/src/linter.rs +++ b/src/linter.rs @@ -779,4 +779,28 @@ mod tests { insta::assert_yaml_snapshot!(checks); Ok(()) } + + #[test] + fn t201() -> Result<()> { + let mut checks = check_path( + Path::new("./resources/test/fixtures/T201.py"), + &settings::Settings::for_rule(CheckCode::T201), + &fixer::Mode::Generate, + )?; + checks.sort_by_key(|check| check.location); + insta::assert_yaml_snapshot!(checks); + Ok(()) + } + + #[test] + fn t203() -> Result<()> { + let mut checks = check_path( + Path::new("./resources/test/fixtures/T203.py"), + &settings::Settings::for_rule(CheckCode::T203), + &fixer::Mode::Generate, + )?; + checks.sort_by_key(|check| check.location); + insta::assert_yaml_snapshot!(checks); + Ok(()) + } } diff --git a/src/snapshots/ruff__linter__tests__t201.snap b/src/snapshots/ruff__linter__tests__t201.snap new file mode 100644 index 0000000000..d580b53f93 --- /dev/null +++ b/src/snapshots/ruff__linter__tests__t201.snap @@ -0,0 +1,21 @@ +--- +source: src/linter.rs +expression: checks +--- +- kind: PrintFound + location: + row: 1 + column: 1 + end_location: + row: 1 + column: 23 + fix: + content: "" + location: + row: 1 + column: 1 + end_location: + row: 2 + column: 1 + applied: false + diff --git a/src/snapshots/ruff__linter__tests__t203.snap b/src/snapshots/ruff__linter__tests__t203.snap new file mode 100644 index 0000000000..cd43b5a802 --- /dev/null +++ b/src/snapshots/ruff__linter__tests__t203.snap @@ -0,0 +1,37 @@ +--- +source: src/linter.rs +expression: checks +--- +- kind: PPrintFound + location: + row: 3 + column: 1 + end_location: + row: 3 + column: 24 + fix: + content: "" + location: + row: 3 + column: 1 + end_location: + row: 4 + column: 1 + applied: false +- kind: PPrintFound + location: + row: 8 + column: 1 + end_location: + row: 8 + column: 31 + fix: + content: "" + location: + row: 8 + column: 1 + end_location: + row: 9 + column: 1 + applied: false +