[pylint] Add duplicate-bases rule (#4411)
This commit is contained in:
24
crates/ruff/resources/test/fixtures/pylint/duplicate_bases.py
vendored
Normal file
24
crates/ruff/resources/test/fixtures/pylint/duplicate_bases.py
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
###
|
||||
# Errors.
|
||||
###
|
||||
class A:
|
||||
...
|
||||
|
||||
|
||||
class B(A, A):
|
||||
...
|
||||
|
||||
|
||||
###
|
||||
# Non-errors.
|
||||
###
|
||||
class C:
|
||||
...
|
||||
|
||||
|
||||
class D(C):
|
||||
...
|
||||
|
||||
|
||||
class E(A, C):
|
||||
...
|
||||
@@ -761,6 +761,10 @@ where
|
||||
if self.settings.rules.enabled(Rule::BuiltinVariableShadowing) {
|
||||
flake8_builtins::rules::builtin_variable_shadowing(self, name, stmt);
|
||||
}
|
||||
|
||||
if self.settings.rules.enabled(Rule::DuplicateBases) {
|
||||
pylint::rules::duplicate_bases(self, name, bases);
|
||||
}
|
||||
}
|
||||
StmtKind::Import(ast::StmtImport { names }) => {
|
||||
if self.settings.rules.enabled(Rule::MultipleImportsOnOneLine) {
|
||||
|
||||
@@ -213,6 +213,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||
(Pylint, "W2901") => Rule::RedefinedLoopName,
|
||||
(Pylint, "E0302") => Rule::UnexpectedSpecialMethodSignature,
|
||||
(Pylint, "W3301") => Rule::NestedMinMax,
|
||||
(Pylint, "E0241") => Rule::DuplicateBases,
|
||||
|
||||
// flake8-builtins
|
||||
(Flake8Builtins, "001") => Rule::BuiltinVariableShadowing,
|
||||
|
||||
@@ -190,6 +190,7 @@ ruff_macros::register_rules!(
|
||||
rules::pylint::rules::LoggingTooManyArgs,
|
||||
rules::pylint::rules::UnexpectedSpecialMethodSignature,
|
||||
rules::pylint::rules::NestedMinMax,
|
||||
rules::pylint::rules::DuplicateBases,
|
||||
// flake8-builtins
|
||||
rules::flake8_builtins::rules::BuiltinVariableShadowing,
|
||||
rules::flake8_builtins::rules::BuiltinArgumentShadowing,
|
||||
|
||||
@@ -46,6 +46,7 @@ mod tests {
|
||||
#[test_case(Rule::ImportSelf, Path::new("import_self/module.py"); "PLW0406")]
|
||||
#[test_case(Rule::InvalidAllFormat, Path::new("invalid_all_format.py"); "PLE0605")]
|
||||
#[test_case(Rule::InvalidAllObject, Path::new("invalid_all_object.py"); "PLE0604")]
|
||||
#[test_case(Rule::DuplicateBases, Path::new("duplicate_bases.py"); "PLE0241")]
|
||||
#[test_case(Rule::InvalidCharacterBackspace, Path::new("invalid_characters.py"); "PLE2510")]
|
||||
#[test_case(Rule::InvalidCharacterEsc, Path::new("invalid_characters.py"); "PLE2513")]
|
||||
#[test_case(Rule::InvalidCharacterNul, Path::new("invalid_characters.py"); "PLE2514")]
|
||||
|
||||
42
crates/ruff/src/rules/pylint/rules/duplicate_bases.rs
Normal file
42
crates/ruff/src/rules/pylint/rules/duplicate_bases.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use std::hash::BuildHasherDefault;
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
use rustpython_parser::ast::{self, Expr, ExprKind, Identifier};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
#[violation]
|
||||
pub struct DuplicateBases {
|
||||
base: String,
|
||||
class: String,
|
||||
}
|
||||
|
||||
impl Violation for DuplicateBases {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let DuplicateBases { base, class } = self;
|
||||
format!("Duplicate base `{base}` for class `{class}`")
|
||||
}
|
||||
}
|
||||
|
||||
/// PLE0241
|
||||
pub(crate) fn duplicate_bases(checker: &mut Checker, name: &str, bases: &[Expr]) {
|
||||
let mut seen: FxHashSet<&Identifier> =
|
||||
FxHashSet::with_capacity_and_hasher(bases.len(), BuildHasherDefault::default());
|
||||
for base in bases {
|
||||
if let ExprKind::Name(ast::ExprName { id, .. }) = &base.node {
|
||||
if !seen.insert(id) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
DuplicateBases {
|
||||
base: id.to_string(),
|
||||
class: name.to_string(),
|
||||
},
|
||||
base.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ pub(crate) use collapsible_else_if::{collapsible_else_if, CollapsibleElseIf};
|
||||
pub(crate) use compare_to_empty_string::{compare_to_empty_string, CompareToEmptyString};
|
||||
pub(crate) use comparison_of_constant::{comparison_of_constant, ComparisonOfConstant};
|
||||
pub(crate) use continue_in_finally::{continue_in_finally, ContinueInFinally};
|
||||
pub(crate) use duplicate_bases::{duplicate_bases, DuplicateBases};
|
||||
pub(crate) use global_statement::{global_statement, GlobalStatement};
|
||||
pub(crate) use global_variable_not_assigned::GlobalVariableNotAssigned;
|
||||
pub(crate) use import_self::{import_from_self, import_self, ImportSelf};
|
||||
@@ -57,6 +58,7 @@ mod collapsible_else_if;
|
||||
mod compare_to_empty_string;
|
||||
mod comparison_of_constant;
|
||||
mod continue_in_finally;
|
||||
mod duplicate_bases;
|
||||
mod global_statement;
|
||||
mod global_variable_not_assigned;
|
||||
mod import_self;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/pylint/mod.rs
|
||||
---
|
||||
duplicate_bases.py:8:12: PLE0241 Duplicate base `A` for class `B`
|
||||
|
|
||||
8 | class B(A, A):
|
||||
| ^ PLE0241
|
||||
9 | ...
|
||||
|
|
||||
|
||||
|
||||
3
ruff.schema.json
generated
3
ruff.schema.json
generated
@@ -1959,6 +1959,9 @@
|
||||
"PLE0116",
|
||||
"PLE0117",
|
||||
"PLE0118",
|
||||
"PLE02",
|
||||
"PLE024",
|
||||
"PLE0241",
|
||||
"PLE03",
|
||||
"PLE030",
|
||||
"PLE0302",
|
||||
|
||||
Reference in New Issue
Block a user