forked from astral-sh/ruff
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implementing the rule https://pylint.readthedocs.io/en/stable/user_guide/messages/error/singledispatch-method.html#singledispatch-method-e1519 Implementation simply checks the function type and name of the decorators.
- Loading branch information
Showing
9 changed files
with
214 additions
and
0 deletions.
There are no files selected for viewing
39 changes: 39 additions & 0 deletions
39
crates/ruff_linter/resources/test/fixtures/pylint/singledispatch_method.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
from functools import singledispatch, singledispatchmethod | ||
|
||
|
||
@singledispatch | ||
def convert_position(position): | ||
pass | ||
|
||
|
||
class Board: | ||
@singledispatch # [singledispatch-method] | ||
@classmethod | ||
def convert_position(cls, position): | ||
pass | ||
|
||
@singledispatch # [singledispatch-method] | ||
def move(self, position): | ||
pass | ||
|
||
@singledispatchmethod | ||
def place(self, position): | ||
pass | ||
|
||
@singledispatch | ||
@staticmethod | ||
def do(position): | ||
pass | ||
|
||
# False negative (flagged by Pylint). | ||
@convert_position.register | ||
@classmethod | ||
def _(cls, position: str) -> tuple: | ||
position_a, position_b = position.split(",") | ||
return (int(position_a), int(position_b)) | ||
|
||
# False negative (flagged by Pylint). | ||
@convert_position.register | ||
@classmethod | ||
def _(cls, position: tuple) -> str: | ||
return f"{position[0]},{position[1]}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
119 changes: 119 additions & 0 deletions
119
crates/ruff_linter/src/rules/pylint/rules/singledispatch_method.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; | ||
use ruff_macros::{derive_message_formats, violation}; | ||
use ruff_python_ast as ast; | ||
use ruff_python_semantic::analyze::function_type; | ||
use ruff_python_semantic::Scope; | ||
use ruff_text_size::Ranged; | ||
|
||
use crate::checkers::ast::Checker; | ||
use crate::importer::ImportRequest; | ||
|
||
/// ## What it does | ||
/// Checks for `@singledispatch` decorators on class and instance methods. | ||
/// | ||
/// ## Why is this bad? | ||
/// The `@singledispatch` decorator is intended for use with functions, not methods. | ||
/// | ||
/// Instead, use the `@singledispatchmethod` decorator, or migrate the method to a | ||
/// standalone function or `@staticmethod`. | ||
/// | ||
/// ## Example | ||
/// ```python | ||
/// from functools import singledispatch | ||
/// | ||
/// | ||
/// class Class: | ||
/// @singledispatch | ||
/// def method(self, arg): | ||
/// ... | ||
/// ``` | ||
/// | ||
/// Use instead: | ||
/// ```python | ||
/// from functools import singledispatchmethod | ||
/// | ||
/// | ||
/// class Class: | ||
/// @singledispatchmethod | ||
/// def method(self, arg): | ||
/// ... | ||
/// ``` | ||
/// | ||
/// ## Fix safety | ||
/// This rule's fix is marked as unsafe, as migrating from `@singledispatch` to | ||
/// `@singledispatchmethod` may change the behavior of the code. | ||
#[violation] | ||
pub struct SingledispatchMethod; | ||
|
||
impl Violation for SingledispatchMethod { | ||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; | ||
|
||
#[derive_message_formats] | ||
fn message(&self) -> String { | ||
format!("`@singledispatch` decorator should not be used on methods") | ||
} | ||
|
||
fn fix_title(&self) -> Option<String> { | ||
Some("Replace with `@singledispatchmethod`".to_string()) | ||
} | ||
} | ||
|
||
/// E1519 | ||
pub(crate) fn singledispatch_method( | ||
checker: &Checker, | ||
scope: &Scope, | ||
diagnostics: &mut Vec<Diagnostic>, | ||
) { | ||
let Some(func) = scope.kind.as_function() else { | ||
return; | ||
}; | ||
|
||
let ast::StmtFunctionDef { | ||
name, | ||
decorator_list, | ||
.. | ||
} = func; | ||
|
||
let Some(parent) = &checker.semantic().first_non_type_parent_scope(scope) else { | ||
return; | ||
}; | ||
|
||
let type_ = function_type::classify( | ||
name, | ||
decorator_list, | ||
parent, | ||
checker.semantic(), | ||
&checker.settings.pep8_naming.classmethod_decorators, | ||
&checker.settings.pep8_naming.staticmethod_decorators, | ||
); | ||
if !matches!( | ||
type_, | ||
function_type::FunctionType::Method | function_type::FunctionType::ClassMethod | ||
) { | ||
return; | ||
} | ||
|
||
for decorator in decorator_list { | ||
if checker | ||
.semantic() | ||
.resolve_call_path(&decorator.expression) | ||
.is_some_and(|call_path| { | ||
matches!(call_path.as_slice(), ["functools", "singledispatch"]) | ||
}) | ||
{ | ||
let mut diagnostic = Diagnostic::new(SingledispatchMethod, decorator.range()); | ||
diagnostic.try_set_fix(|| { | ||
let (import_edit, binding) = checker.importer().get_or_import_symbol( | ||
&ImportRequest::import("functools", "singledispatchmethod"), | ||
decorator.start(), | ||
checker.semantic(), | ||
)?; | ||
Ok(Fix::unsafe_edits( | ||
Edit::range_replacement(binding, decorator.expression.range()), | ||
[import_edit], | ||
)) | ||
}); | ||
diagnostics.push(diagnostic); | ||
} | ||
} | ||
} |
43 changes: 43 additions & 0 deletions
43
...pylint/snapshots/ruff_linter__rules__pylint__tests__PLE1519_singledispatch_method.py.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
--- | ||
source: crates/ruff_linter/src/rules/pylint/mod.rs | ||
--- | ||
singledispatch_method.py:10:5: PLE1519 [*] `@singledispatch` decorator should not be used on methods | ||
| | ||
9 | class Board: | ||
10 | @singledispatch # [singledispatch-method] | ||
| ^^^^^^^^^^^^^^^ PLE1519 | ||
11 | @classmethod | ||
12 | def convert_position(cls, position): | ||
| | ||
= help: Replace with `@singledispatchmethod` | ||
|
||
ℹ Unsafe fix | ||
7 7 | | ||
8 8 | | ||
9 9 | class Board: | ||
10 |- @singledispatch # [singledispatch-method] | ||
10 |+ @singledispatchmethod # [singledispatch-method] | ||
11 11 | @classmethod | ||
12 12 | def convert_position(cls, position): | ||
13 13 | pass | ||
|
||
singledispatch_method.py:15:5: PLE1519 [*] `@singledispatch` decorator should not be used on methods | ||
| | ||
13 | pass | ||
14 | | ||
15 | @singledispatch # [singledispatch-method] | ||
| ^^^^^^^^^^^^^^^ PLE1519 | ||
16 | def move(self, position): | ||
17 | pass | ||
| | ||
= help: Replace with `@singledispatchmethod` | ||
|
||
ℹ Unsafe fix | ||
12 12 | def convert_position(cls, position): | ||
13 13 | pass | ||
14 14 | | ||
15 |- @singledispatch # [singledispatch-method] | ||
15 |+ @singledispatchmethod # [singledispatch-method] | ||
16 16 | def move(self, position): | ||
17 17 | pass | ||
18 18 | |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.