From cc30738148c759d45e9d264d589358072c6cef3c Mon Sep 17 00:00:00 2001 From: Simon Brugman Date: Thu, 16 Feb 2023 05:20:33 +0100 Subject: [PATCH] Implement `flake8-module-naming` (#2855) - 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 | 27 +- .../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/codes.rs | 1 + crates/ruff/src/registry.rs | 3 +- .../ruff/src/rules/flake8_quotes/settings.rs | 2 +- crates/ruff/src/rules/pep8_naming/mod.rs | 13 +- crates/ruff/src/rules/pep8_naming/rules.rs | 573 ------------------ .../rules/camelcase_imported_as_acronym.rs | 72 +++ .../rules/camelcase_imported_as_constant.rs | 69 +++ .../rules/camelcase_imported_as_lowercase.rs | 65 ++ .../constant_imported_as_non_constant.rs | 65 ++ .../pep8_naming/rules/dunder_function_name.rs | 66 ++ .../rules/error_suffix_on_exception_name.rs | 71 +++ .../rules/invalid_argument_name.rs | 35 ++ .../pep8_naming/rules/invalid_class_name.rs | 63 ++ ...id_first_argument_name_for_class_method.rs | 96 +++ .../invalid_first_argument_name_for_method.rs | 92 +++ .../rules/invalid_function_name.rs | 68 +++ .../pep8_naming/rules/invalid_module_name.rs | 64 ++ .../lowercase_imported_as_non_lowercase.rs | 64 ++ .../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 | 81 +++ ...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 | 5 +- 45 files changed, 1263 insertions(+), 591 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 683dae80c1..e34c945e58 100644 --- a/README.md +++ b/README.md @@ -818,21 +818,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) @@ -3236,7 +3237,7 @@ raises-require-match-for = ["requests.RequestException"] #### [`avoid-escape`](#avoid-escape) Whether to avoid using single quotes if a string contains single quotes, -or vice-versa with double quotes, as per [PEP8](https://peps.python.org/pep-0008/#string-quotes). +or vice-versa with double quotes, as per [PEP 8](https://peps.python.org/pep-0008/#string-quotes). This minimizes the need to escape quotation marks within strings. **Default value**: `true` 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 0000000000..e69de29bb2 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 0000000000..e69de29bb2 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 0000000000..e69de29bb2 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 0000000000..e69de29bb2 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 0000000000..e69de29bb2 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 0000000000..e69de29bb2 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 0000000000..e69de29bb2 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 0000000000..e69de29bb2 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 0000000000..e69de29bb2 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 0000000000..e69de29bb2 diff --git a/crates/ruff/src/checkers/filesystem.rs b/crates/ruff/src/checkers/filesystem.rs index 0d6a1c6b71..d3d0847010 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/codes.rs b/crates/ruff/src/codes.rs index 0674e78a1a..9061bf209a 100644 --- a/crates/ruff/src/codes.rs +++ b/crates/ruff/src/codes.rs @@ -391,6 +391,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option { (PEP8Naming, "816") => Rule::MixedCaseVariableInGlobalScope, (PEP8Naming, "817") => Rule::CamelcaseImportedAsAcronym, (PEP8Naming, "818") => Rule::ErrorSuffixOnExceptionName, + (PEP8Naming, "999") => Rule::InvalidModuleName, // isort (Isort, "001") => Rule::UnsortedImports, diff --git a/crates/ruff/src/registry.rs b/crates/ruff/src/registry.rs index 080b9c74ce..acb383710f 100644 --- a/crates/ruff/src/registry.rs +++ b/crates/ruff/src/registry.rs @@ -376,6 +376,7 @@ ruff_macros::register_rules!( rules::pep8_naming::rules::MixedCaseVariableInGlobalScope, rules::pep8_naming::rules::CamelcaseImportedAsAcronym, rules::pep8_naming::rules::ErrorSuffixOnExceptionName, + rules::pep8_naming::rules::InvalidModuleName, // isort rules::isort::rules::UnsortedImports, rules::isort::rules::MissingRequiredImport, @@ -796,7 +797,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/flake8_quotes/settings.rs b/crates/ruff/src/rules/flake8_quotes/settings.rs index 34db0cdbe0..ba82d47caf 100644 --- a/crates/ruff/src/rules/flake8_quotes/settings.rs +++ b/crates/ruff/src/rules/flake8_quotes/settings.rs @@ -66,7 +66,7 @@ pub struct Options { "# )] /// Whether to avoid using single quotes if a string contains single quotes, - /// or vice-versa with double quotes, as per [PEP8](https://peps.python.org/pep-0008/#string-quotes). + /// or vice-versa with double quotes, as per [PEP 8](https://peps.python.org/pep-0008/#string-quotes). /// This minimizes the need to escape quotation marks within strings. pub avoid_escape: Option, } diff --git a/crates/ruff/src/rules/pep8_naming/mod.rs b/crates/ruff/src/rules/pep8_naming/mod.rs index f099bb0bd0..3e10cad7a3 100644 --- a/crates/ruff/src/rules/pep8_naming/mod.rs +++ b/crates/ruff/src/rules/pep8_naming/mod.rs @@ -30,11 +30,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.noqa_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 0000bceb06..0000000000 --- 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 0000000000..7e50a2038b --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/camelcase_imported_as_acronym.rs @@ -0,0 +1,72 @@ +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? + /// [PEP 8] recommends naming conventions for classes, functions, + /// constants, and more. The use of inconsistent naming styles between + /// import and alias names may lead readers to expect an import to be of + /// another type (e.g., confuse a Python class with a constant). + /// + /// Import aliases should thus follow the same naming style as the member + /// being imported. + /// + /// Note that this rule is distinct from `camelcase-imported-as-constant` + /// to accommodate selective enforcement. + /// + /// ## Example + /// ```python + /// from example import MyClassName as MCN + /// ``` + /// + /// Use instead: + /// ```python + /// from example import MyClassName + /// ``` + /// + /// [PEP 8]: https://peps.python.org/pep-0008/ + 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 0000000000..1097b0948d --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/camelcase_imported_as_constant.rs @@ -0,0 +1,69 @@ +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 to constant-style names. + /// + /// ## Why is this bad? + /// [PEP 8] recommends naming conventions for classes, functions, + /// constants, and more. The use of inconsistent naming styles between + /// import and alias names may lead readers to expect an import to be of + /// another type (e.g., confuse a Python class with a constant). + /// + /// Import aliases should thus follow the same naming style as the member + /// being imported. + /// + /// ## Example + /// ```python + /// from example import MyClassName as MY_CLASS_NAME + /// ``` + /// + /// Use instead: + /// ```python + /// from example import MyClassName + /// ``` + /// + /// [PEP 8]: https://peps.python.org/pep-0008/ + 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 0000000000..d225e45e26 --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/camelcase_imported_as_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::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 to lowercase names. + /// + /// ## Why is this bad? + /// [PEP 8] recommends naming conventions for classes, functions, + /// constants, and more. The use of inconsistent naming styles between + /// import and alias names may lead readers to expect an import to be of + /// another type (e.g., confuse a Python class with a constant). + /// + /// Import aliases should thus follow the same naming style as the member + /// being imported. + /// + /// ## Example + /// ```python + /// from example import MyClassName as myclassname + /// ``` + /// + /// Use instead: + /// ```python + /// from example import MyClassName + /// ``` + /// + /// [PEP 8]: https://peps.python.org/pep-0008/ + 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 0000000000..b824ea3356 --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/constant_imported_as_non_constant.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 constant imports that are aliased to non-constant-style + /// names. + /// + /// ## Why is this bad? + /// [PEP 8] recommends naming conventions for classes, functions, + /// constants, and more. The use of inconsistent naming styles between + /// import and alias names may lead readers to expect an import to be of + /// another type (e.g., confuse a Python class with a constant). + /// + /// Import aliases should thus follow the same naming style as the member + /// being imported. + /// + /// ## Example + /// ```python + /// from example import CONSTANT_VALUE as ConstantValue + /// ``` + /// + /// Use instead: + /// ```python + /// from example import CONSTANT_VALUE + /// ``` + /// + /// [PEP 8]: https://peps.python.org/pep-0008/ + 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 0000000000..61c393c8e0 --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/dunder_function_name.rs @@ -0,0 +1,66 @@ +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 (that is, names with two + /// leading and trailing underscores) that are not documented. + /// + /// ## Why is this bad? + /// [PEP 8] recommends that only documented "dunder" methods are used: + /// + /// > ..."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 + /// ``` + /// + /// [PEP 8]: 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 0000000000..07488cb033 --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/error_suffix_on_exception_name.rs @@ -0,0 +1,71 @@ +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 custom exception definitions that omit the `Error` suffix. + /// + /// ## Why is this bad? + /// The `Error` suffix is recommended by [PEP 8]: + /// + /// > 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): + /// ... + /// ``` + /// + /// [PEP 8]: 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 0000000000..b867dc7d0c --- /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 0000000000..42459e067c --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/invalid_class_name.rs @@ -0,0 +1,63 @@ +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? + /// [PEP 8] recommends the use of the `CapWords` (or `CamelCase`) convention + /// for class names: + /// + /// > 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 + /// ``` + /// + /// [PEP 8]: https://peps.python.org/pep-0008/#class-names + 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 0000000000..cba2236e97 --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/invalid_first_argument_name_for_class_method.rs @@ -0,0 +1,96 @@ +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 that use a name other than `cls` for their + /// first argument. + /// + /// ## Why is this bad? + /// [PEP 8] recommends the use of `cls` as the first argument for all class + /// methods: + /// + /// > 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): + /// ... + /// ``` + /// + /// [PEP 8]: 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 0000000000..aa426e8f81 --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/invalid_first_argument_name_for_method.rs @@ -0,0 +1,92 @@ +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 that use a name other than `self` for their + /// first argument. + /// + /// ## Why is this bad? + /// [PEP 8] recommends the use of `self` as first argument for all instance + /// methods: + /// + /// > 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): + /// ... + /// ``` + /// + /// [PEP 8]: 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 0000000000..c36be58cd2 --- /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 do not follow the `snake_case` naming + /// convention. + /// + /// ## Why is this bad? + /// [PEP 8] 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 + /// ``` + /// + /// [PEP 8]: https://peps.python.org/pep-0008/#function-and-variable-names + 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 0000000000..b4a5777add --- /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? + /// [PEP 8] recommends the use of the `snake_case` naming convention for + /// module names: + /// + /// > 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`. + /// + /// [PEP 8]: https://peps.python.org/pep-0008/#package-and-module-names + 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 0000000000..2846af5b16 --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/lowercase_imported_as_non_lowercase.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 lowercase imports that are aliased to non-lowercase names. + /// + /// ## Why is this bad? + /// [PEP 8] recommends naming conventions for classes, functions, + /// constants, and more. The use of inconsistent naming styles between + /// import and alias names may lead readers to expect an import to be of + /// another type (e.g., confuse a Python class with a constant). + /// + /// Import aliases should thus follow the same naming style as the member + /// being imported. + /// + /// ## Example + /// ```python + /// from example import myclassname as MyClassName + /// ``` + /// + /// Use instead: + /// ```python + /// from example import myclassname + /// ``` + /// + /// [PEP 8]: https://peps.python.org/pep-0008/ + 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 0000000000..22f0d4c228 --- /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 0000000000..83707e0771 --- /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 0000000000..52072cd7c0 --- /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 0000000000..d1121498e1 --- /dev/null +++ b/crates/ruff/src/rules/pep8_naming/rules/non_lowercase_variable_in_function.rs @@ -0,0 +1,81 @@ +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 the use of non-lowercase variable names in functions. + /// + /// ## Why is this bad? + /// [PEP 8] recommends that all function variables use lowercase names: + /// + /// > 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 + /// ``` + /// + /// [PEP 8]: 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 0000000000..f157eeabed --- /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 0000000000..b0a1ebbaaf --- /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 0000000000..b0a1ebbaaf --- /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 0000000000..6cccfc15c0 --- /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 0000000000..b0a1ebbaaf --- /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 0000000000..8b7bc92d10 --- /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 0000000000..b0a1ebbaaf --- /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 0000000000..b0a1ebbaaf --- /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 0000000000..c4fe88441b --- /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 5f02194005..b41a467e14 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 bd33b91b8f..f4bcc50cbf 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -775,7 +775,7 @@ "type": "object", "properties": { "avoid-escape": { - "description": "Whether to avoid using single quotes if a string contains single quotes, or vice-versa with double quotes, as per [PEP8](https://peps.python.org/pep-0008/#string-quotes). This minimizes the need to escape quotation marks within strings.", + "description": "Whether to avoid using single quotes if a string contains single quotes, or vice-versa with double quotes, as per [PEP 8](https://peps.python.org/pep-0008/#string-quotes). This minimizes the need to escape quotation marks within strings.", "type": [ "boolean", "null" @@ -1656,6 +1656,9 @@ "N816", "N817", "N818", + "N9", + "N99", + "N999", "NPY", "NPY0", "NPY00",