From 1e5dc51203eaf0f0f7c9f48a0c667c0da9fc77cc Mon Sep 17 00:00:00 2001 From: harupy Date: Wed, 25 Dec 2024 12:25:19 +0900 Subject: [PATCH 1/4] Autofix for none-not-at-end-of-union (RUF036) --- .../resources/test/fixtures/ruff/RUF036.py | 12 ++ .../ruff/rules/none_not_at_end_of_union.rs | 49 +++++- ..._rules__ruff__tests__RUF036_RUF036.py.snap | 144 +++++++++++++++--- ...rules__ruff__tests__RUF036_RUF036.pyi.snap | 80 +++++++++- 4 files changed, 257 insertions(+), 28 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF036.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF036.py index 00aca49ab66bb..4262e920aee87 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF036.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF036.py @@ -9,6 +9,10 @@ def func2() -> None | int: ... +def func2() -> None | int: + ... + + def func3(arg: None | None | int): ... @@ -25,6 +29,14 @@ def func6(arg: U[None, None, int]): ... +def func7() -> U[ + None, + # comment + int +]: + ... + + # Ok def good_func1(arg: int | None): ... diff --git a/crates/ruff_linter/src/rules/ruff/rules/none_not_at_end_of_union.rs b/crates/ruff_linter/src/rules/ruff/rules/none_not_at_end_of_union.rs index cd8fd93f669d9..276bde1a73bae 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/none_not_at_end_of_union.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/none_not_at_end_of_union.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use itertools::Itertools; +use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; -use ruff_python_ast::Expr; +use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::analyze::typing::traverse_union; use ruff_text_size::Ranged; use smallvec::SmallVec; @@ -33,21 +34,29 @@ use crate::checkers::ast::Checker; pub(crate) struct NoneNotAtEndOfUnion; impl Violation for NoneNotAtEndOfUnion { + const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; + #[derive_message_formats] fn message(&self) -> String { "`None` not at the end of the type annotation.".to_string() } + + fn fix_title(&self) -> Option { + Some("Move `None` to the end of the type annotation".to_string()) + } } /// RUF036 pub(crate) fn none_not_at_end_of_union<'a>(checker: &mut Checker, union: &'a Expr) { let semantic = checker.semantic(); let mut none_exprs: SmallVec<[&Expr; 1]> = SmallVec::new(); - + let mut non_none_exprs: SmallVec<[&Expr; 1]> = SmallVec::new(); let mut last_expr: Option<&Expr> = None; let mut find_none = |expr: &'a Expr, _parent: &Expr| { if matches!(expr, Expr::NoneLiteral(_)) { none_exprs.push(expr); + } else { + non_none_exprs.push(expr); } last_expr = Some(expr); }; @@ -59,7 +68,7 @@ pub(crate) fn none_not_at_end_of_union<'a>(checker: &mut Checker, union: &'a Exp return; }; - // The must be at least one `None` expression. + // There must be at least one `None` expression. let Some(last_none) = none_exprs.last() else { return; }; @@ -69,6 +78,38 @@ pub(crate) fn none_not_at_end_of_union<'a>(checker: &mut Checker, union: &'a Exp return; } + if let Some(last) = none_exprs.pop() { + let mut elements = non_none_exprs + .iter() + .map(|expr| checker.locator().slice(expr.range()).to_string()) + .chain(std::iter::once("None".to_string())); + let fix = if let Expr::Subscript(ast::ExprSubscript { slice, .. }) = union { + let applicability = if checker.comment_ranges().intersects(slice.range()) { + Applicability::Unsafe + } else { + Applicability::Safe + }; + Fix::applicable_edit( + Edit::range_replacement(elements.join(", "), slice.range()), + applicability, + ) + } else { + let applicability = if checker.comment_ranges().intersects(union.range()) { + Applicability::Unsafe + } else { + Applicability::Safe + }; + Fix::applicable_edit( + Edit::range_replacement(elements.join(" | "), union.range()), + applicability, + ) + }; + + let mut diagnostic = Diagnostic::new(NoneNotAtEndOfUnion, last.range()); + diagnostic.set_fix(fix); + checker.diagnostics.push(diagnostic); + } + for none_expr in none_exprs { checker .diagnostics diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF036_RUF036.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF036_RUF036.py.snap index 62619ff4b2e49..2d710a33a8036 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF036_RUF036.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF036_RUF036.py.snap @@ -2,58 +2,166 @@ source: crates/ruff_linter/src/rules/ruff/mod.rs snapshot_kind: text --- -RUF036.py:4:16: RUF036 `None` not at the end of the type annotation. +RUF036.py:4:16: RUF036 [*] `None` not at the end of the type annotation. | 4 | def func1(arg: None | int): | ^^^^ RUF036 5 | ... | + = help: Move `None` to the end of the type annotation -RUF036.py:8:16: RUF036 `None` not at the end of the type annotation. +ℹ Safe fix +1 1 | from typing import Union as U +2 2 | +3 3 | +4 |-def func1(arg: None | int): + 4 |+def func1(arg: int | None): +5 5 | ... +6 6 | +7 7 | + +RUF036.py:8:16: RUF036 [*] `None` not at the end of the type annotation. | 8 | def func2() -> None | int: | ^^^^ RUF036 9 | ... | + = help: Move `None` to the end of the type annotation + +ℹ Safe fix +5 5 | ... +6 6 | +7 7 | +8 |-def func2() -> None | int: + 8 |+def func2() -> int | None: +9 9 | ... +10 10 | +11 11 | -RUF036.py:12:16: RUF036 `None` not at the end of the type annotation. +RUF036.py:12:16: RUF036 [*] `None` not at the end of the type annotation. | -12 | def func3(arg: None | None | int): +12 | def func2() -> None | int: | ^^^^ RUF036 13 | ... | + = help: Move `None` to the end of the type annotation + +ℹ Safe fix +9 9 | ... +10 10 | +11 11 | +12 |-def func2() -> None | int: + 12 |+def func2() -> int | None: +13 13 | ... +14 14 | +15 15 | -RUF036.py:12:23: RUF036 `None` not at the end of the type annotation. +RUF036.py:16:16: RUF036 `None` not at the end of the type annotation. | -12 | def func3(arg: None | None | int): - | ^^^^ RUF036 -13 | ... +16 | def func3(arg: None | None | int): + | ^^^^ RUF036 +17 | ... | + = help: Move `None` to the end of the type annotation -RUF036.py:16:18: RUF036 `None` not at the end of the type annotation. +RUF036.py:16:23: RUF036 [*] `None` not at the end of the type annotation. | -16 | def func4(arg: U[None, int]): - | ^^^^ RUF036 +16 | def func3(arg: None | None | int): + | ^^^^ RUF036 17 | ... | + = help: Move `None` to the end of the type annotation -RUF036.py:20:18: RUF036 `None` not at the end of the type annotation. +ℹ Safe fix +13 13 | ... +14 14 | +15 15 | +16 |-def func3(arg: None | None | int): + 16 |+def func3(arg: int | None): +17 17 | ... +18 18 | +19 19 | + +RUF036.py:20:18: RUF036 [*] `None` not at the end of the type annotation. | -20 | def func5() -> U[None, int]: +20 | def func4(arg: U[None, int]): | ^^^^ RUF036 21 | ... | + = help: Move `None` to the end of the type annotation + +ℹ Safe fix +17 17 | ... +18 18 | +19 19 | +20 |-def func4(arg: U[None, int]): + 20 |+def func4(arg: U[int, None]): +21 21 | ... +22 22 | +23 23 | -RUF036.py:24:18: RUF036 `None` not at the end of the type annotation. +RUF036.py:24:18: RUF036 [*] `None` not at the end of the type annotation. | -24 | def func6(arg: U[None, None, int]): +24 | def func5() -> U[None, int]: | ^^^^ RUF036 25 | ... | + = help: Move `None` to the end of the type annotation -RUF036.py:24:24: RUF036 `None` not at the end of the type annotation. +ℹ Safe fix +21 21 | ... +22 22 | +23 23 | +24 |-def func5() -> U[None, int]: + 24 |+def func5() -> U[int, None]: +25 25 | ... +26 26 | +27 27 | + +RUF036.py:28:18: RUF036 `None` not at the end of the type annotation. + | +28 | def func6(arg: U[None, None, int]): + | ^^^^ RUF036 +29 | ... | -24 | def func6(arg: U[None, None, int]): + = help: Move `None` to the end of the type annotation + +RUF036.py:28:24: RUF036 [*] `None` not at the end of the type annotation. + | +28 | def func6(arg: U[None, None, int]): | ^^^^ RUF036 -25 | ... +29 | ... | + = help: Move `None` to the end of the type annotation + +ℹ Safe fix +25 25 | ... +26 26 | +27 27 | +28 |-def func6(arg: U[None, None, int]): + 28 |+def func6(arg: U[int, None]): +29 29 | ... +30 30 | +31 31 | + +RUF036.py:33:5: RUF036 [*] `None` not at the end of the type annotation. + | +32 | def func7() -> U[ +33 | None, + | ^^^^ RUF036 +34 | # comment +35 | int + | + = help: Move `None` to the end of the type annotation + +ℹ Unsafe fix +30 30 | +31 31 | +32 32 | def func7() -> U[ +33 |- None, +34 |- # comment +35 |- int + 33 |+ int, None +36 34 | ]: +37 35 | ... +38 36 | diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF036_RUF036.pyi.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF036_RUF036.pyi.snap index e678092721a59..abd2a7247ccea 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF036_RUF036.pyi.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF036_RUF036.pyi.snap @@ -2,15 +2,26 @@ source: crates/ruff_linter/src/rules/ruff/mod.rs snapshot_kind: text --- -RUF036.pyi:4:16: RUF036 `None` not at the end of the type annotation. +RUF036.pyi:4:16: RUF036 [*] `None` not at the end of the type annotation. | 4 | def func1(arg: None | int): ... | ^^^^ RUF036 5 | 6 | def func2() -> None | int: ... | + = help: Move `None` to the end of the type annotation -RUF036.pyi:6:16: RUF036 `None` not at the end of the type annotation. +ℹ Safe fix +1 1 | from typing import Union as U +2 2 | +3 3 | +4 |-def func1(arg: None | int): ... + 4 |+def func1(arg: int | None): ... +5 5 | +6 6 | def func2() -> None | int: ... +7 7 | + +RUF036.pyi:6:16: RUF036 [*] `None` not at the end of the type annotation. | 4 | def func1(arg: None | int): ... 5 | @@ -19,6 +30,17 @@ RUF036.pyi:6:16: RUF036 `None` not at the end of the type annotation. 7 | 8 | def func3(arg: None | None | int): ... | + = help: Move `None` to the end of the type annotation + +ℹ Safe fix +3 3 | +4 4 | def func1(arg: None | int): ... +5 5 | +6 |-def func2() -> None | int: ... + 6 |+def func2() -> int | None: ... +7 7 | +8 8 | def func3(arg: None | None | int): ... +9 9 | RUF036.pyi:8:16: RUF036 `None` not at the end of the type annotation. | @@ -29,8 +51,9 @@ RUF036.pyi:8:16: RUF036 `None` not at the end of the type annotation. 9 | 10 | def func4(arg: U[None, int]): ... | + = help: Move `None` to the end of the type annotation -RUF036.pyi:8:23: RUF036 `None` not at the end of the type annotation. +RUF036.pyi:8:23: RUF036 [*] `None` not at the end of the type annotation. | 6 | def func2() -> None | int: ... 7 | @@ -39,8 +62,19 @@ RUF036.pyi:8:23: RUF036 `None` not at the end of the type annotation. 9 | 10 | def func4(arg: U[None, int]): ... | + = help: Move `None` to the end of the type annotation + +ℹ Safe fix +5 5 | +6 6 | def func2() -> None | int: ... +7 7 | +8 |-def func3(arg: None | None | int): ... + 8 |+def func3(arg: int | None): ... +9 9 | +10 10 | def func4(arg: U[None, int]): ... +11 11 | -RUF036.pyi:10:18: RUF036 `None` not at the end of the type annotation. +RUF036.pyi:10:18: RUF036 [*] `None` not at the end of the type annotation. | 8 | def func3(arg: None | None | int): ... 9 | @@ -49,8 +83,19 @@ RUF036.pyi:10:18: RUF036 `None` not at the end of the type annotation. 11 | 12 | def func5() -> U[None, int]: ... | + = help: Move `None` to the end of the type annotation -RUF036.pyi:12:18: RUF036 `None` not at the end of the type annotation. +ℹ Safe fix +7 7 | +8 8 | def func3(arg: None | None | int): ... +9 9 | +10 |-def func4(arg: U[None, int]): ... + 10 |+def func4(arg: U[int, None]): ... +11 11 | +12 12 | def func5() -> U[None, int]: ... +13 13 | + +RUF036.pyi:12:18: RUF036 [*] `None` not at the end of the type annotation. | 10 | def func4(arg: U[None, int]): ... 11 | @@ -59,6 +104,17 @@ RUF036.pyi:12:18: RUF036 `None` not at the end of the type annotation. 13 | 14 | def func6(arg: U[None, None, int]): ... | + = help: Move `None` to the end of the type annotation + +ℹ Safe fix +9 9 | +10 10 | def func4(arg: U[None, int]): ... +11 11 | +12 |-def func5() -> U[None, int]: ... + 12 |+def func5() -> U[int, None]: ... +13 13 | +14 14 | def func6(arg: U[None, None, int]): ... +15 15 | RUF036.pyi:14:18: RUF036 `None` not at the end of the type annotation. | @@ -69,8 +125,9 @@ RUF036.pyi:14:18: RUF036 `None` not at the end of the type annotation. 15 | 16 | # Ok | + = help: Move `None` to the end of the type annotation -RUF036.pyi:14:24: RUF036 `None` not at the end of the type annotation. +RUF036.pyi:14:24: RUF036 [*] `None` not at the end of the type annotation. | 12 | def func5() -> U[None, int]: ... 13 | @@ -79,3 +136,14 @@ RUF036.pyi:14:24: RUF036 `None` not at the end of the type annotation. 15 | 16 | # Ok | + = help: Move `None` to the end of the type annotation + +ℹ Safe fix +11 11 | +12 12 | def func5() -> U[None, int]: ... +13 13 | +14 |-def func6(arg: U[None, None, int]): ... + 14 |+def func6(arg: U[int, None]): ... +15 15 | +16 16 | # Ok +17 17 | def good_func1(arg: int | None): ... From e4e7107568c872fb1361a9cba922efb3f4eed764 Mon Sep 17 00:00:00 2001 From: harupy Date: Wed, 25 Dec 2024 12:27:02 +0900 Subject: [PATCH 2/4] Remove func2 --- crates/ruff_linter/resources/test/fixtures/ruff/RUF036.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF036.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF036.py index 4262e920aee87..9ce6de08d9f76 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF036.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF036.py @@ -9,10 +9,6 @@ def func2() -> None | int: ... -def func2() -> None | int: - ... - - def func3(arg: None | None | int): ... From 53bc93fac584ea26b00b949041915b29b1186170 Mon Sep 17 00:00:00 2001 From: harupy Date: Wed, 25 Dec 2024 12:36:00 +0900 Subject: [PATCH 3/4] Use position --- .../ruff/rules/none_not_at_end_of_union.rs | 62 +++++----- ..._rules__ruff__tests__RUF036_RUF036.py.snap | 108 ++++++++---------- 2 files changed, 74 insertions(+), 96 deletions(-) diff --git a/crates/ruff_linter/src/rules/ruff/rules/none_not_at_end_of_union.rs b/crates/ruff_linter/src/rules/ruff/rules/none_not_at_end_of_union.rs index 276bde1a73bae..19b402f3c3a71 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/none_not_at_end_of_union.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/none_not_at_end_of_union.rs @@ -1,4 +1,4 @@ -use itertools::Itertools; +use itertools::{Itertools, Position}; use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_ast::{self as ast, Expr}; @@ -78,41 +78,37 @@ pub(crate) fn none_not_at_end_of_union<'a>(checker: &mut Checker, union: &'a Exp return; } - if let Some(last) = none_exprs.pop() { - let mut elements = non_none_exprs - .iter() - .map(|expr| checker.locator().slice(expr.range()).to_string()) - .chain(std::iter::once("None".to_string())); - let fix = if let Expr::Subscript(ast::ExprSubscript { slice, .. }) = union { - let applicability = if checker.comment_ranges().intersects(slice.range()) { - Applicability::Unsafe + for (pos, none_expr) in none_exprs.iter().with_position() { + let mut diagnostic = Diagnostic::new(NoneNotAtEndOfUnion, none_expr.range()); + if matches!(pos, Position::Last | Position::Only) { + let mut elements = non_none_exprs + .iter() + .map(|expr| checker.locator().slice(expr.range()).to_string()) + .chain(std::iter::once("None".to_string())); + let fix = if let Expr::Subscript(ast::ExprSubscript { slice, .. }) = union { + let applicability = if checker.comment_ranges().intersects(slice.range()) { + Applicability::Unsafe + } else { + Applicability::Safe + }; + Fix::applicable_edit( + Edit::range_replacement(elements.join(", "), slice.range()), + applicability, + ) } else { - Applicability::Safe + let applicability = if checker.comment_ranges().intersects(union.range()) { + Applicability::Unsafe + } else { + Applicability::Safe + }; + Fix::applicable_edit( + Edit::range_replacement(elements.join(" | "), union.range()), + applicability, + ) }; - Fix::applicable_edit( - Edit::range_replacement(elements.join(", "), slice.range()), - applicability, - ) - } else { - let applicability = if checker.comment_ranges().intersects(union.range()) { - Applicability::Unsafe - } else { - Applicability::Safe - }; - Fix::applicable_edit( - Edit::range_replacement(elements.join(" | "), union.range()), - applicability, - ) - }; + diagnostic.set_fix(fix); + } - let mut diagnostic = Diagnostic::new(NoneNotAtEndOfUnion, last.range()); - diagnostic.set_fix(fix); checker.diagnostics.push(diagnostic); } - - for none_expr in none_exprs { - checker - .diagnostics - .push(Diagnostic::new(NoneNotAtEndOfUnion, none_expr.range())); - } } diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF036_RUF036.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF036_RUF036.py.snap index 2d710a33a8036..d6e6b98f0f70e 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF036_RUF036.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF036_RUF036.py.snap @@ -38,36 +38,36 @@ RUF036.py:8:16: RUF036 [*] `None` not at the end of the type annotation. 10 10 | 11 11 | -RUF036.py:12:16: RUF036 [*] `None` not at the end of the type annotation. +RUF036.py:12:16: RUF036 `None` not at the end of the type annotation. | -12 | def func2() -> None | int: +12 | def func3(arg: None | None | int): | ^^^^ RUF036 13 | ... | = help: Move `None` to the end of the type annotation +RUF036.py:12:23: RUF036 [*] `None` not at the end of the type annotation. + | +12 | def func3(arg: None | None | int): + | ^^^^ RUF036 +13 | ... + | + = help: Move `None` to the end of the type annotation + ℹ Safe fix 9 9 | ... 10 10 | 11 11 | -12 |-def func2() -> None | int: - 12 |+def func2() -> int | None: +12 |-def func3(arg: None | None | int): + 12 |+def func3(arg: int | None): 13 13 | ... 14 14 | 15 15 | -RUF036.py:16:16: RUF036 `None` not at the end of the type annotation. - | -16 | def func3(arg: None | None | int): - | ^^^^ RUF036 -17 | ... +RUF036.py:16:18: RUF036 [*] `None` not at the end of the type annotation. | - = help: Move `None` to the end of the type annotation - -RUF036.py:16:23: RUF036 [*] `None` not at the end of the type annotation. - | -16 | def func3(arg: None | None | int): - | ^^^^ RUF036 +16 | def func4(arg: U[None, int]): + | ^^^^ RUF036 17 | ... | = help: Move `None` to the end of the type annotation @@ -76,15 +76,15 @@ RUF036.py:16:23: RUF036 [*] `None` not at the end of the type annotation. 13 13 | ... 14 14 | 15 15 | -16 |-def func3(arg: None | None | int): - 16 |+def func3(arg: int | None): +16 |-def func4(arg: U[None, int]): + 16 |+def func4(arg: U[int, None]): 17 17 | ... 18 18 | 19 19 | RUF036.py:20:18: RUF036 [*] `None` not at the end of the type annotation. | -20 | def func4(arg: U[None, int]): +20 | def func5() -> U[None, int]: | ^^^^ RUF036 21 | ... | @@ -94,74 +94,56 @@ RUF036.py:20:18: RUF036 [*] `None` not at the end of the type annotation. 17 17 | ... 18 18 | 19 19 | -20 |-def func4(arg: U[None, int]): - 20 |+def func4(arg: U[int, None]): +20 |-def func5() -> U[None, int]: + 20 |+def func5() -> U[int, None]: 21 21 | ... 22 22 | 23 23 | -RUF036.py:24:18: RUF036 [*] `None` not at the end of the type annotation. +RUF036.py:24:18: RUF036 `None` not at the end of the type annotation. | -24 | def func5() -> U[None, int]: +24 | def func6(arg: U[None, None, int]): | ^^^^ RUF036 25 | ... | = help: Move `None` to the end of the type annotation -ℹ Safe fix -21 21 | ... -22 22 | -23 23 | -24 |-def func5() -> U[None, int]: - 24 |+def func5() -> U[int, None]: -25 25 | ... -26 26 | -27 27 | - -RUF036.py:28:18: RUF036 `None` not at the end of the type annotation. - | -28 | def func6(arg: U[None, None, int]): - | ^^^^ RUF036 -29 | ... - | - = help: Move `None` to the end of the type annotation - -RUF036.py:28:24: RUF036 [*] `None` not at the end of the type annotation. +RUF036.py:24:24: RUF036 [*] `None` not at the end of the type annotation. | -28 | def func6(arg: U[None, None, int]): +24 | def func6(arg: U[None, None, int]): | ^^^^ RUF036 -29 | ... +25 | ... | = help: Move `None` to the end of the type annotation ℹ Safe fix +21 21 | ... +22 22 | +23 23 | +24 |-def func6(arg: U[None, None, int]): + 24 |+def func6(arg: U[int, None]): 25 25 | ... 26 26 | 27 27 | -28 |-def func6(arg: U[None, None, int]): - 28 |+def func6(arg: U[int, None]): -29 29 | ... -30 30 | -31 31 | -RUF036.py:33:5: RUF036 [*] `None` not at the end of the type annotation. +RUF036.py:29:5: RUF036 [*] `None` not at the end of the type annotation. | -32 | def func7() -> U[ -33 | None, +28 | def func7() -> U[ +29 | None, | ^^^^ RUF036 -34 | # comment -35 | int +30 | # comment +31 | int | = help: Move `None` to the end of the type annotation ℹ Unsafe fix -30 30 | -31 31 | -32 32 | def func7() -> U[ -33 |- None, -34 |- # comment -35 |- int - 33 |+ int, None -36 34 | ]: -37 35 | ... -38 36 | +26 26 | +27 27 | +28 28 | def func7() -> U[ +29 |- None, +30 |- # comment +31 |- int + 29 |+ int, None +32 30 | ]: +33 31 | ... +34 32 | From aa1a5cb2c7ef0dc3b38595a92285066721e626c0 Mon Sep 17 00:00:00 2001 From: harupy Date: Thu, 26 Dec 2024 18:45:31 +0900 Subject: [PATCH 4/4] Address comments --- .../resources/test/fixtures/ruff/RUF036.py | 16 +++ .../ruff/rules/none_not_at_end_of_union.rs | 28 ++--- ..._rules__ruff__tests__RUF036_RUF036.py.snap | 100 +++++++++++++++++- 3 files changed, 126 insertions(+), 18 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF036.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF036.py index 9ce6de08d9f76..27188f34c9b57 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF036.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF036.py @@ -33,6 +33,22 @@ def func7() -> U[ ... +def func8(x: None | U[None ,int]): + ... + + +def func9(x: int | (str | None) | list): + ... + + +def func10(x: U[int, U[None, list | set]]): + ... + + +def func11(x: None | int) -> None | int: + ... + + # Ok def good_func1(arg: int | None): ... diff --git a/crates/ruff_linter/src/rules/ruff/rules/none_not_at_end_of_union.rs b/crates/ruff_linter/src/rules/ruff/rules/none_not_at_end_of_union.rs index 19b402f3c3a71..93d6e05978b5b 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/none_not_at_end_of_union.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/none_not_at_end_of_union.rs @@ -85,27 +85,21 @@ pub(crate) fn none_not_at_end_of_union<'a>(checker: &mut Checker, union: &'a Exp .iter() .map(|expr| checker.locator().slice(expr.range()).to_string()) .chain(std::iter::once("None".to_string())); - let fix = if let Expr::Subscript(ast::ExprSubscript { slice, .. }) = union { - let applicability = if checker.comment_ranges().intersects(slice.range()) { - Applicability::Unsafe + let (range, separator) = + if let Expr::Subscript(ast::ExprSubscript { slice, .. }) = union { + (slice.range(), ", ") } else { - Applicability::Safe + (union.range(), " | ") }; - Fix::applicable_edit( - Edit::range_replacement(elements.join(", "), slice.range()), - applicability, - ) + let applicability = if checker.comment_ranges().intersects(range) { + Applicability::Unsafe } else { - let applicability = if checker.comment_ranges().intersects(union.range()) { - Applicability::Unsafe - } else { - Applicability::Safe - }; - Fix::applicable_edit( - Edit::range_replacement(elements.join(" | "), union.range()), - applicability, - ) + Applicability::Safe }; + let fix = Fix::applicable_edit( + Edit::range_replacement(elements.join(separator), range), + applicability, + ); diagnostic.set_fix(fix); } diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF036_RUF036.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF036_RUF036.py.snap index d6e6b98f0f70e..11cbad35cc0bd 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF036_RUF036.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF036_RUF036.py.snap @@ -146,4 +146,102 @@ RUF036.py:29:5: RUF036 [*] `None` not at the end of the type annotation. 29 |+ int, None 32 30 | ]: 33 31 | ... -34 32 | +34 32 | + +RUF036.py:36:14: RUF036 `None` not at the end of the type annotation. + | +36 | def func8(x: None | U[None ,int]): + | ^^^^ RUF036 +37 | ... + | + = help: Move `None` to the end of the type annotation + +RUF036.py:36:23: RUF036 [*] `None` not at the end of the type annotation. + | +36 | def func8(x: None | U[None ,int]): + | ^^^^ RUF036 +37 | ... + | + = help: Move `None` to the end of the type annotation + +ℹ Safe fix +33 33 | ... +34 34 | +35 35 | +36 |-def func8(x: None | U[None ,int]): + 36 |+def func8(x: int | None): +37 37 | ... +38 38 | +39 39 | + +RUF036.py:40:27: RUF036 [*] `None` not at the end of the type annotation. + | +40 | def func9(x: int | (str | None) | list): + | ^^^^ RUF036 +41 | ... + | + = help: Move `None` to the end of the type annotation + +ℹ Safe fix +37 37 | ... +38 38 | +39 39 | +40 |-def func9(x: int | (str | None) | list): + 40 |+def func9(x: int | str | list | None): +41 41 | ... +42 42 | +43 43 | + +RUF036.py:44:24: RUF036 [*] `None` not at the end of the type annotation. + | +44 | def func10(x: U[int, U[None, list | set]]): + | ^^^^ RUF036 +45 | ... + | + = help: Move `None` to the end of the type annotation + +ℹ Safe fix +41 41 | ... +42 42 | +43 43 | +44 |-def func10(x: U[int, U[None, list | set]]): + 44 |+def func10(x: U[int, list, set, None]): +45 45 | ... +46 46 | +47 47 | + +RUF036.py:48:15: RUF036 [*] `None` not at the end of the type annotation. + | +48 | def func11(x: None | int) -> None | int: + | ^^^^ RUF036 +49 | ... + | + = help: Move `None` to the end of the type annotation + +ℹ Safe fix +45 45 | ... +46 46 | +47 47 | +48 |-def func11(x: None | int) -> None | int: + 48 |+def func11(x: int | None) -> None | int: +49 49 | ... +50 50 | +51 51 | + +RUF036.py:48:30: RUF036 [*] `None` not at the end of the type annotation. + | +48 | def func11(x: None | int) -> None | int: + | ^^^^ RUF036 +49 | ... + | + = help: Move `None` to the end of the type annotation + +ℹ Safe fix +45 45 | ... +46 46 | +47 47 | +48 |-def func11(x: None | int) -> None | int: + 48 |+def func11(x: None | int) -> int | None: +49 49 | ... +50 50 | +51 51 |