diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC008.py b/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC008.py index b6ca0e8e5d..2b17d7cb0d 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC008.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC008.py @@ -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""" \ No newline at end of file diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/rules/type_alias_quotes.rs b/crates/ruff_linter/src/rules/flake8_type_checking/rules/type_alias_quotes.rs index 1a4e250e0f..53c1d004c3 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/rules/type_alias_quotes.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/rules/type_alias_quotes.rs @@ -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 { diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__quoted-type-alias_TC008.py.snap b/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__quoted-type-alias_TC008.py.snap index 626c985dbe..b55796e607 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__quoted-type-alias_TC008.py.snap +++ b/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__quoted-type-alias_TC008.py.snap @@ -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