Skip to content

Commit 3fcc140

Browse files
authored
[pylint] - implement super-without-brackets/W0245 (#9257)
## Summary Implement [`super-without-brackets`/`W0245`](https://pylint.readthedocs.io/en/latest/user_guide/messages/warning/super-without-brackets.html) See: #970 ## Test Plan `cargo test`
1 parent 08c60f5 commit 3fcc140

File tree

8 files changed

+183
-0
lines changed

8 files changed

+183
-0
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
class Animal:
2+
@staticmethod
3+
def speak():
4+
return f"This animal says something."
5+
6+
7+
class BadDog(Animal):
8+
@staticmethod
9+
def speak():
10+
original_speak = super.speak() # PLW0245
11+
return f"{original_speak} But as a dog, it barks!"
12+
13+
14+
class GoodDog(Animal):
15+
@staticmethod
16+
def speak():
17+
original_speak = super().speak() # OK
18+
return f"{original_speak} But as a dog, it barks!"
19+
20+
21+
class FineDog(Animal):
22+
@staticmethod
23+
def speak():
24+
super = "super"
25+
original_speak = super.speak() # OK
26+
return f"{original_speak} But as a dog, it barks!"
27+
28+
29+
def super_without_class() -> None:
30+
super.blah() # OK
31+
32+
33+
super.blah() # OK

crates/ruff_linter/src/checkers/ast/analyze/expression.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
438438
}
439439
}
440440
}
441+
if checker.enabled(Rule::SuperWithoutBrackets) {
442+
pylint::rules::super_without_brackets(checker, func);
443+
}
441444
if checker.enabled(Rule::BitCount) {
442445
refurb::rules::bit_count(checker, call);
443446
}

crates/ruff_linter/src/codes.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
275275
(Pylint, "W0127") => (RuleGroup::Stable, rules::pylint::rules::SelfAssigningVariable),
276276
(Pylint, "W0129") => (RuleGroup::Stable, rules::pylint::rules::AssertOnStringLiteral),
277277
(Pylint, "W0131") => (RuleGroup::Stable, rules::pylint::rules::NamedExprWithoutContext),
278+
(Pylint, "W0245") => (RuleGroup::Preview, rules::pylint::rules::SuperWithoutBrackets),
278279
(Pylint, "W0406") => (RuleGroup::Stable, rules::pylint::rules::ImportSelf),
279280
(Pylint, "W0602") => (RuleGroup::Stable, rules::pylint::rules::GlobalVariableNotAssigned),
280281
(Pylint, "W0604") => (RuleGroup::Preview, rules::pylint::rules::GlobalAtModuleLevel),

crates/ruff_linter/src/rules/pylint/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ mod tests {
162162
)]
163163
#[test_case(Rule::NoClassmethodDecorator, Path::new("no_method_decorator.py"))]
164164
#[test_case(Rule::NoStaticmethodDecorator, Path::new("no_method_decorator.py"))]
165+
#[test_case(Rule::SuperWithoutBrackets, Path::new("super_without_brackets.py"))]
165166
#[test_case(
166167
Rule::UnnecessaryDictIndexLookup,
167168
Path::new("unnecessary_dict_index_lookup.py")

crates/ruff_linter/src/rules/pylint/rules/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ pub(crate) use self_assigning_variable::*;
5252
pub(crate) use single_string_slots::*;
5353
pub(crate) use subprocess_popen_preexec_fn::*;
5454
pub(crate) use subprocess_run_without_check::*;
55+
pub(crate) use super_without_brackets::*;
5556
pub(crate) use sys_exit_alias::*;
5657
pub(crate) use too_many_arguments::*;
5758
pub(crate) use too_many_boolean_expressions::*;
@@ -131,6 +132,7 @@ mod self_assigning_variable;
131132
mod single_string_slots;
132133
mod subprocess_popen_preexec_fn;
133134
mod subprocess_run_without_check;
135+
mod super_without_brackets;
134136
mod sys_exit_alias;
135137
mod too_many_arguments;
136138
mod too_many_boolean_expressions;
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
use ruff_python_ast::{self as ast, Expr};
2+
use ruff_python_semantic::{analyze::function_type, ScopeKind};
3+
4+
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
5+
use ruff_macros::{derive_message_formats, violation};
6+
use ruff_text_size::Ranged;
7+
8+
use crate::checkers::ast::Checker;
9+
10+
/// ## What it does
11+
/// Checks for `super` calls without parentheses.
12+
///
13+
/// ## Why is this bad?
14+
/// When `super` is used without parentheses, it is not an actual call, and
15+
/// thus has no effect.
16+
///
17+
/// ## Example
18+
/// ```python
19+
/// class Animal:
20+
/// @staticmethod
21+
/// def speak():
22+
/// return "This animal says something."
23+
///
24+
///
25+
/// class Dog(Animal):
26+
/// @staticmethod
27+
/// def speak():
28+
/// original_speak = super.speak()
29+
/// return f"{original_speak} But as a dog, it barks!"
30+
/// ```
31+
///
32+
/// Use instead:
33+
/// ```python
34+
/// class Animal:
35+
/// @staticmethod
36+
/// def speak():
37+
/// return "This animal says something."
38+
///
39+
///
40+
/// class Dog(Animal):
41+
/// @staticmethod
42+
/// def speak():
43+
/// original_speak = super().speak()
44+
/// return f"{original_speak} But as a dog, it barks!"
45+
/// ```
46+
#[violation]
47+
pub struct SuperWithoutBrackets;
48+
49+
impl AlwaysFixableViolation for SuperWithoutBrackets {
50+
#[derive_message_formats]
51+
fn message(&self) -> String {
52+
format!("`super` call is missing parentheses")
53+
}
54+
55+
fn fix_title(&self) -> String {
56+
"Add parentheses to `super` call".to_string()
57+
}
58+
}
59+
60+
/// PLW0245
61+
pub(crate) fn super_without_brackets(checker: &mut Checker, func: &Expr) {
62+
// The call must be to `super` (without parentheses).
63+
let Expr::Attribute(ast::ExprAttribute { value, .. }) = func else {
64+
return;
65+
};
66+
67+
let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() else {
68+
return;
69+
};
70+
71+
if id.as_str() != "super" {
72+
return;
73+
}
74+
75+
if !checker.semantic().is_builtin(id.as_str()) {
76+
return;
77+
}
78+
79+
let scope = checker.semantic().current_scope();
80+
81+
// The current scope _must_ be a function.
82+
let ScopeKind::Function(function_def) = scope.kind else {
83+
return;
84+
};
85+
86+
let Some(parent) = &checker.semantic().first_non_type_parent_scope(scope) else {
87+
return;
88+
};
89+
90+
// The function must be a method, class method, or static method.
91+
let classification = function_type::classify(
92+
&function_def.name,
93+
&function_def.decorator_list,
94+
parent,
95+
checker.semantic(),
96+
&checker.settings.pep8_naming.classmethod_decorators,
97+
&checker.settings.pep8_naming.staticmethod_decorators,
98+
);
99+
if !matches!(
100+
classification,
101+
function_type::FunctionType::Method { .. }
102+
| function_type::FunctionType::ClassMethod { .. }
103+
| function_type::FunctionType::StaticMethod { .. }
104+
) {
105+
return;
106+
}
107+
108+
let mut diagnostic = Diagnostic::new(SuperWithoutBrackets, value.range());
109+
110+
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
111+
"super()".to_string(),
112+
value.range(),
113+
)));
114+
115+
checker.diagnostics.push(diagnostic);
116+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
source: crates/ruff_linter/src/rules/pylint/mod.rs
3+
---
4+
super_without_brackets.py:10:26: PLW0245 [*] `super` call is missing parentheses
5+
|
6+
8 | @staticmethod
7+
9 | def speak():
8+
10 | original_speak = super.speak() # PLW0245
9+
| ^^^^^ PLW0245
10+
11 | return f"{original_speak} But as a dog, it barks!"
11+
|
12+
= help: Add parentheses to `super` call
13+
14+
Safe fix
15+
7 7 | class BadDog(Animal):
16+
8 8 | @staticmethod
17+
9 9 | def speak():
18+
10 |- original_speak = super.speak() # PLW0245
19+
10 |+ original_speak = super().speak() # PLW0245
20+
11 11 | return f"{original_speak} But as a dog, it barks!"
21+
12 12 |
22+
13 13 |
23+
24+

ruff.schema.json

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)