mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-13 17:25:20 +00:00
[flake8-pyi] Implement unannotated-assignment-in-stub (PY052) (#4293)
This commit is contained in:
parent
1fe6954150
commit
c711db11ce
11 changed files with 460 additions and 39 deletions
93
crates/ruff/resources/test/fixtures/flake8_pyi/PYI052.py
vendored
Normal file
93
crates/ruff/resources/test/fixtures/flake8_pyi/PYI052.py
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
import builtins
|
||||||
|
import typing
|
||||||
|
from typing import TypeAlias, Final
|
||||||
|
|
||||||
|
field1: int
|
||||||
|
field2: int = ...
|
||||||
|
field3 = ... # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
|
||||||
|
field4: int = 0
|
||||||
|
field41: int = 0xFFFFFFFF
|
||||||
|
field42: int = 1234567890
|
||||||
|
field43: int = -0xFFFFFFFF
|
||||||
|
field44: int = -1234567890
|
||||||
|
field5 = 0 # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int") # Y052 Need type annotation for "field5"
|
||||||
|
field6 = 0 # Y052 Need type annotation for "field6"
|
||||||
|
field7 = b"" # Y052 Need type annotation for "field7"
|
||||||
|
field71 = "foo" # Y052 Need type annotation for "field71"
|
||||||
|
field72: str = "foo"
|
||||||
|
field8 = False # Y052 Need type annotation for "field8"
|
||||||
|
field81 = -1 # Y052 Need type annotation for "field81"
|
||||||
|
field82: float = -98.43
|
||||||
|
field83 = -42j # Y052 Need type annotation for "field83"
|
||||||
|
field84 = 5 + 42j # Y052 Need type annotation for "field84"
|
||||||
|
field85 = -5 - 42j # Y052 Need type annotation for "field85"
|
||||||
|
field9 = None # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "field9: TypeAlias = None"
|
||||||
|
Field95: TypeAlias = None
|
||||||
|
Field96: TypeAlias = int | None
|
||||||
|
Field97: TypeAlias = None | typing.SupportsInt | builtins.str | float | bool
|
||||||
|
field19 = [1, 2, 3] # Y052 Need type annotation for "field19"
|
||||||
|
field191: list[int] = [1, 2, 3]
|
||||||
|
field20 = (1, 2, 3) # Y052 Need type annotation for "field20"
|
||||||
|
field201: tuple[int, ...] = (1, 2, 3)
|
||||||
|
field21 = {1, 2, 3} # Y052 Need type annotation for "field21"
|
||||||
|
field211: set[int] = {1, 2, 3}
|
||||||
|
field212 = {"foo": "bar"} # Y052 Need type annotation for "field212"
|
||||||
|
field213: dict[str, str] = {"foo": "bar"}
|
||||||
|
field22: Final = {"foo": 5}
|
||||||
|
field221: list[int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] # Y015 Only simple default values are allowed for assignments
|
||||||
|
field223: list[int] = [*range(10)] # Y015 Only simple default values are allowed for assignments
|
||||||
|
field224: list[int] = list(range(10)) # Y015 Only simple default values are allowed for assignments
|
||||||
|
field225: list[object] = [{}, 1, 2] # Y015 Only simple default values are allowed for assignments
|
||||||
|
field226: tuple[str | tuple[str, ...], ...] = ("foo", ("foo", "bar")) # Y015 Only simple default values are allowed for assignments
|
||||||
|
field227: dict[str, object] = {"foo": {"foo": "bar"}} # Y015 Only simple default values are allowed for assignments
|
||||||
|
field228: dict[str, list[object]] = {"foo": []} # Y015 Only simple default values are allowed for assignments
|
||||||
|
# When parsed, this case results in `None` being placed in the `.keys` list for the `ast.Dict` node
|
||||||
|
field229: dict[int, int] = {1: 2, **{3: 4}} # Y015 Only simple default values are allowed for assignments
|
||||||
|
field23 = "foo" + "bar" # Y015 Only simple default values are allowed for assignments
|
||||||
|
field24 = b"foo" + b"bar" # Y015 Only simple default values are allowed for assignments
|
||||||
|
field25 = 5 * 5 # Y015 Only simple default values are allowed for assignments
|
||||||
|
|
||||||
|
# We shouldn't emit Y015 within functions
|
||||||
|
def f():
|
||||||
|
field26: list[int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
|
||||||
|
|
||||||
|
|
||||||
|
# We shouldn't emit Y015 for __slots__ or __match_args__
|
||||||
|
class Class1:
|
||||||
|
__slots__ = (
|
||||||
|
'_one',
|
||||||
|
'_two',
|
||||||
|
'_three',
|
||||||
|
'_four',
|
||||||
|
'_five',
|
||||||
|
'_six',
|
||||||
|
'_seven',
|
||||||
|
'_eight',
|
||||||
|
'_nine',
|
||||||
|
'_ten',
|
||||||
|
'_eleven',
|
||||||
|
)
|
||||||
|
|
||||||
|
__match_args__ = (
|
||||||
|
'one',
|
||||||
|
'two',
|
||||||
|
'three',
|
||||||
|
'four',
|
||||||
|
'five',
|
||||||
|
'six',
|
||||||
|
'seven',
|
||||||
|
'eight',
|
||||||
|
'nine',
|
||||||
|
'ten',
|
||||||
|
'eleven',
|
||||||
|
)
|
||||||
|
|
||||||
|
# We shouldn't emit Y015 for __all__
|
||||||
|
__all__ = ["Class1"]
|
||||||
|
|
||||||
|
# Ignore the following for PYI015
|
||||||
|
field26 = typing.Sequence[int]
|
||||||
|
field27 = list[str]
|
||||||
|
field28 = builtins.str
|
||||||
|
field29 = str
|
||||||
|
field30 = str | bytes | None
|
||||||
100
crates/ruff/resources/test/fixtures/flake8_pyi/PYI052.pyi
vendored
Normal file
100
crates/ruff/resources/test/fixtures/flake8_pyi/PYI052.pyi
vendored
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
import builtins
|
||||||
|
import typing
|
||||||
|
from typing import TypeAlias, Final, NewType, TypeVar, TypeVarTuple, ParamSpec
|
||||||
|
|
||||||
|
# We shouldn't emit Y015 for simple default values
|
||||||
|
field1: int
|
||||||
|
field2: int = ...
|
||||||
|
field3 = ... # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
|
||||||
|
field4: int = 0
|
||||||
|
field41: int = 0xFFFFFFFF
|
||||||
|
field42: int = 1234567890
|
||||||
|
field43: int = -0xFFFFFFFF
|
||||||
|
field44: int = -1234567890
|
||||||
|
field5 = 0 # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int") # Y052 Need type annotation for "field5"
|
||||||
|
field6 = 0 # Y052 Need type annotation for "field6"
|
||||||
|
field7 = b"" # Y052 Need type annotation for "field7"
|
||||||
|
field71 = "foo" # Y052 Need type annotation for "field71"
|
||||||
|
field72: str = "foo"
|
||||||
|
field8 = False # Y052 Need type annotation for "field8"
|
||||||
|
field81 = -1 # Y052 Need type annotation for "field81"
|
||||||
|
field82: float = -98.43
|
||||||
|
field83 = -42j # Y052 Need type annotation for "field83"
|
||||||
|
field84 = 5 + 42j # Y052 Need type annotation for "field84"
|
||||||
|
field85 = -5 - 42j # Y052 Need type annotation for "field85"
|
||||||
|
field9 = None # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "field9: TypeAlias = None"
|
||||||
|
Field95: TypeAlias = None
|
||||||
|
Field96: TypeAlias = int | None
|
||||||
|
Field97: TypeAlias = None | typing.SupportsInt | builtins.str | float | bool
|
||||||
|
Field98 = NewType('MyInt', int)
|
||||||
|
Field99 = TypeVar('Field99')
|
||||||
|
Field100 = TypeVarTuple('Field100')
|
||||||
|
Field101 = ParamSpec('Field101')
|
||||||
|
field19 = [1, 2, 3] # Y052 Need type annotation for "field19"
|
||||||
|
field191: list[int] = [1, 2, 3]
|
||||||
|
field20 = (1, 2, 3) # Y052 Need type annotation for "field20"
|
||||||
|
field201: tuple[int, ...] = (1, 2, 3)
|
||||||
|
field21 = {1, 2, 3} # Y052 Need type annotation for "field21"
|
||||||
|
field211: set[int] = {1, 2, 3}
|
||||||
|
field212 = {"foo": "bar"} # Y052 Need type annotation for "field212"
|
||||||
|
field213: dict[str, str] = {"foo": "bar"}
|
||||||
|
field22: Final = {"foo": 5}
|
||||||
|
|
||||||
|
# We *should* emit Y015 for more complex default values
|
||||||
|
field221: list[int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] # Y015 Only simple default values are allowed for assignments
|
||||||
|
field223: list[int] = [*range(10)] # Y015 Only simple default values are allowed for assignments
|
||||||
|
field224: list[int] = list(range(10)) # Y015 Only simple default values are allowed for assignments
|
||||||
|
field225: list[object] = [{}, 1, 2] # Y015 Only simple default values are allowed for assignments
|
||||||
|
field226: tuple[str | tuple[str, ...], ...] = ("foo", ("foo", "bar")) # Y015 Only simple default values are allowed for assignments
|
||||||
|
field227: dict[str, object] = {"foo": {"foo": "bar"}} # Y015 Only simple default values are allowed for assignments
|
||||||
|
field228: dict[str, list[object]] = {"foo": []} # Y015 Only simple default values are allowed for assignments
|
||||||
|
# When parsed, this case results in `None` being placed in the `.keys` list for the `ast.Dict` node
|
||||||
|
field229: dict[int, int] = {1: 2, **{3: 4}} # Y015 Only simple default values are allowed for assignments
|
||||||
|
field23 = "foo" + "bar" # Y015 Only simple default values are allowed for assignments
|
||||||
|
field24 = b"foo" + b"bar" # Y015 Only simple default values are allowed for assignments
|
||||||
|
field25 = 5 * 5 # Y015 Only simple default values are allowed for assignments
|
||||||
|
|
||||||
|
# We shouldn't emit Y015 within functions
|
||||||
|
def f():
|
||||||
|
field26: list[int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
|
||||||
|
|
||||||
|
|
||||||
|
# We shouldn't emit Y015 for __slots__ or __match_args__
|
||||||
|
class Class1:
|
||||||
|
__slots__ = (
|
||||||
|
'_one',
|
||||||
|
'_two',
|
||||||
|
'_three',
|
||||||
|
'_four',
|
||||||
|
'_five',
|
||||||
|
'_six',
|
||||||
|
'_seven',
|
||||||
|
'_eight',
|
||||||
|
'_nine',
|
||||||
|
'_ten',
|
||||||
|
'_eleven',
|
||||||
|
)
|
||||||
|
|
||||||
|
__match_args__ = (
|
||||||
|
'one',
|
||||||
|
'two',
|
||||||
|
'three',
|
||||||
|
'four',
|
||||||
|
'five',
|
||||||
|
'six',
|
||||||
|
'seven',
|
||||||
|
'eight',
|
||||||
|
'nine',
|
||||||
|
'ten',
|
||||||
|
'eleven',
|
||||||
|
)
|
||||||
|
|
||||||
|
# We shouldn't emit Y015 for __all__
|
||||||
|
__all__ = ["Class1"]
|
||||||
|
|
||||||
|
# Ignore the following for PYI015
|
||||||
|
field26 = typing.Sequence[int]
|
||||||
|
field27 = list[str]
|
||||||
|
field28 = builtins.str
|
||||||
|
field29 = str
|
||||||
|
field30 = str | bytes | None
|
||||||
|
|
@ -1752,11 +1752,11 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.is_stub {
|
if self.is_stub {
|
||||||
if self
|
if self.settings.rules.any_enabled(&[
|
||||||
.settings
|
Rule::UnprefixedTypeParam,
|
||||||
.rules
|
Rule::AssignmentDefaultInStub,
|
||||||
.any_enabled(&[Rule::UnprefixedTypeParam, Rule::AssignmentDefaultInStub])
|
Rule::UnannotatedAssignmentInStub,
|
||||||
{
|
]) {
|
||||||
// Ignore assignments in function bodies; those are covered by other rules.
|
// Ignore assignments in function bodies; those are covered by other rules.
|
||||||
if !self.ctx.scopes().any(|scope| scope.kind.is_function()) {
|
if !self.ctx.scopes().any(|scope| scope.kind.is_function()) {
|
||||||
if self.settings.rules.enabled(Rule::UnprefixedTypeParam) {
|
if self.settings.rules.enabled(Rule::UnprefixedTypeParam) {
|
||||||
|
|
@ -1765,6 +1765,15 @@ where
|
||||||
if self.settings.rules.enabled(Rule::AssignmentDefaultInStub) {
|
if self.settings.rules.enabled(Rule::AssignmentDefaultInStub) {
|
||||||
flake8_pyi::rules::assignment_default_in_stub(self, targets, value);
|
flake8_pyi::rules::assignment_default_in_stub(self, targets, value);
|
||||||
}
|
}
|
||||||
|
if self
|
||||||
|
.settings
|
||||||
|
.rules
|
||||||
|
.enabled(Rule::UnannotatedAssignmentInStub)
|
||||||
|
{
|
||||||
|
flake8_pyi::rules::unannotated_assignment_in_stub(
|
||||||
|
self, targets, value,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -610,6 +610,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||||
(Flake8Pyi, "033") => Rule::TypeCommentInStub,
|
(Flake8Pyi, "033") => Rule::TypeCommentInStub,
|
||||||
(Flake8Pyi, "042") => Rule::SnakeCaseTypeAlias,
|
(Flake8Pyi, "042") => Rule::SnakeCaseTypeAlias,
|
||||||
(Flake8Pyi, "043") => Rule::TSuffixedTypeAlias,
|
(Flake8Pyi, "043") => Rule::TSuffixedTypeAlias,
|
||||||
|
(Flake8Pyi, "052") => Rule::UnannotatedAssignmentInStub,
|
||||||
|
|
||||||
// flake8-pytest-style
|
// flake8-pytest-style
|
||||||
(Flake8PytestStyle, "001") => Rule::PytestFixtureIncorrectParenthesesStyle,
|
(Flake8PytestStyle, "001") => Rule::PytestFixtureIncorrectParenthesesStyle,
|
||||||
|
|
|
||||||
|
|
@ -540,18 +540,19 @@ ruff_macros::register_rules!(
|
||||||
rules::flake8_pyi::rules::AssignmentDefaultInStub,
|
rules::flake8_pyi::rules::AssignmentDefaultInStub,
|
||||||
rules::flake8_pyi::rules::BadVersionInfoComparison,
|
rules::flake8_pyi::rules::BadVersionInfoComparison,
|
||||||
rules::flake8_pyi::rules::DocstringInStub,
|
rules::flake8_pyi::rules::DocstringInStub,
|
||||||
rules::flake8_pyi::rules::NonEmptyStubBody,
|
|
||||||
rules::flake8_pyi::rules::PassStatementStubBody,
|
|
||||||
rules::flake8_pyi::rules::TypeCommentInStub,
|
|
||||||
rules::flake8_pyi::rules::TypedArgumentDefaultInStub,
|
|
||||||
rules::flake8_pyi::rules::UnprefixedTypeParam,
|
|
||||||
rules::flake8_pyi::rules::UnrecognizedPlatformCheck,
|
|
||||||
rules::flake8_pyi::rules::UnrecognizedPlatformName,
|
|
||||||
rules::flake8_pyi::rules::PassInClassBody,
|
|
||||||
rules::flake8_pyi::rules::DuplicateUnionMember,
|
rules::flake8_pyi::rules::DuplicateUnionMember,
|
||||||
|
rules::flake8_pyi::rules::NonEmptyStubBody,
|
||||||
|
rules::flake8_pyi::rules::PassInClassBody,
|
||||||
|
rules::flake8_pyi::rules::PassStatementStubBody,
|
||||||
rules::flake8_pyi::rules::QuotedAnnotationInStub,
|
rules::flake8_pyi::rules::QuotedAnnotationInStub,
|
||||||
rules::flake8_pyi::rules::SnakeCaseTypeAlias,
|
rules::flake8_pyi::rules::SnakeCaseTypeAlias,
|
||||||
rules::flake8_pyi::rules::TSuffixedTypeAlias,
|
rules::flake8_pyi::rules::TSuffixedTypeAlias,
|
||||||
|
rules::flake8_pyi::rules::TypeCommentInStub,
|
||||||
|
rules::flake8_pyi::rules::TypedArgumentDefaultInStub,
|
||||||
|
rules::flake8_pyi::rules::UnannotatedAssignmentInStub,
|
||||||
|
rules::flake8_pyi::rules::UnprefixedTypeParam,
|
||||||
|
rules::flake8_pyi::rules::UnrecognizedPlatformCheck,
|
||||||
|
rules::flake8_pyi::rules::UnrecognizedPlatformName,
|
||||||
// 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,
|
||||||
|
|
|
||||||
|
|
@ -13,38 +13,40 @@ mod tests {
|
||||||
use crate::test::test_path;
|
use crate::test::test_path;
|
||||||
use crate::{assert_messages, settings};
|
use crate::{assert_messages, settings};
|
||||||
|
|
||||||
#[test_case(Rule::UnprefixedTypeParam, Path::new("PYI001.py"))]
|
|
||||||
#[test_case(Rule::UnprefixedTypeParam, Path::new("PYI001.pyi"))]
|
|
||||||
#[test_case(Rule::BadVersionInfoComparison, Path::new("PYI006.py"))]
|
|
||||||
#[test_case(Rule::BadVersionInfoComparison, Path::new("PYI006.pyi"))]
|
|
||||||
#[test_case(Rule::UnrecognizedPlatformCheck, Path::new("PYI007.py"))]
|
|
||||||
#[test_case(Rule::UnrecognizedPlatformCheck, Path::new("PYI007.pyi"))]
|
|
||||||
#[test_case(Rule::UnrecognizedPlatformName, Path::new("PYI008.py"))]
|
|
||||||
#[test_case(Rule::UnrecognizedPlatformName, Path::new("PYI008.pyi"))]
|
|
||||||
#[test_case(Rule::PassStatementStubBody, Path::new("PYI009.py"))]
|
|
||||||
#[test_case(Rule::PassStatementStubBody, Path::new("PYI009.pyi"))]
|
|
||||||
#[test_case(Rule::NonEmptyStubBody, Path::new("PYI010.py"))]
|
|
||||||
#[test_case(Rule::NonEmptyStubBody, Path::new("PYI010.pyi"))]
|
|
||||||
#[test_case(Rule::TypedArgumentDefaultInStub, Path::new("PYI011.py"))]
|
|
||||||
#[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"))]
|
||||||
#[test_case(Rule::AssignmentDefaultInStub, Path::new("PYI015.pyi"))]
|
#[test_case(Rule::AssignmentDefaultInStub, Path::new("PYI015.pyi"))]
|
||||||
#[test_case(Rule::DuplicateUnionMember, Path::new("PYI016.py"))]
|
#[test_case(Rule::BadVersionInfoComparison, Path::new("PYI006.py"))]
|
||||||
#[test_case(Rule::DuplicateUnionMember, Path::new("PYI016.pyi"))]
|
#[test_case(Rule::BadVersionInfoComparison, Path::new("PYI006.pyi"))]
|
||||||
#[test_case(Rule::QuotedAnnotationInStub, Path::new("PYI020.py"))]
|
|
||||||
#[test_case(Rule::QuotedAnnotationInStub, Path::new("PYI020.pyi"))]
|
|
||||||
#[test_case(Rule::DocstringInStub, Path::new("PYI021.py"))]
|
#[test_case(Rule::DocstringInStub, Path::new("PYI021.py"))]
|
||||||
#[test_case(Rule::DocstringInStub, Path::new("PYI021.pyi"))]
|
#[test_case(Rule::DocstringInStub, Path::new("PYI021.pyi"))]
|
||||||
#[test_case(Rule::TypeCommentInStub, Path::new("PYI033.py"))]
|
#[test_case(Rule::DuplicateUnionMember, Path::new("PYI016.py"))]
|
||||||
#[test_case(Rule::TypeCommentInStub, Path::new("PYI033.pyi"))]
|
#[test_case(Rule::DuplicateUnionMember, Path::new("PYI016.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"))]
|
||||||
|
#[test_case(Rule::PassInClassBody, Path::new("PYI012.pyi"))]
|
||||||
|
#[test_case(Rule::PassStatementStubBody, Path::new("PYI009.py"))]
|
||||||
|
#[test_case(Rule::PassStatementStubBody, Path::new("PYI009.pyi"))]
|
||||||
|
#[test_case(Rule::QuotedAnnotationInStub, Path::new("PYI020.py"))]
|
||||||
|
#[test_case(Rule::QuotedAnnotationInStub, Path::new("PYI020.pyi"))]
|
||||||
#[test_case(Rule::SnakeCaseTypeAlias, Path::new("PYI042.py"))]
|
#[test_case(Rule::SnakeCaseTypeAlias, Path::new("PYI042.py"))]
|
||||||
#[test_case(Rule::SnakeCaseTypeAlias, Path::new("PYI042.pyi"))]
|
#[test_case(Rule::SnakeCaseTypeAlias, Path::new("PYI042.pyi"))]
|
||||||
#[test_case(Rule::TSuffixedTypeAlias, Path::new("PYI043.py"))]
|
#[test_case(Rule::TSuffixedTypeAlias, Path::new("PYI043.py"))]
|
||||||
#[test_case(Rule::TSuffixedTypeAlias, Path::new("PYI043.pyi"))]
|
#[test_case(Rule::TSuffixedTypeAlias, Path::new("PYI043.pyi"))]
|
||||||
|
#[test_case(Rule::TypeCommentInStub, Path::new("PYI033.py"))]
|
||||||
|
#[test_case(Rule::TypeCommentInStub, Path::new("PYI033.pyi"))]
|
||||||
|
#[test_case(Rule::TypedArgumentDefaultInStub, Path::new("PYI011.py"))]
|
||||||
|
#[test_case(Rule::TypedArgumentDefaultInStub, Path::new("PYI011.pyi"))]
|
||||||
|
#[test_case(Rule::UnannotatedAssignmentInStub, Path::new("PYI052.py"))]
|
||||||
|
#[test_case(Rule::UnannotatedAssignmentInStub, Path::new("PYI052.pyi"))]
|
||||||
|
#[test_case(Rule::UnprefixedTypeParam, Path::new("PYI001.py"))]
|
||||||
|
#[test_case(Rule::UnprefixedTypeParam, Path::new("PYI001.pyi"))]
|
||||||
|
#[test_case(Rule::UnrecognizedPlatformCheck, Path::new("PYI007.py"))]
|
||||||
|
#[test_case(Rule::UnrecognizedPlatformCheck, Path::new("PYI007.pyi"))]
|
||||||
|
#[test_case(Rule::UnrecognizedPlatformName, Path::new("PYI008.py"))]
|
||||||
|
#[test_case(Rule::UnrecognizedPlatformName, Path::new("PYI008.pyi"))]
|
||||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||||
let diagnostics = test_path(
|
let diagnostics = test_path(
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ 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 quoted_annotation_in_stub::{quoted_annotation_in_stub, QuotedAnnotationInStub};
|
||||||
pub(crate) use simple_defaults::{
|
pub(crate) use simple_defaults::{
|
||||||
annotated_assignment_default_in_stub, argument_simple_defaults, assignment_default_in_stub,
|
annotated_assignment_default_in_stub, argument_simple_defaults, assignment_default_in_stub,
|
||||||
typed_argument_simple_defaults, ArgumentDefaultInStub, AssignmentDefaultInStub,
|
typed_argument_simple_defaults, unannotated_assignment_in_stub, ArgumentDefaultInStub,
|
||||||
TypedArgumentDefaultInStub,
|
AssignmentDefaultInStub, TypedArgumentDefaultInStub, UnannotatedAssignmentInStub,
|
||||||
};
|
};
|
||||||
pub(crate) use type_alias_naming::{
|
pub(crate) use type_alias_naming::{
|
||||||
snake_case_type_alias, t_suffixed_type_alias, SnakeCaseTypeAlias, TSuffixedTypeAlias,
|
snake_case_type_alias, t_suffixed_type_alias, SnakeCaseTypeAlias, TSuffixedTypeAlias,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
use rustpython_parser::ast::{self, Arguments, Constant, Expr, ExprKind, Operator, Unaryop};
|
use rustpython_parser::ast::{self, Arguments, Constant, Expr, ExprKind, Operator, Unaryop};
|
||||||
|
|
||||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_semantic::context::Context;
|
use ruff_python_semantic::context::Context;
|
||||||
|
use ruff_python_semantic::scope::{ClassDef, ScopeKind};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::registry::AsRule;
|
use crate::registry::AsRule;
|
||||||
|
|
@ -49,6 +50,19 @@ impl AlwaysAutofixableViolation for AssignmentDefaultInStub {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[violation]
|
||||||
|
pub struct UnannotatedAssignmentInStub {
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Violation for UnannotatedAssignmentInStub {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
let UnannotatedAssignmentInStub { name } = self;
|
||||||
|
format!("Need type annotation for `{name}`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const ALLOWED_MATH_ATTRIBUTES_IN_DEFAULTS: &[&[&str]] = &[
|
const ALLOWED_MATH_ATTRIBUTES_IN_DEFAULTS: &[&[&str]] = &[
|
||||||
&["math", "inf"],
|
&["math", "inf"],
|
||||||
&["math", "nan"],
|
&["math", "nan"],
|
||||||
|
|
@ -285,6 +299,21 @@ fn is_special_assignment(context: &Context, target: &Expr) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the a class is an enum, based on its base classes.
|
||||||
|
fn is_enum(context: &Context, bases: &[Expr]) -> bool {
|
||||||
|
return bases.iter().any(|expr| {
|
||||||
|
context.resolve_call_path(expr).map_or(false, |call_path| {
|
||||||
|
matches!(
|
||||||
|
call_path.as_slice(),
|
||||||
|
[
|
||||||
|
"enum",
|
||||||
|
"Enum" | "Flag" | "IntEnum" | "IntFlag" | "StrEnum" | "ReprEnum"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// PYI011
|
/// PYI011
|
||||||
pub(crate) fn typed_argument_simple_defaults(checker: &mut Checker, args: &Arguments) {
|
pub(crate) fn typed_argument_simple_defaults(checker: &mut Checker, args: &Arguments) {
|
||||||
if !args.defaults.is_empty() {
|
if !args.defaults.is_empty() {
|
||||||
|
|
@ -401,7 +430,14 @@ pub(crate) fn argument_simple_defaults(checker: &mut Checker, args: &Arguments)
|
||||||
|
|
||||||
/// PYI015
|
/// PYI015
|
||||||
pub(crate) fn assignment_default_in_stub(checker: &mut Checker, targets: &[Expr], value: &Expr) {
|
pub(crate) fn assignment_default_in_stub(checker: &mut Checker, targets: &[Expr], value: &Expr) {
|
||||||
if targets.len() == 1 && is_special_assignment(&checker.ctx, &targets[0]) {
|
if targets.len() != 1 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let target = &targets[0];
|
||||||
|
if !matches!(target.node, ExprKind::Name(..)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if is_special_assignment(&checker.ctx, target) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if is_type_var_like_call(&checker.ctx, value) {
|
if is_type_var_like_call(&checker.ctx, value) {
|
||||||
|
|
@ -455,3 +491,42 @@ pub(crate) fn annotated_assignment_default_in_stub(
|
||||||
}
|
}
|
||||||
checker.diagnostics.push(diagnostic);
|
checker.diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// PYI052
|
||||||
|
pub(crate) fn unannotated_assignment_in_stub(
|
||||||
|
checker: &mut Checker,
|
||||||
|
targets: &[Expr],
|
||||||
|
value: &Expr,
|
||||||
|
) {
|
||||||
|
if targets.len() != 1 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let target = &targets[0];
|
||||||
|
let ExprKind::Name(ast::ExprName { id, .. }) = &target.node else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if is_special_assignment(&checker.ctx, target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if is_type_var_like_call(&checker.ctx, value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if is_valid_default_value_without_annotation(value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if !is_valid_default_value_with_annotation(value, checker, true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let ScopeKind::Class(ClassDef { bases, .. }) = &checker.ctx.scope().kind {
|
||||||
|
if is_enum(&checker.ctx, bases) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
|
UnannotatedAssignmentInStub {
|
||||||
|
name: id.to_string(),
|
||||||
|
},
|
||||||
|
value.range(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||||
|
---
|
||||||
|
|
||||||
|
|
@ -0,0 +1,134 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||||
|
---
|
||||||
|
PYI052.pyi:14:10: PYI052 Need type annotation for `field5`
|
||||||
|
|
|
||||||
|
14 | field43: int = -0xFFFFFFFF
|
||||||
|
15 | field44: int = -1234567890
|
||||||
|
16 | field5 = 0 # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int") # Y052 Need type annotation for "field5"
|
||||||
|
| ^ PYI052
|
||||||
|
17 | field6 = 0 # Y052 Need type annotation for "field6"
|
||||||
|
18 | field7 = b"" # Y052 Need type annotation for "field7"
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI052.pyi:15:10: PYI052 Need type annotation for `field6`
|
||||||
|
|
|
||||||
|
15 | field44: int = -1234567890
|
||||||
|
16 | field5 = 0 # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int") # Y052 Need type annotation for "field5"
|
||||||
|
17 | field6 = 0 # Y052 Need type annotation for "field6"
|
||||||
|
| ^ PYI052
|
||||||
|
18 | field7 = b"" # Y052 Need type annotation for "field7"
|
||||||
|
19 | field71 = "foo" # Y052 Need type annotation for "field71"
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI052.pyi:16:10: PYI052 Need type annotation for `field7`
|
||||||
|
|
|
||||||
|
16 | field5 = 0 # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int") # Y052 Need type annotation for "field5"
|
||||||
|
17 | field6 = 0 # Y052 Need type annotation for "field6"
|
||||||
|
18 | field7 = b"" # Y052 Need type annotation for "field7"
|
||||||
|
| ^^^ PYI052
|
||||||
|
19 | field71 = "foo" # Y052 Need type annotation for "field71"
|
||||||
|
20 | field72: str = "foo"
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI052.pyi:17:11: PYI052 Need type annotation for `field71`
|
||||||
|
|
|
||||||
|
17 | field6 = 0 # Y052 Need type annotation for "field6"
|
||||||
|
18 | field7 = b"" # Y052 Need type annotation for "field7"
|
||||||
|
19 | field71 = "foo" # Y052 Need type annotation for "field71"
|
||||||
|
| ^^^^^ PYI052
|
||||||
|
20 | field72: str = "foo"
|
||||||
|
21 | field8 = False # Y052 Need type annotation for "field8"
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI052.pyi:19:10: PYI052 Need type annotation for `field8`
|
||||||
|
|
|
||||||
|
19 | field71 = "foo" # Y052 Need type annotation for "field71"
|
||||||
|
20 | field72: str = "foo"
|
||||||
|
21 | field8 = False # Y052 Need type annotation for "field8"
|
||||||
|
| ^^^^^ PYI052
|
||||||
|
22 | field81 = -1 # Y052 Need type annotation for "field81"
|
||||||
|
23 | field82: float = -98.43
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI052.pyi:20:11: PYI052 Need type annotation for `field81`
|
||||||
|
|
|
||||||
|
20 | field72: str = "foo"
|
||||||
|
21 | field8 = False # Y052 Need type annotation for "field8"
|
||||||
|
22 | field81 = -1 # Y052 Need type annotation for "field81"
|
||||||
|
| ^^ PYI052
|
||||||
|
23 | field82: float = -98.43
|
||||||
|
24 | field83 = -42j # Y052 Need type annotation for "field83"
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI052.pyi:22:11: PYI052 Need type annotation for `field83`
|
||||||
|
|
|
||||||
|
22 | field81 = -1 # Y052 Need type annotation for "field81"
|
||||||
|
23 | field82: float = -98.43
|
||||||
|
24 | field83 = -42j # Y052 Need type annotation for "field83"
|
||||||
|
| ^^^^ PYI052
|
||||||
|
25 | field84 = 5 + 42j # Y052 Need type annotation for "field84"
|
||||||
|
26 | field85 = -5 - 42j # Y052 Need type annotation for "field85"
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI052.pyi:23:11: PYI052 Need type annotation for `field84`
|
||||||
|
|
|
||||||
|
23 | field82: float = -98.43
|
||||||
|
24 | field83 = -42j # Y052 Need type annotation for "field83"
|
||||||
|
25 | field84 = 5 + 42j # Y052 Need type annotation for "field84"
|
||||||
|
| ^^^^^^^ PYI052
|
||||||
|
26 | field85 = -5 - 42j # Y052 Need type annotation for "field85"
|
||||||
|
27 | field9 = None # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "field9: TypeAlias = None"
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI052.pyi:24:11: PYI052 Need type annotation for `field85`
|
||||||
|
|
|
||||||
|
24 | field83 = -42j # Y052 Need type annotation for "field83"
|
||||||
|
25 | field84 = 5 + 42j # Y052 Need type annotation for "field84"
|
||||||
|
26 | field85 = -5 - 42j # Y052 Need type annotation for "field85"
|
||||||
|
| ^^^^^^^^ PYI052
|
||||||
|
27 | field9 = None # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "field9: TypeAlias = None"
|
||||||
|
28 | Field95: TypeAlias = None
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI052.pyi:33:11: PYI052 Need type annotation for `field19`
|
||||||
|
|
|
||||||
|
33 | Field100 = TypeVarTuple('Field100')
|
||||||
|
34 | Field101 = ParamSpec('Field101')
|
||||||
|
35 | field19 = [1, 2, 3] # Y052 Need type annotation for "field19"
|
||||||
|
| ^^^^^^^^^ PYI052
|
||||||
|
36 | field191: list[int] = [1, 2, 3]
|
||||||
|
37 | field20 = (1, 2, 3) # Y052 Need type annotation for "field20"
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI052.pyi:35:11: PYI052 Need type annotation for `field20`
|
||||||
|
|
|
||||||
|
35 | field19 = [1, 2, 3] # Y052 Need type annotation for "field19"
|
||||||
|
36 | field191: list[int] = [1, 2, 3]
|
||||||
|
37 | field20 = (1, 2, 3) # Y052 Need type annotation for "field20"
|
||||||
|
| ^^^^^^^^^ PYI052
|
||||||
|
38 | field201: tuple[int, ...] = (1, 2, 3)
|
||||||
|
39 | field21 = {1, 2, 3} # Y052 Need type annotation for "field21"
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI052.pyi:37:11: PYI052 Need type annotation for `field21`
|
||||||
|
|
|
||||||
|
37 | field20 = (1, 2, 3) # Y052 Need type annotation for "field20"
|
||||||
|
38 | field201: tuple[int, ...] = (1, 2, 3)
|
||||||
|
39 | field21 = {1, 2, 3} # Y052 Need type annotation for "field21"
|
||||||
|
| ^^^^^^^^^ PYI052
|
||||||
|
40 | field211: set[int] = {1, 2, 3}
|
||||||
|
41 | field212 = {"foo": "bar"} # Y052 Need type annotation for "field212"
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI052.pyi:39:12: PYI052 Need type annotation for `field212`
|
||||||
|
|
|
||||||
|
39 | field21 = {1, 2, 3} # Y052 Need type annotation for "field21"
|
||||||
|
40 | field211: set[int] = {1, 2, 3}
|
||||||
|
41 | field212 = {"foo": "bar"} # Y052 Need type annotation for "field212"
|
||||||
|
| ^^^^^^^^^^^^^^ PYI052
|
||||||
|
42 | field213: dict[str, str] = {"foo": "bar"}
|
||||||
|
43 | field22: Final = {"foo": 5}
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
2
ruff.schema.json
generated
2
ruff.schema.json
generated
|
|
@ -2127,6 +2127,8 @@
|
||||||
"PYI04",
|
"PYI04",
|
||||||
"PYI042",
|
"PYI042",
|
||||||
"PYI043",
|
"PYI043",
|
||||||
|
"PYI05",
|
||||||
|
"PYI052",
|
||||||
"Q",
|
"Q",
|
||||||
"Q0",
|
"Q0",
|
||||||
"Q00",
|
"Q00",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue