[pep8-naming] Ignore @override methods (N803) (#15954)

## Summary

Resolves #15925.

`N803` now checks for functions instead of parameters. In preview mode,
if a method is decorated with `@override` and the current scope is that
of a class, it will be ignored.

## Test Plan

`cargo nextest run` and `cargo insta test`.
This commit is contained in:
InSync 2025-02-05 15:35:57 +07:00 committed by GitHub
parent 5852217198
commit 82cb8675dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 162 additions and 28 deletions

View file

@ -9,3 +9,21 @@ class Class:
def func(_, setUp):
return _, setUp
from typing import override
class Extended(Class):
@override
def method(self, _, a, A): ...
@override # Incorrect usage
def func(_, a, A): ...
func = lambda _, a, A: ...
class Extended(Class):
method = override(lambda self, _, a, A: ...) # Incorrect usage

View file

@ -1746,9 +1746,12 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
ruff::rules::parenthesize_chained_logical_operators(checker, bool_op);
}
}
Expr::Lambda(lambda_expr) => {
Expr::Lambda(lambda) => {
if checker.enabled(Rule::ReimplementedOperator) {
refurb::rules::reimplemented_operator(checker, &lambda_expr.into());
refurb::rules::reimplemented_operator(checker, &lambda.into());
}
if checker.enabled(Rule::InvalidArgumentName) {
pep8_naming::rules::invalid_argument_name_lambda(checker, lambda);
}
}
_ => {}

View file

@ -3,7 +3,7 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::codes::Rule;
use crate::rules::{flake8_builtins, pep8_naming, pycodestyle};
use crate::rules::{flake8_builtins, pycodestyle};
/// Run lint rules over a [`Parameter`] syntax node.
pub(crate) fn parameter(parameter: &Parameter, checker: &mut Checker) {
@ -14,15 +14,6 @@ pub(crate) fn parameter(parameter: &Parameter, checker: &mut Checker) {
parameter.name.range(),
);
}
if checker.enabled(Rule::InvalidArgumentName) {
if let Some(diagnostic) = pep8_naming::rules::invalid_argument_name(
&parameter.name,
parameter,
&checker.settings.pep8_naming.ignore_names,
) {
checker.diagnostics.push(diagnostic);
}
}
if checker.enabled(Rule::BuiltinArgumentShadowing) {
flake8_builtins::rules::builtin_argument_shadowing(checker, parameter);
}

View file

@ -379,6 +379,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::NonPEP695GenericFunction) {
pyupgrade::rules::non_pep695_generic_function(checker, function_def);
}
if checker.enabled(Rule::InvalidArgumentName) {
pep8_naming::rules::invalid_argument_name_function(checker, function_def);
}
}
Stmt::Return(_) => {
if checker.enabled(Rule::ReturnOutsideFunction) {

View file

@ -14,6 +14,7 @@ mod tests {
use crate::registry::Rule;
use crate::rules::pep8_naming::settings::IgnoreNames;
use crate::rules::{flake8_import_conventions, pep8_naming};
use crate::settings::types::PreviewMode;
use crate::test::test_path;
use crate::{assert_messages, settings};
@ -88,6 +89,24 @@ mod tests {
Ok(())
}
#[test_case(Rule::InvalidArgumentName, Path::new("N803.py"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"preview__{}_{}",
rule_code.noqa_code(),
path.to_string_lossy()
);
let diagnostics = test_path(
Path::new("pep8_naming").join(path).as_path(),
&settings::LinterSettings {
preview: PreviewMode::Enabled,
..settings::LinterSettings::for_rule(rule_code)
},
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}
#[test]
fn camelcase_imported_as_incorrect_convention() -> Result<()> {
let diagnostics = test_path(

View file

@ -1,11 +1,12 @@
use ruff_python_ast::Parameter;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::{ExprLambda, Parameters, StmtFunctionDef};
use ruff_python_semantic::analyze::visibility::is_override;
use ruff_python_semantic::ScopeKind;
use ruff_python_stdlib::str;
use ruff_text_size::Ranged;
use crate::rules::pep8_naming::settings::IgnoreNames;
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for argument names that do not follow the `snake_case` convention.
@ -22,6 +23,8 @@ use crate::rules::pep8_naming::settings::IgnoreNames;
/// > mixedCase is allowed only in contexts where thats already the
/// > prevailing style (e.g. threading.py), to retain backwards compatibility.
///
/// In [preview], overridden methods are ignored.
///
/// ## Example
/// ```python
/// def my_function(A, myArg):
@ -35,6 +38,7 @@ use crate::rules::pep8_naming::settings::IgnoreNames;
/// ```
///
/// [PEP 8]: https://peps.python.org/pep-0008/#function-and-method-arguments
/// [preview]: https://docs.astral.sh/ruff/preview/
#[derive(ViolationMetadata)]
pub(crate) struct InvalidArgumentName {
name: String,
@ -49,22 +53,54 @@ impl Violation for InvalidArgumentName {
}
/// N803
pub(crate) fn invalid_argument_name(
name: &str,
parameter: &Parameter,
ignore_names: &IgnoreNames,
) -> Option<Diagnostic> {
if !str::is_lowercase(name) {
// Ignore any explicitly-allowed names.
if ignore_names.matches(name) {
return None;
pub(crate) fn invalid_argument_name_function(
checker: &mut Checker,
function_def: &StmtFunctionDef,
) {
let semantic = checker.semantic();
let scope = semantic.current_scope();
if checker.settings.preview.is_enabled()
&& matches!(scope.kind, ScopeKind::Class(_))
&& is_override(&function_def.decorator_list, semantic)
{
return;
}
invalid_argument_name(checker, &function_def.parameters);
}
/// N803
pub(crate) fn invalid_argument_name_lambda(checker: &mut Checker, lambda: &ExprLambda) {
let Some(parameters) = &lambda.parameters else {
return;
};
invalid_argument_name(checker, parameters);
}
/// N803
fn invalid_argument_name(checker: &mut Checker, parameters: &Parameters) {
let ignore_names = &checker.settings.pep8_naming.ignore_names;
for parameter in parameters {
let name = parameter.name().as_str();
if str::is_lowercase(name) {
continue;
}
return Some(Diagnostic::new(
if ignore_names.matches(name) {
continue;
}
let diagnostic = Diagnostic::new(
InvalidArgumentName {
name: name.to_string(),
},
parameter.range(),
));
);
checker.diagnostics.push(diagnostic);
}
None
}

View file

@ -1,6 +1,5 @@
---
source: crates/ruff_linter/src/rules/pep8_naming/mod.rs
snapshot_kind: text
---
N803.py:1:16: N803 Argument name `A` should be lowercase
|
@ -16,3 +15,31 @@ N803.py:6:28: N803 Argument name `A` should be lowercase
| ^ N803
7 | return _, a, A
|
N803.py:18:28: N803 Argument name `A` should be lowercase
|
16 | class Extended(Class):
17 | @override
18 | def method(self, _, a, A): ...
| ^ N803
|
N803.py:22:16: N803 Argument name `A` should be lowercase
|
21 | @override # Incorrect usage
22 | def func(_, a, A): ...
| ^ N803
|
N803.py:25:21: N803 Argument name `A` should be lowercase
|
25 | func = lambda _, a, A: ...
| ^ N803
|
N803.py:29:42: N803 Argument name `A` should be lowercase
|
28 | class Extended(Class):
29 | method = override(lambda self, _, a, A: ...) # Incorrect usage
| ^ N803
|

View file

@ -0,0 +1,37 @@
---
source: crates/ruff_linter/src/rules/pep8_naming/mod.rs
---
N803.py:1:16: N803 Argument name `A` should be lowercase
|
1 | def func(_, a, A):
| ^ N803
2 | return _, a, A
|
N803.py:6:28: N803 Argument name `A` should be lowercase
|
5 | class Class:
6 | def method(self, _, a, A):
| ^ N803
7 | return _, a, A
|
N803.py:22:16: N803 Argument name `A` should be lowercase
|
21 | @override # Incorrect usage
22 | def func(_, a, A): ...
| ^ N803
|
N803.py:25:21: N803 Argument name `A` should be lowercase
|
25 | func = lambda _, a, A: ...
| ^ N803
|
N803.py:29:42: N803 Argument name `A` should be lowercase
|
28 | class Extended(Class):
29 | method = override(lambda self, _, a, A: ...) # Incorrect usage
| ^ N803
|