[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`
This commit is contained in:
Steve C 2024-01-02 16:57:53 -05:00 committed by GitHub
parent 08c60f513b
commit 3fcc1402f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 183 additions and 0 deletions

View file

@ -0,0 +1,33 @@
class Animal:
@staticmethod
def speak():
return f"This animal says something."
class BadDog(Animal):
@staticmethod
def speak():
original_speak = super.speak() # PLW0245
return f"{original_speak} But as a dog, it barks!"
class GoodDog(Animal):
@staticmethod
def speak():
original_speak = super().speak() # OK
return f"{original_speak} But as a dog, it barks!"
class FineDog(Animal):
@staticmethod
def speak():
super = "super"
original_speak = super.speak() # OK
return f"{original_speak} But as a dog, it barks!"
def super_without_class() -> None:
super.blah() # OK
super.blah() # OK

View file

@ -438,6 +438,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
} }
} }
} }
if checker.enabled(Rule::SuperWithoutBrackets) {
pylint::rules::super_without_brackets(checker, func);
}
if checker.enabled(Rule::BitCount) { if checker.enabled(Rule::BitCount) {
refurb::rules::bit_count(checker, call); refurb::rules::bit_count(checker, call);
} }

View file

@ -275,6 +275,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "W0127") => (RuleGroup::Stable, rules::pylint::rules::SelfAssigningVariable), (Pylint, "W0127") => (RuleGroup::Stable, rules::pylint::rules::SelfAssigningVariable),
(Pylint, "W0129") => (RuleGroup::Stable, rules::pylint::rules::AssertOnStringLiteral), (Pylint, "W0129") => (RuleGroup::Stable, rules::pylint::rules::AssertOnStringLiteral),
(Pylint, "W0131") => (RuleGroup::Stable, rules::pylint::rules::NamedExprWithoutContext), (Pylint, "W0131") => (RuleGroup::Stable, rules::pylint::rules::NamedExprWithoutContext),
(Pylint, "W0245") => (RuleGroup::Preview, rules::pylint::rules::SuperWithoutBrackets),
(Pylint, "W0406") => (RuleGroup::Stable, rules::pylint::rules::ImportSelf), (Pylint, "W0406") => (RuleGroup::Stable, rules::pylint::rules::ImportSelf),
(Pylint, "W0602") => (RuleGroup::Stable, rules::pylint::rules::GlobalVariableNotAssigned), (Pylint, "W0602") => (RuleGroup::Stable, rules::pylint::rules::GlobalVariableNotAssigned),
(Pylint, "W0604") => (RuleGroup::Preview, rules::pylint::rules::GlobalAtModuleLevel), (Pylint, "W0604") => (RuleGroup::Preview, rules::pylint::rules::GlobalAtModuleLevel),

View file

@ -162,6 +162,7 @@ mod tests {
)] )]
#[test_case(Rule::NoClassmethodDecorator, Path::new("no_method_decorator.py"))] #[test_case(Rule::NoClassmethodDecorator, Path::new("no_method_decorator.py"))]
#[test_case(Rule::NoStaticmethodDecorator, Path::new("no_method_decorator.py"))] #[test_case(Rule::NoStaticmethodDecorator, Path::new("no_method_decorator.py"))]
#[test_case(Rule::SuperWithoutBrackets, Path::new("super_without_brackets.py"))]
#[test_case( #[test_case(
Rule::UnnecessaryDictIndexLookup, Rule::UnnecessaryDictIndexLookup,
Path::new("unnecessary_dict_index_lookup.py") Path::new("unnecessary_dict_index_lookup.py")

View file

@ -52,6 +52,7 @@ pub(crate) use self_assigning_variable::*;
pub(crate) use single_string_slots::*; pub(crate) use single_string_slots::*;
pub(crate) use subprocess_popen_preexec_fn::*; pub(crate) use subprocess_popen_preexec_fn::*;
pub(crate) use subprocess_run_without_check::*; pub(crate) use subprocess_run_without_check::*;
pub(crate) use super_without_brackets::*;
pub(crate) use sys_exit_alias::*; pub(crate) use sys_exit_alias::*;
pub(crate) use too_many_arguments::*; pub(crate) use too_many_arguments::*;
pub(crate) use too_many_boolean_expressions::*; pub(crate) use too_many_boolean_expressions::*;
@ -131,6 +132,7 @@ mod self_assigning_variable;
mod single_string_slots; mod single_string_slots;
mod subprocess_popen_preexec_fn; mod subprocess_popen_preexec_fn;
mod subprocess_run_without_check; mod subprocess_run_without_check;
mod super_without_brackets;
mod sys_exit_alias; mod sys_exit_alias;
mod too_many_arguments; mod too_many_arguments;
mod too_many_boolean_expressions; mod too_many_boolean_expressions;

View file

@ -0,0 +1,116 @@
use ruff_python_ast::{self as ast, Expr};
use ruff_python_semantic::{analyze::function_type, ScopeKind};
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for `super` calls without parentheses.
///
/// ## Why is this bad?
/// When `super` is used without parentheses, it is not an actual call, and
/// thus has no effect.
///
/// ## Example
/// ```python
/// class Animal:
/// @staticmethod
/// def speak():
/// return "This animal says something."
///
///
/// class Dog(Animal):
/// @staticmethod
/// def speak():
/// original_speak = super.speak()
/// return f"{original_speak} But as a dog, it barks!"
/// ```
///
/// Use instead:
/// ```python
/// class Animal:
/// @staticmethod
/// def speak():
/// return "This animal says something."
///
///
/// class Dog(Animal):
/// @staticmethod
/// def speak():
/// original_speak = super().speak()
/// return f"{original_speak} But as a dog, it barks!"
/// ```
#[violation]
pub struct SuperWithoutBrackets;
impl AlwaysFixableViolation for SuperWithoutBrackets {
#[derive_message_formats]
fn message(&self) -> String {
format!("`super` call is missing parentheses")
}
fn fix_title(&self) -> String {
"Add parentheses to `super` call".to_string()
}
}
/// PLW0245
pub(crate) fn super_without_brackets(checker: &mut Checker, func: &Expr) {
// The call must be to `super` (without parentheses).
let Expr::Attribute(ast::ExprAttribute { value, .. }) = func else {
return;
};
let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() else {
return;
};
if id.as_str() != "super" {
return;
}
if !checker.semantic().is_builtin(id.as_str()) {
return;
}
let scope = checker.semantic().current_scope();
// The current scope _must_ be a function.
let ScopeKind::Function(function_def) = scope.kind else {
return;
};
let Some(parent) = &checker.semantic().first_non_type_parent_scope(scope) else {
return;
};
// The function must be a method, class method, or static method.
let classification = function_type::classify(
&function_def.name,
&function_def.decorator_list,
parent,
checker.semantic(),
&checker.settings.pep8_naming.classmethod_decorators,
&checker.settings.pep8_naming.staticmethod_decorators,
);
if !matches!(
classification,
function_type::FunctionType::Method { .. }
| function_type::FunctionType::ClassMethod { .. }
| function_type::FunctionType::StaticMethod { .. }
) {
return;
}
let mut diagnostic = Diagnostic::new(SuperWithoutBrackets, value.range());
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
"super()".to_string(),
value.range(),
)));
checker.diagnostics.push(diagnostic);
}

View file

@ -0,0 +1,24 @@
---
source: crates/ruff_linter/src/rules/pylint/mod.rs
---
super_without_brackets.py:10:26: PLW0245 [*] `super` call is missing parentheses
|
8 | @staticmethod
9 | def speak():
10 | original_speak = super.speak() # PLW0245
| ^^^^^ PLW0245
11 | return f"{original_speak} But as a dog, it barks!"
|
= help: Add parentheses to `super` call
Safe fix
7 7 | class BadDog(Animal):
8 8 | @staticmethod
9 9 | def speak():
10 |- original_speak = super.speak() # PLW0245
10 |+ original_speak = super().speak() # PLW0245
11 11 | return f"{original_speak} But as a dog, it barks!"
12 12 |
13 13 |

3
ruff.schema.json generated
View file

@ -3225,6 +3225,9 @@
"PLW0129", "PLW0129",
"PLW013", "PLW013",
"PLW0131", "PLW0131",
"PLW02",
"PLW024",
"PLW0245",
"PLW04", "PLW04",
"PLW040", "PLW040",
"PLW0406", "PLW0406",