Skip to content

Commit 36d80ed

Browse files
committed
FURB180: add list of base class exceptions
1 parent c6e55f6 commit 36d80ed

File tree

8 files changed

+228
-52
lines changed

8 files changed

+228
-52
lines changed

crates/ruff_linter/resources/test/fixtures/refurb/FURB180.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import abc
2+
import typing
3+
import typing_extensions
24
from abc import abstractmethod, ABCMeta
5+
from typing import Protocol as TProtocol
6+
from typing_extensions import Protocol as TEProtocol
37

48

59
# Errors
@@ -32,6 +36,14 @@ class A3(B0, before_metaclass=1, metaclass=abc.ABCMeta):
3236
pass
3337

3438

39+
class Protocol:
40+
pass
41+
42+
43+
class C1(Protocol, metaclass=ABCMeta):
44+
pass
45+
46+
3547
# OK
3648

3749
class Meta(type):
@@ -56,3 +68,23 @@ def foo(self): pass
5668
class A7(B0, abc.ABC, B1):
5769
@abstractmethod
5870
def foo(self): pass
71+
72+
73+
class A8(typing.Protocol, metaclass=ABCMeta):
74+
@abstractmethod
75+
def foo(self): pass
76+
77+
78+
class A9(typing_extensions.Protocol, metaclass=ABCMeta):
79+
@abstractmethod
80+
def foo(self): pass
81+
82+
83+
class A10(TProtocol, metaclass=ABCMeta):
84+
@abstractmethod
85+
def foo(self): pass
86+
87+
88+
class A11(TEProtocol, metaclass=ABCMeta):
89+
@abstractmethod
90+
def foo(self): pass

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
mod helpers;
44
pub(crate) mod rules;
5+
pub mod settings;
56

67
#[cfg(test)]
78
mod tests {

crates/ruff_linter/src/rules/refurb/rules/metaclass_abcmeta.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use itertools::Itertools;
2+
use rustc_hash::FxHashSet;
23

34
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
45
use ruff_macros::{ViolationMetadata, derive_message_formats};
@@ -31,6 +32,9 @@ use crate::importer::ImportRequest;
3132
/// pass
3233
/// ```
3334
///
35+
/// ## Options
36+
/// - `lint.refurb.allow-abc-meta-bases`
37+
///
3438
/// ## References
3539
/// - [Python documentation: `abc.ABC`](https://docs.python.org/3/library/abc.html#abc.ABC)
3640
/// - [Python documentation: `abc.ABCMeta`](https://docs.python.org/3/library/abc.html#abc.ABCMeta)
@@ -69,6 +73,23 @@ pub(crate) fn metaclass_abcmeta(checker: &Checker, class_def: &StmtClassDef) {
6973
return;
7074
}
7175

76+
// Determine if all base classes are in the configured list of exceptions.
77+
let bases: Option<FxHashSet<String>> = class_def
78+
.bases()
79+
.iter()
80+
.map(|base| {
81+
checker
82+
.semantic()
83+
.resolve_qualified_name(base)
84+
.map(|qualified_name| qualified_name.to_string())
85+
})
86+
.collect();
87+
if let Some(bases) = &bases {
88+
if !bases.is_empty() && bases.is_subset(&checker.settings.refurb.allow_abc_meta_bases) {
89+
return;
90+
}
91+
}
92+
7293
let mut diagnostic = Diagnostic::new(MetaClassABCMeta, keyword.range);
7394

7495
diagnostic.try_set_fix(|| {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//! Settings for the `refurb` plugin.
2+
3+
use std::fmt;
4+
5+
use ruff_macros::CacheKey;
6+
use rustc_hash::FxHashSet;
7+
8+
use crate::display_settings;
9+
10+
pub fn default_allow_abc_meta_bases() -> FxHashSet<String> {
11+
["typing.Protocol", "typing_extensions.Protocol"]
12+
.into_iter()
13+
.map(ToString::to_string)
14+
.collect()
15+
}
16+
17+
#[derive(Debug, Clone, CacheKey)]
18+
pub struct Settings {
19+
pub allow_abc_meta_bases: FxHashSet<String>,
20+
}
21+
22+
impl Default for Settings {
23+
fn default() -> Self {
24+
Self {
25+
allow_abc_meta_bases: default_allow_abc_meta_bases(),
26+
}
27+
}
28+
}
29+
30+
impl fmt::Display for Settings {
31+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32+
display_settings! {
33+
formatter = f,
34+
namespace = "linter.refurb",
35+
fields = [
36+
self.allow_abc_meta_bases | set
37+
]
38+
}
39+
Ok(())
40+
}
41+
}
Lines changed: 67 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,97 @@
11
---
22
source: crates/ruff_linter/src/rules/refurb/mod.rs
33
---
4-
FURB180.py:7:10: FURB180 [*] Use of `metaclass=abc.ABCMeta` to define abstract base class
5-
|
6-
5 | # Errors
7-
6 |
8-
7 | class A0(metaclass=abc.ABCMeta):
9-
| ^^^^^^^^^^^^^^^^^^^^^ FURB180
10-
8 | @abstractmethod
11-
9 | def foo(self): pass
12-
|
13-
= help: Replace with `abc.ABC`
4+
FURB180.py:11:10: FURB180 [*] Use of `metaclass=abc.ABCMeta` to define abstract base class
5+
|
6+
9 | # Errors
7+
10 |
8+
11 | class A0(metaclass=abc.ABCMeta):
9+
| ^^^^^^^^^^^^^^^^^^^^^ FURB180
10+
12 | @abstractmethod
11+
13 | def foo(self): pass
12+
|
13+
= help: Replace with `abc.ABC`
1414

1515
Safe fix
16-
4 4 |
17-
5 5 | # Errors
18-
6 6 |
19-
7 |-class A0(metaclass=abc.ABCMeta):
20-
7 |+class A0(abc.ABC):
21-
8 8 | @abstractmethod
22-
9 9 | def foo(self): pass
16+
8 8 |
17+
9 9 | # Errors
2318
10 10 |
19+
11 |-class A0(metaclass=abc.ABCMeta):
20+
11 |+class A0(abc.ABC):
21+
12 12 | @abstractmethod
22+
13 13 | def foo(self): pass
23+
14 14 |
2424

25-
FURB180.py:12:10: FURB180 [*] Use of `metaclass=abc.ABCMeta` to define abstract base class
25+
FURB180.py:16:10: FURB180 [*] Use of `metaclass=abc.ABCMeta` to define abstract base class
2626
|
27-
12 | class A1(metaclass=ABCMeta):
27+
16 | class A1(metaclass=ABCMeta):
2828
| ^^^^^^^^^^^^^^^^^ FURB180
29-
13 | @abstractmethod
30-
14 | def foo(self): pass
29+
17 | @abstractmethod
30+
18 | def foo(self): pass
3131
|
3232
= help: Replace with `abc.ABC`
3333

3434
Safe fix
35-
9 9 | def foo(self): pass
36-
10 10 |
37-
11 11 |
38-
12 |-class A1(metaclass=ABCMeta):
39-
12 |+class A1(abc.ABC):
40-
13 13 | @abstractmethod
41-
14 14 | def foo(self): pass
35+
13 13 | def foo(self): pass
36+
14 14 |
4237
15 15 |
38+
16 |-class A1(metaclass=ABCMeta):
39+
16 |+class A1(abc.ABC):
40+
17 17 | @abstractmethod
41+
18 18 | def foo(self): pass
42+
19 19 |
4343

44-
FURB180.py:26:18: FURB180 [*] Use of `metaclass=abc.ABCMeta` to define abstract base class
44+
FURB180.py:30:18: FURB180 [*] Use of `metaclass=abc.ABCMeta` to define abstract base class
4545
|
46-
26 | class A2(B0, B1, metaclass=ABCMeta):
46+
30 | class A2(B0, B1, metaclass=ABCMeta):
4747
| ^^^^^^^^^^^^^^^^^ FURB180
48-
27 | @abstractmethod
49-
28 | def foo(self): pass
48+
31 | @abstractmethod
49+
32 | def foo(self): pass
5050
|
5151
= help: Replace with `abc.ABC`
5252

5353
Safe fix
54-
23 23 | pass
55-
24 24 |
56-
25 25 |
57-
26 |-class A2(B0, B1, metaclass=ABCMeta):
58-
26 |+class A2(B0, B1, abc.ABC):
59-
27 27 | @abstractmethod
60-
28 28 | def foo(self): pass
54+
27 27 | pass
55+
28 28 |
6156
29 29 |
57+
30 |-class A2(B0, B1, metaclass=ABCMeta):
58+
30 |+class A2(B0, B1, abc.ABC):
59+
31 31 | @abstractmethod
60+
32 32 | def foo(self): pass
61+
33 33 |
6262

63-
FURB180.py:31:34: FURB180 [*] Use of `metaclass=abc.ABCMeta` to define abstract base class
63+
FURB180.py:35:34: FURB180 [*] Use of `metaclass=abc.ABCMeta` to define abstract base class
6464
|
65-
31 | class A3(B0, before_metaclass=1, metaclass=abc.ABCMeta):
65+
35 | class A3(B0, before_metaclass=1, metaclass=abc.ABCMeta):
6666
| ^^^^^^^^^^^^^^^^^^^^^ FURB180
67-
32 | pass
67+
36 | pass
6868
|
6969
= help: Replace with `abc.ABC`
7070

7171
Safe fix
72-
28 28 | def foo(self): pass
73-
29 29 |
74-
30 30 |
75-
31 |-class A3(B0, before_metaclass=1, metaclass=abc.ABCMeta):
76-
31 |+class A3(B0, abc.ABC, before_metaclass=1):
77-
32 32 | pass
72+
32 32 | def foo(self): pass
7873
33 33 |
79-
34 34 |
74+
34 34 |
75+
35 |-class A3(B0, before_metaclass=1, metaclass=abc.ABCMeta):
76+
35 |+class A3(B0, abc.ABC, before_metaclass=1):
77+
36 36 | pass
78+
37 37 |
79+
38 38 |
80+
81+
FURB180.py:43:20: FURB180 [*] Use of `metaclass=abc.ABCMeta` to define abstract base class
82+
|
83+
43 | class C1(Protocol, metaclass=ABCMeta):
84+
| ^^^^^^^^^^^^^^^^^ FURB180
85+
44 | pass
86+
|
87+
= help: Replace with `abc.ABC`
88+
89+
Safe fix
90+
40 40 | pass
91+
41 41 |
92+
42 42 |
93+
43 |-class C1(Protocol, metaclass=ABCMeta):
94+
43 |+class C1(Protocol, abc.ABC):
95+
44 44 | pass
96+
45 45 |
97+
46 46 |

crates/ruff_linter/src/settings/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use crate::rules::{
2020
flake8_comprehensions, flake8_copyright, flake8_errmsg, flake8_gettext,
2121
flake8_implicit_str_concat, flake8_import_conventions, flake8_pytest_style, flake8_quotes,
2222
flake8_self, flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, isort, mccabe,
23-
pep8_naming, pycodestyle, pydoclint, pydocstyle, pyflakes, pylint, pyupgrade, ruff,
23+
pep8_naming, pycodestyle, pydoclint, pydocstyle, pyflakes, pylint, pyupgrade, refurb, ruff,
2424
};
2525
use crate::settings::types::{CompiledPerFileIgnoreList, ExtensionMapping, FilePatternSet};
2626
use crate::{RuleSelector, codes, fs};
@@ -279,6 +279,7 @@ pub struct LinterSettings {
279279
pub pyflakes: pyflakes::settings::Settings,
280280
pub pylint: pylint::settings::Settings,
281281
pub pyupgrade: pyupgrade::settings::Settings,
282+
pub refurb: refurb::settings::Settings,
282283
pub ruff: ruff::settings::Settings,
283284
}
284285

@@ -448,6 +449,7 @@ impl LinterSettings {
448449
pyflakes: pyflakes::settings::Settings::default(),
449450
pylint: pylint::settings::Settings::default(),
450451
pyupgrade: pyupgrade::settings::Settings::default(),
452+
refurb: refurb::settings::Settings::default(),
451453
ruff: ruff::settings::Settings::default(),
452454
preview: PreviewMode::default(),
453455
explicit_preview_rules: false,

crates/ruff_workspace/src/configuration.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ use crate::options::{
5353
Flake8QuotesOptions, Flake8SelfOptions, Flake8TidyImportsOptions, Flake8TypeCheckingOptions,
5454
Flake8UnusedArgumentsOptions, FormatOptions, IsortOptions, LintCommonOptions, LintOptions,
5555
McCabeOptions, Options, Pep8NamingOptions, PyUpgradeOptions, PycodestyleOptions,
56-
PydoclintOptions, PydocstyleOptions, PyflakesOptions, PylintOptions, RuffOptions,
56+
PydoclintOptions, PydocstyleOptions, PyflakesOptions, PylintOptions, RefurbOptions,
57+
RuffOptions,
5758
};
5859
use crate::settings::{
5960
EXCLUDE, FileResolverSettings, FormatterSettings, INCLUDE, LineEnding, Settings,
@@ -428,6 +429,10 @@ impl Configuration {
428429
.pyupgrade
429430
.map(PyUpgradeOptions::into_settings)
430431
.unwrap_or_default(),
432+
refurb: lint
433+
.refurb
434+
.map(RefurbOptions::into_settings)
435+
.unwrap_or_default(),
431436
ruff: lint
432437
.ruff
433438
.map(RuffOptions::into_settings)
@@ -665,6 +670,7 @@ pub struct LintConfiguration {
665670
pub pyflakes: Option<PyflakesOptions>,
666671
pub pylint: Option<PylintOptions>,
667672
pub pyupgrade: Option<PyUpgradeOptions>,
673+
pub refurb: Option<RefurbOptions>,
668674
pub ruff: Option<RuffOptions>,
669675
}
670676

@@ -781,6 +787,7 @@ impl LintConfiguration {
781787
pyflakes: options.common.pyflakes,
782788
pylint: options.common.pylint,
783789
pyupgrade: options.common.pyupgrade,
790+
refurb: options.refurb,
784791
ruff: options.ruff,
785792
})
786793
}
@@ -1178,6 +1185,7 @@ impl LintConfiguration {
11781185
pyflakes: self.pyflakes.combine(config.pyflakes),
11791186
pylint: self.pylint.combine(config.pylint),
11801187
pyupgrade: self.pyupgrade.combine(config.pyupgrade),
1188+
refurb: self.refurb.combine(config.refurb),
11811189
ruff: self.ruff.combine(config.ruff),
11821190
typing_extensions: self.typing_extensions.or(config.typing_extensions),
11831191
}

0 commit comments

Comments
 (0)