mirror of https://github.com/astral-sh/ruff
[`flake8-pyi`] Implement `PYI013` (#4517)
This commit is contained in:
parent
7ebe372122
commit
837e70677b
|
|
@ -0,0 +1,65 @@
|
|||
class OneAttributeClass:
|
||||
value: int
|
||||
...
|
||||
|
||||
|
||||
class OneAttributeClass2:
|
||||
...
|
||||
value: int
|
||||
|
||||
|
||||
class TwoEllipsesClass:
|
||||
...
|
||||
...
|
||||
|
||||
|
||||
class DocstringClass:
|
||||
"""
|
||||
My body only contains an ellipsis.
|
||||
"""
|
||||
|
||||
...
|
||||
|
||||
|
||||
class NonEmptyChild(Exception):
|
||||
value: int
|
||||
...
|
||||
|
||||
|
||||
class NonEmptyChild2(Exception):
|
||||
...
|
||||
value: int
|
||||
|
||||
|
||||
class NonEmptyWithInit:
|
||||
value: int
|
||||
...
|
||||
|
||||
def __init__():
|
||||
pass
|
||||
|
||||
|
||||
class EmptyClass:
|
||||
...
|
||||
|
||||
|
||||
class EmptyEllipsis:
|
||||
...
|
||||
|
||||
|
||||
class Dog:
|
||||
eyes: int = 2
|
||||
|
||||
|
||||
class WithInit:
|
||||
value: int = 0
|
||||
|
||||
def __init__():
|
||||
...
|
||||
|
||||
|
||||
def function():
|
||||
...
|
||||
|
||||
|
||||
...
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
# Violations of PYI013
|
||||
|
||||
class OneAttributeClass:
|
||||
value: int
|
||||
... # Error
|
||||
|
||||
class OneAttributeClass2:
|
||||
... # Error
|
||||
value: int
|
||||
|
||||
class MyClass:
|
||||
...
|
||||
value: int
|
||||
|
||||
class TwoEllipsesClass:
|
||||
...
|
||||
... # Error
|
||||
|
||||
class DocstringClass:
|
||||
"""
|
||||
My body only contains an ellipsis.
|
||||
"""
|
||||
|
||||
... # Error
|
||||
|
||||
class NonEmptyChild(Exception):
|
||||
value: int
|
||||
... # Error
|
||||
|
||||
class NonEmptyChild2(Exception):
|
||||
... # Error
|
||||
value: int
|
||||
|
||||
class NonEmptyWithInit:
|
||||
value: int
|
||||
... # Error
|
||||
|
||||
def __init__():
|
||||
pass
|
||||
|
||||
# Not violations
|
||||
|
||||
class EmptyClass: ...
|
||||
class EmptyEllipsis: ...
|
||||
|
||||
class Dog:
|
||||
eyes: int = 2
|
||||
|
||||
class WithInit:
|
||||
value: int = 0
|
||||
|
||||
def __init__(): ...
|
||||
|
||||
def function(): ...
|
||||
|
||||
...
|
||||
|
|
@ -742,6 +742,13 @@ where
|
|||
if self.settings.rules.enabled(Rule::PassInClassBody) {
|
||||
flake8_pyi::rules::pass_in_class_body(self, stmt, body);
|
||||
}
|
||||
if self
|
||||
.settings
|
||||
.rules
|
||||
.enabled(Rule::EllipsisInNonEmptyClassBody)
|
||||
{
|
||||
flake8_pyi::rules::ellipsis_in_non_empty_class_body(self, stmt, body);
|
||||
}
|
||||
}
|
||||
|
||||
if self
|
||||
|
|
|
|||
|
|
@ -582,6 +582,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
|||
(Flake8Pyi, "010") => (RuleGroup::Unspecified, Rule::NonEmptyStubBody),
|
||||
(Flake8Pyi, "011") => (RuleGroup::Unspecified, Rule::TypedArgumentDefaultInStub),
|
||||
(Flake8Pyi, "012") => (RuleGroup::Unspecified, Rule::PassInClassBody),
|
||||
(Flake8Pyi, "013") => (RuleGroup::Unspecified, Rule::EllipsisInNonEmptyClassBody),
|
||||
(Flake8Pyi, "014") => (RuleGroup::Unspecified, Rule::ArgumentDefaultInStub),
|
||||
(Flake8Pyi, "015") => (RuleGroup::Unspecified, Rule::AssignmentDefaultInStub),
|
||||
(Flake8Pyi, "016") => (RuleGroup::Unspecified, Rule::DuplicateUnionMember),
|
||||
|
|
|
|||
|
|
@ -512,6 +512,7 @@ ruff_macros::register_rules!(
|
|||
rules::flake8_pyi::rules::BadVersionInfoComparison,
|
||||
rules::flake8_pyi::rules::DocstringInStub,
|
||||
rules::flake8_pyi::rules::DuplicateUnionMember,
|
||||
rules::flake8_pyi::rules::EllipsisInNonEmptyClassBody,
|
||||
rules::flake8_pyi::rules::NonEmptyStubBody,
|
||||
rules::flake8_pyi::rules::PassInClassBody,
|
||||
rules::flake8_pyi::rules::PassStatementStubBody,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ mod tests {
|
|||
#[test_case(Rule::DocstringInStub, Path::new("PYI021.pyi"))]
|
||||
#[test_case(Rule::DuplicateUnionMember, Path::new("PYI016.py"))]
|
||||
#[test_case(Rule::DuplicateUnionMember, Path::new("PYI016.pyi"))]
|
||||
#[test_case(Rule::EllipsisInNonEmptyClassBody, Path::new("PYI013.py"))]
|
||||
#[test_case(Rule::EllipsisInNonEmptyClassBody, Path::new("PYI013.pyi"))]
|
||||
#[test_case(Rule::NonEmptyStubBody, Path::new("PYI010.py"))]
|
||||
#[test_case(Rule::NonEmptyStubBody, Path::new("PYI010.pyi"))]
|
||||
#[test_case(Rule::PassInClassBody, Path::new("PYI012.py"))]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
use rustpython_parser::ast::{Expr, ExprConstant, Ranged, Stmt, StmtExpr};
|
||||
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::types::RefEquality;
|
||||
|
||||
use crate::autofix::actions::delete_stmt;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
/// ## What it does
|
||||
/// Removes ellipses (`...`) in otherwise non-empty class bodies.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// An ellipsis in a class body is only necessary if the class body is
|
||||
/// otherwise empty. If the class body is non-empty, then the ellipsis
|
||||
/// is redundant.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// class Foo:
|
||||
/// ...
|
||||
/// value: int
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// class Foo:
|
||||
/// value: int
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct EllipsisInNonEmptyClassBody;
|
||||
|
||||
impl Violation for EllipsisInNonEmptyClassBody {
|
||||
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Non-empty class body must not contain `...`")
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> Option<String> {
|
||||
Some("Remove unnecessary `...`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PYI013
|
||||
pub(crate) fn ellipsis_in_non_empty_class_body<'a>(
|
||||
checker: &mut Checker<'a>,
|
||||
parent: &'a Stmt,
|
||||
body: &'a [Stmt],
|
||||
) {
|
||||
// If the class body contains a single statement, then it's fine for it to be an ellipsis.
|
||||
if body.len() == 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
for stmt in body {
|
||||
if let Stmt::Expr(StmtExpr { value, .. }) = &stmt {
|
||||
if let Expr::Constant(ExprConstant { value, .. }) = value.as_ref() {
|
||||
if value.is_ellipsis() {
|
||||
let mut diagnostic = Diagnostic::new(EllipsisInNonEmptyClassBody, stmt.range());
|
||||
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
let deleted: Vec<&Stmt> =
|
||||
checker.deletions.iter().map(Into::into).collect();
|
||||
let edit = delete_stmt(
|
||||
stmt,
|
||||
Some(parent),
|
||||
&deleted,
|
||||
checker.locator,
|
||||
checker.indexer,
|
||||
checker.stylist,
|
||||
)?;
|
||||
|
||||
// In the unlikely event the class body consists solely of several
|
||||
// consecutive ellipses, `delete_stmt` can actually result in a
|
||||
// `pass`.
|
||||
if edit.is_deletion() || edit.content() == Some("pass") {
|
||||
checker.deletions.insert(RefEquality(stmt));
|
||||
}
|
||||
|
||||
Ok(Fix::automatic(edit))
|
||||
});
|
||||
}
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,9 @@ pub(crate) use bad_version_info_comparison::{
|
|||
};
|
||||
pub(crate) use docstring_in_stubs::{docstring_in_stubs, DocstringInStub};
|
||||
pub(crate) use duplicate_union_member::{duplicate_union_member, DuplicateUnionMember};
|
||||
pub(crate) use ellipsis_in_non_empty_class_body::{
|
||||
ellipsis_in_non_empty_class_body, EllipsisInNonEmptyClassBody,
|
||||
};
|
||||
pub(crate) use non_empty_stub_body::{non_empty_stub_body, NonEmptyStubBody};
|
||||
pub(crate) use pass_in_class_body::{pass_in_class_body, PassInClassBody};
|
||||
pub(crate) use pass_statement_stub_body::{pass_statement_stub_body, PassStatementStubBody};
|
||||
|
|
@ -24,6 +27,7 @@ pub(crate) use unrecognized_platform::{
|
|||
mod bad_version_info_comparison;
|
||||
mod docstring_in_stubs;
|
||||
mod duplicate_union_member;
|
||||
mod ellipsis_in_non_empty_class_body;
|
||||
mod non_empty_stub_body;
|
||||
mod pass_in_class_body;
|
||||
mod pass_statement_stub_body;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||
---
|
||||
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
---
|
||||
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||
---
|
||||
PYI013.pyi:5:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
|
|
||||
5 | class OneAttributeClass:
|
||||
6 | value: int
|
||||
7 | ... # Error
|
||||
| ^^^ PYI013
|
||||
8 |
|
||||
9 | class OneAttributeClass2:
|
||||
|
|
||||
= help: Remove unnecessary `...`
|
||||
|
||||
ℹ Fix
|
||||
2 2 |
|
||||
3 3 | class OneAttributeClass:
|
||||
4 4 | value: int
|
||||
5 |- ... # Error
|
||||
6 5 |
|
||||
7 6 | class OneAttributeClass2:
|
||||
8 7 | ... # Error
|
||||
|
||||
PYI013.pyi:8:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
|
|
||||
8 | class OneAttributeClass2:
|
||||
9 | ... # Error
|
||||
| ^^^ PYI013
|
||||
10 | value: int
|
||||
|
|
||||
= help: Remove unnecessary `...`
|
||||
|
||||
ℹ Fix
|
||||
5 5 | ... # Error
|
||||
6 6 |
|
||||
7 7 | class OneAttributeClass2:
|
||||
8 |- ... # Error
|
||||
9 8 | value: int
|
||||
10 9 |
|
||||
11 10 | class MyClass:
|
||||
|
||||
PYI013.pyi:12:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
|
|
||||
12 | class MyClass:
|
||||
13 | ...
|
||||
| ^^^ PYI013
|
||||
14 | value: int
|
||||
|
|
||||
= help: Remove unnecessary `...`
|
||||
|
||||
ℹ Fix
|
||||
9 9 | value: int
|
||||
10 10 |
|
||||
11 11 | class MyClass:
|
||||
12 |- ...
|
||||
13 12 | value: int
|
||||
14 13 |
|
||||
15 14 | class TwoEllipsesClass:
|
||||
|
||||
PYI013.pyi:16:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
|
|
||||
16 | class TwoEllipsesClass:
|
||||
17 | ...
|
||||
| ^^^ PYI013
|
||||
18 | ... # Error
|
||||
|
|
||||
= help: Remove unnecessary `...`
|
||||
|
||||
ℹ Fix
|
||||
13 13 | value: int
|
||||
14 14 |
|
||||
15 15 | class TwoEllipsesClass:
|
||||
16 |- ...
|
||||
17 16 | ... # Error
|
||||
18 17 |
|
||||
19 18 | class DocstringClass:
|
||||
|
||||
PYI013.pyi:17:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
|
|
||||
17 | class TwoEllipsesClass:
|
||||
18 | ...
|
||||
19 | ... # Error
|
||||
| ^^^ PYI013
|
||||
20 |
|
||||
21 | class DocstringClass:
|
||||
|
|
||||
= help: Remove unnecessary `...`
|
||||
|
||||
ℹ Fix
|
||||
14 14 |
|
||||
15 15 | class TwoEllipsesClass:
|
||||
16 16 | ...
|
||||
17 |- ... # Error
|
||||
17 |+ pass # Error
|
||||
18 18 |
|
||||
19 19 | class DocstringClass:
|
||||
20 20 | """
|
||||
|
||||
PYI013.pyi:24:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
|
|
||||
24 | """
|
||||
25 |
|
||||
26 | ... # Error
|
||||
| ^^^ PYI013
|
||||
27 |
|
||||
28 | class NonEmptyChild(Exception):
|
||||
|
|
||||
= help: Remove unnecessary `...`
|
||||
|
||||
ℹ Fix
|
||||
21 21 | My body only contains an ellipsis.
|
||||
22 22 | """
|
||||
23 23 |
|
||||
24 |- ... # Error
|
||||
25 24 |
|
||||
26 25 | class NonEmptyChild(Exception):
|
||||
27 26 | value: int
|
||||
|
||||
PYI013.pyi:28:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
|
|
||||
28 | class NonEmptyChild(Exception):
|
||||
29 | value: int
|
||||
30 | ... # Error
|
||||
| ^^^ PYI013
|
||||
31 |
|
||||
32 | class NonEmptyChild2(Exception):
|
||||
|
|
||||
= help: Remove unnecessary `...`
|
||||
|
||||
ℹ Fix
|
||||
25 25 |
|
||||
26 26 | class NonEmptyChild(Exception):
|
||||
27 27 | value: int
|
||||
28 |- ... # Error
|
||||
29 28 |
|
||||
30 29 | class NonEmptyChild2(Exception):
|
||||
31 30 | ... # Error
|
||||
|
||||
PYI013.pyi:31:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
|
|
||||
31 | class NonEmptyChild2(Exception):
|
||||
32 | ... # Error
|
||||
| ^^^ PYI013
|
||||
33 | value: int
|
||||
|
|
||||
= help: Remove unnecessary `...`
|
||||
|
||||
ℹ Fix
|
||||
28 28 | ... # Error
|
||||
29 29 |
|
||||
30 30 | class NonEmptyChild2(Exception):
|
||||
31 |- ... # Error
|
||||
32 31 | value: int
|
||||
33 32 |
|
||||
34 33 | class NonEmptyWithInit:
|
||||
|
||||
PYI013.pyi:36:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
|
|
||||
36 | class NonEmptyWithInit:
|
||||
37 | value: int
|
||||
38 | ... # Error
|
||||
| ^^^ PYI013
|
||||
39 |
|
||||
40 | def __init__():
|
||||
|
|
||||
= help: Remove unnecessary `...`
|
||||
|
||||
ℹ Fix
|
||||
33 33 |
|
||||
34 34 | class NonEmptyWithInit:
|
||||
35 35 | value: int
|
||||
36 |- ... # Error
|
||||
37 36 |
|
||||
38 37 | def __init__():
|
||||
39 38 | pass
|
||||
|
||||
|
||||
|
|
@ -2159,6 +2159,7 @@
|
|||
"PYI010",
|
||||
"PYI011",
|
||||
"PYI012",
|
||||
"PYI013",
|
||||
"PYI014",
|
||||
"PYI015",
|
||||
"PYI016",
|
||||
|
|
|
|||
Loading…
Reference in New Issue