From 395a17056fbc42bebb736472c82c64f38315689a Mon Sep 17 00:00:00 2001 From: LaBatata101 Date: Thu, 10 Aug 2023 18:18:16 -0300 Subject: [PATCH 1/5] [`pylint`] Implement `bad-dunder-name` (`W3201`) --- .../test/fixtures/pylint/bad_dunder_name.py | 46 ++++ .../src/checkers/ast/analyze/statement.rs | 3 + crates/ruff/src/codes.rs | 1 + crates/ruff/src/rules/pylint/mod.rs | 1 + .../pylint/rules/bad_dunder_method_name.rs | 211 ++++++++++++++++++ crates/ruff/src/rules/pylint/rules/mod.rs | 2 + ...nt__tests__PLW3201_bad_dunder_name.py.snap | 61 +++++ ruff.schema.json | 3 + 8 files changed, 328 insertions(+) create mode 100644 crates/ruff/resources/test/fixtures/pylint/bad_dunder_name.py create mode 100644 crates/ruff/src/rules/pylint/rules/bad_dunder_method_name.rs create mode 100644 crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLW3201_bad_dunder_name.py.snap diff --git a/crates/ruff/resources/test/fixtures/pylint/bad_dunder_name.py b/crates/ruff/resources/test/fixtures/pylint/bad_dunder_name.py new file mode 100644 index 0000000000000..46d8be249bc95 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/pylint/bad_dunder_name.py @@ -0,0 +1,46 @@ +class Apples: + def _init_(self): # [bad-dunder-name] + pass + + def __hello__(self): # [bad-dunder-name] + print("hello") + + def __init_(self): # [bad-dunder-name] + # author likely unintentionally misspelled the correct init dunder. + pass + + def _init_(self): # [bad-dunder-name] + # author likely unintentionally misspelled the correct init dunder. + pass + + def ___neg__(self): # [bad-dunder-name] + # author likely accidentally added an additional `_` + pass + + def __inv__(self): # [bad-dunder-name] + # author likely meant to call the invert dunder method + pass + + def hello(self): + print("hello") + + def __init__(self): + pass + + def init(self): + # valid name even though someone could accidentally mean __init__ + pass + + def _protected_method(self): + print("Protected") + + def __private_method(self): + print("Private") + + @property + def __doc__(self): + return "Docstring" + + +def __foo_bar__(): # this is not checked by the [bad-dunder-name] rule + ... diff --git a/crates/ruff/src/checkers/ast/analyze/statement.rs b/crates/ruff/src/checkers/ast/analyze/statement.rs index db0fa91f56349..98ab3282f8534 100644 --- a/crates/ruff/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff/src/checkers/ast/analyze/statement.rs @@ -506,6 +506,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { if checker.enabled(Rule::SingleStringSlots) { pylint::rules::single_string_slots(checker, class_def); } + if checker.enabled(Rule::BadDunderMethodName) { + pylint::rules::bad_dunder_method_name(checker, body); + } } Stmt::Import(ast::StmtImport { names, range: _ }) => { if checker.enabled(Rule::MultipleImportsOnOneLine) { diff --git a/crates/ruff/src/codes.rs b/crates/ruff/src/codes.rs index 5e3cfe478a7f5..2d0c9aa7d56de 100644 --- a/crates/ruff/src/codes.rs +++ b/crates/ruff/src/codes.rs @@ -228,6 +228,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pylint, "W1509") => (RuleGroup::Unspecified, rules::pylint::rules::SubprocessPopenPreexecFn), (Pylint, "W1641") => (RuleGroup::Nursery, rules::pylint::rules::EqWithoutHash), (Pylint, "W2901") => (RuleGroup::Unspecified, rules::pylint::rules::RedefinedLoopName), + (Pylint, "W3201") => (RuleGroup::Unspecified, rules::pylint::rules::BadDunderMethodName), (Pylint, "W3301") => (RuleGroup::Unspecified, rules::pylint::rules::NestedMinMax), // flake8-async diff --git a/crates/ruff/src/rules/pylint/mod.rs b/crates/ruff/src/rules/pylint/mod.rs index fbbb859126a3d..611547e526914 100644 --- a/crates/ruff/src/rules/pylint/mod.rs +++ b/crates/ruff/src/rules/pylint/mod.rs @@ -126,6 +126,7 @@ mod tests { Rule::SubprocessPopenPreexecFn, Path::new("subprocess_popen_preexec_fn.py") )] + #[test_case(Rule::BadDunderMethodName, Path::new("bad_dunder_name.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( diff --git a/crates/ruff/src/rules/pylint/rules/bad_dunder_method_name.rs b/crates/ruff/src/rules/pylint/rules/bad_dunder_method_name.rs new file mode 100644 index 0000000000000..1ee6ee4d5d71f --- /dev/null +++ b/crates/ruff/src/rules/pylint/rules/bad_dunder_method_name.rs @@ -0,0 +1,211 @@ +use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::identifier::Identifier; +use ruff_python_ast::Stmt; + +use crate::{checkers::ast::Checker, settings::types::PythonVersion}; + +/// ## What it does +/// Checks for any misspelled dunder name method and for any method +/// defined with `__...__` that's not one of the pre-defined methods. +/// +/// The pre-defined methods encompass all of Python's standard dunder +/// methods. +/// +/// ## Why is this bad? +/// Misspelled dunder name methods may cause your code to not function +/// as expected. +/// +/// Since dunder methods are associated with customizing the behavior +/// of a class in Python, introducing a dunder method such as `__foo__` +/// that diverges from standard Python dunder methods could potentially +/// confuse someone reading the code. +/// +/// ## Example +/// ```python +/// class Foo: +/// def __init_(self): +/// ... +/// ``` +/// +/// Use instead: +/// ```python +/// class Foo: +/// def __init__(self): +/// ... +/// ``` +#[violation] +pub struct BadDunderMethodName { + name: String, +} + +impl Violation for BadDunderMethodName { + #[derive_message_formats] + fn message(&self) -> String { + let BadDunderMethodName { name } = self; + format!("Bad or misspelled dunder method name `{name}`. (bad-dunder-name)") + } +} + +const DUNDER_METHODS: &[&str] = &[ + "__abs__", + "__add__", + "__aenter__", + "__aexit__", + "__and__", + "__await__", + "__bool__", + "__bytes__", + "__call__", + "__ceil__", + "__class__", + "__class_getitem__", + "__complex__", + "__contains__", + "__copy__", + "__deepcopy__", + "__del__", + "__delattr__", + "__delete__", + "__delitem__", + "__dict__", + "__dir__", + "__divmod__", + "__doc__", + "__enter__", + "__eq__", + "__exit__", + "__float__", + "__floor__", + "__floordiv__", + "__format__", + "__format__", + "__fspath__", + "__ge__", + "__get__", + "__getattr__", + "__getattribute__", + "__getitem__", + "__getnewargs__", + "__getnewargs_ex__", + "__getstate__", + "__gt__", + "__hash__", + "__iadd__", + "__iand__", + "__ifloordiv__", + "__ilshift__", + "__imatmul__", + "__imod__", + "__imul__", + "__init__", + "__init_subclass__", + "__instancecheck__", + "__int__", + "__invert__", + "__ior__", + "__ipow__", + "__irshift__", + "__isub__", + "__iter__", + "__itruediv__", + "__ixor__", + "__le__", + "__len__", + "__length_hint__", + "__lshift__", + "__lt__", + "__matmul__", + "__missing__", + "__mod__", + "__module__", + "__mul__", + "__ne__", + "__neg__", + "__new__", + "__new__", + "__next__", + "__or__", + "__pos__", + "__pow__", + "__radd__", + "__rand__", + "__rdivmod__", + "__reduce__", + "__reduce_ex__", + "__repr__", + "__reversed__", + "__rfloordiv__", + "__rlshift__", + "__rmatmul__", + "__rmod__", + "__rmul__", + "__ror__", + "__round__", + "__rpow__", + "__rrshift__", + "__rshift__", + "__rsub__", + "__rtruediv__", + "__rxor__", + "__set__", + "__set_name__", + "__setattr__", + "__setitem__", + "__setstate__", + "__sizeof__", + "__str__", + "__sub__", + "__subclasscheck__", + "__subclasses__", + "__subclasshook__", + "__truediv__", + "__trunc__", + "__weakref__", + "__xor__", + // part of `dataclasses` module + "__post_init__", +]; + +const DUNDER_METHODS_PY310: &[&str] = &["__aiter__", "__anext__"]; + +/// PLW3201 +pub(crate) fn bad_dunder_method_name(checker: &mut Checker, class_body: &[Stmt]) { + // Collects all methods in a class that starts and ends with a `_` and are + // not one of Python's standard dunder methods. + let bad_dunder_methods: Vec<&Stmt> = class_body + .iter() + .filter(|stmt| stmt.is_function_def_stmt()) + .filter(|stmt| { + let Some(method) = stmt.as_function_def_stmt() else { + return false; + }; + + let contains_dunder_method = if checker.settings.target_version >= PythonVersion::Py310 + { + DUNDER_METHODS.contains(&method.name.as_str()) + || DUNDER_METHODS_PY310.contains(&method.name.as_str()) + } else { + DUNDER_METHODS.contains(&method.name.as_str()) + }; + + method.name.starts_with('_') && method.name.ends_with('_') && !contains_dunder_method + }) + .collect(); + + if bad_dunder_methods.is_empty() { + return; + } + + for bad_dunder_method in bad_dunder_methods { + let Some(method) = bad_dunder_method.as_function_def_stmt() else { + break; + }; + checker.diagnostics.push(Diagnostic::new( + BadDunderMethodName { + name: method.name.to_string(), + }, + bad_dunder_method.identifier(), + )); + } +} diff --git a/crates/ruff/src/rules/pylint/rules/mod.rs b/crates/ruff/src/rules/pylint/rules/mod.rs index bfd44d030e610..a82b668e3c16a 100644 --- a/crates/ruff/src/rules/pylint/rules/mod.rs +++ b/crates/ruff/src/rules/pylint/rules/mod.rs @@ -1,5 +1,6 @@ pub(crate) use assert_on_string_literal::*; pub(crate) use await_outside_async::*; +pub(crate) use bad_dunder_method_name::*; pub(crate) use bad_str_strip_call::*; pub(crate) use bad_string_format_character::BadStringFormatCharacter; pub(crate) use bad_string_format_type::*; @@ -55,6 +56,7 @@ pub(crate) use yield_in_init::*; mod assert_on_string_literal; mod await_outside_async; +mod bad_dunder_method_name; mod bad_str_strip_call; pub(crate) mod bad_string_format_character; mod bad_string_format_type; diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLW3201_bad_dunder_name.py.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLW3201_bad_dunder_name.py.snap new file mode 100644 index 0000000000000..64d461d5aa752 --- /dev/null +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLW3201_bad_dunder_name.py.snap @@ -0,0 +1,61 @@ +--- +source: crates/ruff/src/rules/pylint/mod.rs +--- +bad_dunder_name.py:2:9: PLW3201 Bad or misspelled dunder method name `_init_`. (bad-dunder-name) + | +1 | class Apples: +2 | def _init_(self): # [bad-dunder-name] + | ^^^^^^ PLW3201 +3 | pass + | + +bad_dunder_name.py:5:9: PLW3201 Bad or misspelled dunder method name `__hello__`. (bad-dunder-name) + | +3 | pass +4 | +5 | def __hello__(self): # [bad-dunder-name] + | ^^^^^^^^^ PLW3201 +6 | print("hello") + | + +bad_dunder_name.py:8:9: PLW3201 Bad or misspelled dunder method name `__init_`. (bad-dunder-name) + | + 6 | print("hello") + 7 | + 8 | def __init_(self): # [bad-dunder-name] + | ^^^^^^^ PLW3201 + 9 | # author likely unintentionally misspelled the correct init dunder. +10 | pass + | + +bad_dunder_name.py:12:9: PLW3201 Bad or misspelled dunder method name `_init_`. (bad-dunder-name) + | +10 | pass +11 | +12 | def _init_(self): # [bad-dunder-name] + | ^^^^^^ PLW3201 +13 | # author likely unintentionally misspelled the correct init dunder. +14 | pass + | + +bad_dunder_name.py:16:9: PLW3201 Bad or misspelled dunder method name `___neg__`. (bad-dunder-name) + | +14 | pass +15 | +16 | def ___neg__(self): # [bad-dunder-name] + | ^^^^^^^^ PLW3201 +17 | # author likely accidentally added an additional `_` +18 | pass + | + +bad_dunder_name.py:20:9: PLW3201 Bad or misspelled dunder method name `__inv__`. (bad-dunder-name) + | +18 | pass +19 | +20 | def __inv__(self): # [bad-dunder-name] + | ^^^^^^^ PLW3201 +21 | # author likely meant to call the invert dunder method +22 | pass + | + + diff --git a/ruff.schema.json b/ruff.schema.json index 9d47d617dcc23..4c66fd6f6bfbf 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -2289,6 +2289,9 @@ "PLW290", "PLW2901", "PLW3", + "PLW32", + "PLW320", + "PLW3201", "PLW33", "PLW330", "PLW3301", From 242232a231c8f272b6eb663296102e4775ba4bdb Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 10 Aug 2023 20:58:30 -0400 Subject: [PATCH 2/5] Change fixture name --- ...{bad_dunder_name.py => bad_dunder_method_name.py} | 0 crates/ruff/src/rules/pylint/mod.rs | 2 +- ...t__tests__PLW3201_bad_dunder_method_name.py.snap} | 12 ++++++------ 3 files changed, 7 insertions(+), 7 deletions(-) rename crates/ruff/resources/test/fixtures/pylint/{bad_dunder_name.py => bad_dunder_method_name.py} (100%) rename crates/ruff/src/rules/pylint/snapshots/{ruff__rules__pylint__tests__PLW3201_bad_dunder_name.py.snap => ruff__rules__pylint__tests__PLW3201_bad_dunder_method_name.py.snap} (64%) diff --git a/crates/ruff/resources/test/fixtures/pylint/bad_dunder_name.py b/crates/ruff/resources/test/fixtures/pylint/bad_dunder_method_name.py similarity index 100% rename from crates/ruff/resources/test/fixtures/pylint/bad_dunder_name.py rename to crates/ruff/resources/test/fixtures/pylint/bad_dunder_method_name.py diff --git a/crates/ruff/src/rules/pylint/mod.rs b/crates/ruff/src/rules/pylint/mod.rs index 337393f2942ed..7611f18fba27d 100644 --- a/crates/ruff/src/rules/pylint/mod.rs +++ b/crates/ruff/src/rules/pylint/mod.rs @@ -130,7 +130,7 @@ mod tests { Rule::SubprocessRunWithoutCheck, Path::new("subprocess_run_without_check.py") )] - #[test_case(Rule::BadDunderMethodName, Path::new("bad_dunder_name.py"))] + #[test_case(Rule::BadDunderMethodName, Path::new("bad_dunder_method_name.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLW3201_bad_dunder_name.py.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLW3201_bad_dunder_method_name.py.snap similarity index 64% rename from crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLW3201_bad_dunder_name.py.snap rename to crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLW3201_bad_dunder_method_name.py.snap index 64d461d5aa752..f3c6b8166115c 100644 --- a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLW3201_bad_dunder_name.py.snap +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLW3201_bad_dunder_method_name.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff/src/rules/pylint/mod.rs --- -bad_dunder_name.py:2:9: PLW3201 Bad or misspelled dunder method name `_init_`. (bad-dunder-name) +bad_dunder_method_name.py:2:9: PLW3201 Bad or misspelled dunder method name `_init_`. (bad-dunder-name) | 1 | class Apples: 2 | def _init_(self): # [bad-dunder-name] @@ -9,7 +9,7 @@ bad_dunder_name.py:2:9: PLW3201 Bad or misspelled dunder method name `_init_`. ( 3 | pass | -bad_dunder_name.py:5:9: PLW3201 Bad or misspelled dunder method name `__hello__`. (bad-dunder-name) +bad_dunder_method_name.py:5:9: PLW3201 Bad or misspelled dunder method name `__hello__`. (bad-dunder-name) | 3 | pass 4 | @@ -18,7 +18,7 @@ bad_dunder_name.py:5:9: PLW3201 Bad or misspelled dunder method name `__hello__` 6 | print("hello") | -bad_dunder_name.py:8:9: PLW3201 Bad or misspelled dunder method name `__init_`. (bad-dunder-name) +bad_dunder_method_name.py:8:9: PLW3201 Bad or misspelled dunder method name `__init_`. (bad-dunder-name) | 6 | print("hello") 7 | @@ -28,7 +28,7 @@ bad_dunder_name.py:8:9: PLW3201 Bad or misspelled dunder method name `__init_`. 10 | pass | -bad_dunder_name.py:12:9: PLW3201 Bad or misspelled dunder method name `_init_`. (bad-dunder-name) +bad_dunder_method_name.py:12:9: PLW3201 Bad or misspelled dunder method name `_init_`. (bad-dunder-name) | 10 | pass 11 | @@ -38,7 +38,7 @@ bad_dunder_name.py:12:9: PLW3201 Bad or misspelled dunder method name `_init_`. 14 | pass | -bad_dunder_name.py:16:9: PLW3201 Bad or misspelled dunder method name `___neg__`. (bad-dunder-name) +bad_dunder_method_name.py:16:9: PLW3201 Bad or misspelled dunder method name `___neg__`. (bad-dunder-name) | 14 | pass 15 | @@ -48,7 +48,7 @@ bad_dunder_name.py:16:9: PLW3201 Bad or misspelled dunder method name `___neg__` 18 | pass | -bad_dunder_name.py:20:9: PLW3201 Bad or misspelled dunder method name `__inv__`. (bad-dunder-name) +bad_dunder_method_name.py:20:9: PLW3201 Bad or misspelled dunder method name `__inv__`. (bad-dunder-name) | 18 | pass 19 | From d9f3c33e97e9c233d4784c89332c2bd4de754be8 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 10 Aug 2023 21:04:54 -0400 Subject: [PATCH 3/5] Use a match --- crates/ruff/src/codes.rs | 2 +- .../pylint/rules/bad_dunder_method_name.rs | 289 +++++++++--------- ruff.schema.json | 2 - 3 files changed, 140 insertions(+), 153 deletions(-) diff --git a/crates/ruff/src/codes.rs b/crates/ruff/src/codes.rs index 3fddbb6468018..d7bb7cf7a5f0b 100644 --- a/crates/ruff/src/codes.rs +++ b/crates/ruff/src/codes.rs @@ -229,7 +229,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pylint, "W1510") => (RuleGroup::Unspecified, rules::pylint::rules::SubprocessRunWithoutCheck), (Pylint, "W1641") => (RuleGroup::Nursery, rules::pylint::rules::EqWithoutHash), (Pylint, "W2901") => (RuleGroup::Unspecified, rules::pylint::rules::RedefinedLoopName), - (Pylint, "W3201") => (RuleGroup::Unspecified, rules::pylint::rules::BadDunderMethodName), + (Pylint, "W3201") => (RuleGroup::Nursery, rules::pylint::rules::BadDunderMethodName), (Pylint, "W3301") => (RuleGroup::Unspecified, rules::pylint::rules::NestedMinMax), // flake8-async diff --git a/crates/ruff/src/rules/pylint/rules/bad_dunder_method_name.rs b/crates/ruff/src/rules/pylint/rules/bad_dunder_method_name.rs index 1ee6ee4d5d71f..c9839ce91c0f9 100644 --- a/crates/ruff/src/rules/pylint/rules/bad_dunder_method_name.rs +++ b/crates/ruff/src/rules/pylint/rules/bad_dunder_method_name.rs @@ -47,165 +47,154 @@ impl Violation for BadDunderMethodName { } } -const DUNDER_METHODS: &[&str] = &[ - "__abs__", - "__add__", - "__aenter__", - "__aexit__", - "__and__", - "__await__", - "__bool__", - "__bytes__", - "__call__", - "__ceil__", - "__class__", - "__class_getitem__", - "__complex__", - "__contains__", - "__copy__", - "__deepcopy__", - "__del__", - "__delattr__", - "__delete__", - "__delitem__", - "__dict__", - "__dir__", - "__divmod__", - "__doc__", - "__enter__", - "__eq__", - "__exit__", - "__float__", - "__floor__", - "__floordiv__", - "__format__", - "__format__", - "__fspath__", - "__ge__", - "__get__", - "__getattr__", - "__getattribute__", - "__getitem__", - "__getnewargs__", - "__getnewargs_ex__", - "__getstate__", - "__gt__", - "__hash__", - "__iadd__", - "__iand__", - "__ifloordiv__", - "__ilshift__", - "__imatmul__", - "__imod__", - "__imul__", - "__init__", - "__init_subclass__", - "__instancecheck__", - "__int__", - "__invert__", - "__ior__", - "__ipow__", - "__irshift__", - "__isub__", - "__iter__", - "__itruediv__", - "__ixor__", - "__le__", - "__len__", - "__length_hint__", - "__lshift__", - "__lt__", - "__matmul__", - "__missing__", - "__mod__", - "__module__", - "__mul__", - "__ne__", - "__neg__", - "__new__", - "__new__", - "__next__", - "__or__", - "__pos__", - "__pow__", - "__radd__", - "__rand__", - "__rdivmod__", - "__reduce__", - "__reduce_ex__", - "__repr__", - "__reversed__", - "__rfloordiv__", - "__rlshift__", - "__rmatmul__", - "__rmod__", - "__rmul__", - "__ror__", - "__round__", - "__rpow__", - "__rrshift__", - "__rshift__", - "__rsub__", - "__rtruediv__", - "__rxor__", - "__set__", - "__set_name__", - "__setattr__", - "__setitem__", - "__setstate__", - "__sizeof__", - "__str__", - "__sub__", - "__subclasscheck__", - "__subclasses__", - "__subclasshook__", - "__truediv__", - "__trunc__", - "__weakref__", - "__xor__", - // part of `dataclasses` module - "__post_init__", -]; - -const DUNDER_METHODS_PY310: &[&str] = &["__aiter__", "__anext__"]; - /// PLW3201 pub(crate) fn bad_dunder_method_name(checker: &mut Checker, class_body: &[Stmt]) { - // Collects all methods in a class that starts and ends with a `_` and are - // not one of Python's standard dunder methods. - let bad_dunder_methods: Vec<&Stmt> = class_body + for method in class_body .iter() - .filter(|stmt| stmt.is_function_def_stmt()) - .filter(|stmt| { - let Some(method) = stmt.as_function_def_stmt() else { + .filter_map(ruff_python_ast::Stmt::as_function_def_stmt) + .filter(|method| { + if is_known_dunder_method(&method.name, checker.settings.target_version) { return false; - }; - - let contains_dunder_method = if checker.settings.target_version >= PythonVersion::Py310 - { - DUNDER_METHODS.contains(&method.name.as_str()) - || DUNDER_METHODS_PY310.contains(&method.name.as_str()) - } else { - DUNDER_METHODS.contains(&method.name.as_str()) - }; - - method.name.starts_with('_') && method.name.ends_with('_') && !contains_dunder_method + } + method.name.starts_with('_') && method.name.ends_with('_') }) - .collect(); - - if bad_dunder_methods.is_empty() { - return; - } - - for bad_dunder_method in bad_dunder_methods { - let Some(method) = bad_dunder_method.as_function_def_stmt() else { - break; - }; + { checker.diagnostics.push(Diagnostic::new( BadDunderMethodName { name: method.name.to_string(), }, - bad_dunder_method.identifier(), + method.identifier(), )); } } + +/// Returns `true` if a method is a known dunder method. +fn is_known_dunder_method(method: &str, target_version: PythonVersion) -> bool { + if matches!( + method, + "__abs__" + | "__add__" + | "__aenter__" + | "__aexit__" + | "__and__" + | "__await__" + | "__bool__" + | "__bytes__" + | "__call__" + | "__ceil__" + | "__class__" + | "__class_getitem__" + | "__complex__" + | "__contains__" + | "__copy__" + | "__deepcopy__" + | "__del__" + | "__delattr__" + | "__delete__" + | "__delitem__" + | "__dict__" + | "__dir__" + | "__divmod__" + | "__doc__" + | "__enter__" + | "__eq__" + | "__exit__" + | "__float__" + | "__floor__" + | "__floordiv__" + | "__format__" + | "__fspath__" + | "__ge__" + | "__get__" + | "__getattr__" + | "__getattribute__" + | "__getitem__" + | "__getnewargs__" + | "__getnewargs_ex__" + | "__getstate__" + | "__gt__" + | "__hash__" + | "__iadd__" + | "__iand__" + | "__ifloordiv__" + | "__ilshift__" + | "__imatmul__" + | "__imod__" + | "__imul__" + | "__init__" + | "__init_subclass__" + | "__instancecheck__" + | "__int__" + | "__invert__" + | "__ior__" + | "__ipow__" + | "__irshift__" + | "__isub__" + | "__iter__" + | "__itruediv__" + | "__ixor__" + | "__le__" + | "__len__" + | "__length_hint__" + | "__lshift__" + | "__lt__" + | "__matmul__" + | "__missing__" + | "__mod__" + | "__module__" + | "__mul__" + | "__ne__" + | "__neg__" + | "__new__" + | "__next__" + | "__or__" + | "__pos__" + | "__pow__" + | "__radd__" + | "__rand__" + | "__rdivmod__" + | "__reduce__" + | "__reduce_ex__" + | "__repr__" + | "__reversed__" + | "__rfloordiv__" + | "__rlshift__" + | "__rmatmul__" + | "__rmod__" + | "__rmul__" + | "__ror__" + | "__round__" + | "__rpow__" + | "__rrshift__" + | "__rshift__" + | "__rsub__" + | "__rtruediv__" + | "__rxor__" + | "__set__" + | "__set_name__" + | "__setattr__" + | "__setitem__" + | "__setstate__" + | "__sizeof__" + | "__str__" + | "__sub__" + | "__subclasscheck__" + | "__subclasses__" + | "__subclasshook__" + | "__truediv__" + | "__trunc__" + | "__weakref__" + | "__xor__" + | "__post_init__" + ) { + return true; + } + + if target_version >= PythonVersion::Py310 { + if matches!(method, "__aiter__" | "__anext__") { + return true; + } + } + + false +} diff --git a/ruff.schema.json b/ruff.schema.json index a9e8af5deb8b7..d28a8b49d5459 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -2291,8 +2291,6 @@ "PLW290", "PLW2901", "PLW3", - "PLW32", - "PLW320", "PLW3201", "PLW33", "PLW330", From eb55112e5dff461468d09a9e540be9c1452ff19b Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 10 Aug 2023 21:10:22 -0400 Subject: [PATCH 4/5] Remove 3.10 gate --- .../pylint/rules/bad_dunder_method_name.rs | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/crates/ruff/src/rules/pylint/rules/bad_dunder_method_name.rs b/crates/ruff/src/rules/pylint/rules/bad_dunder_method_name.rs index c9839ce91c0f9..d42a87e00f2ba 100644 --- a/crates/ruff/src/rules/pylint/rules/bad_dunder_method_name.rs +++ b/crates/ruff/src/rules/pylint/rules/bad_dunder_method_name.rs @@ -53,7 +53,7 @@ pub(crate) fn bad_dunder_method_name(checker: &mut Checker, class_body: &[Stmt]) .iter() .filter_map(ruff_python_ast::Stmt::as_function_def_stmt) .filter(|method| { - if is_known_dunder_method(&method.name, checker.settings.target_version) { + if is_known_dunder_method(&method.name) { return false; } method.name.starts_with('_') && method.name.ends_with('_') @@ -69,14 +69,16 @@ pub(crate) fn bad_dunder_method_name(checker: &mut Checker, class_body: &[Stmt]) } /// Returns `true` if a method is a known dunder method. -fn is_known_dunder_method(method: &str, target_version: PythonVersion) -> bool { - if matches!( +fn is_known_dunder_method(method: &str) -> bool { + matches!( method, "__abs__" | "__add__" | "__aenter__" | "__aexit__" + | "__aiter__" | "__and__" + | "__anext__" | "__await__" | "__bool__" | "__bytes__" @@ -149,6 +151,7 @@ fn is_known_dunder_method(method: &str, target_version: PythonVersion) -> bool { | "__next__" | "__or__" | "__pos__" + | "__post_init__" | "__pow__" | "__radd__" | "__rand__" @@ -185,16 +188,5 @@ fn is_known_dunder_method(method: &str, target_version: PythonVersion) -> bool { | "__trunc__" | "__weakref__" | "__xor__" - | "__post_init__" - ) { - return true; - } - - if target_version >= PythonVersion::Py310 { - if matches!(method, "__aiter__" | "__anext__") { - return true; - } - } - - false + ) } From ce3775bd14c5ca1bdfdbebbd3b89fba492a164bc Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 10 Aug 2023 21:21:07 -0400 Subject: [PATCH 5/5] Clippy --- crates/ruff/src/rules/pylint/rules/bad_dunder_method_name.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff/src/rules/pylint/rules/bad_dunder_method_name.rs b/crates/ruff/src/rules/pylint/rules/bad_dunder_method_name.rs index d42a87e00f2ba..3887ab252af9c 100644 --- a/crates/ruff/src/rules/pylint/rules/bad_dunder_method_name.rs +++ b/crates/ruff/src/rules/pylint/rules/bad_dunder_method_name.rs @@ -3,7 +3,7 @@ use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::identifier::Identifier; use ruff_python_ast::Stmt; -use crate::{checkers::ast::Checker, settings::types::PythonVersion}; +use crate::checkers::ast::Checker; /// ## What it does /// Checks for any misspelled dunder name method and for any method