[flake8-pyi]: PYI011, PYI014 (#3238)

Implement PYI011 and PYI014 with the latest changes:

https://github.com/PyCQA/flake8-pyi/pull/326
https://github.com/PyCQA/flake8-pyi/issues/316

rel: https://github.com/charliermarsh/ruff/issues/848
rel: 4212bec43d/pyi.py (L718)
This commit is contained in:
Steve Dignam 2023-02-26 17:11:58 -05:00 committed by GitHub
parent 5f83851329
commit 3a78b59314
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 759 additions and 0 deletions

View file

@ -0,0 +1,79 @@
def f12(
x,
y: str = os.pathsep, # OK
) -> None:
...
def f11(*, x: str = "x") -> None: # OK
...
def f13(
x: list[str] = [
"foo",
"bar",
"baz",
] # OK
) -> None:
...
def f14(
x: tuple[str, ...] = (
"foo",
"bar",
"baz",
) # OK
) -> None:
...
def f15(
x: set[str] = {
"foo",
"bar",
"baz",
} # OK
) -> None:
...
def f16(x: frozenset[bytes] = frozenset({b"foo", b"bar", b"baz"})) -> None: # OK
...
def f17(
x: str = "foo" + "bar", # OK
) -> None:
...
def f18(
x: str = b"foo" + b"bar", # OK
) -> None:
...
def f19(
x: object = "foo" + 4, # OK
) -> None:
...
def f20(
x: int = 5 + 5, # OK
) -> None:
...
def f21(
x: complex = 3j - 3j, # OK
) -> None:
...
def f22(
x: complex = -42.5j + 4.3j, # OK
) -> None:
...

View file

@ -0,0 +1,63 @@
def f12(
x,
y: str = os.pathsep, # Error PYI011 Only simple default values allowed for typed arguments
) -> None: ...
def f11(*, x: str = "x") -> None: ... # OK
def f13(
x: list[
str
] = [ # Error PYI011 Only simple default values allowed for typed arguments
"foo",
"bar",
"baz",
]
) -> None: ...
def f14(
x: tuple[
str, ...
] = ( # Error PYI011 Only simple default values allowed for typed arguments
"foo",
"bar",
"baz",
)
) -> None: ...
def f15(
x: set[
str
] = { # Error PYI011 Only simple default values allowed for typed arguments
"foo",
"bar",
"baz",
}
) -> None: ...
def f16(
x: frozenset[
bytes
] = frozenset( # Error PYI011 Only simple default values allowed for typed arguments
{b"foo", b"bar", b"baz"}
)
) -> None: ...
def f17(
x: str = "foo" # Error PYI011 Only simple default values allowed for typed arguments
+ "bar",
) -> None: ...
def f18(
x: str = b"foo" # Error PYI011 Only simple default values allowed for typed arguments
+ b"bar",
) -> None: ...
def f19(
x: object = "foo" # Error PYI011 Only simple default values allowed for typed arguments
+ 4,
) -> None: ...
def f20(
x: int = 5
+ 5, # Error PYI011 Only simple default values allowed for typed arguments
) -> None: ...
def f21(
x: complex = 3j
- 3j, # Error PYI011 Only simple default values allowed for typed arguments
) -> None: ...
def f22(
x: complex = -42.5j # Error PYI011 Only simple default values allowed for typed arguments
+ 4.3j,
) -> None: ...

View file

@ -0,0 +1,79 @@
def f12(
x,
y=os.pathsep, # OK
) -> None:
...
def f11(*, x="x") -> None:
... # OK
def f13(
x=[ # OK
"foo",
"bar",
"baz",
]
) -> None:
...
def f14(
x=( # OK
"foo",
"bar",
"baz",
)
) -> None:
...
def f15(
x={ # OK
"foo",
"bar",
"baz",
}
) -> None:
...
def f16(x=frozenset({b"foo", b"bar", b"baz"})) -> None:
... # OK
def f17(
x="foo" + "bar", # OK
) -> None:
...
def f18(
x=b"foo" + b"bar", # OK
) -> None:
...
def f19(
x="foo" + 4, # OK
) -> None:
...
def f20(
x=5 + 5, # OK
) -> None:
...
def f21(
x=3j - 3j, # OK
) -> None:
...
def f22(
x=-42.5j + 4.3j, # OK
) -> None:
...

View file

@ -0,0 +1,45 @@
def f12(
x,
y=os.pathsep, # Error PYI014
) -> None: ...
def f11(*, x="x") -> None: ... # OK
def f13(
x=[ # Error PYI014
"foo",
"bar",
"baz",
]
) -> None: ...
def f14(
x=( # Error PYI014
"foo",
"bar",
"baz",
)
) -> None: ...
def f15(
x={ # Error PYI014
"foo",
"bar",
"baz",
}
) -> None: ...
def f16(x=frozenset({b"foo", b"bar", b"baz"})) -> None: ... # Error PYI014
def f17(
x="foo" + "bar", # Error PYI014
) -> None: ...
def f18(
x=b"foo" + b"bar", # Error PYI014
) -> None: ...
def f19(
x="foo" + 4, # Error PYI014
) -> None: ...
def f20(
x=5 + 5, # Error PYI014
) -> None: ...
def f21(
x=3j - 3j, # Error PYI014
) -> None: ...
def f22(
x=-42.5j + 4.3j, # Error PYI014
) -> None: ...

View file

@ -3930,6 +3930,21 @@ where
flake8_bugbear::rules::function_call_argument_default(self, arguments);
}
if self.is_interface_definition {
if self
.settings
.rules
.enabled(&Rule::TypedArgumentSimpleDefaults)
{
flake8_pyi::rules::typed_argument_simple_defaults(self, arguments);
}
}
if self.is_interface_definition {
if self.settings.rules.enabled(&Rule::ArgumentSimpleDefaults) {
flake8_pyi::rules::argument_simple_defaults(self, arguments);
}
}
// Bind, but intentionally avoid walking default expressions, as we handle them
// upstream.
for arg in &arguments.posonlyargs {

View file

@ -489,6 +489,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8Pyi, "008") => Rule::UnrecognizedPlatformName,
(Flake8Pyi, "009") => Rule::PassStatementStubBody,
(Flake8Pyi, "010") => Rule::NonEmptyStubBody,
(Flake8Pyi, "011") => Rule::TypedArgumentSimpleDefaults,
(Flake8Pyi, "014") => Rule::ArgumentSimpleDefaults,
(Flake8Pyi, "021") => Rule::DocstringInStub,
// flake8-pytest-style

View file

@ -464,6 +464,8 @@ ruff_macros::register_rules!(
rules::flake8_pyi::rules::PassStatementStubBody,
rules::flake8_pyi::rules::NonEmptyStubBody,
rules::flake8_pyi::rules::DocstringInStub,
rules::flake8_pyi::rules::TypedArgumentSimpleDefaults,
rules::flake8_pyi::rules::ArgumentSimpleDefaults,
// flake8-pytest-style
rules::flake8_pytest_style::rules::IncorrectFixtureParenthesesStyle,
rules::flake8_pytest_style::rules::FixturePositionalArgs,

View file

@ -23,6 +23,10 @@ mod tests {
#[test_case(Rule::NonEmptyStubBody, Path::new("PYI010.pyi"))]
#[test_case(Rule::PassStatementStubBody, Path::new("PYI009.py"))]
#[test_case(Rule::PassStatementStubBody, Path::new("PYI009.pyi"))]
#[test_case(Rule::TypedArgumentSimpleDefaults, Path::new("PYI011.py"))]
#[test_case(Rule::TypedArgumentSimpleDefaults, Path::new("PYI011.pyi"))]
#[test_case(Rule::ArgumentSimpleDefaults, Path::new("PYI014.py"))]
#[test_case(Rule::ArgumentSimpleDefaults, Path::new("PYI014.pyi"))]
#[test_case(Rule::DocstringInStub, Path::new("PYI021.py"))]
#[test_case(Rule::DocstringInStub, Path::new("PYI021.pyi"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {

View file

@ -2,6 +2,10 @@ pub use docstring_in_stubs::{docstring_in_stubs, DocstringInStub};
pub use non_empty_stub_body::{non_empty_stub_body, NonEmptyStubBody};
pub use pass_statement_stub_body::{pass_statement_stub_body, PassStatementStubBody};
pub use prefix_type_params::{prefix_type_params, PrefixTypeParams};
pub use simple_defaults::{
argument_simple_defaults, typed_argument_simple_defaults, ArgumentSimpleDefaults,
TypedArgumentSimpleDefaults,
};
pub use unrecognized_platform::{
unrecognized_platform, UnrecognizedPlatformCheck, UnrecognizedPlatformName,
};
@ -11,3 +15,5 @@ mod non_empty_stub_body;
mod pass_statement_stub_body;
mod prefix_type_params;
mod unrecognized_platform;
mod simple_defaults;

View file

@ -0,0 +1,220 @@
use rustpython_parser::ast::{Arguments, Constant, Expr, ExprKind, Operator, Unaryop};
use ruff_macros::{define_violation, derive_message_formats};
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::Diagnostic;
use crate::violation::Violation;
define_violation!(
pub struct TypedArgumentSimpleDefaults;
);
/// PYI011
impl Violation for TypedArgumentSimpleDefaults {
#[derive_message_formats]
fn message(&self) -> String {
format!("Only simple default values allowed for typed arguments")
}
}
define_violation!(
pub struct ArgumentSimpleDefaults;
);
/// PYI014
impl Violation for ArgumentSimpleDefaults {
#[derive_message_formats]
fn message(&self) -> String {
format!("Only simple default values allowed for arguments")
}
}
const ALLOWED_ATTRIBUTES_IN_DEFAULTS: &[&[&str]] = &[
&["sys", "stdin"],
&["sys", "stdout"],
&["sys", "stderr"],
&["sys", "version"],
&["sys", "version_info"],
&["sys", "platform"],
&["sys", "executable"],
&["sys", "prefix"],
&["sys", "exec_prefix"],
&["sys", "base_prefix"],
&["sys", "byteorder"],
&["sys", "maxsize"],
&["sys", "hexversion"],
&["sys", "winver"],
];
fn is_valid_default_value_with_annotation(default: &Expr, checker: &Checker) -> bool {
match &default.node {
ExprKind::Constant {
value: Constant::Ellipsis | Constant::None,
..
} => {
return true;
}
ExprKind::Constant {
value: Constant::Str(..),
..
} => return checker.locator.slice(&Range::from_located(default)).len() <= 50,
ExprKind::Constant {
value: Constant::Bytes(..),
..
} => return checker.locator.slice(&Range::from_located(default)).len() <= 50,
ExprKind::Constant {
value: Constant::Int(..),
..
} => {
return checker.locator.slice(&Range::from_located(default)).len() <= 10;
}
ExprKind::UnaryOp {
op: Unaryop::USub,
operand,
} => {
if let ExprKind::Constant {
value: Constant::Int(..),
..
} = &operand.node
{
return checker.locator.slice(&Range::from_located(operand)).len() <= 10;
}
}
ExprKind::BinOp {
left,
op: Operator::Add | Operator::Sub,
right,
} => {
// 1 + 2j
// 1 - 2j
// -1 - 2j
// -1 + 2j
if let ExprKind::Constant {
value: Constant::Complex { .. },
..
} = right.node
{
// 1 + 2j
// 1 - 2j
if let ExprKind::Constant {
value: Constant::Int(..),
..
} = &left.node
{
return checker.locator.slice(&Range::from_located(left)).len() <= 10;
} else if let ExprKind::UnaryOp {
op: Unaryop::USub,
operand,
} = &left.node
{
// -1 + 2j
// -1 - 2j
if let ExprKind::Constant {
value: Constant::Int(..),
..
} = &operand.node
{
return checker.locator.slice(&Range::from_located(operand)).len() <= 10;
}
}
}
}
// `sys.stdin`, etc.
ExprKind::Attribute { .. } => {
if checker
.resolve_call_path(default)
.map_or(false, |call_path| {
ALLOWED_ATTRIBUTES_IN_DEFAULTS
.iter()
.any(|target| call_path.as_slice() == *target)
})
{
return true;
}
}
_ => {}
}
false
}
/// PYI011
pub fn typed_argument_simple_defaults(checker: &mut Checker, args: &Arguments) {
if !args.defaults.is_empty() {
let defaults_start = args.posonlyargs.len() + args.args.len() - args.defaults.len();
for (i, arg) in args.posonlyargs.iter().chain(&args.args).enumerate() {
if let Some(default) = i
.checked_sub(defaults_start)
.and_then(|i| args.defaults.get(i))
{
if arg.node.annotation.is_some() {
if !is_valid_default_value_with_annotation(default, checker) {
checker.diagnostics.push(Diagnostic::new(
TypedArgumentSimpleDefaults,
Range::from_located(default),
));
}
}
}
}
}
if !args.kw_defaults.is_empty() {
let defaults_start = args.kwonlyargs.len() - args.kw_defaults.len();
for (i, kwarg) in args.kwonlyargs.iter().enumerate() {
if let Some(default) = i
.checked_sub(defaults_start)
.and_then(|i| args.kw_defaults.get(i))
{
if kwarg.node.annotation.is_some() {
if !is_valid_default_value_with_annotation(default, checker) {
checker.diagnostics.push(Diagnostic::new(
TypedArgumentSimpleDefaults,
Range::from_located(default),
));
}
}
}
}
}
}
/// PYI014
pub fn argument_simple_defaults(checker: &mut Checker, args: &Arguments) {
if !args.defaults.is_empty() {
let defaults_start = args.posonlyargs.len() + args.args.len() - args.defaults.len();
for (i, arg) in args.posonlyargs.iter().chain(&args.args).enumerate() {
if let Some(default) = i
.checked_sub(defaults_start)
.and_then(|i| args.defaults.get(i))
{
if arg.node.annotation.is_none() {
if !is_valid_default_value_with_annotation(default, checker) {
checker.diagnostics.push(Diagnostic::new(
ArgumentSimpleDefaults,
Range::from_located(default),
));
}
}
}
}
}
if !args.kw_defaults.is_empty() {
let defaults_start = args.kwonlyargs.len() - args.kw_defaults.len();
for (i, kwarg) in args.kwonlyargs.iter().enumerate() {
if let Some(default) = i
.checked_sub(defaults_start)
.and_then(|i| args.kw_defaults.get(i))
{
if kwarg.node.annotation.is_none() {
if !is_valid_default_value_with_annotation(default, checker) {
checker.diagnostics.push(Diagnostic::new(
ArgumentSimpleDefaults,
Range::from_located(default),
));
}
}
}
}
}
}

View file

@ -0,0 +1,6 @@
---
source: crates/ruff/src/rules/flake8_pyi/mod.rs
expression: diagnostics
---
[]

View file

@ -0,0 +1,115 @@
---
source: crates/ruff/src/rules/flake8_pyi/mod.rs
expression: diagnostics
---
- kind:
TypedArgumentSimpleDefaults: ~
location:
row: 3
column: 13
end_location:
row: 3
column: 23
fix: ~
parent: ~
- kind:
TypedArgumentSimpleDefaults: ~
location:
row: 9
column: 8
end_location:
row: 13
column: 5
fix: ~
parent: ~
- kind:
TypedArgumentSimpleDefaults: ~
location:
row: 18
column: 8
end_location:
row: 22
column: 5
fix: ~
parent: ~
- kind:
TypedArgumentSimpleDefaults: ~
location:
row: 27
column: 8
end_location:
row: 31
column: 5
fix: ~
parent: ~
- kind:
TypedArgumentSimpleDefaults: ~
location:
row: 36
column: 8
end_location:
row: 38
column: 5
fix: ~
parent: ~
- kind:
TypedArgumentSimpleDefaults: ~
location:
row: 41
column: 13
end_location:
row: 42
column: 11
fix: ~
parent: ~
- kind:
TypedArgumentSimpleDefaults: ~
location:
row: 45
column: 13
end_location:
row: 46
column: 12
fix: ~
parent: ~
- kind:
TypedArgumentSimpleDefaults: ~
location:
row: 49
column: 16
end_location:
row: 50
column: 7
fix: ~
parent: ~
- kind:
TypedArgumentSimpleDefaults: ~
location:
row: 53
column: 13
end_location:
row: 54
column: 7
fix: ~
parent: ~
- kind:
TypedArgumentSimpleDefaults: ~
location:
row: 57
column: 17
end_location:
row: 58
column: 8
fix: ~
parent: ~
- kind:
TypedArgumentSimpleDefaults: ~
location:
row: 61
column: 17
end_location:
row: 62
column: 10
fix: ~
parent: ~

View file

@ -0,0 +1,6 @@
---
source: crates/ruff/src/rules/flake8_pyi/mod.rs
expression: diagnostics
---
[]

View file

@ -0,0 +1,115 @@
---
source: crates/ruff/src/rules/flake8_pyi/mod.rs
expression: diagnostics
---
- kind:
ArgumentSimpleDefaults: ~
location:
row: 3
column: 6
end_location:
row: 3
column: 16
fix: ~
parent: ~
- kind:
ArgumentSimpleDefaults: ~
location:
row: 7
column: 6
end_location:
row: 11
column: 5
fix: ~
parent: ~
- kind:
ArgumentSimpleDefaults: ~
location:
row: 14
column: 6
end_location:
row: 18
column: 5
fix: ~
parent: ~
- kind:
ArgumentSimpleDefaults: ~
location:
row: 21
column: 6
end_location:
row: 25
column: 5
fix: ~
parent: ~
- kind:
ArgumentSimpleDefaults: ~
location:
row: 27
column: 10
end_location:
row: 27
column: 45
fix: ~
parent: ~
- kind:
ArgumentSimpleDefaults: ~
location:
row: 29
column: 6
end_location:
row: 29
column: 19
fix: ~
parent: ~
- kind:
ArgumentSimpleDefaults: ~
location:
row: 32
column: 6
end_location:
row: 32
column: 21
fix: ~
parent: ~
- kind:
ArgumentSimpleDefaults: ~
location:
row: 35
column: 6
end_location:
row: 35
column: 15
fix: ~
parent: ~
- kind:
ArgumentSimpleDefaults: ~
location:
row: 38
column: 6
end_location:
row: 38
column: 11
fix: ~
parent: ~
- kind:
ArgumentSimpleDefaults: ~
location:
row: 41
column: 6
end_location:
row: 41
column: 13
fix: ~
parent: ~
- kind:
ArgumentSimpleDefaults: ~
location:
row: 44
column: 6
end_location:
row: 44
column: 19
fix: ~
parent: ~

2
ruff.schema.json generated
View file

@ -1925,6 +1925,8 @@
"PYI009",
"PYI01",
"PYI010",
"PYI011",
"PYI014",
"PYI02",
"PYI021",
"Q",