diff --git a/README.md b/README.md index 3756adc2ef..97075e6874 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,7 @@ Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis F | E714 | NotIsTest | Test for object identity should be `is not` | | E731 | DoNotAssignLambda | Do not assign a lambda expression, use a def | | E741 | AmbiguousVariableName | ambiguous variable name '...' | +| E742 | AmbiguousClassName | ambiguous class name '...' | | E902 | IOError | No such file or directory: `...` | | F401 | UnusedImport | `...` imported but unused | | F403 | ImportStarUsage | Unable to detect undefined names | diff --git a/examples/generate_rules_table.rs b/examples/generate_rules_table.rs index cd5f038101..21491c6c4c 100644 --- a/examples/generate_rules_table.rs +++ b/examples/generate_rules_table.rs @@ -4,6 +4,7 @@ use ruff::checks::{CheckKind, RejectedCmpop}; fn main() { let mut check_kinds: Vec = vec![ CheckKind::AmbiguousVariableName("...".to_string()), + CheckKind::AmbiguousClassName("...".to_string()), CheckKind::AssertTuple, CheckKind::DefaultExceptNotLast, CheckKind::DoNotAssignLambda, diff --git a/resources/test/fixtures/E742.py b/resources/test/fixtures/E742.py new file mode 100644 index 0000000000..0c0a13debd --- /dev/null +++ b/resources/test/fixtures/E742.py @@ -0,0 +1,14 @@ +class l: + pass + + +class I: + pass + + +class O: + pass + + +class X: + pass diff --git a/resources/test/fixtures/pyproject.toml b/resources/test/fixtures/pyproject.toml index 8892c03189..f5f1af9de5 100644 --- a/resources/test/fixtures/pyproject.toml +++ b/resources/test/fixtures/pyproject.toml @@ -10,6 +10,7 @@ select = [ "E714", "E731", "E741", + "E742", "E902", "F401", "F403", diff --git a/src/ast/checks.rs b/src/ast/checks.rs index 503a6477f0..04918898ed 100644 --- a/src/ast/checks.rs +++ b/src/ast/checks.rs @@ -109,6 +109,18 @@ pub fn check_ambiguous_variable_name(name: &str, location: Location) -> Option Option { + if is_ambiguous_name(name) { + Some(Check::new( + CheckKind::AmbiguousClassName(name.to_string()), + location, + )) + } else { + None + } +} + /// Check UselessObjectInheritance compliance. pub fn check_useless_object_inheritance( stmt: &Stmt, diff --git a/src/check_ast.rs b/src/check_ast.rs index f84e8e0fcf..c929b5c9ff 100644 --- a/src/check_ast.rs +++ b/src/check_ast.rs @@ -296,6 +296,12 @@ where } } + if self.settings.select.contains(&CheckCode::E742) { + if let Some(check) = checks::check_ambiguous_class_name(name, stmt.location) { + self.checks.push(check); + } + } + for expr in bases { self.visit_expr(expr) } diff --git a/src/checks.rs b/src/checks.rs index 54dd663e23..eeb19ed027 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -16,6 +16,7 @@ pub enum CheckCode { E714, E731, E741, + E742, E902, F401, F403, @@ -54,6 +55,7 @@ impl FromStr for CheckCode { "E714" => Ok(CheckCode::E714), "E731" => Ok(CheckCode::E731), "E741" => Ok(CheckCode::E741), + "E742" => Ok(CheckCode::E742), "E902" => Ok(CheckCode::E902), "F401" => Ok(CheckCode::F401), "F403" => Ok(CheckCode::F403), @@ -93,6 +95,7 @@ impl CheckCode { CheckCode::E714 => "E714", CheckCode::E731 => "E731", CheckCode::E741 => "E741", + CheckCode::E742 => "E742", CheckCode::E902 => "E902", CheckCode::F401 => "F401", CheckCode::F403 => "F403", @@ -130,6 +133,7 @@ impl CheckCode { CheckCode::E714 => &LintSource::AST, CheckCode::E731 => &LintSource::AST, CheckCode::E741 => &LintSource::AST, + CheckCode::E742 => &LintSource::AST, CheckCode::E902 => &LintSource::FileSystem, CheckCode::F401 => &LintSource::AST, CheckCode::F403 => &LintSource::AST, @@ -174,6 +178,7 @@ pub enum RejectedCmpop { pub enum CheckKind { AssertTuple, AmbiguousVariableName(String), + AmbiguousClassName(String), DefaultExceptNotLast, DoNotAssignLambda, DuplicateArgumentName, @@ -211,6 +216,7 @@ impl CheckKind { match self { CheckKind::AssertTuple => "AssertTuple", CheckKind::AmbiguousVariableName(_) => "AmbiguousVariableName", + CheckKind::AmbiguousClassName(_) => "AmbiguousClassName", CheckKind::DefaultExceptNotLast => "DefaultExceptNotLast", CheckKind::DuplicateArgumentName => "DuplicateArgumentName", CheckKind::FStringMissingPlaceholders => "FStringMissingPlaceholders", @@ -260,6 +266,7 @@ impl CheckKind { CheckKind::LineTooLong => &CheckCode::E501, CheckKind::DoNotAssignLambda => &CheckCode::E731, CheckKind::AmbiguousVariableName(_) => &CheckCode::E741, + CheckKind::AmbiguousClassName(_) => &CheckCode::E742, CheckKind::ModuleImportNotAtTopOfFile => &CheckCode::E402, CheckKind::MultiValueRepeatedKeyLiteral => &CheckCode::F601, CheckKind::MultiValueRepeatedKeyVariable(_) => &CheckCode::F602, @@ -312,6 +319,9 @@ impl CheckKind { CheckKind::DoNotAssignLambda => { "Do not assign a lambda expression, use a def".to_string() } + CheckKind::AmbiguousClassName(name) => { + format!("ambiguous class name '{}'", name) + } CheckKind::AmbiguousVariableName(name) => { format!("ambiguous variable name '{}'", name) } @@ -388,6 +398,7 @@ impl CheckKind { /// Whether the check kind is (potentially) fixable. pub fn fixable(&self) -> bool { match self { + CheckKind::AmbiguousClassName(_) => true, CheckKind::AmbiguousVariableName(_) => false, CheckKind::AssertTuple => false, CheckKind::DefaultExceptNotLast => false, diff --git a/src/linter.rs b/src/linter.rs index 0f524147c5..91dd4d559e 100644 --- a/src/linter.rs +++ b/src/linter.rs @@ -441,6 +441,44 @@ mod tests { Ok(()) } + #[test] + fn e742() -> Result<()> { + let mut actual = check_path( + Path::new("./resources/test/fixtures/E742.py"), + &settings::Settings { + line_length: 88, + exclude: vec![], + select: BTreeSet::from([CheckCode::E742]), + }, + &fixer::Mode::Generate, + )?; + actual.sort_by_key(|check| check.location); + let expected = vec![ + Check { + kind: CheckKind::AmbiguousClassName("l".to_string()), + location: Location::new(1, 1), + fix: None, + }, + Check { + kind: CheckKind::AmbiguousClassName("I".to_string()), + location: Location::new(5, 1), + fix: None, + }, + Check { + kind: CheckKind::AmbiguousClassName("O".to_string()), + location: Location::new(9, 1), + fix: None, + }, + ]; + + assert_eq!(actual.len(), expected.len()); + for i in 0..actual.len() { + assert_eq!(actual[i], expected[i]); + } + + Ok(()) + } + #[test] fn f401() -> Result<()> { let mut actual = check_path( diff --git a/src/pyproject.rs b/src/pyproject.rs index 80d4d56746..ddb63f8820 100644 --- a/src/pyproject.rs +++ b/src/pyproject.rs @@ -267,6 +267,7 @@ other-attribute = 1 CheckCode::E714, CheckCode::E731, CheckCode::E741, + CheckCode::E742, CheckCode::E902, CheckCode::F401, CheckCode::F403, diff --git a/src/settings.rs b/src/settings.rs index dc33b31897..691d27ff4b 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -52,6 +52,7 @@ impl Settings { CheckCode::E714, CheckCode::E731, CheckCode::E741, + CheckCode::E742, CheckCode::E902, CheckCode::F401, CheckCode::F403,