Avoid no-self-use for attrs-style validators (#13166)

## Summary

Closes https://github.com/astral-sh/ruff/issues/12568.
This commit is contained in:
Charlie Marsh 2024-08-30 12:39:05 -04:00 committed by GitHub
parent 34dafb67a2
commit a73bebcf15
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 51 additions and 2 deletions

View file

@ -83,3 +83,23 @@ class B(A):
def baz(self):
if super().foo():
...
# See: https://github.com/astral-sh/ruff/issues/12568
from attrs import define, field
@define
class Foo:
x: int = field()
y: int
@x.validator
def validate_x(self, attribute, value):
if value <= 0:
raise ValueError("x must be a positive integer")
@y.validator
def validate_y(self, attribute, value):
if value <= 0:
raise ValueError("y must be a positive integer")

View file

@ -87,6 +87,7 @@ pub(crate) fn no_self_use(
|| visibility::is_override(decorator_list, semantic)
|| visibility::is_overload(decorator_list, semantic)
|| visibility::is_property(decorator_list, extra_property_decorators, semantic)
|| visibility::is_validator(decorator_list, semantic)
{
return;
}

View file

@ -27,4 +27,11 @@ no_self_use.py:13:9: PLR6301 Method `greeting_2` could be a function, class meth
14 | print("Hi!")
|
no_self_use.py:103:9: PLR6301 Method `validate_y` could be a function, class method, or static method
|
102 | @y.validator
103 | def validate_y(self, attribute, value):
| ^^^^^^^^^^ PLR6301
104 | if value <= 0:
105 | raise ValueError("y must be a positive integer")
|

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Decorator};
use ruff_python_ast::{self as ast, Decorator, Expr};
use ruff_python_ast::helpers::map_callable;
use ruff_python_ast::name::{QualifiedName, UnqualifiedName};
@ -90,6 +90,27 @@ where
})
}
/// Returns `true` if a function definition is an `attrs`-like validator based on its decorators.
pub fn is_validator(decorator_list: &[Decorator], semantic: &SemanticModel) -> bool {
decorator_list.iter().any(|decorator| {
let Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = &decorator.expression else {
return false;
};
if attr.as_str() != "validator" {
return false;
}
let Expr::Name(value) = value.as_ref() else {
return false;
};
semantic
.resolve_name(value)
.is_some_and(|id| semantic.binding(id).kind.is_assignment())
})
}
/// Returns `true` if a class is an `final`.
pub fn is_final(decorator_list: &[Decorator], semantic: &SemanticModel) -> bool {
decorator_list