mirror of https://github.com/astral-sh/ruff
[`pylint`] Implement `no-self-use` (`R6301`) (#6574)
This commit is contained in:
parent
424b8d4ad2
commit
0aad0c41f6
|
|
@ -0,0 +1,62 @@
|
||||||
|
import abc
|
||||||
|
|
||||||
|
|
||||||
|
class Person:
|
||||||
|
def developer_greeting(self, name): # [no-self-use]
|
||||||
|
print(f"Greetings {name}!")
|
||||||
|
|
||||||
|
def greeting_1(self): # [no-self-use]
|
||||||
|
print("Hello!")
|
||||||
|
|
||||||
|
def greeting_2(self): # [no-self-use]
|
||||||
|
print("Hi!")
|
||||||
|
|
||||||
|
|
||||||
|
# OK
|
||||||
|
def developer_greeting():
|
||||||
|
print("Greetings developer!")
|
||||||
|
|
||||||
|
|
||||||
|
# OK
|
||||||
|
class Person:
|
||||||
|
name = "Paris"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __cmp__(self, other):
|
||||||
|
print(24)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "Person"
|
||||||
|
|
||||||
|
def func(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
def greeting_1(self):
|
||||||
|
print(f"Hello from {self.name} !")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def greeting_2():
|
||||||
|
print("Hi!")
|
||||||
|
|
||||||
|
|
||||||
|
class Base(abc.ABC):
|
||||||
|
"""abstract class"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def abstract_method(self):
|
||||||
|
"""abstract method could not be a function"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class Sub(Base):
|
||||||
|
@override
|
||||||
|
def abstract_method(self):
|
||||||
|
print("concret method")
|
||||||
|
|
||||||
|
|
||||||
|
class Prop:
|
||||||
|
@property
|
||||||
|
def count(self):
|
||||||
|
return 24
|
||||||
|
|
@ -30,6 +30,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||||
Rule::UnusedPrivateTypedDict,
|
Rule::UnusedPrivateTypedDict,
|
||||||
Rule::UnusedStaticMethodArgument,
|
Rule::UnusedStaticMethodArgument,
|
||||||
Rule::UnusedVariable,
|
Rule::UnusedVariable,
|
||||||
|
Rule::NoSelfUse,
|
||||||
]) {
|
]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -302,6 +303,12 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||||
pyflakes::rules::unused_import(checker, scope, &mut diagnostics);
|
pyflakes::rules::unused_import(checker, scope, &mut diagnostics);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if scope.kind.is_function() {
|
||||||
|
if checker.enabled(Rule::NoSelfUse) {
|
||||||
|
pylint::rules::no_self_use(checker, scope, &mut diagnostics);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
checker.diagnostics.extend(diagnostics);
|
checker.diagnostics.extend(diagnostics);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -216,6 +216,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||||
(Pylint, "R1722") => (RuleGroup::Unspecified, rules::pylint::rules::SysExitAlias),
|
(Pylint, "R1722") => (RuleGroup::Unspecified, rules::pylint::rules::SysExitAlias),
|
||||||
(Pylint, "R2004") => (RuleGroup::Unspecified, rules::pylint::rules::MagicValueComparison),
|
(Pylint, "R2004") => (RuleGroup::Unspecified, rules::pylint::rules::MagicValueComparison),
|
||||||
(Pylint, "R5501") => (RuleGroup::Unspecified, rules::pylint::rules::CollapsibleElseIf),
|
(Pylint, "R5501") => (RuleGroup::Unspecified, rules::pylint::rules::CollapsibleElseIf),
|
||||||
|
(Pylint, "R6301") => (RuleGroup::Nursery, rules::pylint::rules::NoSelfUse),
|
||||||
(Pylint, "W0120") => (RuleGroup::Unspecified, rules::pylint::rules::UselessElseOnLoop),
|
(Pylint, "W0120") => (RuleGroup::Unspecified, rules::pylint::rules::UselessElseOnLoop),
|
||||||
(Pylint, "W0127") => (RuleGroup::Unspecified, rules::pylint::rules::SelfAssigningVariable),
|
(Pylint, "W0127") => (RuleGroup::Unspecified, rules::pylint::rules::SelfAssigningVariable),
|
||||||
(Pylint, "W0129") => (RuleGroup::Unspecified, rules::pylint::rules::AssertOnStringLiteral),
|
(Pylint, "W0129") => (RuleGroup::Unspecified, rules::pylint::rules::AssertOnStringLiteral),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
//! Rules from [flake8-unused-arguments](https://pypi.org/project/flake8-unused-arguments/).
|
//! Rules from [flake8-unused-arguments](https://pypi.org/project/flake8-unused-arguments/).
|
||||||
mod helpers;
|
pub(crate) mod helpers;
|
||||||
pub(crate) mod rules;
|
pub(crate) mod rules;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,7 @@ mod tests {
|
||||||
Path::new("subprocess_run_without_check.py")
|
Path::new("subprocess_run_without_check.py")
|
||||||
)]
|
)]
|
||||||
#[test_case(Rule::BadDunderMethodName, Path::new("bad_dunder_method_name.py"))]
|
#[test_case(Rule::BadDunderMethodName, Path::new("bad_dunder_method_name.py"))]
|
||||||
|
#[test_case(Rule::NoSelfUse, Path::new("no_self_use.py"))]
|
||||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||||
let diagnostics = test_path(
|
let diagnostics = test_path(
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ pub(crate) use magic_value_comparison::*;
|
||||||
pub(crate) use manual_import_from::*;
|
pub(crate) use manual_import_from::*;
|
||||||
pub(crate) use named_expr_without_context::*;
|
pub(crate) use named_expr_without_context::*;
|
||||||
pub(crate) use nested_min_max::*;
|
pub(crate) use nested_min_max::*;
|
||||||
|
pub(crate) use no_self_use::*;
|
||||||
pub(crate) use nonlocal_without_binding::*;
|
pub(crate) use nonlocal_without_binding::*;
|
||||||
pub(crate) use property_with_parameters::*;
|
pub(crate) use property_with_parameters::*;
|
||||||
pub(crate) use redefined_loop_name::*;
|
pub(crate) use redefined_loop_name::*;
|
||||||
|
|
@ -86,6 +87,7 @@ mod magic_value_comparison;
|
||||||
mod manual_import_from;
|
mod manual_import_from;
|
||||||
mod named_expr_without_context;
|
mod named_expr_without_context;
|
||||||
mod nested_min_max;
|
mod nested_min_max;
|
||||||
|
mod no_self_use;
|
||||||
mod nonlocal_without_binding;
|
mod nonlocal_without_binding;
|
||||||
mod property_with_parameters;
|
mod property_with_parameters;
|
||||||
mod redefined_loop_name;
|
mod redefined_loop_name;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
use ruff_python_ast::call_path::{from_qualified_name, CallPath};
|
||||||
|
use ruff_python_ast::{self as ast, ParameterWithDefault, Ranged};
|
||||||
|
use ruff_python_semantic::{
|
||||||
|
analyze::{function_type, visibility},
|
||||||
|
Scope, ScopeKind,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{checkers::ast::Checker, rules::flake8_unused_arguments::helpers};
|
||||||
|
|
||||||
|
/// ## What it does
|
||||||
|
/// Checks for the presence of unused `self` parameter in methods definitions.
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// Unused `self` parameters are usually a sign of a method that could be
|
||||||
|
/// replaced by a function or a static method.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```python
|
||||||
|
/// class Person:
|
||||||
|
/// def greeting(self):
|
||||||
|
/// print("Greetings friend!")
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Use instead:
|
||||||
|
/// ```python
|
||||||
|
/// class Person:
|
||||||
|
/// @staticmethod
|
||||||
|
/// def greeting():
|
||||||
|
/// print(f"Greetings friend!")
|
||||||
|
/// ```
|
||||||
|
#[violation]
|
||||||
|
pub struct NoSelfUse {
|
||||||
|
method_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Violation for NoSelfUse {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
let NoSelfUse { method_name } = self;
|
||||||
|
format!("Method `{method_name}` could be a function or static method")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PLR6301
|
||||||
|
pub(crate) fn no_self_use(checker: &Checker, scope: &Scope, diagnostics: &mut Vec<Diagnostic>) {
|
||||||
|
let Some(parent) = &checker.semantic().first_non_type_parent_scope(scope) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let ScopeKind::Function(ast::StmtFunctionDef {
|
||||||
|
name,
|
||||||
|
parameters,
|
||||||
|
body,
|
||||||
|
decorator_list,
|
||||||
|
..
|
||||||
|
}) = scope.kind
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !matches!(
|
||||||
|
function_type::classify(
|
||||||
|
name,
|
||||||
|
decorator_list,
|
||||||
|
parent,
|
||||||
|
checker.semantic(),
|
||||||
|
&checker.settings.pep8_naming.classmethod_decorators,
|
||||||
|
&checker.settings.pep8_naming.staticmethod_decorators,
|
||||||
|
),
|
||||||
|
function_type::FunctionType::Method
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let property_decorators = checker
|
||||||
|
.settings
|
||||||
|
.pydocstyle
|
||||||
|
.property_decorators
|
||||||
|
.iter()
|
||||||
|
.map(|decorator| from_qualified_name(decorator))
|
||||||
|
.collect::<Vec<CallPath>>();
|
||||||
|
|
||||||
|
if helpers::is_empty(body)
|
||||||
|
|| visibility::is_magic(name)
|
||||||
|
|| visibility::is_abstract(decorator_list, checker.semantic())
|
||||||
|
|| visibility::is_override(decorator_list, checker.semantic())
|
||||||
|
|| visibility::is_overload(decorator_list, checker.semantic())
|
||||||
|
|| visibility::is_property(decorator_list, &property_decorators, checker.semantic())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identify the `self` parameter.
|
||||||
|
let Some(parameter) = parameters
|
||||||
|
.posonlyargs
|
||||||
|
.iter()
|
||||||
|
.chain(¶meters.args)
|
||||||
|
.chain(¶meters.kwonlyargs)
|
||||||
|
.next()
|
||||||
|
.map(ParameterWithDefault::as_parameter)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if parameter.name.as_str() == "self"
|
||||||
|
&& scope
|
||||||
|
.get("self")
|
||||||
|
.map(|binding_id| checker.semantic().binding(binding_id))
|
||||||
|
.is_some_and(|binding| binding.kind.is_argument() && !binding.is_used())
|
||||||
|
{
|
||||||
|
diagnostics.push(Diagnostic::new(
|
||||||
|
NoSelfUse {
|
||||||
|
method_name: name.to_string(),
|
||||||
|
},
|
||||||
|
parameter.range(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/pylint/mod.rs
|
||||||
|
---
|
||||||
|
no_self_use.py:5:28: PLR6301 Method `developer_greeting` could be a function or static method
|
||||||
|
|
|
||||||
|
4 | class Person:
|
||||||
|
5 | def developer_greeting(self, name): # [no-self-use]
|
||||||
|
| ^^^^ PLR6301
|
||||||
|
6 | print(f"Greetings {name}!")
|
||||||
|
|
|
||||||
|
|
||||||
|
no_self_use.py:8:20: PLR6301 Method `greeting_1` could be a function or static method
|
||||||
|
|
|
||||||
|
6 | print(f"Greetings {name}!")
|
||||||
|
7 |
|
||||||
|
8 | def greeting_1(self): # [no-self-use]
|
||||||
|
| ^^^^ PLR6301
|
||||||
|
9 | print("Hello!")
|
||||||
|
|
|
||||||
|
|
||||||
|
no_self_use.py:11:20: PLR6301 Method `greeting_2` could be a function or static method
|
||||||
|
|
|
||||||
|
9 | print("Hello!")
|
||||||
|
10 |
|
||||||
|
11 | def greeting_2(self): # [no-self-use]
|
||||||
|
| ^^^^ PLR6301
|
||||||
|
12 | print("Hi!")
|
||||||
|
|
|
||||||
|
|
||||||
|
no_self_use.py:55:25: PLR6301 Method `abstract_method` could be a function or static method
|
||||||
|
|
|
||||||
|
53 | class Sub(Base):
|
||||||
|
54 | @override
|
||||||
|
55 | def abstract_method(self):
|
||||||
|
| ^^^^ PLR6301
|
||||||
|
56 | print("concret method")
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2270,6 +2270,7 @@
|
||||||
"PLR55",
|
"PLR55",
|
||||||
"PLR550",
|
"PLR550",
|
||||||
"PLR5501",
|
"PLR5501",
|
||||||
|
"PLR6301",
|
||||||
"PLW",
|
"PLW",
|
||||||
"PLW0",
|
"PLW0",
|
||||||
"PLW01",
|
"PLW01",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue