diff --git a/resources/test/fixtures/U003.py b/resources/test/fixtures/U003.py new file mode 100644 index 0000000000..f92cbd1ee5 --- /dev/null +++ b/resources/test/fixtures/U003.py @@ -0,0 +1,5 @@ +type('') +type(b'') +type(0) +type(0.) +type(0j) diff --git a/src/ast/checks.rs b/src/ast/checks.rs index 97b9cdf6c5..c82c167bed 100644 --- a/src/ast/checks.rs +++ b/src/ast/checks.rs @@ -6,6 +6,7 @@ use rustpython_parser::ast::{ Arg, ArgData, Arguments, Cmpop, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Stmt, StmtKind, Unaryop, }; +use serde::{Deserialize, Serialize}; use crate::ast::types::{ Binding, BindingKind, CheckLocator, FunctionScope, Range, Scope, ScopeKind, @@ -155,6 +156,65 @@ pub fn check_unnecessary_abspath(func: &Expr, args: &Vec, location: Range) None } +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum Primitive { + Bool, + Str, + Bytes, + Int, + Float, + Complex, +} + +impl Primitive { + fn from_constant(constant: &Constant) -> Option { + match constant { + Constant::Bool(_) => Some(Primitive::Bool), + Constant::Str(_) => Some(Primitive::Str), + Constant::Bytes(_) => Some(Primitive::Bytes), + Constant::Int(_) => Some(Primitive::Int), + Constant::Float(_) => Some(Primitive::Float), + Constant::Complex { .. } => Some(Primitive::Complex), + _ => None, + } + } + + pub fn builtin(&self) -> String { + match self { + Primitive::Bool => "bool".to_string(), + Primitive::Str => "str".to_string(), + Primitive::Bytes => "bytes".to_string(), + Primitive::Int => "int".to_string(), + Primitive::Float => "float".to_string(), + Primitive::Complex => "complex".to_string(), + } + } +} + +/// Check TypeOfPrimitive compliance. +pub fn check_type_of_primitive(func: &Expr, args: &Vec, location: Range) -> Option { + // Validate the arguments. + if args.len() == 1 { + match &func.node { + ExprKind::Attribute { attr: id, .. } | ExprKind::Name { id, .. } => { + if id == "type" { + if let ExprKind::Constant { value, .. } = &args[0].node { + if let Some(primitive) = Primitive::from_constant(value) { + return Some(Check::new( + CheckKind::TypeOfPrimitive(primitive), + location, + )); + } + } + } + } + _ => {} + } + } + + None +} + fn is_ambiguous_name(name: &str) -> bool { name == "l" || name == "I" || name == "O" } diff --git a/src/check_ast.rs b/src/check_ast.rs index 74bbc7c0ae..5c97182b5b 100644 --- a/src/check_ast.rs +++ b/src/check_ast.rs @@ -801,6 +801,10 @@ where plugins::unnecessary_abspath(self, expr, func, args); } + if self.settings.enabled.contains(&CheckCode::U003) { + plugins::type_of_primitive(self, expr, func, args); + } + 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 7962af35f3..789c27f47b 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -1,11 +1,13 @@ use std::str::FromStr; -use crate::ast::types::Range; +use crate::ast::checks::Primitive; use anyhow::Result; use itertools::Itertools; use rustpython_parser::ast::Location; use serde::{Deserialize, Serialize}; +use crate::ast::types::Range; + pub const DEFAULT_CHECK_CODES: [CheckCode; 42] = [ // pycodestyle CheckCode::E402, @@ -53,7 +55,7 @@ pub const DEFAULT_CHECK_CODES: [CheckCode; 42] = [ CheckCode::F901, ]; -pub const ALL_CHECK_CODES: [CheckCode; 57] = [ +pub const ALL_CHECK_CODES: [CheckCode; 58] = [ // pycodestyle CheckCode::E402, CheckCode::E501, @@ -115,6 +117,7 @@ pub const ALL_CHECK_CODES: [CheckCode; 57] = [ // pyupgrade CheckCode::U001, CheckCode::U002, + CheckCode::U003, // Refactor CheckCode::R001, CheckCode::R002, @@ -185,6 +188,7 @@ pub enum CheckCode { // pyupgrade U001, U002, + U003, // Refactor R001, R002, @@ -256,6 +260,7 @@ impl FromStr for CheckCode { // pyupgrade "U001" => Ok(CheckCode::U001), "U002" => Ok(CheckCode::U002), + "U003" => Ok(CheckCode::U003), // Refactor "R001" => Ok(CheckCode::R001), "R002" => Ok(CheckCode::R002), @@ -330,6 +335,7 @@ impl CheckCode { // pyupgrade CheckCode::U001 => "U001", CheckCode::U002 => "U002", + CheckCode::U003 => "U003", // Refactor CheckCode::R001 => "R001", CheckCode::R002 => "R002", @@ -413,6 +419,7 @@ impl CheckCode { // pyupgrade CheckCode::U001 => CheckKind::UselessMetaclassType, CheckCode::U002 => CheckKind::UnnecessaryAbspath, + CheckCode::U003 => CheckKind::UnnecessaryAbspath, // Refactor CheckCode::R001 => CheckKind::UselessObjectInheritance("...".to_string()), CheckCode::R002 => CheckKind::NoAssertEquals, @@ -494,6 +501,7 @@ pub enum CheckKind { PrintFound, PPrintFound, // pyupgrade + TypeOfPrimitive(Primitive), UnnecessaryAbspath, UselessMetaclassType, // Refactor @@ -564,6 +572,7 @@ impl CheckKind { CheckKind::PrintFound => "PrintFound", CheckKind::PPrintFound => "PPrintFound", // pyupgrade + CheckKind::TypeOfPrimitive(_) => "TypeOfPrimitive", CheckKind::UnnecessaryAbspath => "UnnecessaryAbspath", CheckKind::UselessMetaclassType => "UselessMetaclassType", // Refactor @@ -634,6 +643,7 @@ impl CheckKind { CheckKind::PrintFound => &CheckCode::T201, CheckKind::PPrintFound => &CheckCode::T203, // pyupgrade + CheckKind::TypeOfPrimitive(_) => &CheckCode::U003, CheckKind::UnnecessaryAbspath => &CheckCode::U002, CheckKind::UselessMetaclassType => &CheckCode::U001, // Refactor @@ -806,6 +816,9 @@ impl CheckKind { CheckKind::PrintFound => "`print` found".to_string(), CheckKind::PPrintFound => "`pprint` found".to_string(), // pyupgrade + CheckKind::TypeOfPrimitive(primitive) => { + format!("Use `{}` instead of `type(...)`", primitive.builtin()) + } CheckKind::UnnecessaryAbspath => { "`abspath(__file__)` is unnecessary in Python 3.9 and later".to_string() } @@ -833,6 +846,7 @@ impl CheckKind { | CheckKind::PPrintFound | CheckKind::PrintFound | CheckKind::SuperCallWithParameters + | CheckKind::TypeOfPrimitive(_) | CheckKind::UnnecessaryAbspath | CheckKind::UnusedImport(_) | CheckKind::UnusedNOQA(_) diff --git a/src/linter.rs b/src/linter.rs index 972e4d6650..717bcdc304 100644 --- a/src/linter.rs +++ b/src/linter.rs @@ -893,4 +893,16 @@ mod tests { insta::assert_yaml_snapshot!(checks); Ok(()) } + + #[test] + fn u003() -> Result<()> { + let mut checks = check_path( + Path::new("./resources/test/fixtures/U003.py"), + &settings::Settings::for_rule(CheckCode::U003), + &fixer::Mode::Generate, + )?; + checks.sort_by_key(|check| check.location); + insta::assert_yaml_snapshot!(checks); + Ok(()) + } } diff --git a/src/plugins.rs b/src/plugins.rs index d4963b157c..e4b8a442e5 100644 --- a/src/plugins.rs +++ b/src/plugins.rs @@ -4,6 +4,7 @@ mod if_tuple; mod invalid_print_syntax; mod print_call; mod super_call_with_parameters; +mod type_of_primitive; mod unnecessary_abspath; mod useless_metaclass_type; mod useless_object_inheritance; @@ -14,6 +15,7 @@ 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 type_of_primitive::type_of_primitive; pub use unnecessary_abspath::unnecessary_abspath; pub use useless_metaclass_type::useless_metaclass_type; pub use useless_object_inheritance::useless_object_inheritance; diff --git a/src/plugins/type_of_primitive.rs b/src/plugins/type_of_primitive.rs new file mode 100644 index 0000000000..772fdfe780 --- /dev/null +++ b/src/plugins/type_of_primitive.rs @@ -0,0 +1,25 @@ +use rustpython_ast::Expr; + +use crate::ast::checks; +use crate::ast::types::{CheckLocator, Range}; +use crate::autofix::fixer; +use crate::check_ast::Checker; +use crate::checks::{CheckKind, Fix}; + +pub fn type_of_primitive(checker: &mut Checker, expr: &Expr, func: &Expr, args: &Vec) { + if let Some(mut check) = + checks::check_type_of_primitive(func, args, checker.locate_check(Range::from_located(expr))) + { + if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) { + if let CheckKind::TypeOfPrimitive(primitive) = &check.kind { + check.amend(Fix { + content: primitive.builtin(), + location: expr.location, + end_location: expr.end_location, + applied: false, + }); + } + } + checker.add_check(check); + } +} diff --git a/src/snapshots/ruff__linter__tests__u003.snap b/src/snapshots/ruff__linter__tests__u003.snap new file mode 100644 index 0000000000..cf2c0debcf --- /dev/null +++ b/src/snapshots/ruff__linter__tests__u003.snap @@ -0,0 +1,90 @@ +--- +source: src/linter.rs +expression: checks +--- +- kind: + TypeOfPrimitive: Str + location: + row: 1 + column: 1 + end_location: + row: 1 + column: 9 + fix: + content: str + location: + row: 1 + column: 1 + end_location: + row: 1 + column: 9 + applied: false +- kind: + TypeOfPrimitive: Bytes + location: + row: 2 + column: 1 + end_location: + row: 2 + column: 10 + fix: + content: bytes + location: + row: 2 + column: 1 + end_location: + row: 2 + column: 10 + applied: false +- kind: + TypeOfPrimitive: Int + location: + row: 3 + column: 1 + end_location: + row: 3 + column: 8 + fix: + content: int + location: + row: 3 + column: 1 + end_location: + row: 3 + column: 8 + applied: false +- kind: + TypeOfPrimitive: Float + location: + row: 4 + column: 1 + end_location: + row: 4 + column: 9 + fix: + content: float + location: + row: 4 + column: 1 + end_location: + row: 4 + column: 9 + applied: false +- kind: + TypeOfPrimitive: Complex + location: + row: 5 + column: 1 + end_location: + row: 5 + column: 9 + fix: + content: complex + location: + row: 5 + column: 1 + end_location: + row: 5 + column: 9 + applied: false +