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
This commit is contained in:
Simon Brugman 2023-02-16 05:20:33 +01:00 committed by GitHub
parent 147c6ff1db
commit cc30738148
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 1263 additions and 591 deletions

View file

@ -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`

View file

@ -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
}

View file

@ -391,6 +391,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(PEP8Naming, "816") => Rule::MixedCaseVariableInGlobalScope,
(PEP8Naming, "817") => Rule::CamelcaseImportedAsAcronym,
(PEP8Naming, "818") => Rule::ErrorSuffixOnExceptionName,
(PEP8Naming, "999") => Rule::InvalidModuleName,
// isort
(Isort, "001") => Rule::UnsortedImports,

View file

@ -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

View file

@ -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<bool>,
}

View file

@ -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(())

View file

@ -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<Diagnostic> {
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<Diagnostic> {
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<Diagnostic> {
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<Diagnostic> {
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<Diagnostic> {
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<Diagnostic> {
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<Diagnostic> {
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<Diagnostic> {
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<Diagnostic> {
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<Diagnostic> {
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<Diagnostic> {
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<Diagnostic> {
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),
))
}

View file

@ -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<Diagnostic> {
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
}

View file

@ -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<Diagnostic> {
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
}

View file

@ -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<Diagnostic> {
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
}

View file

@ -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<Diagnostic> {
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
}

View file

@ -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<Diagnostic> {
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),
))
}

View file

@ -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<Diagnostic> {
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),
))
}

View file

@ -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<Diagnostic> {
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
}

View file

@ -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<Diagnostic> {
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
}

View file

@ -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 arguments 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<Diagnostic> {
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
}

View file

@ -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 arguments 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<Diagnostic> {
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),
))
}

View file

@ -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 thats 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<Diagnostic> {
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
}

View file

@ -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<Diagnostic> {
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
}

View file

@ -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<Diagnostic> {
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
}

View file

@ -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),
));
}
}

View file

@ -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),
));
}
}

View file

@ -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;

View file

@ -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),
));
}
}

View file

@ -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: ~

View file

@ -0,0 +1,6 @@
---
source: crates/ruff/src/rules/pep8_naming/mod.rs
expression: diagnostics
---
[]

View file

@ -0,0 +1,6 @@
---
source: crates/ruff/src/rules/pep8_naming/mod.rs
expression: diagnostics
---
[]

View file

@ -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: ~

View file

@ -0,0 +1,6 @@
---
source: crates/ruff/src/rules/pep8_naming/mod.rs
expression: diagnostics
---
[]

View file

@ -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: ~

View file

@ -0,0 +1,6 @@
---
source: crates/ruff/src/rules/pep8_naming/mod.rs
expression: diagnostics
---
[]

View file

@ -0,0 +1,6 @@
---
source: crates/ruff/src/rules/pep8_naming/mod.rs
expression: diagnostics
---
[]

View file

@ -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: ~

View file

@ -3,7 +3,8 @@ use regex::Regex;
pub static STRING_QUOTE_PREFIX_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r#"^(?i)[urb]*['"](?P<raw>.*)['"]$"#).unwrap());
pub static LOWER_OR_UNDERSCORE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^[a-z_]+$").unwrap());
pub static LOWER_OR_UNDERSCORE: Lazy<Regex> =
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]

View file

@ -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",