Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 7 commits into from
Jan 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
};
AlexWaygood marked this conversation as resolved.
Show resolved Hide resolved
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: ...


Loading