From 65b7dc29bd9b48fe2ba0d2781e06095b6a17fe2b Mon Sep 17 00:00:00 2001 From: Simon Brugman Date: Mon, 13 Feb 2023 13:50:35 +0100 Subject: [PATCH] Implement `flake8-module-naming` - Implement N999 (following flake8-module-naming) in pep8_naming - Refactor pep8_naming: split rules.rs into file per rule - Documentation for majority of the violations Closes https://github.com/charliermarsh/ruff/issues/2734 --- README.md | 25 +- .../N999/module/MODULE/__init__.py | 0 .../pep8_naming/N999/module/MODULE/file.py | 0 .../N999/module/flake9/__init__.py | 0 .../N999/module/mod with spaces/__init__.py | 0 .../N999/module/mod with spaces/file.py | 0 .../N999/module/mod with spaces/file2.py | 0 .../N999/module/mod-with-dashes/__init__.py | 0 .../N999/module/no_module/test.txt | 0 .../N999/module/valid_name/__init__.py | 0 .../module/valid_name/file-with-dashes.py | 0 crates/ruff/src/checkers/filesystem.rs | 8 + crates/ruff/src/registry.rs | 3 +- crates/ruff/src/rules/pep8_naming/mod.rs | 14 +- crates/ruff/src/rules/pep8_naming/rules.rs | 573 ------------------ .../rules/camelcase_imported_as_acronym.rs | 73 +++ .../rules/camelcase_imported_as_constant.rs | 70 +++ .../rules/camelcase_imported_as_lowercase.rs | 66 ++ .../constant_imported_as_non_constant.rs | 64 ++ .../pep8_naming/rules/dunder_function_name.rs | 65 ++ .../rules/error_suffix_on_exception_name.rs | 70 +++ .../rules/invalid_argument_name.rs | 35 ++ .../pep8_naming/rules/invalid_class_name.rs | 64 ++ ...id_first_argument_name_for_class_method.rs | 94 +++ .../invalid_first_argument_name_for_method.rs | 91 +++ .../rules/invalid_function_name.rs | 68 +++ .../pep8_naming/rules/invalid_module_name.rs | 64 ++ .../lowercase_imported_as_non_lowercase.rs | 65 ++ .../mixed_case_variable_in_class_scope.rs | 47 ++ .../mixed_case_variable_in_global_scope.rs | 47 ++ .../ruff/src/rules/pep8_naming/rules/mod.rs | 55 ++ .../non_lowercase_variable_in_function.rs | 82 +++ ...999_N999__module__MODULE____init__.py.snap | 16 + ...s__N999_N999__module__MODULE__file.py.snap | 6 + ...999_N999__module__flake9____init__.py.snap | 6 + ..._module__mod with spaces____init__.py.snap | 16 + ...999__module__mod with spaces__file.py.snap | 6 + ..._module__mod-with-dashes____init__.py.snap | 16 + ...999_N999__module__no_module__test.txt.snap | 6 + ...N999__module__valid_name____init__.py.snap | 6 + ...dule__valid_name__file-with-dashes.py.snap | 16 + crates/ruff_python/src/string.rs | 8 +- ruff.schema.json | 3 + 43 files changed, 1260 insertions(+), 588 deletions(-) create mode 100644 crates/ruff/resources/test/fixtures/pep8_naming/N999/module/MODULE/__init__.py create mode 100644 crates/ruff/resources/test/fixtures/pep8_naming/N999/module/MODULE/file.py create mode 100644 crates/ruff/resources/test/fixtures/pep8_naming/N999/module/flake9/__init__.py create mode 100644 crates/ruff/resources/test/fixtures/pep8_naming/N999/module/mod with spaces/__init__.py create mode 100644 crates/ruff/resources/test/fixtures/pep8_naming/N999/module/mod with spaces/file.py create mode 100644 crates/ruff/resources/test/fixtures/pep8_naming/N999/module/mod with spaces/file2.py create mode 100644 crates/ruff/resources/test/fixtures/pep8_naming/N999/module/mod-with-dashes/__init__.py create mode 100644 crates/ruff/resources/test/fixtures/pep8_naming/N999/module/no_module/test.txt create mode 100644 crates/ruff/resources/test/fixtures/pep8_naming/N999/module/valid_name/__init__.py create mode 100644 crates/ruff/resources/test/fixtures/pep8_naming/N999/module/valid_name/file-with-dashes.py delete mode 100644 crates/ruff/src/rules/pep8_naming/rules.rs create mode 100644 crates/ruff/src/rules/pep8_naming/rules/camelcase_imported_as_acronym.rs create mode 100644 crates/ruff/src/rules/pep8_naming/rules/camelcase_imported_as_constant.rs create mode 100644 crates/ruff/src/rules/pep8_naming/rules/camelcase_imported_as_lowercase.rs create mode 100644 crates/ruff/src/rules/pep8_naming/rules/constant_imported_as_non_constant.rs create mode 100644 crates/ruff/src/rules/pep8_naming/rules/dunder_function_name.rs create mode 100644 crates/ruff/src/rules/pep8_naming/rules/error_suffix_on_exception_name.rs create mode 100644 crates/ruff/src/rules/pep8_naming/rules/invalid_argument_name.rs create mode 100644 crates/ruff/src/rules/pep8_naming/rules/invalid_class_name.rs create mode 100644 crates/ruff/src/rules/pep8_naming/rules/invalid_first_argument_name_for_class_method.rs create mode 100644 crates/ruff/src/rules/pep8_naming/rules/invalid_first_argument_name_for_method.rs create mode 100644 crates/ruff/src/rules/pep8_naming/rules/invalid_function_name.rs create mode 100644 crates/ruff/src/rules/pep8_naming/rules/invalid_module_name.rs create mode 100644 crates/ruff/src/rules/pep8_naming/rules/lowercase_imported_as_non_lowercase.rs create mode 100644 crates/ruff/src/rules/pep8_naming/rules/mixed_case_variable_in_class_scope.rs create mode 100644 crates/ruff/src/rules/pep8_naming/rules/mixed_case_variable_in_global_scope.rs create mode 100644 crates/ruff/src/rules/pep8_naming/rules/mod.rs create mode 100644 crates/ruff/src/rules/pep8_naming/rules/non_lowercase_variable_in_function.rs create mode 100644 crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__MODULE____init__.py.snap create mode 100644 crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__MODULE__file.py.snap create mode 100644 crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__flake9____init__.py.snap create mode 100644 crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__mod with spaces____init__.py.snap create mode 100644 crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__mod with spaces__file.py.snap create mode 100644 crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__mod-with-dashes____init__.py.snap create mode 100644 crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__no_module__test.txt.snap create mode 100644 crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__valid_name____init__.py.snap create mode 100644 crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__valid_name__file-with-dashes.py.snap diff --git a/README.md b/README.md index a7c4dead91da94..b73fcff947253e 100644 --- a/README.md +++ b/README.md @@ -813,21 +813,22 @@ For more, see [pep8-naming](https://pypi.org/project/pep8-naming/) on PyPI. | Code | Name | Message | Fix | | ---- | ---- | ------- | --- | -| N801 | invalid-class-name | Class name `{name}` should use CapWords convention | | -| N802 | invalid-function-name | Function name `{name}` should be lowercase | | +| N801 | [invalid-class-name](https://beta.ruff.rs/docs/rules/invalid-class-name/) | Class name `{name}` should use CapWords convention | | +| N802 | [invalid-function-name](https://beta.ruff.rs/docs/rules/invalid-function-name/) | Function name `{name}` should be lowercase | | | N803 | invalid-argument-name | Argument name `{name}` should be lowercase | | -| N804 | invalid-first-argument-name-for-class-method | First argument of a class method should be named `cls` | | -| N805 | invalid-first-argument-name-for-method | First argument of a method should be named `self` | | -| N806 | non-lowercase-variable-in-function | Variable `{name}` in function should be lowercase | | -| N807 | dunder-function-name | Function name should not start and end with `__` | | -| N811 | constant-imported-as-non-constant | Constant `{name}` imported as non-constant `{asname}` | | -| N812 | lowercase-imported-as-non-lowercase | Lowercase `{name}` imported as non-lowercase `{asname}` | | -| N813 | camelcase-imported-as-lowercase | Camelcase `{name}` imported as lowercase `{asname}` | | -| N814 | camelcase-imported-as-constant | Camelcase `{name}` imported as constant `{asname}` | | +| N804 | [invalid-first-argument-name-for-class-method](https://beta.ruff.rs/docs/rules/invalid-first-argument-name-for-class-method/) | First argument of a class method should be named `cls` | | +| N805 | [invalid-first-argument-name-for-method](https://beta.ruff.rs/docs/rules/invalid-first-argument-name-for-method/) | First argument of a method should be named `self` | | +| N806 | [non-lowercase-variable-in-function](https://beta.ruff.rs/docs/rules/non-lowercase-variable-in-function/) | Variable `{name}` in function should be lowercase | | +| N807 | [dunder-function-name](https://beta.ruff.rs/docs/rules/dunder-function-name/) | Function name should not start and end with `__` | | +| N811 | [constant-imported-as-non-constant](https://beta.ruff.rs/docs/rules/constant-imported-as-non-constant/) | Constant `{name}` imported as non-constant `{asname}` | | +| N812 | [lowercase-imported-as-non-lowercase](https://beta.ruff.rs/docs/rules/lowercase-imported-as-non-lowercase/) | Lowercase `{name}` imported as non-lowercase `{asname}` | | +| N813 | [camelcase-imported-as-lowercase](https://beta.ruff.rs/docs/rules/camelcase-imported-as-lowercase/) | Camelcase `{name}` imported as lowercase `{asname}` | | +| N814 | [camelcase-imported-as-constant](https://beta.ruff.rs/docs/rules/camelcase-imported-as-constant/) | Camelcase `{name}` imported as constant `{asname}` | | | N815 | mixed-case-variable-in-class-scope | Variable `{name}` in class scope should not be mixedCase | | | N816 | mixed-case-variable-in-global-scope | Variable `{name}` in global scope should not be mixedCase | | -| N817 | camelcase-imported-as-acronym | Camelcase `{name}` imported as acronym `{asname}` | | -| N818 | error-suffix-on-exception-name | Exception name `{name}` should be named with an Error suffix | | +| N817 | [camelcase-imported-as-acronym](https://beta.ruff.rs/docs/rules/camelcase-imported-as-acronym/) | Camelcase `{name}` imported as acronym `{asname}` | | +| N818 | [error-suffix-on-exception-name](https://beta.ruff.rs/docs/rules/error-suffix-on-exception-name/) | Exception name `{name}` should be named with an Error suffix | | +| N999 | [invalid-module-name](https://beta.ruff.rs/docs/rules/invalid-module-name/) | Invalid module name: '{name}' | | ### pydocstyle (D) diff --git a/crates/ruff/resources/test/fixtures/pep8_naming/N999/module/MODULE/__init__.py b/crates/ruff/resources/test/fixtures/pep8_naming/N999/module/MODULE/__init__.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/crates/ruff/resources/test/fixtures/pep8_naming/N999/module/MODULE/file.py b/crates/ruff/resources/test/fixtures/pep8_naming/N999/module/MODULE/file.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/crates/ruff/resources/test/fixtures/pep8_naming/N999/module/flake9/__init__.py b/crates/ruff/resources/test/fixtures/pep8_naming/N999/module/flake9/__init__.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/crates/ruff/resources/test/fixtures/pep8_naming/N999/module/mod with spaces/__init__.py b/crates/ruff/resources/test/fixtures/pep8_naming/N999/module/mod with spaces/__init__.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/crates/ruff/resources/test/fixtures/pep8_naming/N999/module/mod with spaces/file.py b/crates/ruff/resources/test/fixtures/pep8_naming/N999/module/mod with spaces/file.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/crates/ruff/resources/test/fixtures/pep8_naming/N999/module/mod with spaces/file2.py b/crates/ruff/resources/test/fixtures/pep8_naming/N999/module/mod with spaces/file2.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/crates/ruff/resources/test/fixtures/pep8_naming/N999/module/mod-with-dashes/__init__.py b/crates/ruff/resources/test/fixtures/pep8_naming/N999/module/mod-with-dashes/__init__.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/crates/ruff/resources/test/fixtures/pep8_naming/N999/module/no_module/test.txt b/crates/ruff/resources/test/fixtures/pep8_naming/N999/module/no_module/test.txt new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/crates/ruff/resources/test/fixtures/pep8_naming/N999/module/valid_name/__init__.py b/crates/ruff/resources/test/fixtures/pep8_naming/N999/module/valid_name/__init__.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/crates/ruff/resources/test/fixtures/pep8_naming/N999/module/valid_name/file-with-dashes.py b/crates/ruff/resources/test/fixtures/pep8_naming/N999/module/valid_name/file-with-dashes.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/crates/ruff/src/checkers/filesystem.rs b/crates/ruff/src/checkers/filesystem.rs index 0d6a1c6b71c7bd..d3d0847010041f 100644 --- a/crates/ruff/src/checkers/filesystem.rs +++ b/crates/ruff/src/checkers/filesystem.rs @@ -2,6 +2,7 @@ use std::path::Path; use crate::registry::{Diagnostic, Rule}; use crate::rules::flake8_no_pep420::rules::implicit_namespace_package; +use crate::rules::pep8_naming::rules::invalid_module_name; use crate::settings::Settings; pub fn check_file_path( @@ -20,5 +21,12 @@ pub fn check_file_path( } } + // pep8-naming + if settings.rules.enabled(&Rule::InvalidModuleName) { + if let Some(diagnostic) = invalid_module_name(path, package) { + diagnostics.push(diagnostic); + } + } + diagnostics } diff --git a/crates/ruff/src/registry.rs b/crates/ruff/src/registry.rs index f5dc4dd5873bf6..ef8f591488e31c 100644 --- a/crates/ruff/src/registry.rs +++ b/crates/ruff/src/registry.rs @@ -376,6 +376,7 @@ ruff_macros::define_rule_mapping!( N816 => rules::pep8_naming::rules::MixedCaseVariableInGlobalScope, N817 => rules::pep8_naming::rules::CamelcaseImportedAsAcronym, N818 => rules::pep8_naming::rules::ErrorSuffixOnExceptionName, + N999 => rules::pep8_naming::rules::InvalidModuleName, // isort I001 => rules::isort::rules::UnsortedImports, I002 => rules::isort::rules::MissingRequiredImport, @@ -787,7 +788,7 @@ impl Rule { | Rule::TrailingCommaProhibited => &LintSource::Tokens, Rule::IOError => &LintSource::Io, Rule::UnsortedImports | Rule::MissingRequiredImport => &LintSource::Imports, - Rule::ImplicitNamespacePackage => &LintSource::Filesystem, + Rule::ImplicitNamespacePackage | Rule::InvalidModuleName => &LintSource::Filesystem, #[cfg(feature = "logical_lines")] Rule::IndentationWithInvalidMultiple | Rule::IndentationWithInvalidMultipleComment diff --git a/crates/ruff/src/rules/pep8_naming/mod.rs b/crates/ruff/src/rules/pep8_naming/mod.rs index 74143ab716143b..024012a497df27 100644 --- a/crates/ruff/src/rules/pep8_naming/mod.rs +++ b/crates/ruff/src/rules/pep8_naming/mod.rs @@ -1,4 +1,5 @@ //! Rules from [pep8-naming](https://pypi.org/project/pep8-naming/). +//! Rule N999 from [flake8-module-name](https://pypi.org/project/flake8-module-name/). mod helpers; pub(crate) mod rules; pub mod settings; @@ -30,11 +31,22 @@ mod tests { #[test_case(Rule::MixedCaseVariableInGlobalScope, Path::new("N816.py"); "N816")] #[test_case(Rule::CamelcaseImportedAsAcronym, Path::new("N817.py"); "N817")] #[test_case(Rule::ErrorSuffixOnExceptionName, Path::new("N818.py"); "N818")] + #[test_case(Rule::InvalidModuleName, Path::new("N999/module/mod with spaces/__init__.py"); "N999_1")] + #[test_case(Rule::InvalidModuleName, Path::new("N999/module/mod with spaces/file.py"); "N999_2")] + #[test_case(Rule::InvalidModuleName, Path::new("N999/module/flake9/__init__.py"); "N999_3")] + #[test_case(Rule::InvalidModuleName, Path::new("N999/module/MODULE/__init__.py"); "N999_4")] + #[test_case(Rule::InvalidModuleName, Path::new("N999/module/MODULE/file.py"); "N999_5")] + #[test_case(Rule::InvalidModuleName, Path::new("N999/module/mod-with-dashes/__init__.py"); "N999_6")] + #[test_case(Rule::InvalidModuleName, Path::new("N999/module/valid_name/__init__.py"); "N999_7")] + #[test_case(Rule::InvalidModuleName, Path::new("N999/module/no_module/test.txt"); "N999_8")] + #[test_case(Rule::InvalidModuleName, Path::new("N999/module/valid_name/file-with-dashes.py"); "N999_9")] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy()); let diagnostics = test_path( Path::new("pep8_naming").join(path).as_path(), - &settings::Settings::for_rule(rule_code), + &settings::Settings { + ..settings::Settings::for_rule(rule_code) + }, )?; assert_yaml_snapshot!(snapshot, diagnostics); Ok(()) diff --git a/crates/ruff/src/rules/pep8_naming/rules.rs b/crates/ruff/src/rules/pep8_naming/rules.rs deleted file mode 100644 index 0000bceb06b077..00000000000000 --- a/crates/ruff/src/rules/pep8_naming/rules.rs +++ /dev/null @@ -1,573 +0,0 @@ -use ruff_macros::{define_violation, derive_message_formats}; -use ruff_python::string::{self}; -use rustpython_parser::ast::{Arg, Arguments, Expr, ExprKind, Stmt}; - -use super::helpers; -use crate::ast::function_type; -use crate::ast::helpers::identifier_range; -use crate::ast::types::{Range, Scope, ScopeKind}; -use crate::checkers::ast::Checker; -use crate::registry::Diagnostic; -use crate::source_code::Locator; -use crate::violation::Violation; - -define_violation!( - pub struct InvalidClassName { - pub name: String, - } -); -impl Violation for InvalidClassName { - #[derive_message_formats] - fn message(&self) -> String { - let InvalidClassName { name } = self; - format!("Class name `{name}` should use CapWords convention ") - } -} - -define_violation!( - pub struct InvalidFunctionName { - pub name: String, - } -); -impl Violation for InvalidFunctionName { - #[derive_message_formats] - fn message(&self) -> String { - let InvalidFunctionName { name } = self; - format!("Function name `{name}` should be lowercase") - } -} - -define_violation!( - pub struct InvalidArgumentName { - pub name: String, - } -); -impl Violation for InvalidArgumentName { - #[derive_message_formats] - fn message(&self) -> String { - let InvalidArgumentName { name } = self; - format!("Argument name `{name}` should be lowercase") - } -} - -define_violation!( - pub struct InvalidFirstArgumentNameForClassMethod; -); -impl Violation for InvalidFirstArgumentNameForClassMethod { - #[derive_message_formats] - fn message(&self) -> String { - format!("First argument of a class method should be named `cls`") - } -} - -define_violation!( - pub struct InvalidFirstArgumentNameForMethod; -); -impl Violation for InvalidFirstArgumentNameForMethod { - #[derive_message_formats] - fn message(&self) -> String { - format!("First argument of a method should be named `self`") - } -} - -define_violation!( - pub struct NonLowercaseVariableInFunction { - pub name: String, - } -); -impl Violation for NonLowercaseVariableInFunction { - #[derive_message_formats] - fn message(&self) -> String { - let NonLowercaseVariableInFunction { name } = self; - format!("Variable `{name}` in function should be lowercase") - } -} - -define_violation!( - pub struct DunderFunctionName; -); -impl Violation for DunderFunctionName { - #[derive_message_formats] - fn message(&self) -> String { - format!("Function name should not start and end with `__`") - } -} - -define_violation!( - pub struct ConstantImportedAsNonConstant { - pub name: String, - pub asname: String, - } -); -impl Violation for ConstantImportedAsNonConstant { - #[derive_message_formats] - fn message(&self) -> String { - let ConstantImportedAsNonConstant { name, asname } = self; - format!("Constant `{name}` imported as non-constant `{asname}`") - } -} - -define_violation!( - pub struct LowercaseImportedAsNonLowercase { - pub name: String, - pub asname: String, - } -); -impl Violation for LowercaseImportedAsNonLowercase { - #[derive_message_formats] - fn message(&self) -> String { - let LowercaseImportedAsNonLowercase { name, asname } = self; - format!("Lowercase `{name}` imported as non-lowercase `{asname}`") - } -} - -define_violation!( - pub struct CamelcaseImportedAsLowercase { - pub name: String, - pub asname: String, - } -); -impl Violation for CamelcaseImportedAsLowercase { - #[derive_message_formats] - fn message(&self) -> String { - let CamelcaseImportedAsLowercase { name, asname } = self; - format!("Camelcase `{name}` imported as lowercase `{asname}`") - } -} - -define_violation!( - pub struct CamelcaseImportedAsConstant { - pub name: String, - pub asname: String, - } -); -impl Violation for CamelcaseImportedAsConstant { - #[derive_message_formats] - fn message(&self) -> String { - let CamelcaseImportedAsConstant { name, asname } = self; - format!("Camelcase `{name}` imported as constant `{asname}`") - } -} - -define_violation!( - pub struct MixedCaseVariableInClassScope { - pub name: String, - } -); -impl Violation for MixedCaseVariableInClassScope { - #[derive_message_formats] - fn message(&self) -> String { - let MixedCaseVariableInClassScope { name } = self; - format!("Variable `{name}` in class scope should not be mixedCase") - } -} - -define_violation!( - pub struct MixedCaseVariableInGlobalScope { - pub name: String, - } -); -impl Violation for MixedCaseVariableInGlobalScope { - #[derive_message_formats] - fn message(&self) -> String { - let MixedCaseVariableInGlobalScope { name } = self; - format!("Variable `{name}` in global scope should not be mixedCase") - } -} - -define_violation!( - pub struct CamelcaseImportedAsAcronym { - pub name: String, - pub asname: String, - } -); -impl Violation for CamelcaseImportedAsAcronym { - #[derive_message_formats] - fn message(&self) -> String { - let CamelcaseImportedAsAcronym { name, asname } = self; - format!("Camelcase `{name}` imported as acronym `{asname}`") - } -} - -define_violation!( - pub struct ErrorSuffixOnExceptionName { - pub name: String, - } -); -impl Violation for ErrorSuffixOnExceptionName { - #[derive_message_formats] - fn message(&self) -> String { - let ErrorSuffixOnExceptionName { name } = self; - format!("Exception name `{name}` should be named with an Error suffix") - } -} - -/// N801 -pub fn invalid_class_name(class_def: &Stmt, name: &str, locator: &Locator) -> Option { - let stripped = name.strip_prefix('_').unwrap_or(name); - if !stripped.chars().next().map_or(false, char::is_uppercase) || stripped.contains('_') { - return Some(Diagnostic::new( - InvalidClassName { - name: name.to_string(), - }, - identifier_range(class_def, locator), - )); - } - None -} - -/// N802 -pub fn invalid_function_name( - func_def: &Stmt, - name: &str, - ignore_names: &[String], - locator: &Locator, -) -> Option { - if ignore_names.iter().any(|ignore_name| ignore_name == name) { - return None; - } - if name.to_lowercase() != name { - return Some(Diagnostic::new( - InvalidFunctionName { - name: name.to_string(), - }, - identifier_range(func_def, locator), - )); - } - None -} - -/// N803 -pub fn invalid_argument_name(name: &str, arg: &Arg, ignore_names: &[String]) -> Option { - if ignore_names.iter().any(|ignore_name| ignore_name == name) { - return None; - } - if name.to_lowercase() != name { - return Some(Diagnostic::new( - InvalidArgumentName { - name: name.to_string(), - }, - Range::from_located(arg), - )); - } - None -} - -/// N804 -pub fn invalid_first_argument_name_for_class_method( - checker: &Checker, - scope: &Scope, - name: &str, - decorator_list: &[Expr], - args: &Arguments, -) -> Option { - if !matches!( - function_type::classify( - checker, - scope, - name, - decorator_list, - &checker.settings.pep8_naming.classmethod_decorators, - &checker.settings.pep8_naming.staticmethod_decorators, - ), - function_type::FunctionType::ClassMethod - ) { - return None; - } - if let Some(arg) = args.posonlyargs.first().or_else(|| args.args.first()) { - if arg.node.arg != "cls" { - if checker - .settings - .pep8_naming - .ignore_names - .iter() - .any(|ignore_name| ignore_name == name) - { - return None; - } - return Some(Diagnostic::new( - InvalidFirstArgumentNameForClassMethod, - Range::from_located(arg), - )); - } - } - None -} - -/// N805 -pub fn invalid_first_argument_name_for_method( - checker: &Checker, - scope: &Scope, - name: &str, - decorator_list: &[Expr], - args: &Arguments, -) -> Option { - if !matches!( - function_type::classify( - checker, - scope, - name, - decorator_list, - &checker.settings.pep8_naming.classmethod_decorators, - &checker.settings.pep8_naming.staticmethod_decorators, - ), - function_type::FunctionType::Method - ) { - return None; - } - let arg = args.posonlyargs.first().or_else(|| args.args.first())?; - if arg.node.arg == "self" { - return None; - } - if checker - .settings - .pep8_naming - .ignore_names - .iter() - .any(|ignore_name| ignore_name == name) - { - return None; - } - Some(Diagnostic::new( - InvalidFirstArgumentNameForMethod, - Range::from_located(arg), - )) -} - -/// N806 -pub fn non_lowercase_variable_in_function( - checker: &mut Checker, - expr: &Expr, - stmt: &Stmt, - name: &str, -) { - if checker - .settings - .pep8_naming - .ignore_names - .iter() - .any(|ignore_name| ignore_name == name) - { - return; - } - - if name.to_lowercase() != name - && !helpers::is_namedtuple_assignment(checker, stmt) - && !helpers::is_typeddict_assignment(checker, stmt) - && !helpers::is_type_var_assignment(checker, stmt) - { - checker.diagnostics.push(Diagnostic::new( - NonLowercaseVariableInFunction { - name: name.to_string(), - }, - Range::from_located(expr), - )); - } -} - -/// N807 -pub fn dunder_function_name( - scope: &Scope, - stmt: &Stmt, - name: &str, - locator: &Locator, -) -> Option { - if matches!(scope.kind, ScopeKind::Class(_)) { - return None; - } - if !(name.starts_with("__") && name.ends_with("__")) { - return None; - } - // Allowed under PEP 562 (https://peps.python.org/pep-0562/). - if matches!(scope.kind, ScopeKind::Module) && (name == "__getattr__" || name == "__dir__") { - return None; - } - - Some(Diagnostic::new( - DunderFunctionName, - identifier_range(stmt, locator), - )) -} - -/// N811 -pub fn constant_imported_as_non_constant( - import_from: &Stmt, - name: &str, - asname: &str, - locator: &Locator, -) -> Option { - if string::is_upper(name) && !string::is_upper(asname) { - return Some(Diagnostic::new( - ConstantImportedAsNonConstant { - name: name.to_string(), - asname: asname.to_string(), - }, - identifier_range(import_from, locator), - )); - } - None -} - -/// N812 -pub fn lowercase_imported_as_non_lowercase( - import_from: &Stmt, - name: &str, - asname: &str, - locator: &Locator, -) -> Option { - if !string::is_upper(name) && string::is_lower(name) && asname.to_lowercase() != asname { - return Some(Diagnostic::new( - LowercaseImportedAsNonLowercase { - name: name.to_string(), - asname: asname.to_string(), - }, - identifier_range(import_from, locator), - )); - } - None -} - -/// N813 -pub fn camelcase_imported_as_lowercase( - import_from: &Stmt, - name: &str, - asname: &str, - locator: &Locator, -) -> Option { - if helpers::is_camelcase(name) && string::is_lower(asname) { - return Some(Diagnostic::new( - CamelcaseImportedAsLowercase { - name: name.to_string(), - asname: asname.to_string(), - }, - identifier_range(import_from, locator), - )); - } - None -} - -/// N814 -pub fn camelcase_imported_as_constant( - import_from: &Stmt, - name: &str, - asname: &str, - locator: &Locator, -) -> Option { - if helpers::is_camelcase(name) - && !string::is_lower(asname) - && string::is_upper(asname) - && !helpers::is_acronym(name, asname) - { - return Some(Diagnostic::new( - CamelcaseImportedAsConstant { - name: name.to_string(), - asname: asname.to_string(), - }, - identifier_range(import_from, locator), - )); - } - None -} - -/// N815 -pub fn mixed_case_variable_in_class_scope( - checker: &mut Checker, - expr: &Expr, - stmt: &Stmt, - name: &str, -) { - if checker - .settings - .pep8_naming - .ignore_names - .iter() - .any(|ignore_name| ignore_name == name) - { - return; - } - if helpers::is_mixed_case(name) && !helpers::is_namedtuple_assignment(checker, stmt) { - checker.diagnostics.push(Diagnostic::new( - MixedCaseVariableInClassScope { - name: 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 checker - .settings - .pep8_naming - .ignore_names - .iter() - .any(|ignore_name| ignore_name == name) - { - return; - } - if helpers::is_mixed_case(name) && !helpers::is_namedtuple_assignment(checker, stmt) { - checker.diagnostics.push(Diagnostic::new( - MixedCaseVariableInGlobalScope { - name: name.to_string(), - }, - Range::from_located(expr), - )); - } -} - -/// N817 -pub fn camelcase_imported_as_acronym( - import_from: &Stmt, - name: &str, - asname: &str, - locator: &Locator, -) -> Option { - if helpers::is_camelcase(name) - && !string::is_lower(asname) - && string::is_upper(asname) - && helpers::is_acronym(name, asname) - { - return Some(Diagnostic::new( - CamelcaseImportedAsAcronym { - name: name.to_string(), - asname: asname.to_string(), - }, - identifier_range(import_from, locator), - )); - } - None -} - -/// N818 -pub fn error_suffix_on_exception_name( - class_def: &Stmt, - bases: &[Expr], - name: &str, - locator: &Locator, -) -> Option { - if !bases.iter().any(|base| { - if let ExprKind::Name { id, .. } = &base.node { - id == "Exception" || id.ends_with("Error") - } else { - false - } - }) { - return None; - } - - if name.ends_with("Error") { - return None; - } - Some(Diagnostic::new( - ErrorSuffixOnExceptionName { - name: name.to_string(), - }, - identifier_range(class_def, locator), - )) -} diff --git a/crates/ruff/src/rules/pep8_naming/rules/camelcase_imported_as_acronym.rs b/crates/ruff/src/rules/pep8_naming/rules/camelcase_imported_as_acronym.rs new file mode 100644 index 00000000000000..8086e02bb06b09 --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/camelcase_imported_as_acronym.rs @@ -0,0 +1,73 @@ +use ruff_macros::{define_violation, derive_message_formats}; +use ruff_python::string::{self}; +use rustpython_parser::ast::Stmt; + +use crate::ast::helpers::identifier_range; +use crate::registry::Diagnostic; +use crate::rules::pep8_naming::helpers; +use crate::source_code::Locator; +use crate::violation::Violation; + +define_violation!( + /// ## What it does + /// Checks for [`CamelCase`] imports that are aliased as acronyms. + /// + /// ## Why is this bad? + /// [PEP8] recommends naming conventions for class names, + /// function names, constants etc. Inconsistenct [naming styles] + /// between import name and the alias may lead developers to expect an import to be of another + /// type (e.g. a confuse class with a constant). + /// + /// Importing aliases should thus follow the same naming conventions. + /// + /// This rule is distinct from 'camelcase-imported-as-constant' for selective enforcement. + /// + /// ## Example + /// ```python + /// from example import MyClassName as MCN + /// ``` + /// + /// Use instead: + /// ```python + /// from example import MyClassName + /// ``` + /// + /// [PEP8]: https://peps.python.org/pep-0008/ + /// [naming styles]: https://peps.python.org/pep-0008/#descriptive-naming-styles + /// [`CamelCase`]: https://en.wikipedia.org/wiki/Camel_case + + pub struct CamelcaseImportedAsAcronym { + pub name: String, + pub asname: String, + } +); +impl Violation for CamelcaseImportedAsAcronym { + #[derive_message_formats] + fn message(&self) -> String { + let CamelcaseImportedAsAcronym { name, asname } = self; + format!("Camelcase `{name}` imported as acronym `{asname}`") + } +} + +/// N817 +pub fn camelcase_imported_as_acronym( + import_from: &Stmt, + name: &str, + asname: &str, + locator: &Locator, +) -> Option { + if helpers::is_camelcase(name) + && !string::is_lower(asname) + && string::is_upper(asname) + && helpers::is_acronym(name, asname) + { + return Some(Diagnostic::new( + CamelcaseImportedAsAcronym { + name: name.to_string(), + asname: asname.to_string(), + }, + identifier_range(import_from, locator), + )); + } + None +} diff --git a/crates/ruff/src/rules/pep8_naming/rules/camelcase_imported_as_constant.rs b/crates/ruff/src/rules/pep8_naming/rules/camelcase_imported_as_constant.rs new file mode 100644 index 00000000000000..7f60693beed768 --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/camelcase_imported_as_constant.rs @@ -0,0 +1,70 @@ +use ruff_macros::{define_violation, derive_message_formats}; +use ruff_python::string::{self}; +use rustpython_parser::ast::Stmt; + +use crate::ast::helpers::identifier_range; +use crate::registry::Diagnostic; +use crate::rules::pep8_naming::helpers; +use crate::source_code::Locator; +use crate::violation::Violation; + +define_violation!( + /// ## What it does + /// Checks for [`CamelCase`] imports that are aliased as constant. + /// + /// ## Why is this bad? + /// [PEP8] recommends naming conventions for class names, + /// function names, constants etc. Inconsistenct [naming styles] + /// between import name and the alias may lead developers to expect an import to be of another + /// type (e.g. a confuse class with a constant). + /// + /// Importing aliases should thus follow the same naming conventions. + /// + /// ## Example + /// ```python + /// from example import MyClassName as MY_CLASS_NAME + /// ``` + /// + /// Use instead: + /// ```python + /// from example import MyClassName + /// ``` + /// + /// [PEP8]: https://peps.python.org/pep-0008/ + /// [naming styles]: https://peps.python.org/pep-0008/#descriptive-naming-styles + /// [`CamelCase`]: https://en.wikipedia.org/wiki/Camel_case + pub struct CamelcaseImportedAsConstant { + pub name: String, + pub asname: String, + } +); +impl Violation for CamelcaseImportedAsConstant { + #[derive_message_formats] + fn message(&self) -> String { + let CamelcaseImportedAsConstant { name, asname } = self; + format!("Camelcase `{name}` imported as constant `{asname}`") + } +} + +/// N814 +pub fn camelcase_imported_as_constant( + import_from: &Stmt, + name: &str, + asname: &str, + locator: &Locator, +) -> Option { + if helpers::is_camelcase(name) + && !string::is_lower(asname) + && string::is_upper(asname) + && !helpers::is_acronym(name, asname) + { + return Some(Diagnostic::new( + CamelcaseImportedAsConstant { + name: name.to_string(), + asname: asname.to_string(), + }, + identifier_range(import_from, locator), + )); + } + None +} diff --git a/crates/ruff/src/rules/pep8_naming/rules/camelcase_imported_as_lowercase.rs b/crates/ruff/src/rules/pep8_naming/rules/camelcase_imported_as_lowercase.rs new file mode 100644 index 00000000000000..88ce50e94e3a01 --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/camelcase_imported_as_lowercase.rs @@ -0,0 +1,66 @@ +use ruff_macros::{define_violation, derive_message_formats}; +use ruff_python::string; +use rustpython_parser::ast::Stmt; + +use crate::ast::helpers::identifier_range; +use crate::registry::Diagnostic; +use crate::rules::pep8_naming::helpers; +use crate::source_code::Locator; +use crate::violation::Violation; + +define_violation!( + /// ## What it does + /// Checks for [`CamelCase`] imports that are aliased as lowercase. + /// + /// ## Why is this bad? + /// [PEP8] recommends naming conventions for class names, + /// function names, constants etc. Inconsistenct [naming styles] + /// between import name and the alias may lead developers to expect an import to be of another + /// type (e.g. confuse a class with a function). + /// + /// Importing aliases should thus follow the same naming conventions. + /// + /// ## Example + /// ```python + /// from example import MyClassName as myclassname + /// ``` + /// + /// Use instead: + /// ```python + /// from example import MyClassName + /// ``` + /// + /// [PEP8]: https://peps.python.org/pep-0008/ + /// [naming styles]: https://peps.python.org/pep-0008/#descriptive-naming-styles + /// [`CamelCase`]: https://en.wikipedia.org/wiki/Camel_case + pub struct CamelcaseImportedAsLowercase { + pub name: String, + pub asname: String, + } +); +impl Violation for CamelcaseImportedAsLowercase { + #[derive_message_formats] + fn message(&self) -> String { + let CamelcaseImportedAsLowercase { name, asname } = self; + format!("Camelcase `{name}` imported as lowercase `{asname}`") + } +} + +/// N813 +pub fn camelcase_imported_as_lowercase( + import_from: &Stmt, + name: &str, + asname: &str, + locator: &Locator, +) -> Option { + if helpers::is_camelcase(name) && string::is_lower(asname) { + return Some(Diagnostic::new( + CamelcaseImportedAsLowercase { + name: name.to_string(), + asname: asname.to_string(), + }, + identifier_range(import_from, locator), + )); + } + None +} diff --git a/crates/ruff/src/rules/pep8_naming/rules/constant_imported_as_non_constant.rs b/crates/ruff/src/rules/pep8_naming/rules/constant_imported_as_non_constant.rs new file mode 100644 index 00000000000000..74af8e1ec3b944 --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/constant_imported_as_non_constant.rs @@ -0,0 +1,64 @@ +use ruff_macros::{define_violation, derive_message_formats}; +use ruff_python::string; +use rustpython_parser::ast::Stmt; + +use crate::ast::helpers::identifier_range; +use crate::registry::Diagnostic; +use crate::source_code::Locator; +use crate::violation::Violation; + +define_violation!( + /// ## What it does + /// Checks for constant imports that are aliased as non constant. + /// + /// ## Why is this bad? + /// [PEP8] recommends naming conventions for class names, + /// function names, constants etc. Inconsistenct [naming styles] + /// between import name and the alias may lead developers to expect an import to be of another + /// type (e.g. a confuse class with a constant). + /// + /// Importing aliases should thus follow the same naming conventions. + /// + /// ## Example + /// ```python + /// from example import CONSTANT_VALUE as ConstantValue + /// ``` + /// + /// Use instead: + /// ```python + /// from example import CONSTANT_VALUE + /// ``` + /// + /// [PEP8]: https://peps.python.org/pep-0008/ + /// [naming styles]: https://peps.python.org/pep-0008/#descriptive-naming-styles + pub struct ConstantImportedAsNonConstant { + pub name: String, + pub asname: String, + } +); +impl Violation for ConstantImportedAsNonConstant { + #[derive_message_formats] + fn message(&self) -> String { + let ConstantImportedAsNonConstant { name, asname } = self; + format!("Constant `{name}` imported as non-constant `{asname}`") + } +} + +/// N811 +pub fn constant_imported_as_non_constant( + import_from: &Stmt, + name: &str, + asname: &str, + locator: &Locator, +) -> Option { + if string::is_upper(name) && !string::is_upper(asname) { + return Some(Diagnostic::new( + ConstantImportedAsNonConstant { + name: name.to_string(), + asname: asname.to_string(), + }, + identifier_range(import_from, locator), + )); + } + None +} diff --git a/crates/ruff/src/rules/pep8_naming/rules/dunder_function_name.rs b/crates/ruff/src/rules/pep8_naming/rules/dunder_function_name.rs new file mode 100644 index 00000000000000..6e1eded7b269cf --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/dunder_function_name.rs @@ -0,0 +1,65 @@ +use ruff_macros::{define_violation, derive_message_formats}; +use rustpython_parser::ast::Stmt; + +use crate::ast::helpers::identifier_range; +use crate::ast::types::{Scope, ScopeKind}; +use crate::registry::Diagnostic; +use crate::source_code::Locator; +use crate::violation::Violation; + +define_violation!( + /// ## What it does + /// Checks for functions with “dunder” names (i.e. names with two leading and two trailing underscores). + /// + /// ## Why is this bad? + /// [PEP8] recommends to only use documented + /// dunder methods: + /// + /// > “magic” objects or attributes that live in user-controlled namespaces. E.g. `__init__`, + /// > `__import__` or `__file__`. Never invent such names; only use them as documented. + /// + /// ## Example + /// ```python + /// def __my_function__(): + /// pass + /// ``` + /// + /// Use instead: + /// ```python + /// def my_function(): + /// pass + /// ``` + /// + /// [PEP8]: https://peps.python.org/pep-0008/ + pub struct DunderFunctionName; +); +impl Violation for DunderFunctionName { + #[derive_message_formats] + fn message(&self) -> String { + format!("Function name should not start and end with `__`") + } +} + +/// N807 +pub fn dunder_function_name( + scope: &Scope, + stmt: &Stmt, + name: &str, + locator: &Locator, +) -> Option { + if matches!(scope.kind, ScopeKind::Class(_)) { + return None; + } + if !(name.starts_with("__") && name.ends_with("__")) { + return None; + } + // Allowed under PEP 562 (https://peps.python.org/pep-0562/). + if matches!(scope.kind, ScopeKind::Module) && (name == "__getattr__" || name == "__dir__") { + return None; + } + + Some(Diagnostic::new( + DunderFunctionName, + identifier_range(stmt, locator), + )) +} diff --git a/crates/ruff/src/rules/pep8_naming/rules/error_suffix_on_exception_name.rs b/crates/ruff/src/rules/pep8_naming/rules/error_suffix_on_exception_name.rs new file mode 100644 index 00000000000000..fe3225c536f004 --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/error_suffix_on_exception_name.rs @@ -0,0 +1,70 @@ +use ruff_macros::{define_violation, derive_message_formats}; +use rustpython_parser::ast::{Expr, ExprKind, Stmt}; + +use crate::ast::helpers::identifier_range; +use crate::registry::Diagnostic; +use crate::source_code::Locator; +use crate::violation::Violation; + +define_violation!( + /// ## What it does + /// Checks for definitions of custom exceptions without the `Error` suffix. + /// + /// ## Why is this bad? + /// The `Error` suffix is recommended by [PEP8]: + /// + /// > Because exceptions should be classes, the class naming convention applies here. However, + /// > you should use the suffix `“Error”` on your exception names (if the exception actually is an error). + /// + /// ## Example + /// ```python + /// class Validation(Exception): + /// ... + /// ``` + /// + /// Use instead: + /// ```python + /// class ValidationError(Exception): + /// ... + /// ``` + /// + /// [PEP8]: https://peps.python.org/pep-0008/#exception-names + pub struct ErrorSuffixOnExceptionName { + pub name: String, + } +); +impl Violation for ErrorSuffixOnExceptionName { + #[derive_message_formats] + fn message(&self) -> String { + let ErrorSuffixOnExceptionName { name } = self; + format!("Exception name `{name}` should be named with an Error suffix") + } +} + +/// N818 +pub fn error_suffix_on_exception_name( + class_def: &Stmt, + bases: &[Expr], + name: &str, + locator: &Locator, +) -> Option { + if !bases.iter().any(|base| { + if let ExprKind::Name { id, .. } = &base.node { + id == "Exception" || id.ends_with("Error") + } else { + false + } + }) { + return None; + } + + if name.ends_with("Error") { + return None; + } + Some(Diagnostic::new( + ErrorSuffixOnExceptionName { + name: name.to_string(), + }, + identifier_range(class_def, locator), + )) +} diff --git a/crates/ruff/src/rules/pep8_naming/rules/invalid_argument_name.rs b/crates/ruff/src/rules/pep8_naming/rules/invalid_argument_name.rs new file mode 100644 index 00000000000000..b867dc7d0cc308 --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/invalid_argument_name.rs @@ -0,0 +1,35 @@ +use ruff_macros::{define_violation, derive_message_formats}; +use rustpython_parser::ast::Arg; + +use crate::ast::types::Range; +use crate::registry::Diagnostic; +use crate::violation::Violation; + +define_violation!( + pub struct InvalidArgumentName { + pub name: String, + } +); +impl Violation for InvalidArgumentName { + #[derive_message_formats] + fn message(&self) -> String { + let InvalidArgumentName { name } = self; + format!("Argument name `{name}` should be lowercase") + } +} + +/// N803 +pub fn invalid_argument_name(name: &str, arg: &Arg, ignore_names: &[String]) -> Option { + if ignore_names.iter().any(|ignore_name| ignore_name == name) { + return None; + } + if name.to_lowercase() != name { + return Some(Diagnostic::new( + InvalidArgumentName { + name: name.to_string(), + }, + Range::from_located(arg), + )); + } + None +} diff --git a/crates/ruff/src/rules/pep8_naming/rules/invalid_class_name.rs b/crates/ruff/src/rules/pep8_naming/rules/invalid_class_name.rs new file mode 100644 index 00000000000000..0da9aa3cc0a5f7 --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/invalid_class_name.rs @@ -0,0 +1,64 @@ +use ruff_macros::{define_violation, derive_message_formats}; +use rustpython_parser::ast::Stmt; + +use crate::ast::helpers::identifier_range; +use crate::registry::Diagnostic; +use crate::source_code::Locator; +use crate::violation::Violation; + +define_violation!( + /// ## What it does + /// Checks for class names that do not follow the [`CamelCase`] convention. + /// + /// ## Why is this bad? + /// Following the `CapitalizedWords` convention for class names is recommended by [PEP8]: + /// + /// > Class names should normally use the `CapWords` convention. + /// > + /// > The naming convention for functions may be used instead in cases where the interface is + /// > documented and used primarily as a callable. + /// > + /// > Note that there is a separate convention for builtin names: most builtin names are single + /// > words (or two words run together), with the `CapWords` convention used only for exception + /// > names and builtin constants. + /// + /// ## Example + /// ```python + /// class my_class: + /// pass + /// ``` + /// + /// Use instead: + /// ```python + /// class MyClass: + /// pass + /// ``` + /// + /// [PEP8]: https://peps.python.org/pep-0008/#class-names + /// [`CamelCase`]: https://en.wikipedia.org/wiki/Camel_case + /// + pub struct InvalidClassName { + pub name: String, + } +); +impl Violation for InvalidClassName { + #[derive_message_formats] + fn message(&self) -> String { + let InvalidClassName { name } = self; + format!("Class name `{name}` should use CapWords convention ") + } +} + +/// N801 +pub fn invalid_class_name(class_def: &Stmt, name: &str, locator: &Locator) -> Option { + let stripped = name.strip_prefix('_').unwrap_or(name); + if !stripped.chars().next().map_or(false, char::is_uppercase) || stripped.contains('_') { + return Some(Diagnostic::new( + InvalidClassName { + name: name.to_string(), + }, + identifier_range(class_def, locator), + )); + } + None +} diff --git a/crates/ruff/src/rules/pep8_naming/rules/invalid_first_argument_name_for_class_method.rs b/crates/ruff/src/rules/pep8_naming/rules/invalid_first_argument_name_for_class_method.rs new file mode 100644 index 00000000000000..866503917c160a --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/invalid_first_argument_name_for_class_method.rs @@ -0,0 +1,94 @@ +use ruff_macros::{define_violation, derive_message_formats}; +use rustpython_parser::ast::{Arguments, Expr}; + +use crate::ast::function_type; +use crate::ast::types::{Range, Scope}; +use crate::checkers::ast::Checker; +use crate::registry::Diagnostic; +use crate::violation::Violation; + +define_violation!( + /// ## What it does + /// Checks for class methods to have `cls` as the first argument. + /// + /// ## Why is this bad? + /// Using `cls` as first argument for class methods is recommended by [PEP8]: + /// + /// > Always use cls for the first argument to class methods. + /// > + /// > If a function argument’s name clashes with a reserved keyword, it is generally better to + /// > append a single trailing underscore rather than use an abbreviation or spelling corruption. + /// > Thus class_ is better than clss. (Perhaps better is to avoid such clashes by using a synonym.) + /// + /// ## Options + /// * `pep8-naming.classmethod-decorators` + /// * `pep8-naming.staticmethod-decorators` + /// * `pep8-naming.ignore-names` + /// + /// ## Example + /// ```python + /// class Example: + /// @classmethod + /// def function(self, data): + /// ... + /// ``` + /// + /// Use instead: + /// ```python + /// class Example: + /// @classmethod + /// def function(cls, data): + /// ... + /// ``` + /// + /// [PEP8]: https://peps.python.org/pep-0008/#function-and-method-arguments + /// + pub struct InvalidFirstArgumentNameForClassMethod; +); +impl Violation for InvalidFirstArgumentNameForClassMethod { + #[derive_message_formats] + fn message(&self) -> String { + format!("First argument of a class method should be named `cls`") + } +} + +/// N804 +pub fn invalid_first_argument_name_for_class_method( + checker: &Checker, + scope: &Scope, + name: &str, + decorator_list: &[Expr], + args: &Arguments, +) -> Option { + if !matches!( + function_type::classify( + checker, + scope, + name, + decorator_list, + &checker.settings.pep8_naming.classmethod_decorators, + &checker.settings.pep8_naming.staticmethod_decorators, + ), + function_type::FunctionType::ClassMethod + ) { + return None; + } + if let Some(arg) = args.posonlyargs.first().or_else(|| args.args.first()) { + if arg.node.arg != "cls" { + if checker + .settings + .pep8_naming + .ignore_names + .iter() + .any(|ignore_name| ignore_name == name) + { + return None; + } + return Some(Diagnostic::new( + InvalidFirstArgumentNameForClassMethod, + Range::from_located(arg), + )); + } + } + None +} diff --git a/crates/ruff/src/rules/pep8_naming/rules/invalid_first_argument_name_for_method.rs b/crates/ruff/src/rules/pep8_naming/rules/invalid_first_argument_name_for_method.rs new file mode 100644 index 00000000000000..7ba0017b7a7d8e --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/invalid_first_argument_name_for_method.rs @@ -0,0 +1,91 @@ +use ruff_macros::{define_violation, derive_message_formats}; +use rustpython_parser::ast::{Arguments, Expr}; + +use crate::ast::function_type; +use crate::ast::types::{Range, Scope}; +use crate::checkers::ast::Checker; +use crate::registry::Diagnostic; +use crate::violation::Violation; + +define_violation!( + /// ## What it does + /// Checks for instance methods to have `self` as the first argument. + /// + /// ## Why is this bad? + /// Using `self` as first argument for instance methods is recommended by [PEP8]: + /// + /// > Always use self for the first argument to instance methods. + /// > + /// > If a function argument’s name clashes with a reserved keyword, it is generally better to + /// > append a single trailing underscore rather than use an abbreviation or spelling corruption. + /// > Thus class_ is better than clss. (Perhaps better is to avoid such clashes by using a synonym.) + /// + /// ## Options + /// * `pep8-naming.classmethod-decorators` + /// * `pep8-naming.staticmethod-decorators` + /// * `pep8-naming.ignore-names` + /// + /// ## Example + /// ```python + /// class Example: + /// def function(cls, data): + /// ... + /// ``` + /// + /// Use instead: + /// ```python + /// class Example: + /// def function(self, data): + /// ... + /// ``` + /// + /// [PEP8]: https://peps.python.org/pep-0008/#function-and-method-arguments + /// + pub struct InvalidFirstArgumentNameForMethod; +); +impl Violation for InvalidFirstArgumentNameForMethod { + #[derive_message_formats] + fn message(&self) -> String { + format!("First argument of a method should be named `self`") + } +} + +/// N805 +pub fn invalid_first_argument_name_for_method( + checker: &Checker, + scope: &Scope, + name: &str, + decorator_list: &[Expr], + args: &Arguments, +) -> Option { + if !matches!( + function_type::classify( + checker, + scope, + name, + decorator_list, + &checker.settings.pep8_naming.classmethod_decorators, + &checker.settings.pep8_naming.staticmethod_decorators, + ), + function_type::FunctionType::Method + ) { + return None; + } + let arg = args.posonlyargs.first().or_else(|| args.args.first())?; + if arg.node.arg == "self" { + return None; + } + if checker + .settings + .pep8_naming + .ignore_names + .iter() + .any(|ignore_name| ignore_name == name) + { + return None; + } + Some(Diagnostic::new( + InvalidFirstArgumentNameForMethod, + Range::from_located(arg), + )) +} diff --git a/crates/ruff/src/rules/pep8_naming/rules/invalid_function_name.rs b/crates/ruff/src/rules/pep8_naming/rules/invalid_function_name.rs new file mode 100644 index 00000000000000..04fb1ed3035093 --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/invalid_function_name.rs @@ -0,0 +1,68 @@ +use ruff_macros::{define_violation, derive_message_formats}; +use rustpython_parser::ast::Stmt; + +use crate::ast::helpers::identifier_range; +use crate::registry::Diagnostic; +use crate::source_code::Locator; +use crate::violation::Violation; + +define_violation!( + /// ## What it does + /// Checks for functions names that are following the [`snake_case`] naming convention. + /// + /// ## Why is this bad? + /// [PEP8] recommends that function names follow `snake_case`: + /// + /// > Function names should be lowercase, with words separated by underscores as necessary to + /// > improve readability. mixedCase is allowed only in contexts where that’s already the + /// > prevailing style (e.g. threading.py), to retain backwards compatibility. + /// + /// ## Options + /// * `pep8-naming.ignore-names` + /// + /// ## Example + /// ```python + /// def myFunction(): + /// pass + /// ``` + /// + /// Use instead: + /// ```python + /// def my_function(): + /// pass + /// ``` + /// + /// [PEP8]: https://peps.python.org/pep-0008/#function-and-variable-names + /// [`snake_case`]: https://en.wikipedia.org/wiki/Snake_case + pub struct InvalidFunctionName { + pub name: String, + } +); +impl Violation for InvalidFunctionName { + #[derive_message_formats] + fn message(&self) -> String { + let InvalidFunctionName { name } = self; + format!("Function name `{name}` should be lowercase") + } +} + +/// N802 +pub fn invalid_function_name( + func_def: &Stmt, + name: &str, + ignore_names: &[String], + locator: &Locator, +) -> Option { + if ignore_names.iter().any(|ignore_name| ignore_name == name) { + return None; + } + if name.to_lowercase() != name { + return Some(Diagnostic::new( + InvalidFunctionName { + name: name.to_string(), + }, + identifier_range(func_def, locator), + )); + } + None +} diff --git a/crates/ruff/src/rules/pep8_naming/rules/invalid_module_name.rs b/crates/ruff/src/rules/pep8_naming/rules/invalid_module_name.rs new file mode 100644 index 00000000000000..ba487c0de11d53 --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/invalid_module_name.rs @@ -0,0 +1,64 @@ +use std::path::Path; + +use ruff_macros::{define_violation, derive_message_formats}; +use ruff_python::string::is_lower_with_underscore; + +use crate::ast::types::Range; +use crate::registry::Diagnostic; +use crate::violation::Violation; + +define_violation!( + /// ## What it does + /// Checks for module names that do not follow the [`snake_case`] naming convention. + /// + /// ## Why is this bad? + /// Module names that follow the `snake_case` naming convention are recommended by [PEP8]: + /// + /// > Modules should have short, all-lowercase names. Underscores can be used in the + /// > module name if it improves readability. Python packages should also have short, + /// > all-lowercase names, although the use of underscores is discouraged. + /// > + /// > When an extension module written in C or C++ has an accompanying Python module that + /// > provides a higher level (e.g. more object oriented) interface, the C/C++ module has + /// > a leading underscore (e.g. `_socket`). + /// + /// ## Example + /// * Instead of `example-module-name` or `example module name`, use `example_module_name` + /// * Instead of `ExampleModule`, use `example_module`, + /// + /// [PEP8]: https://peps.python.org/pep-0008/#package-and-module-names + /// [`snake_case`]: https://en.wikipedia.org/wiki/Snake_case + /// + pub struct InvalidModuleName { + pub name: String, + } +); +impl Violation for InvalidModuleName { + #[derive_message_formats] + fn message(&self) -> String { + let InvalidModuleName { name } = self; + format!("Invalid module name: '{name}'") + } +} + +/// N999 +pub fn invalid_module_name(path: &Path, package: Option<&Path>) -> Option { + if let Some(package) = package { + let module_name = if path.file_name().unwrap().to_string_lossy() == "__init__.py" { + package.file_name().unwrap().to_string_lossy() + } else { + path.file_stem().unwrap().to_string_lossy() + }; + + if !is_lower_with_underscore(&module_name) { + return Some(Diagnostic::new( + InvalidModuleName { + name: module_name.to_string(), + }, + Range::default(), + )); + } + } + + None +} diff --git a/crates/ruff/src/rules/pep8_naming/rules/lowercase_imported_as_non_lowercase.rs b/crates/ruff/src/rules/pep8_naming/rules/lowercase_imported_as_non_lowercase.rs new file mode 100644 index 00000000000000..9112a5b01a467e --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/lowercase_imported_as_non_lowercase.rs @@ -0,0 +1,65 @@ +use ruff_macros::{define_violation, derive_message_formats}; +use ruff_python::string; +use rustpython_parser::ast::Stmt; + +use crate::ast::helpers::identifier_range; +use crate::registry::Diagnostic; +use crate::source_code::Locator; +use crate::violation::Violation; + +define_violation!( + /// ## What it does + /// Checks for lowercase imports that are aliased as non-lowercase. + /// + /// ## Why is this bad? + /// [PEP8] recommends naming conventions for class names, + /// function names, constants etc. Inconsistenct [naming styles] + /// between import name and the alias may lead developers to expect an import to be of another + /// type (e.g. confuse a class with a function). + /// + /// Importing aliases should thus follow the same naming conventions. + /// + /// ## Example + /// ```python + /// from example import myclassname as MyClassName + /// ``` + /// + /// Use instead: + /// ```python + /// from example import myclassname + /// ``` + /// + /// [PEP8]: https://peps.python.org/pep-0008/ + /// [naming styles]: https://peps.python.org/pep-0008/#descriptive-naming-styles + /// + pub struct LowercaseImportedAsNonLowercase { + pub name: String, + pub asname: String, + } +); +impl Violation for LowercaseImportedAsNonLowercase { + #[derive_message_formats] + fn message(&self) -> String { + let LowercaseImportedAsNonLowercase { name, asname } = self; + format!("Lowercase `{name}` imported as non-lowercase `{asname}`") + } +} + +/// N812 +pub fn lowercase_imported_as_non_lowercase( + import_from: &Stmt, + name: &str, + asname: &str, + locator: &Locator, +) -> Option { + if !string::is_upper(name) && string::is_lower(name) && asname.to_lowercase() != asname { + return Some(Diagnostic::new( + LowercaseImportedAsNonLowercase { + name: name.to_string(), + asname: asname.to_string(), + }, + identifier_range(import_from, locator), + )); + } + None +} diff --git a/crates/ruff/src/rules/pep8_naming/rules/mixed_case_variable_in_class_scope.rs b/crates/ruff/src/rules/pep8_naming/rules/mixed_case_variable_in_class_scope.rs new file mode 100644 index 00000000000000..22f0d4c2287bc8 --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/mixed_case_variable_in_class_scope.rs @@ -0,0 +1,47 @@ +use ruff_macros::{define_violation, derive_message_formats}; +use rustpython_parser::ast::{Expr, Stmt}; + +use crate::ast::types::Range; +use crate::checkers::ast::Checker; +use crate::registry::Diagnostic; +use crate::rules::pep8_naming::helpers; +use crate::violation::Violation; + +define_violation!( + pub struct MixedCaseVariableInClassScope { + pub name: String, + } +); +impl Violation for MixedCaseVariableInClassScope { + #[derive_message_formats] + fn message(&self) -> String { + let MixedCaseVariableInClassScope { name } = self; + format!("Variable `{name}` in class scope should not be mixedCase") + } +} + +/// N815 +pub fn mixed_case_variable_in_class_scope( + checker: &mut Checker, + expr: &Expr, + stmt: &Stmt, + name: &str, +) { + if checker + .settings + .pep8_naming + .ignore_names + .iter() + .any(|ignore_name| ignore_name == name) + { + return; + } + if helpers::is_mixed_case(name) && !helpers::is_namedtuple_assignment(checker, stmt) { + checker.diagnostics.push(Diagnostic::new( + MixedCaseVariableInClassScope { + name: name.to_string(), + }, + Range::from_located(expr), + )); + } +} diff --git a/crates/ruff/src/rules/pep8_naming/rules/mixed_case_variable_in_global_scope.rs b/crates/ruff/src/rules/pep8_naming/rules/mixed_case_variable_in_global_scope.rs new file mode 100644 index 00000000000000..83707e077166eb --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/mixed_case_variable_in_global_scope.rs @@ -0,0 +1,47 @@ +use ruff_macros::{define_violation, derive_message_formats}; +use rustpython_parser::ast::{Expr, Stmt}; + +use crate::ast::types::Range; +use crate::checkers::ast::Checker; +use crate::registry::Diagnostic; +use crate::rules::pep8_naming::helpers; +use crate::violation::Violation; + +define_violation!( + pub struct MixedCaseVariableInGlobalScope { + pub name: String, + } +); +impl Violation for MixedCaseVariableInGlobalScope { + #[derive_message_formats] + fn message(&self) -> String { + let MixedCaseVariableInGlobalScope { name } = self; + format!("Variable `{name}` in global scope should not be mixedCase") + } +} + +/// N816 +pub fn mixed_case_variable_in_global_scope( + checker: &mut Checker, + expr: &Expr, + stmt: &Stmt, + name: &str, +) { + if checker + .settings + .pep8_naming + .ignore_names + .iter() + .any(|ignore_name| ignore_name == name) + { + return; + } + if helpers::is_mixed_case(name) && !helpers::is_namedtuple_assignment(checker, stmt) { + checker.diagnostics.push(Diagnostic::new( + MixedCaseVariableInGlobalScope { + name: name.to_string(), + }, + Range::from_located(expr), + )); + } +} diff --git a/crates/ruff/src/rules/pep8_naming/rules/mod.rs b/crates/ruff/src/rules/pep8_naming/rules/mod.rs new file mode 100644 index 00000000000000..52072cd7c0099d --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/mod.rs @@ -0,0 +1,55 @@ +pub use camelcase_imported_as_acronym::{ + camelcase_imported_as_acronym, CamelcaseImportedAsAcronym, +}; +pub use camelcase_imported_as_constant::{ + camelcase_imported_as_constant, CamelcaseImportedAsConstant, +}; +pub use camelcase_imported_as_lowercase::{ + camelcase_imported_as_lowercase, CamelcaseImportedAsLowercase, +}; +pub use constant_imported_as_non_constant::{ + constant_imported_as_non_constant, ConstantImportedAsNonConstant, +}; +pub use dunder_function_name::{dunder_function_name, DunderFunctionName}; +pub use error_suffix_on_exception_name::{ + error_suffix_on_exception_name, ErrorSuffixOnExceptionName, +}; +pub use invalid_argument_name::{invalid_argument_name, InvalidArgumentName}; +pub use invalid_class_name::{invalid_class_name, InvalidClassName}; +pub use invalid_first_argument_name_for_class_method::{ + invalid_first_argument_name_for_class_method, InvalidFirstArgumentNameForClassMethod, +}; +pub use invalid_first_argument_name_for_method::{ + invalid_first_argument_name_for_method, InvalidFirstArgumentNameForMethod, +}; +pub use invalid_function_name::{invalid_function_name, InvalidFunctionName}; +pub use invalid_module_name::{invalid_module_name, InvalidModuleName}; +pub use lowercase_imported_as_non_lowercase::{ + lowercase_imported_as_non_lowercase, LowercaseImportedAsNonLowercase, +}; +pub use mixed_case_variable_in_class_scope::{ + mixed_case_variable_in_class_scope, MixedCaseVariableInClassScope, +}; +pub use mixed_case_variable_in_global_scope::{ + mixed_case_variable_in_global_scope, MixedCaseVariableInGlobalScope, +}; +pub use non_lowercase_variable_in_function::{ + non_lowercase_variable_in_function, NonLowercaseVariableInFunction, +}; + +mod camelcase_imported_as_acronym; +mod camelcase_imported_as_constant; +mod camelcase_imported_as_lowercase; +mod constant_imported_as_non_constant; +mod dunder_function_name; +mod error_suffix_on_exception_name; +mod invalid_argument_name; +mod invalid_class_name; +mod invalid_first_argument_name_for_class_method; +mod invalid_first_argument_name_for_method; +mod invalid_function_name; +mod invalid_module_name; +mod lowercase_imported_as_non_lowercase; +mod mixed_case_variable_in_class_scope; +mod mixed_case_variable_in_global_scope; +mod non_lowercase_variable_in_function; diff --git a/crates/ruff/src/rules/pep8_naming/rules/non_lowercase_variable_in_function.rs b/crates/ruff/src/rules/pep8_naming/rules/non_lowercase_variable_in_function.rs new file mode 100644 index 00000000000000..5e6224d6c53199 --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/non_lowercase_variable_in_function.rs @@ -0,0 +1,82 @@ +use ruff_macros::{define_violation, derive_message_formats}; +use rustpython_parser::ast::{Expr, Stmt}; + +use crate::ast::types::Range; +use crate::checkers::ast::Checker; +use crate::registry::Diagnostic; +use crate::rules::pep8_naming::helpers; +use crate::violation::Violation; + +define_violation!( + /// ## What it does + /// Checks for non-lowercase variables in functions. + /// + /// ## Why is this bad? + /// Variable names in functions should be lowercase as recommended by [PEP8]: + /// + /// > Function names should be lowercase, with words separated by underscores as necessary to + /// > improve readability. Variable names follow the same convention as function names. mixedCase + /// > is allowed only in contexts where that's already the prevailing style (e.g. threading.py), + /// to retain backwards compatibility. + /// + /// ## Options + /// * `pep8-naming.ignore-names` + /// + /// ## Example + /// ```python + /// def my_function(a): + /// B = a + 3 + /// return B + /// ``` + /// + /// Use instead: + /// ```python + /// def my_function(a): + /// b = a + 3 + /// return b + /// ``` + /// + /// [PEP8]: https://peps.python.org/pep-0008/#function-and-variable-names + /// + pub struct NonLowercaseVariableInFunction { + pub name: String, + } +); +impl Violation for NonLowercaseVariableInFunction { + #[derive_message_formats] + fn message(&self) -> String { + let NonLowercaseVariableInFunction { name } = self; + format!("Variable `{name}` in function should be lowercase") + } +} + +/// N806 +pub fn non_lowercase_variable_in_function( + checker: &mut Checker, + expr: &Expr, + stmt: &Stmt, + name: &str, +) { + if checker + .settings + .pep8_naming + .ignore_names + .iter() + .any(|ignore_name| ignore_name == name) + { + return; + } + + if name.to_lowercase() != name + && !helpers::is_namedtuple_assignment(checker, stmt) + && !helpers::is_typeddict_assignment(checker, stmt) + && !helpers::is_type_var_assignment(checker, stmt) + { + checker.diagnostics.push(Diagnostic::new( + NonLowercaseVariableInFunction { + name: name.to_string(), + }, + Range::from_located(expr), + )); + } +} diff --git a/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__MODULE____init__.py.snap b/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__MODULE____init__.py.snap new file mode 100644 index 00000000000000..f157eeabed6aa6 --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__MODULE____init__.py.snap @@ -0,0 +1,16 @@ +--- +source: crates/ruff/src/rules/pep8_naming/mod.rs +expression: diagnostics +--- +- kind: + InvalidModuleName: + name: MODULE + location: + row: 1 + column: 0 + end_location: + row: 1 + column: 0 + fix: ~ + parent: ~ + diff --git a/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__MODULE__file.py.snap b/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__MODULE__file.py.snap new file mode 100644 index 00000000000000..b0a1ebbaaf500b --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__MODULE__file.py.snap @@ -0,0 +1,6 @@ +--- +source: crates/ruff/src/rules/pep8_naming/mod.rs +expression: diagnostics +--- +[] + diff --git a/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__flake9____init__.py.snap b/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__flake9____init__.py.snap new file mode 100644 index 00000000000000..b0a1ebbaaf500b --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__flake9____init__.py.snap @@ -0,0 +1,6 @@ +--- +source: crates/ruff/src/rules/pep8_naming/mod.rs +expression: diagnostics +--- +[] + diff --git a/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__mod with spaces____init__.py.snap b/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__mod with spaces____init__.py.snap new file mode 100644 index 00000000000000..6cccfc15c00bf2 --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__mod with spaces____init__.py.snap @@ -0,0 +1,16 @@ +--- +source: crates/ruff/src/rules/pep8_naming/mod.rs +expression: diagnostics +--- +- kind: + InvalidModuleName: + name: mod with spaces + location: + row: 1 + column: 0 + end_location: + row: 1 + column: 0 + fix: ~ + parent: ~ + diff --git a/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__mod with spaces__file.py.snap b/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__mod with spaces__file.py.snap new file mode 100644 index 00000000000000..b0a1ebbaaf500b --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__mod with spaces__file.py.snap @@ -0,0 +1,6 @@ +--- +source: crates/ruff/src/rules/pep8_naming/mod.rs +expression: diagnostics +--- +[] + diff --git a/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__mod-with-dashes____init__.py.snap b/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__mod-with-dashes____init__.py.snap new file mode 100644 index 00000000000000..8b7bc92d107785 --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__mod-with-dashes____init__.py.snap @@ -0,0 +1,16 @@ +--- +source: crates/ruff/src/rules/pep8_naming/mod.rs +expression: diagnostics +--- +- kind: + InvalidModuleName: + name: mod-with-dashes + location: + row: 1 + column: 0 + end_location: + row: 1 + column: 0 + fix: ~ + parent: ~ + diff --git a/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__no_module__test.txt.snap b/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__no_module__test.txt.snap new file mode 100644 index 00000000000000..b0a1ebbaaf500b --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__no_module__test.txt.snap @@ -0,0 +1,6 @@ +--- +source: crates/ruff/src/rules/pep8_naming/mod.rs +expression: diagnostics +--- +[] + diff --git a/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__valid_name____init__.py.snap b/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__valid_name____init__.py.snap new file mode 100644 index 00000000000000..b0a1ebbaaf500b --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__valid_name____init__.py.snap @@ -0,0 +1,6 @@ +--- +source: crates/ruff/src/rules/pep8_naming/mod.rs +expression: diagnostics +--- +[] + diff --git a/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__valid_name__file-with-dashes.py.snap b/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__valid_name__file-with-dashes.py.snap new file mode 100644 index 00000000000000..c4fe88441b5fd4 --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/snapshots/ruff__rules__pep8_naming__tests__N999_N999__module__valid_name__file-with-dashes.py.snap @@ -0,0 +1,16 @@ +--- +source: crates/ruff/src/rules/pep8_naming/mod.rs +expression: diagnostics +--- +- kind: + InvalidModuleName: + name: file-with-dashes + location: + row: 1 + column: 0 + end_location: + row: 1 + column: 0 + fix: ~ + parent: ~ + diff --git a/crates/ruff_python/src/string.rs b/crates/ruff_python/src/string.rs index 5f021940051cb2..b41a467e14c187 100644 --- a/crates/ruff_python/src/string.rs +++ b/crates/ruff_python/src/string.rs @@ -3,7 +3,8 @@ use regex::Regex; pub static STRING_QUOTE_PREFIX_REGEX: Lazy = Lazy::new(|| Regex::new(r#"^(?i)[urb]*['"](?P.*)['"]$"#).unwrap()); -pub static LOWER_OR_UNDERSCORE: Lazy = Lazy::new(|| Regex::new(r"^[a-z_]+$").unwrap()); +pub static LOWER_OR_UNDERSCORE: Lazy = + Lazy::new(|| Regex::new(r"^[a-z][a-z0-9_]*$").unwrap()); pub fn is_lower(s: &str) -> bool { let mut cased = false; @@ -64,10 +65,15 @@ mod tests { #[test] fn test_is_lower_underscore() { + assert!(is_lower_with_underscore("a")); assert!(is_lower_with_underscore("abc")); + assert!(is_lower_with_underscore("abc0")); + assert!(is_lower_with_underscore("abc_")); assert!(is_lower_with_underscore("a_b_c")); assert!(!is_lower_with_underscore("a-b-c")); assert!(!is_lower_with_underscore("a_B_c")); + assert!(!is_lower_with_underscore("0abc")); + assert!(!is_lower_with_underscore("_abc")); } #[test] diff --git a/ruff.schema.json b/ruff.schema.json index 2b4a3dc463e329..be85fa01520e72 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -1658,6 +1658,9 @@ "N816", "N817", "N818", + "N9", + "N99", + "N999", "PD", "PD0", "PD00",