mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 18:58:04 +00:00
[flake8-type-checking
] Fix syntax error introduced by fix (TC008
) (#19150)
<!-- Thank you for contributing to Ruff/ty! To help us out with reviewing, please consider the following: - Does this pull request include a summary of the change? (See below.) - Does this pull request include a descriptive title? (Please prefix with `[ty]` for ty pull requests.) - Does this pull request include references to any relevant issues? --> ## Summary <!-- What's the purpose of the change? What does it do, and why? --> I noticed this while working on #18972. If the string targeted by [quoted-type-alias (TC008)](https://docs.astral.sh/ruff/rules/quoted-type-alias/#quoted-type-alias-tc008) is a multiline string, the fix would introduce a syntax error. This PR fixes that by adding parenthesis around the resulting replacement if the string contained any newline characters (`\n`, `\r`) if it doesn't already have parenthesis outside `("""...""")` or inside `"""(...)"""` the annotation. Failing examples: https://play.ruff.rs/8793eb95-860a-4bb3-9cbc-6a042fee2946 ``` PS D:\rust_projects\ruff> Get-Content issue.py ``` ```py from typing import TypeAlias OptInt: TypeAlias = """int | None""" type OptInt = """int | None""" ``` ``` PS D:\rust_projects\ruff> uvx ruff check issue.py --isolated --select TC008 --fix --diff --preview ``` ``` error: Fix introduced a syntax error. Reverting all changes. This indicates a bug in Ruff. If you could open an issue at: https://github.com/astral-sh/ruff/issues/new?title=%5BFix%20error%5D ...quoting the contents of `issue.py`, the rule codes TC008, along with the `pyproject.toml` settings and executed command, we'd be very appreciative! ``` This PR also makes the example error out-of-the-box for #18972 Old example: https://play.ruff.rs/f6cd5adb-7f9b-444d-bb3e-8c045241d93e ```py OptInt: TypeAlias = "int | None" ``` New example: https://play.ruff.rs/906c1056-72c0-4777-b70b-2114eb9e6eaf ```py from typing import TypeAlias OptInt: TypeAlias = "int | None" ``` The import was also added to the "Use instead" section. ## Test Plan <!-- How was it tested? --> Added multiple test cases
This commit is contained in:
parent
6e77e1b760
commit
47f88b3008
3 changed files with 235 additions and 1 deletions
|
@ -50,3 +50,23 @@ class Baz:
|
|||
class Nested:
|
||||
a: TypeAlias = 'Baz' # OK
|
||||
type A = 'Baz' # TC008
|
||||
|
||||
# O should have parenthesis added
|
||||
o: TypeAlias = """int
|
||||
| None"""
|
||||
type O = """int
|
||||
| None"""
|
||||
|
||||
# P, Q, and R should not have parenthesis added
|
||||
p: TypeAlias = ("""int
|
||||
| None""")
|
||||
type P = ("""int
|
||||
| None""")
|
||||
|
||||
q: TypeAlias = """(int
|
||||
| None)"""
|
||||
type Q = """(int
|
||||
| None)"""
|
||||
|
||||
r: TypeAlias = """int | None"""
|
||||
type R = """int | None"""
|
|
@ -11,6 +11,7 @@ use crate::registry::Rule;
|
|||
use crate::rules::flake8_type_checking::helpers::quote_type_expression;
|
||||
use crate::{AlwaysFixableViolation, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks if [PEP 613] explicit type aliases contain references to
|
||||
|
@ -87,11 +88,15 @@ impl Violation for UnquotedTypeAlias {
|
|||
/// ## Example
|
||||
/// Given:
|
||||
/// ```python
|
||||
/// from typing import TypeAlias
|
||||
///
|
||||
/// OptInt: TypeAlias = "int | None"
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from typing import TypeAlias
|
||||
///
|
||||
/// OptInt: TypeAlias = int | None
|
||||
/// ```
|
||||
///
|
||||
|
@ -287,7 +292,30 @@ pub(crate) fn quoted_type_alias(
|
|||
|
||||
let range = annotation_expr.range();
|
||||
let mut diagnostic = checker.report_diagnostic(QuotedTypeAlias, range);
|
||||
let edit = Edit::range_replacement(annotation_expr.value.to_string(), range);
|
||||
let fix_string = annotation_expr.value.to_string();
|
||||
let fix_string = if (fix_string.contains('\n') || fix_string.contains('\r'))
|
||||
&& parenthesized_range(
|
||||
// Check for parenthesis outside string ("""...""")
|
||||
annotation_expr.into(),
|
||||
checker.semantic().current_statement().into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.is_none()
|
||||
&& parenthesized_range(
|
||||
// Check for parenthesis inside string """(...)"""
|
||||
expr.into(),
|
||||
annotation_expr.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.is_none()
|
||||
{
|
||||
format!("({fix_string})")
|
||||
} else {
|
||||
fix_string
|
||||
};
|
||||
let edit = Edit::range_replacement(fix_string, range);
|
||||
if checker.comment_ranges().intersects(range) {
|
||||
diagnostic.set_fix(Fix::unsafe_edit(edit));
|
||||
} else {
|
||||
|
|
|
@ -409,6 +409,8 @@ TC008.py:52:18: TC008 [*] Remove quotes from type alias
|
|||
51 | a: TypeAlias = 'Baz' # OK
|
||||
52 | type A = 'Baz' # TC008
|
||||
| ^^^^^ TC008
|
||||
53 |
|
||||
54 | # O should have parenthesis added
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
|
@ -418,3 +420,187 @@ TC008.py:52:18: TC008 [*] Remove quotes from type alias
|
|||
51 51 | a: TypeAlias = 'Baz' # OK
|
||||
52 |- type A = 'Baz' # TC008
|
||||
52 |+ type A = Baz # TC008
|
||||
53 53 |
|
||||
54 54 | # O should have parenthesis added
|
||||
55 55 | o: TypeAlias = """int
|
||||
|
||||
TC008.py:55:16: TC008 [*] Remove quotes from type alias
|
||||
|
|
||||
54 | # O should have parenthesis added
|
||||
55 | o: TypeAlias = """int
|
||||
| ________________^
|
||||
56 | | | None"""
|
||||
| |_________^ TC008
|
||||
57 | type O = """int
|
||||
58 | | None"""
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
52 52 | type A = 'Baz' # TC008
|
||||
53 53 |
|
||||
54 54 | # O should have parenthesis added
|
||||
55 |-o: TypeAlias = """int
|
||||
56 |-| None"""
|
||||
55 |+o: TypeAlias = (int
|
||||
56 |+| None)
|
||||
57 57 | type O = """int
|
||||
58 58 | | None"""
|
||||
59 59 |
|
||||
|
||||
TC008.py:57:10: TC008 [*] Remove quotes from type alias
|
||||
|
|
||||
55 | o: TypeAlias = """int
|
||||
56 | | None"""
|
||||
57 | type O = """int
|
||||
| __________^
|
||||
58 | | | None"""
|
||||
| |_________^ TC008
|
||||
59 |
|
||||
60 | # P, Q, and R should not have parenthesis added
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
54 54 | # O should have parenthesis added
|
||||
55 55 | o: TypeAlias = """int
|
||||
56 56 | | None"""
|
||||
57 |-type O = """int
|
||||
58 |-| None"""
|
||||
57 |+type O = (int
|
||||
58 |+| None)
|
||||
59 59 |
|
||||
60 60 | # P, Q, and R should not have parenthesis added
|
||||
61 61 | p: TypeAlias = ("""int
|
||||
|
||||
TC008.py:61:17: TC008 [*] Remove quotes from type alias
|
||||
|
|
||||
60 | # P, Q, and R should not have parenthesis added
|
||||
61 | p: TypeAlias = ("""int
|
||||
| _________________^
|
||||
62 | | | None""")
|
||||
| |_________^ TC008
|
||||
63 | type P = ("""int
|
||||
64 | | None""")
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
58 58 | | None"""
|
||||
59 59 |
|
||||
60 60 | # P, Q, and R should not have parenthesis added
|
||||
61 |-p: TypeAlias = ("""int
|
||||
62 |-| None""")
|
||||
61 |+p: TypeAlias = (int
|
||||
62 |+| None)
|
||||
63 63 | type P = ("""int
|
||||
64 64 | | None""")
|
||||
65 65 |
|
||||
|
||||
TC008.py:63:11: TC008 [*] Remove quotes from type alias
|
||||
|
|
||||
61 | p: TypeAlias = ("""int
|
||||
62 | | None""")
|
||||
63 | type P = ("""int
|
||||
| ___________^
|
||||
64 | | | None""")
|
||||
| |_________^ TC008
|
||||
65 |
|
||||
66 | q: TypeAlias = """(int
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
60 60 | # P, Q, and R should not have parenthesis added
|
||||
61 61 | p: TypeAlias = ("""int
|
||||
62 62 | | None""")
|
||||
63 |-type P = ("""int
|
||||
64 |-| None""")
|
||||
63 |+type P = (int
|
||||
64 |+| None)
|
||||
65 65 |
|
||||
66 66 | q: TypeAlias = """(int
|
||||
67 67 | | None)"""
|
||||
|
||||
TC008.py:66:16: TC008 [*] Remove quotes from type alias
|
||||
|
|
||||
64 | | None""")
|
||||
65 |
|
||||
66 | q: TypeAlias = """(int
|
||||
| ________________^
|
||||
67 | | | None)"""
|
||||
| |__________^ TC008
|
||||
68 | type Q = """(int
|
||||
69 | | None)"""
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
63 63 | type P = ("""int
|
||||
64 64 | | None""")
|
||||
65 65 |
|
||||
66 |-q: TypeAlias = """(int
|
||||
67 |-| None)"""
|
||||
66 |+q: TypeAlias = (int
|
||||
67 |+| None)
|
||||
68 68 | type Q = """(int
|
||||
69 69 | | None)"""
|
||||
70 70 |
|
||||
|
||||
TC008.py:68:10: TC008 [*] Remove quotes from type alias
|
||||
|
|
||||
66 | q: TypeAlias = """(int
|
||||
67 | | None)"""
|
||||
68 | type Q = """(int
|
||||
| __________^
|
||||
69 | | | None)"""
|
||||
| |__________^ TC008
|
||||
70 |
|
||||
71 | r: TypeAlias = """int | None"""
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
65 65 |
|
||||
66 66 | q: TypeAlias = """(int
|
||||
67 67 | | None)"""
|
||||
68 |-type Q = """(int
|
||||
69 |-| None)"""
|
||||
68 |+type Q = (int
|
||||
69 |+| None)
|
||||
70 70 |
|
||||
71 71 | r: TypeAlias = """int | None"""
|
||||
72 72 | type R = """int | None"""
|
||||
|
||||
TC008.py:71:16: TC008 [*] Remove quotes from type alias
|
||||
|
|
||||
69 | | None)"""
|
||||
70 |
|
||||
71 | r: TypeAlias = """int | None"""
|
||||
| ^^^^^^^^^^^^^^^^ TC008
|
||||
72 | type R = """int | None"""
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
68 68 | type Q = """(int
|
||||
69 69 | | None)"""
|
||||
70 70 |
|
||||
71 |-r: TypeAlias = """int | None"""
|
||||
71 |+r: TypeAlias = int | None
|
||||
72 72 | type R = """int | None"""
|
||||
|
||||
TC008.py:72:10: TC008 [*] Remove quotes from type alias
|
||||
|
|
||||
71 | r: TypeAlias = """int | None"""
|
||||
72 | type R = """int | None"""
|
||||
| ^^^^^^^^^^^^^^^^ TC008
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
69 69 | | None)"""
|
||||
70 70 |
|
||||
71 71 | r: TypeAlias = """int | None"""
|
||||
72 |-type R = """int | None"""
|
||||
72 |+type R = int | None
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue