Skip to content

Commit

Permalink
[pylint] - implement super-without-brackets/W0245 (#9257)
Browse files Browse the repository at this point in the history
## 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`
  • Loading branch information
diceroll123 authored Jan 2, 2024
1 parent 08c60f5 commit 3fcc140
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions crates/ruff_linter/src/checkers/ast/analyze/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
refurb::rules::bit_count(checker, call);
}
Expand Down
1 change: 1 addition & 0 deletions crates/ruff_linter/src/codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, "W0129") => (RuleGroup::Stable, rules::pylint::rules::AssertOnStringLiteral),
(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, "W0602") => (RuleGroup::Stable, rules::pylint::rules::GlobalVariableNotAssigned),
(Pylint, "W0604") => (RuleGroup::Preview, rules::pylint::rules::GlobalAtModuleLevel),
Expand Down
1 change: 1 addition & 0 deletions crates/ruff_linter/src/rules/pylint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ mod tests {
)]
#[test_case(Rule::NoClassmethodDecorator, 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(
Rule::UnnecessaryDictIndexLookup,
Path::new("unnecessary_dict_index_lookup.py")
Expand Down
2 changes: 2 additions & 0 deletions crates/ruff_linter/src/rules/pylint/rules/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ pub(crate) use self_assigning_variable::*;
pub(crate) use single_string_slots::*;
pub(crate) use subprocess_popen_preexec_fn::*;
pub(crate) use subprocess_run_without_check::*;
pub(crate) use super_without_brackets::*;
pub(crate) use sys_exit_alias::*;
pub(crate) use too_many_arguments::*;
pub(crate) use too_many_boolean_expressions::*;
Expand Down Expand Up @@ -131,6 +132,7 @@ mod self_assigning_variable;
mod single_string_slots;
mod subprocess_popen_preexec_fn;
mod subprocess_run_without_check;
mod super_without_brackets;
mod sys_exit_alias;
mod too_many_arguments;
mod too_many_boolean_expressions;
Expand Down
116 changes: 116 additions & 0 deletions crates/ruff_linter/src/rules/pylint/rules/super_without_brackets.rs
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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 changes: 3 additions & 0 deletions ruff.schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 3fcc140

Please sign in to comment.