diff --git a/resources/test/fixtures/N806.py b/resources/test/fixtures/N806.py index 6a3772cc60..effd9d4cd2 100644 --- a/resources/test/fixtures/N806.py +++ b/resources/test/fixtures/N806.py @@ -1,5 +1,11 @@ +import collections +from collections import namedtuple + + def f(): lower = 0 Camel = 0 CONSTANT = 0 _ = 0 + MyObj1 = collections.namedtuple("MyObj1", ["a", "b"]) + MyObj2 = namedtuple("MyObj12", ["a", "b"]) diff --git a/resources/test/fixtures/N815.py b/resources/test/fixtures/N815.py index 077ba0e85e..fc2bf1993c 100644 --- a/resources/test/fixtures/N815.py +++ b/resources/test/fixtures/N815.py @@ -1,6 +1,12 @@ +import collections +from collections import namedtuple + + class C: lower = 0 CONSTANT = 0 mixedCase = 0 _mixedCase = 0 mixed_Case = 0 + myObj1 = collections.namedtuple("MyObj1", ["a", "b"]) + myObj2 = namedtuple("MyObj2", ["a", "b"]) diff --git a/resources/test/fixtures/N816.py b/resources/test/fixtures/N816.py index abed196632..b814f5eecf 100644 --- a/resources/test/fixtures/N816.py +++ b/resources/test/fixtures/N816.py @@ -1,5 +1,10 @@ +import collections +from collections import namedtuple + lower = 0 CONSTANT = 0 mixedCase = 0 _mixedCase = 0 mixed_Case = 0 +myObj1 = collections.namedtuple("MyObj1", ["a", "b"]) +myObj2 = namedtuple("MyObj2", ["a", "b"]) diff --git a/src/check_ast.rs b/src/check_ast.rs index 1e587bb54d..6c15703601 100644 --- a/src/check_ast.rs +++ b/src/check_ast.rs @@ -2174,32 +2174,22 @@ impl<'a> Checker<'a> { } if self.settings.enabled.contains(&CheckCode::N806) { - let current = - &self.scopes[*(self.scope_stack.last().expect("No current scope found."))]; - if let Some(check) = - pep8_naming::checks::non_lowercase_variable_in_function(current, expr, id) - { - self.add_check(check); + if matches!(self.current_scope().kind, ScopeKind::Function(..)) { + pep8_naming::plugins::non_lowercase_variable_in_function(self, expr, parent, id) } } if self.settings.enabled.contains(&CheckCode::N815) { - let current = - &self.scopes[*(self.scope_stack.last().expect("No current scope found."))]; - if let Some(check) = - pep8_naming::checks::mixed_case_variable_in_class_scope(current, expr, id) - { - self.add_check(check); + if matches!(self.current_scope().kind, ScopeKind::Class(..)) { + pep8_naming::plugins::mixed_case_variable_in_class_scope(self, expr, parent, id) } } if self.settings.enabled.contains(&CheckCode::N816) { - let current = - &self.scopes[*(self.scope_stack.last().expect("No current scope found."))]; - if let Some(check) = - pep8_naming::checks::mixed_case_variable_in_global_scope(current, expr, id) - { - self.add_check(check); + if matches!(self.current_scope().kind, ScopeKind::Module) { + pep8_naming::plugins::mixed_case_variable_in_global_scope( + self, expr, parent, id, + ) } } diff --git a/src/pep8_naming/checks.rs b/src/pep8_naming/checks.rs index 524966061d..c398b06006 100644 --- a/src/pep8_naming/checks.rs +++ b/src/pep8_naming/checks.rs @@ -1,11 +1,11 @@ use rustpython_ast::{Arguments, Expr, ExprKind, Stmt}; -use crate::ast::types::{FunctionScope, Range, Scope, ScopeKind}; +use crate::ast::types::{Range, Scope, ScopeKind}; use crate::checks::{Check, CheckKind}; use crate::pep8_naming::helpers; use crate::pep8_naming::helpers::FunctionType; use crate::pep8_naming::settings::Settings; -use crate::python::string; +use crate::python::string::{self}; /// N801 pub fn invalid_class_name(class_def: &Stmt, name: &str) -> Option { @@ -100,20 +100,6 @@ pub fn invalid_first_argument_name_for_method( None } -/// N806 -pub fn non_lowercase_variable_in_function(scope: &Scope, expr: &Expr, name: &str) -> Option { - if !matches!(scope.kind, ScopeKind::Function(FunctionScope { .. })) { - return None; - } - if name.to_lowercase() != name { - return Some(Check::new( - CheckKind::NonLowercaseVariableInFunction(name.to_string()), - Range::from_located(expr), - )); - } - None -} - /// N807 pub fn dunder_function_name(scope: &Scope, stmt: &Stmt, name: &str) -> Option { if matches!(scope.kind, ScopeKind::Class(_)) { @@ -192,38 +178,6 @@ pub fn camelcase_imported_as_constant( None } -/// N815 -pub fn mixed_case_variable_in_class_scope(scope: &Scope, expr: &Expr, name: &str) -> Option { - if !matches!(scope.kind, ScopeKind::Class(_)) { - return None; - } - if helpers::is_mixed_case(name) { - return Some(Check::new( - CheckKind::MixedCaseVariableInClassScope(name.to_string()), - Range::from_located(expr), - )); - } - None -} - -/// N816 -pub fn mixed_case_variable_in_global_scope( - scope: &Scope, - expr: &Expr, - name: &str, -) -> Option { - if !matches!(scope.kind, ScopeKind::Module) { - return None; - } - if helpers::is_mixed_case(name) { - return Some(Check::new( - CheckKind::MixedCaseVariableInGlobalScope(name.to_string()), - Range::from_located(expr), - )); - } - None -} - /// N817 pub fn camelcase_imported_as_acronym( import_from: &Stmt, diff --git a/src/pep8_naming/helpers.rs b/src/pep8_naming/helpers.rs index f868bb1402..c819a66f43 100644 --- a/src/pep8_naming/helpers.rs +++ b/src/pep8_naming/helpers.rs @@ -1,7 +1,8 @@ +use fnv::{FnvHashMap, FnvHashSet}; use itertools::Itertools; -use rustpython_ast::{Expr, ExprKind}; +use rustpython_ast::{Expr, ExprKind, Stmt, StmtKind}; -use crate::ast::helpers::match_name_or_attr; +use crate::ast::helpers::{compose_call_path, match_call_path, match_name_or_attr}; use crate::ast::types::{Scope, ScopeKind}; use crate::pep8_naming::settings::Settings; use crate::python::string::{is_lower, is_upper}; @@ -78,6 +79,19 @@ pub fn is_acronym(name: &str, asname: &str) -> bool { name.chars().filter(|c| c.is_uppercase()).join("") == asname } +pub fn is_namedtuple_assignment( + stmt: &Stmt, + from_imports: &FnvHashMap<&str, FnvHashSet<&str>>, +) -> bool { + if let StmtKind::Assign { value, .. } = &stmt.node { + compose_call_path(value) + .map(|call_path| match_call_path(&call_path, "collections.namedtuple", from_imports)) + .unwrap_or(false) + } else { + false + } +} + #[cfg(test)] mod tests { use crate::pep8_naming::helpers::{is_acronym, is_camelcase, is_mixed_case}; diff --git a/src/pep8_naming/mod.rs b/src/pep8_naming/mod.rs index 3189309628..3708822895 100644 --- a/src/pep8_naming/mod.rs +++ b/src/pep8_naming/mod.rs @@ -1,3 +1,4 @@ pub mod checks; mod helpers; +pub mod plugins; pub mod settings; diff --git a/src/pep8_naming/plugins.rs b/src/pep8_naming/plugins.rs new file mode 100644 index 0000000000..de4878faaa --- /dev/null +++ b/src/pep8_naming/plugins.rs @@ -0,0 +1,58 @@ +use rustpython_ast::{Expr, Stmt}; + +use crate::ast::types::Range; +use crate::check_ast::Checker; +use crate::checks::CheckKind; +use crate::pep8_naming::helpers; +use crate::Check; + +/// N806 +pub fn non_lowercase_variable_in_function( + checker: &mut Checker, + expr: &Expr, + stmt: &Stmt, + name: &str, +) { + if name.to_lowercase() != name + && !helpers::is_namedtuple_assignment(stmt, &checker.from_imports) + { + checker.add_check(Check::new( + CheckKind::NonLowercaseVariableInFunction(name.to_string()), + Range::from_located(expr), + )); + } +} + +/// N815 +pub fn mixed_case_variable_in_class_scope( + checker: &mut Checker, + expr: &Expr, + stmt: &Stmt, + name: &str, +) { + if helpers::is_mixed_case(name) + && !helpers::is_namedtuple_assignment(stmt, &checker.from_imports) + { + checker.add_check(Check::new( + CheckKind::MixedCaseVariableInClassScope(name.to_string()), + Range::from_located(expr), + )); + } +} + +/// N816 +pub fn mixed_case_variable_in_global_scope( + checker: &mut Checker, + expr: &Expr, + stmt: &Stmt, + name: &str, +) { + if helpers::is_mixed_case(name) + && !helpers::is_namedtuple_assignment(stmt, &checker.from_imports) + { + checker.add_check(Check::new( + CheckKind::MixedCaseVariableInGlobalScope(name.to_string()), + Range::from_located(expr), + )); + } +} diff --git a/src/snapshots/ruff__linter__tests__N806_N806.py.snap b/src/snapshots/ruff__linter__tests__N806_N806.py.snap index 7e7b0c8d28..aa4b4f8a14 100644 --- a/src/snapshots/ruff__linter__tests__N806_N806.py.snap +++ b/src/snapshots/ruff__linter__tests__N806_N806.py.snap @@ -5,19 +5,19 @@ expression: checks - kind: NonLowercaseVariableInFunction: Camel location: - row: 3 + row: 7 column: 4 end_location: - row: 3 + row: 7 column: 9 fix: ~ - kind: NonLowercaseVariableInFunction: CONSTANT location: - row: 4 + row: 8 column: 4 end_location: - row: 4 + row: 8 column: 12 fix: ~ diff --git a/src/snapshots/ruff__linter__tests__N815_N815.py.snap b/src/snapshots/ruff__linter__tests__N815_N815.py.snap index 99dc827f09..8da0b6d9ea 100644 --- a/src/snapshots/ruff__linter__tests__N815_N815.py.snap +++ b/src/snapshots/ruff__linter__tests__N815_N815.py.snap @@ -5,28 +5,28 @@ expression: checks - kind: MixedCaseVariableInClassScope: mixedCase location: - row: 4 + row: 8 column: 4 end_location: - row: 4 + row: 8 column: 13 fix: ~ - kind: MixedCaseVariableInClassScope: _mixedCase location: - row: 5 + row: 9 column: 4 end_location: - row: 5 + row: 9 column: 14 fix: ~ - kind: MixedCaseVariableInClassScope: mixed_Case location: - row: 6 + row: 10 column: 4 end_location: - row: 6 + row: 10 column: 14 fix: ~ diff --git a/src/snapshots/ruff__linter__tests__N816_N816.py.snap b/src/snapshots/ruff__linter__tests__N816_N816.py.snap index cfaa442dd6..1615a63550 100644 --- a/src/snapshots/ruff__linter__tests__N816_N816.py.snap +++ b/src/snapshots/ruff__linter__tests__N816_N816.py.snap @@ -5,28 +5,28 @@ expression: checks - kind: MixedCaseVariableInGlobalScope: mixedCase location: - row: 3 + row: 6 column: 0 end_location: - row: 3 + row: 6 column: 9 fix: ~ - kind: MixedCaseVariableInGlobalScope: _mixedCase location: - row: 4 + row: 7 column: 0 end_location: - row: 4 + row: 7 column: 10 fix: ~ - kind: MixedCaseVariableInGlobalScope: mixed_Case location: - row: 5 + row: 8 column: 0 end_location: - row: 5 + row: 8 column: 10 fix: ~