mirror of https://github.com/astral-sh/ruff
[`flake8-pyi`] Implement `PYI012` (#3743)
This commit is contained in:
parent
450c6780ff
commit
0eb5a22dd1
|
|
@ -0,0 +1,75 @@
|
||||||
|
# Violations of PYI012
|
||||||
|
|
||||||
|
|
||||||
|
class OneAttributeClass:
|
||||||
|
value: int
|
||||||
|
pass # PYI012 Class body must not contain `pass`
|
||||||
|
|
||||||
|
|
||||||
|
class OneAttributeClassRev:
|
||||||
|
pass # PYI012 Class body must not contain `pass`
|
||||||
|
value: int
|
||||||
|
|
||||||
|
|
||||||
|
class DocstringClass:
|
||||||
|
"""
|
||||||
|
My body only contains pass.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass # PYI012 Class body must not contain `pass`
|
||||||
|
|
||||||
|
|
||||||
|
class NonEmptyChild(Exception):
|
||||||
|
value: int
|
||||||
|
pass # PYI012 Class body must not contain `pass`
|
||||||
|
|
||||||
|
|
||||||
|
class NonEmptyChild2(Exception):
|
||||||
|
pass # PYI012 Class body must not contain `pass`
|
||||||
|
value: int
|
||||||
|
|
||||||
|
|
||||||
|
class NonEmptyWithInit:
|
||||||
|
value: int
|
||||||
|
pass # PYI012 Class body must not contain `pass`
|
||||||
|
|
||||||
|
def __init__():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Not violations (of PYI012)
|
||||||
|
|
||||||
|
|
||||||
|
class EmptyClass:
|
||||||
|
pass # Y009 Empty body should contain `...`, not `pass`
|
||||||
|
|
||||||
|
|
||||||
|
class EmptyOneLine:
|
||||||
|
pass # Y009 Empty body should contain `...`, not `pass`
|
||||||
|
|
||||||
|
|
||||||
|
class Dog:
|
||||||
|
eyes: int = 2
|
||||||
|
|
||||||
|
|
||||||
|
class EmptyEllipsis:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class NonEmptyEllipsis:
|
||||||
|
value: int
|
||||||
|
... # Y013 Non-empty class body must not contain `...`
|
||||||
|
|
||||||
|
|
||||||
|
class WithInit:
|
||||||
|
value: int = 0
|
||||||
|
|
||||||
|
def __init__():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def function():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
# Violations of PYI012
|
||||||
|
|
||||||
|
class OneAttributeClass:
|
||||||
|
value: int
|
||||||
|
pass # PYI012 Class body must not contain `pass`
|
||||||
|
|
||||||
|
class OneAttributeClassRev:
|
||||||
|
pass # PYI012 Class body must not contain `pass`
|
||||||
|
value: int
|
||||||
|
|
||||||
|
class DocstringClass:
|
||||||
|
"""
|
||||||
|
My body only contains pass.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass # PYI012 Class body must not contain `pass`
|
||||||
|
|
||||||
|
class NonEmptyChild(Exception):
|
||||||
|
value: int
|
||||||
|
pass # PYI012 Class body must not contain `pass`
|
||||||
|
|
||||||
|
class NonEmptyChild2(Exception):
|
||||||
|
pass # PYI012 Class body must not contain `pass`
|
||||||
|
value: int
|
||||||
|
|
||||||
|
class NonEmptyWithInit:
|
||||||
|
value: int
|
||||||
|
pass # PYI012 Class body must not contain `pass`
|
||||||
|
|
||||||
|
def __init__():
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Not violations (of PYI012)
|
||||||
|
|
||||||
|
class EmptyClass:
|
||||||
|
pass # Y009 Empty body should contain `...`, not `pass`
|
||||||
|
|
||||||
|
class EmptyOneLine:
|
||||||
|
pass # Y009 Empty body should contain `...`, not `pass`
|
||||||
|
|
||||||
|
class Dog:
|
||||||
|
eyes: int = 2
|
||||||
|
|
||||||
|
class EmptyEllipsis: ...
|
||||||
|
|
||||||
|
class NonEmptyEllipsis:
|
||||||
|
value: int
|
||||||
|
... # Y013 Non-empty class body must not contain `...`
|
||||||
|
|
||||||
|
class WithInit:
|
||||||
|
value: int = 0
|
||||||
|
|
||||||
|
def __init__():
|
||||||
|
pass
|
||||||
|
|
||||||
|
def function():
|
||||||
|
pass
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
@ -775,6 +775,9 @@ where
|
||||||
if self.settings.rules.enabled(Rule::PassStatementStubBody) {
|
if self.settings.rules.enabled(Rule::PassStatementStubBody) {
|
||||||
flake8_pyi::rules::pass_statement_stub_body(self, body);
|
flake8_pyi::rules::pass_statement_stub_body(self, body);
|
||||||
}
|
}
|
||||||
|
if self.settings.rules.enabled(Rule::PassInClassBody) {
|
||||||
|
flake8_pyi::rules::pass_in_class_body(self, stmt, body);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self
|
if self
|
||||||
|
|
|
||||||
|
|
@ -569,6 +569,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||||
(Flake8Pyi, "009") => Rule::PassStatementStubBody,
|
(Flake8Pyi, "009") => Rule::PassStatementStubBody,
|
||||||
(Flake8Pyi, "010") => Rule::NonEmptyStubBody,
|
(Flake8Pyi, "010") => Rule::NonEmptyStubBody,
|
||||||
(Flake8Pyi, "011") => Rule::TypedArgumentDefaultInStub,
|
(Flake8Pyi, "011") => Rule::TypedArgumentDefaultInStub,
|
||||||
|
(Flake8Pyi, "012") => Rule::PassInClassBody,
|
||||||
(Flake8Pyi, "014") => Rule::ArgumentDefaultInStub,
|
(Flake8Pyi, "014") => Rule::ArgumentDefaultInStub,
|
||||||
(Flake8Pyi, "015") => Rule::AssignmentDefaultInStub,
|
(Flake8Pyi, "015") => Rule::AssignmentDefaultInStub,
|
||||||
(Flake8Pyi, "021") => Rule::DocstringInStub,
|
(Flake8Pyi, "021") => Rule::DocstringInStub,
|
||||||
|
|
|
||||||
|
|
@ -525,6 +525,7 @@ ruff_macros::register_rules!(
|
||||||
rules::flake8_pyi::rules::UnprefixedTypeParam,
|
rules::flake8_pyi::rules::UnprefixedTypeParam,
|
||||||
rules::flake8_pyi::rules::UnrecognizedPlatformCheck,
|
rules::flake8_pyi::rules::UnrecognizedPlatformCheck,
|
||||||
rules::flake8_pyi::rules::UnrecognizedPlatformName,
|
rules::flake8_pyi::rules::UnrecognizedPlatformName,
|
||||||
|
rules::flake8_pyi::rules::PassInClassBody,
|
||||||
// flake8-pytest-style
|
// flake8-pytest-style
|
||||||
rules::flake8_pytest_style::rules::PytestFixtureIncorrectParenthesesStyle,
|
rules::flake8_pytest_style::rules::PytestFixtureIncorrectParenthesesStyle,
|
||||||
rules::flake8_pytest_style::rules::PytestFixturePositionalArgs,
|
rules::flake8_pytest_style::rules::PytestFixturePositionalArgs,
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ mod tests {
|
||||||
#[test_case(Rule::NonEmptyStubBody, Path::new("PYI010.pyi"))]
|
#[test_case(Rule::NonEmptyStubBody, Path::new("PYI010.pyi"))]
|
||||||
#[test_case(Rule::TypedArgumentDefaultInStub, Path::new("PYI011.py"))]
|
#[test_case(Rule::TypedArgumentDefaultInStub, Path::new("PYI011.py"))]
|
||||||
#[test_case(Rule::TypedArgumentDefaultInStub, Path::new("PYI011.pyi"))]
|
#[test_case(Rule::TypedArgumentDefaultInStub, Path::new("PYI011.pyi"))]
|
||||||
|
#[test_case(Rule::PassInClassBody, Path::new("PYI012.py"))]
|
||||||
|
#[test_case(Rule::PassInClassBody, Path::new("PYI012.pyi"))]
|
||||||
#[test_case(Rule::ArgumentDefaultInStub, Path::new("PYI014.py"))]
|
#[test_case(Rule::ArgumentDefaultInStub, Path::new("PYI014.py"))]
|
||||||
#[test_case(Rule::ArgumentDefaultInStub, Path::new("PYI014.pyi"))]
|
#[test_case(Rule::ArgumentDefaultInStub, Path::new("PYI014.pyi"))]
|
||||||
#[test_case(Rule::AssignmentDefaultInStub, Path::new("PYI015.py"))]
|
#[test_case(Rule::AssignmentDefaultInStub, Path::new("PYI015.py"))]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
pub use bad_version_info_comparison::{bad_version_info_comparison, BadVersionInfoComparison};
|
pub use bad_version_info_comparison::{bad_version_info_comparison, BadVersionInfoComparison};
|
||||||
pub use docstring_in_stubs::{docstring_in_stubs, DocstringInStub};
|
pub use docstring_in_stubs::{docstring_in_stubs, DocstringInStub};
|
||||||
pub use non_empty_stub_body::{non_empty_stub_body, NonEmptyStubBody};
|
pub use non_empty_stub_body::{non_empty_stub_body, NonEmptyStubBody};
|
||||||
|
pub use pass_in_class_body::{pass_in_class_body, PassInClassBody};
|
||||||
pub use pass_statement_stub_body::{pass_statement_stub_body, PassStatementStubBody};
|
pub use pass_statement_stub_body::{pass_statement_stub_body, PassStatementStubBody};
|
||||||
pub use prefix_type_params::{prefix_type_params, UnprefixedTypeParam};
|
pub use prefix_type_params::{prefix_type_params, UnprefixedTypeParam};
|
||||||
pub use simple_defaults::{
|
pub use simple_defaults::{
|
||||||
|
|
@ -15,6 +16,7 @@ pub use unrecognized_platform::{
|
||||||
mod bad_version_info_comparison;
|
mod bad_version_info_comparison;
|
||||||
mod docstring_in_stubs;
|
mod docstring_in_stubs;
|
||||||
mod non_empty_stub_body;
|
mod non_empty_stub_body;
|
||||||
|
mod pass_in_class_body;
|
||||||
mod pass_statement_stub_body;
|
mod pass_statement_stub_body;
|
||||||
mod prefix_type_params;
|
mod prefix_type_params;
|
||||||
mod simple_defaults;
|
mod simple_defaults;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
use crate::autofix::helpers::delete_stmt;
|
||||||
|
use log::error;
|
||||||
|
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic};
|
||||||
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
use ruff_python_ast::types::{Range, RefEquality};
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
|
||||||
|
use crate::registry::AsRule;
|
||||||
|
use rustpython_parser::ast::{Stmt, StmtKind};
|
||||||
|
|
||||||
|
#[violation]
|
||||||
|
pub struct PassInClassBody;
|
||||||
|
|
||||||
|
impl AlwaysAutofixableViolation for PassInClassBody {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
format!("Class body must not contain `pass`")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn autofix_title(&self) -> String {
|
||||||
|
format!("Remove unnecessary `pass`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PYI012
|
||||||
|
pub fn pass_in_class_body<'a>(checker: &mut Checker<'a>, parent: &'a Stmt, body: &'a [Stmt]) {
|
||||||
|
// `pass` is required in these situations (or handled by `pass_statement_stub_body`).
|
||||||
|
if body.len() < 2 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for stmt in body {
|
||||||
|
if matches!(stmt.node, StmtKind::Pass) {
|
||||||
|
let mut diagnostic = Diagnostic::new(PassInClassBody, Range::from(stmt));
|
||||||
|
|
||||||
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
|
let deleted: Vec<&Stmt> = checker.deletions.iter().map(Into::into).collect();
|
||||||
|
match delete_stmt(
|
||||||
|
stmt,
|
||||||
|
Some(parent),
|
||||||
|
&deleted,
|
||||||
|
checker.locator,
|
||||||
|
checker.indexer,
|
||||||
|
checker.stylist,
|
||||||
|
) {
|
||||||
|
Ok(fix) => {
|
||||||
|
if fix.content.is_empty() || fix.content == "pass" {
|
||||||
|
checker.deletions.insert(RefEquality(stmt));
|
||||||
|
}
|
||||||
|
diagnostic.set_fix(fix);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to delete `pass` statement: {}", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
checker.diagnostics.push(diagnostic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||||
|
expression: diagnostics
|
||||||
|
---
|
||||||
|
[]
|
||||||
|
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||||
|
expression: diagnostics
|
||||||
|
---
|
||||||
|
- kind:
|
||||||
|
name: PassInClassBody
|
||||||
|
body: "Class body must not contain `pass`"
|
||||||
|
suggestion: "Remove unnecessary `pass`"
|
||||||
|
fixable: true
|
||||||
|
location:
|
||||||
|
row: 5
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 5
|
||||||
|
column: 8
|
||||||
|
fix:
|
||||||
|
edits:
|
||||||
|
- content: ""
|
||||||
|
location:
|
||||||
|
row: 5
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 6
|
||||||
|
column: 0
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
name: PassInClassBody
|
||||||
|
body: "Class body must not contain `pass`"
|
||||||
|
suggestion: "Remove unnecessary `pass`"
|
||||||
|
fixable: true
|
||||||
|
location:
|
||||||
|
row: 8
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 8
|
||||||
|
column: 8
|
||||||
|
fix:
|
||||||
|
edits:
|
||||||
|
- content: ""
|
||||||
|
location:
|
||||||
|
row: 8
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 9
|
||||||
|
column: 0
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
name: PassInClassBody
|
||||||
|
body: "Class body must not contain `pass`"
|
||||||
|
suggestion: "Remove unnecessary `pass`"
|
||||||
|
fixable: true
|
||||||
|
location:
|
||||||
|
row: 16
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 16
|
||||||
|
column: 8
|
||||||
|
fix:
|
||||||
|
edits:
|
||||||
|
- content: ""
|
||||||
|
location:
|
||||||
|
row: 16
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 17
|
||||||
|
column: 0
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
name: PassInClassBody
|
||||||
|
body: "Class body must not contain `pass`"
|
||||||
|
suggestion: "Remove unnecessary `pass`"
|
||||||
|
fixable: true
|
||||||
|
location:
|
||||||
|
row: 20
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 20
|
||||||
|
column: 8
|
||||||
|
fix:
|
||||||
|
edits:
|
||||||
|
- content: ""
|
||||||
|
location:
|
||||||
|
row: 20
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 21
|
||||||
|
column: 0
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
name: PassInClassBody
|
||||||
|
body: "Class body must not contain `pass`"
|
||||||
|
suggestion: "Remove unnecessary `pass`"
|
||||||
|
fixable: true
|
||||||
|
location:
|
||||||
|
row: 23
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 23
|
||||||
|
column: 8
|
||||||
|
fix:
|
||||||
|
edits:
|
||||||
|
- content: ""
|
||||||
|
location:
|
||||||
|
row: 23
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 24
|
||||||
|
column: 0
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
name: PassInClassBody
|
||||||
|
body: "Class body must not contain `pass`"
|
||||||
|
suggestion: "Remove unnecessary `pass`"
|
||||||
|
fixable: true
|
||||||
|
location:
|
||||||
|
row: 28
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 28
|
||||||
|
column: 8
|
||||||
|
fix:
|
||||||
|
edits:
|
||||||
|
- content: ""
|
||||||
|
location:
|
||||||
|
row: 28
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 29
|
||||||
|
column: 0
|
||||||
|
parent: ~
|
||||||
|
|
||||||
|
|
@ -2034,6 +2034,7 @@
|
||||||
"PYI01",
|
"PYI01",
|
||||||
"PYI010",
|
"PYI010",
|
||||||
"PYI011",
|
"PYI011",
|
||||||
|
"PYI012",
|
||||||
"PYI014",
|
"PYI014",
|
||||||
"PYI015",
|
"PYI015",
|
||||||
"PYI02",
|
"PYI02",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue