Skip to content

Commit

Permalink
[flake8-pyi] Implement PYI035 (#4820)
Browse files Browse the repository at this point in the history
  • Loading branch information
density authored and konstin committed Jun 13, 2023
1 parent 45cfd33 commit 874b228
Show file tree
Hide file tree
Showing 10 changed files with 130 additions and 3 deletions.
15 changes: 15 additions & 0 deletions crates/ruff/resources/test/fixtures/flake8_pyi/PYI035.py
@@ -0,0 +1,15 @@
__all__: list[str]

__all__: list[str] = ["foo"]


class Foo:
__all__: list[str]
__match_args__: tuple[str, ...]
__slots__: tuple[str, ...]


class Bar:
__all__: list[str] = ["foo"]
__match_args__: tuple[str, ...] = (1,)
__slots__: tuple[str, ...] = "foo"
13 changes: 13 additions & 0 deletions crates/ruff/resources/test/fixtures/flake8_pyi/PYI035.pyi
@@ -0,0 +1,13 @@
__all__: list[str] # Error: PYI035

__all__: list[str] = ["foo"]

class Foo:
__all__: list[str]
__match_args__: tuple[str, ...] # Error: PYI035
__slots__: tuple[str, ...] # Error: PYI035

class Bar:
__all__: list[str] = ["foo"]
__match_args__: tuple[str, ...] = (1,)
__slots__: tuple[str, ...] = "foo"
6 changes: 6 additions & 0 deletions crates/ruff/src/checkers/ast/mod.rs
Expand Up @@ -1785,6 +1785,12 @@ where
);
}
}
} else {
if self.enabled(Rule::UnassignedSpecialVariableInStub) {
flake8_pyi::rules::unassigned_special_variable_in_stub(
self, target, stmt,
);
}
}
if self
.semantic_model
Expand Down
1 change: 1 addition & 0 deletions crates/ruff/src/codes.rs
Expand Up @@ -607,6 +607,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Pyi, "032") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::AnyEqNeAnnotation),
(Flake8Pyi, "033") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::TypeCommentInStub),
(Flake8Pyi, "034") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::NonSelfReturnType),
(Flake8Pyi, "035") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnassignedSpecialVariableInStub),
(Flake8Pyi, "042") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::SnakeCaseTypeAlias),
(Flake8Pyi, "043") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::TSuffixedTypeAlias),
(Flake8Pyi, "045") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::IterMethodReturnIterable),
Expand Down
2 changes: 2 additions & 0 deletions crates/ruff/src/rules/flake8_pyi/mod.rs
Expand Up @@ -44,6 +44,8 @@ mod tests {
#[test_case(Rule::QuotedAnnotationInStub, Path::new("PYI020.pyi"))]
#[test_case(Rule::SnakeCaseTypeAlias, Path::new("PYI042.py"))]
#[test_case(Rule::SnakeCaseTypeAlias, Path::new("PYI042.pyi"))]
#[test_case(Rule::UnassignedSpecialVariableInStub, Path::new("PYI035.py"))]
#[test_case(Rule::UnassignedSpecialVariableInStub, Path::new("PYI035.pyi"))]
#[test_case(Rule::StubBodyMultipleStatements, Path::new("PYI048.py"))]
#[test_case(Rule::StubBodyMultipleStatements, Path::new("PYI048.pyi"))]
#[test_case(Rule::TSuffixedTypeAlias, Path::new("PYI043.py"))]
Expand Down
5 changes: 3 additions & 2 deletions crates/ruff/src/rules/flake8_pyi/rules/mod.rs
Expand Up @@ -20,8 +20,9 @@ pub(crate) use prefix_type_params::{prefix_type_params, UnprefixedTypeParam};
pub(crate) use quoted_annotation_in_stub::{quoted_annotation_in_stub, QuotedAnnotationInStub};
pub(crate) use simple_defaults::{
annotated_assignment_default_in_stub, argument_simple_defaults, assignment_default_in_stub,
typed_argument_simple_defaults, unannotated_assignment_in_stub, ArgumentDefaultInStub,
AssignmentDefaultInStub, TypedArgumentDefaultInStub, UnannotatedAssignmentInStub,
typed_argument_simple_defaults, unannotated_assignment_in_stub,
unassigned_special_variable_in_stub, ArgumentDefaultInStub, AssignmentDefaultInStub,
TypedArgumentDefaultInStub, UnannotatedAssignmentInStub, UnassignedSpecialVariableInStub,
};
pub(crate) use string_or_bytes_too_long::{string_or_bytes_too_long, StringOrBytesTooLong};
pub(crate) use stub_body_multiple_statements::{
Expand Down
55 changes: 54 additions & 1 deletion crates/ruff/src/rules/flake8_pyi/rules/simple_defaults.rs
@@ -1,4 +1,4 @@
use rustpython_parser::ast::{self, Arguments, Constant, Expr, Operator, Ranged, Unaryop};
use rustpython_parser::ast::{self, Arguments, Constant, Expr, Operator, Ranged, Stmt, Unaryop};

use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
Expand Down Expand Up @@ -64,6 +64,37 @@ impl Violation for UnannotatedAssignmentInStub {
}
}

#[violation]
pub struct UnassignedSpecialVariableInStub {
name: String,
}

/// ## What it does
/// Checks that `__all__`, `__match_args__`, and `__slots__` variables are
/// assigned to values when defined in stub files.
///
/// ## Why is this bad?
/// Special variables like `__all__` have the same semantics in stub files
/// as they do in Python modules, and so should be consistent with their
/// runtime counterparts.
///
/// ## Example
/// ```python
/// __all__: list[str]
/// ```
///
/// Use instead:
/// ```python
/// __all__: list[str] = ["foo", "bar"]
/// ```
impl Violation for UnassignedSpecialVariableInStub {
#[derive_message_formats]
fn message(&self) -> String {
let UnassignedSpecialVariableInStub { name } = self;
format!("`{name}` in a stub file must have a value, as it has the same semantics as `{name}` at runtime")
}
}

const ALLOWED_MATH_ATTRIBUTES_IN_DEFAULTS: &[&[&str]] = &[
&["math", "inf"],
&["math", "nan"],
Expand Down Expand Up @@ -528,3 +559,25 @@ pub(crate) fn unannotated_assignment_in_stub(
value.range(),
));
}

/// PYI035
pub(crate) fn unassigned_special_variable_in_stub(
checker: &mut Checker,
target: &Expr,
stmt: &Stmt,
) {
let Expr::Name(ast::ExprName { id, .. }) = target else {
return;
};

if !is_special_assignment(checker.semantic_model(), target) {
return;
}

checker.diagnostics.push(Diagnostic::new(
UnassignedSpecialVariableInStub {
name: id.to_string(),
},
stmt.range(),
));
}
@@ -0,0 +1,4 @@
---
source: crates/ruff/src/rules/flake8_pyi/mod.rs
---

@@ -0,0 +1,31 @@
---
source: crates/ruff/src/rules/flake8_pyi/mod.rs
---
PYI035.pyi:1:1: PYI035 `__all__` in a stub file must have a value, as it has the same semantics as `__all__` at runtime
|
1 | __all__: list[str] # Error: PYI035
| ^^^^^^^^^^^^^^^^^^ PYI035
2 |
3 | __all__: list[str] = ["foo"]
|

PYI035.pyi:7:5: PYI035 `__match_args__` in a stub file must have a value, as it has the same semantics as `__match_args__` at runtime
|
7 | class Foo:
8 | __all__: list[str]
9 | __match_args__: tuple[str, ...] # Error: PYI035
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI035
10 | __slots__: tuple[str, ...] # Error: PYI035
|

PYI035.pyi:8:5: PYI035 `__slots__` in a stub file must have a value, as it has the same semantics as `__slots__` at runtime
|
8 | __all__: list[str]
9 | __match_args__: tuple[str, ...] # Error: PYI035
10 | __slots__: tuple[str, ...] # Error: PYI035
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI035
11 |
12 | class Bar:
|


1 change: 1 addition & 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 874b228

Please sign in to comment.