Skip to content

Commit

Permalink
[flake8-pyi] PYI053: Exclude string literals that are the first argum…
Browse files Browse the repository at this point in the history
…ent to `warnings.deprecated` or `typing_extensions.deprecated` (#9423)

Fixes #9420
  • Loading branch information
AlexWaygood authored Jan 7, 2024
1 parent 6395343 commit d5a439c
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 78 deletions.
27 changes: 27 additions & 0 deletions crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI053.py
Original file line number Diff line number Diff line change
@@ -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:
...

Expand Down Expand Up @@ -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: ...
26 changes: 26 additions & 0 deletions crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI053.pyi
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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: ...
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

Expand All @@ -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"]
)
})
}
Original file line number Diff line number Diff line change
@@ -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
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 | foo: str = "50 character stringggggggggggggggggggggggggggggggg" # OK

PYI053.pyi:26:12: 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
|
24 | foo: str = "50 character stringggggggggggggggggggggggggggggggg" # OK
25 |
26 | bar: str = "51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
28 | foo: str = "50 character stringggggggggggggggggggggggggggggggg" # OK
29 |
30 | bar: str = "51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
27 |
28 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
31 |
32 | 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
27 27 |
28 28 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
28 28 | foo: str = "50 character stringggggggggggggggggggggggggggggggg" # OK
29 29 |
30 |-bar: str = "51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
30 |+bar: str = ... # Error: PYI053
31 31 |
32 32 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
33 33 |

PYI053.pyi:30:14: 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
|
28 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
29 |
30 | qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff" # Error: PYI053
32 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
33 |
34 | qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff" # Error: PYI053
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
31 |
32 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
35 |
36 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
|
= help: Replace with `...`

Safe fix
27 27 |
28 28 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
29 29 |
30 |-qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff" # Error: PYI053
30 |+qux: bytes = ... # 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 |
34 |-qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff" # Error: PYI053
34 |+qux: bytes = ... # Error: PYI053
35 35 |
36 36 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
37 37 |

PYI053.pyi:34:15: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
PYI053.pyi:38:15: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
32 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
33 |
34 | fbar: str = f"51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
36 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
37 |
38 | fbar: str = f"51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
35 |
36 | class Demo:
39 |
40 | class Demo:
|
= help: Replace with `...`

Safe fix
31 31 |
32 32 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
33 33 |
34 |-fbar: str = f"51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
34 |+fbar: str = f"..." # 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 |
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: ...


0 comments on commit d5a439c

Please sign in to comment.