[flake8-pyi] PYI053: Exclude string literals that are the first argument to warnings.deprecated or typing_extensions.deprecated (#9423)

Fixes #9420
This commit is contained in:
Alex Waygood
2024-01-07 23:41:14 +00:00
committed by GitHub
parent 63953431a6
commit d5a439cbd3
4 changed files with 178 additions and 80 deletions

View File

@@ -1,3 +1,9 @@
import warnings
import typing_extensions
from collections.abc import Callable
from warnings import deprecated
def f1(x: str = "50 character stringggggggggggggggggggggggggggggggg") -> None:
...
@@ -45,3 +51,24 @@ class Demo:
def func() -> None:
"""Docstrings are excluded from this rule. Some padding."""
@warnings.deprecated("Veeeeeeeeeeeeeeeeeeeeeeery long deprecation message, but that's okay")
def deprecated_function() -> None: ...
@typing_extensions.deprecated("Another loooooooooooooooooooooong deprecation message, it's still okay")
def another_deprecated_function() -> None: ...
@deprecated("A third loooooooooooooooooooooooooooooong deprecation message")
def a_third_deprecated_function() -> None: ...
def not_warnings_dot_deprecated(
msg: str
) -> Callable[[Callable[[], None]], Callable[[], None]]: ...
@not_warnings_dot_deprecated("Not warnings.deprecated, so this one *should* lead to PYI053 in a stub!")
def not_a_deprecated_function() -> None: ...

View File

@@ -1,3 +1,7 @@
import warnings
import typing_extensions
from typing_extensions import deprecated
def f1(x: str = "50 character stringggggggggggggggggggggggggggggggg") -> None: ... # OK
def f2(
x: str = "51 character stringgggggggggggggggggggggggggggggggg", # Error: PYI053
@@ -38,3 +42,25 @@ class Demo:
def func() -> None:
"""Docstrings are excluded from this rule. Some padding.""" # OK
@warnings.deprecated(
"Veeeeeeeeeeeeeeeeeeeeeeery long deprecation message, but that's okay" # OK
)
def deprecated_function() -> None: ...
@typing_extensions.deprecated(
"Another loooooooooooooooooooooong deprecation message, it's still okay" # OK
)
def another_deprecated_function() -> None: ...
@deprecated("A third loooooooooooooooooooooooooooooong deprecation message") # OK
def a_third_deprecated_function() -> None: ...
def not_warnings_dot_deprecated(
msg: str
) -> Callable[[Callable[[], None]], Callable[[], None]]: ...
@not_warnings_dot_deprecated(
"Not warnings.deprecated, so this one *should* lead to PYI053 in a stub!" # Error: PYI053
)
def not_a_deprecated_function() -> None: ...

View File

@@ -2,6 +2,7 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::is_docstring_stmt;
use ruff_python_ast::{self as ast, StringLike};
use ruff_python_semantic::SemanticModel;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
@@ -44,8 +45,14 @@ impl AlwaysFixableViolation for StringOrBytesTooLong {
/// PYI053
pub(crate) fn string_or_bytes_too_long(checker: &mut Checker, string: StringLike) {
let semantic = checker.semantic();
// Ignore docstrings.
if is_docstring_stmt(checker.semantic().current_statement()) {
if is_docstring_stmt(semantic.current_statement()) {
return;
}
if is_warnings_dot_deprecated(semantic.current_expression_parent(), semantic) {
return;
}
@@ -67,3 +74,21 @@ pub(crate) fn string_or_bytes_too_long(checker: &mut Checker, string: StringLike
)));
checker.diagnostics.push(diagnostic);
}
fn is_warnings_dot_deprecated(expr: Option<&ast::Expr>, semantic: &SemanticModel) -> bool {
// Does `expr` represent a call to `warnings.deprecated` or `typing_extensions.deprecated`?
let Some(expr) = expr else {
return false;
};
let Some(call) = expr.as_call_expr() else {
return false;
};
semantic
.resolve_call_path(&call.func)
.is_some_and(|call_path| {
matches!(
call_path.as_slice(),
["warnings" | "typing_extensions", "deprecated"]
)
})
}

View File

@@ -1,128 +1,148 @@
---
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
---
PYI053.pyi:3:14: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
PYI053.pyi:7:14: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
1 | def f1(x: str = "50 character stringggggggggggggggggggggggggggggggg") -> None: ... # OK
2 | def f2(
3 | x: str = "51 character stringgggggggggggggggggggggggggggggggg", # Error: PYI053
5 | def f1(x: str = "50 character stringggggggggggggggggggggggggggggggg") -> None: ... # OK
6 | def f2(
7 | x: str = "51 character stringgggggggggggggggggggggggggggggggg", # Error: PYI053
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
4 | ) -> None: ...
5 | def f3(
8 | ) -> None: ...
9 | def f3(
|
= help: Replace with `...`
Safe fix
1 1 | def f1(x: str = "50 character stringggggggggggggggggggggggggggggggg") -> None: ... # OK
2 2 | def f2(
3 |- x: str = "51 character stringgggggggggggggggggggggggggggggggg", # Error: PYI053
3 |+ x: str = ..., # Error: PYI053
4 4 | ) -> None: ...
5 5 | def f3(
6 6 | x: str = "50 character stringgggggggggggggggggggggggggggggg\U0001f600", # OK
4 4 |
5 5 | def f1(x: str = "50 character stringggggggggggggggggggggggggggggggg") -> None: ... # OK
6 6 | def f2(
7 |- x: str = "51 character stringgggggggggggggggggggggggggggggggg", # Error: PYI053
7 |+ x: str = ..., # Error: PYI053
8 8 | ) -> None: ...
9 9 | def f3(
10 10 | x: str = "50 character stringgggggggggggggggggggggggggggggg\U0001f600", # OK
PYI053.pyi:9:14: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
PYI053.pyi:13:14: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
7 | ) -> None: ...
8 | def f4(
9 | x: str = "51 character stringggggggggggggggggggggggggggggggg\U0001f600", # Error: PYI053
11 | ) -> None: ...
12 | def f4(
13 | x: str = "51 character stringggggggggggggggggggggggggggggggg\U0001f600", # Error: PYI053
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
10 | ) -> None: ...
11 | def f5(
14 | ) -> None: ...
15 | def f5(
|
= help: Replace with `...`
Safe fix
6 6 | x: str = "50 character stringgggggggggggggggggggggggggggggg\U0001f600", # OK
7 7 | ) -> None: ...
8 8 | def f4(
9 |- x: str = "51 character stringggggggggggggggggggggggggggggggg\U0001f600", # Error: PYI053
9 |+ x: str = ..., # Error: PYI053
10 10 | ) -> None: ...
11 11 | def f5(
12 12 | x: bytes = b"50 character byte stringgggggggggggggggggggggggggg", # OK
10 10 | x: str = "50 character stringgggggggggggggggggggggggggggggg\U0001f600", # OK
11 11 | ) -> None: ...
12 12 | def f4(
13 |- x: str = "51 character stringggggggggggggggggggggggggggggggg\U0001f600", # Error: PYI053
13 |+ x: str = ..., # Error: PYI053
14 14 | ) -> None: ...
15 15 | def f5(
16 16 | x: bytes = b"50 character byte stringgggggggggggggggggggggggggg", # OK
PYI053.pyi:21:16: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
PYI053.pyi:25:16: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
19 | ) -> None: ...
20 | def f8(
21 | x: bytes = b"51 character byte stringgggggggggggggggggggggggggg\xff", # Error: PYI053
23 | ) -> None: ...
24 | def f8(
25 | x: bytes = b"51 character byte stringgggggggggggggggggggggggggg\xff", # Error: PYI053
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
22 | ) -> None: ...
26 | ) -> None: ...
|
= help: Replace with `...`
Safe fix
18 18 | x: bytes = b"50 character byte stringggggggggggggggggggggggggg\xff", # OK
19 19 | ) -> None: ...
20 20 | def f8(
21 |- x: bytes = b"51 character byte stringgggggggggggggggggggggggggg\xff", # Error: PYI053
21 |+ x: bytes = ..., # Error: PYI053
22 22 | ) -> None: ...
23 23 |
24 24 | foo: str = "50 character stringggggggggggggggggggggggggggggggg" # OK
PYI053.pyi:26:12: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
24 | foo: str = "50 character stringggggggggggggggggggggggggggggggg" # OK
25 |
26 | bar: str = "51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
27 |
28 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
|
= help: Replace with `...`
Safe fix
23 23 |
24 24 | foo: str = "50 character stringggggggggggggggggggggggggggggggg" # OK
25 25 |
26 |-bar: str = "51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
26 |+bar: str = ... # Error: PYI053
22 22 | x: bytes = b"50 character byte stringggggggggggggggggggggggggg\xff", # OK
23 23 | ) -> None: ...
24 24 | def f8(
25 |- x: bytes = b"51 character byte stringgggggggggggggggggggggggggg\xff", # Error: PYI053
25 |+ x: bytes = ..., # Error: PYI053
26 26 | ) -> None: ...
27 27 |
28 28 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
29 29 |
28 28 | foo: str = "50 character stringggggggggggggggggggggggggggggggg" # OK
PYI053.pyi:30:14: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
PYI053.pyi:30:12: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
28 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
28 | foo: str = "50 character stringggggggggggggggggggggggggggggggg" # OK
29 |
30 | qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff" # Error: PYI053
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
30 | bar: str = "51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
31 |
32 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
32 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
|
= help: Replace with `...`
Safe fix
27 27 |
28 28 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
28 28 | foo: str = "50 character stringggggggggggggggggggggggggggggggg" # OK
29 29 |
30 |-qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff" # Error: PYI053
30 |+qux: bytes = ... # Error: PYI053
30 |-bar: str = "51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
30 |+bar: str = ... # Error: PYI053
31 31 |
32 32 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
32 32 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
33 33 |
PYI053.pyi:34:15: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
PYI053.pyi:34:14: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
32 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
32 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
33 |
34 | fbar: str = f"51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
34 | qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff" # Error: PYI053
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
35 |
36 | class Demo:
36 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
|
= help: Replace with `...`
Safe fix
31 31 |
32 32 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
32 32 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
33 33 |
34 |-fbar: str = f"51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
34 |+fbar: str = f"..." # Error: PYI053
34 |-qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff" # Error: PYI053
34 |+qux: bytes = ... # Error: PYI053
35 35 |
36 36 | class Demo:
37 37 | """Docstrings are excluded from this rule. Some padding.""" # OK
36 36 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
37 37 |
PYI053.pyi:38:15: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
36 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
37 |
38 | fbar: str = f"51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
39 |
40 | class Demo:
|
= help: Replace with `...`
Safe fix
35 35 |
36 36 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
37 37 |
38 |-fbar: str = f"51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
38 |+fbar: str = f"..." # Error: PYI053
39 39 |
40 40 | class Demo:
41 41 | """Docstrings are excluded from this rule. Some padding.""" # OK
PYI053.pyi:64:5: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
63 | @not_warnings_dot_deprecated(
64 | "Not warnings.deprecated, so this one *should* lead to PYI053 in a stub!" # Error: PYI053
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
65 | )
66 | def not_a_deprecated_function() -> None: ...
|
= help: Replace with `...`
Safe fix
61 61 | ) -> Callable[[Callable[[], None]], Callable[[], None]]: ...
62 62 |
63 63 | @not_warnings_dot_deprecated(
64 |- "Not warnings.deprecated, so this one *should* lead to PYI053 in a stub!" # Error: PYI053
64 |+ ... # Error: PYI053
65 65 | )
66 66 | def not_a_deprecated_function() -> None: ...