diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP025.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP025.py index 899f827f80..e1354c3375 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP025.py +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP025.py @@ -26,3 +26,9 @@ def hello(): f"foo"u"bar" # OK f"foo" u"bar" # OK + +# https://github.com/astral-sh/ruff/issues/18895 +""u"" +""u"hi" +""""""""""""""""""""u"hi" +""U"helloooo" \ No newline at end of file diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/unicode_kind_prefix.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/unicode_kind_prefix.rs index 99f1fe8ce6..7d62237e1f 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/unicode_kind_prefix.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/unicode_kind_prefix.rs @@ -42,9 +42,28 @@ impl AlwaysFixableViolation for UnicodeKindPrefix { pub(crate) fn unicode_kind_prefix(checker: &Checker, string: &StringLiteral) { if string.flags.prefix().is_unicode() { let mut diagnostic = checker.report_diagnostic(UnicodeKindPrefix, string.range); - diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(TextRange::at( - string.start(), - TextSize::from(1), - )))); + + let prefix_range = TextRange::at(string.start(), TextSize::new(1)); + let locator = checker.locator(); + let content = locator + .slice(TextRange::new(prefix_range.end(), string.end())) + .to_owned(); + + // If the preceding character is equivalent to the quote character, insert a space to avoid a + // syntax error. For example, when removing the `u` prefix in `""u""`, rewrite to `"" ""` + // instead of `""""`. + // see https://github.com/astral-sh/ruff/issues/18895 + let edit = if locator + .slice(TextRange::up_to(prefix_range.start())) + .chars() + .last() + .is_some_and(|char| content.starts_with(char)) + { + Edit::range_replacement(" ".to_string(), prefix_range) + } else { + Edit::range_deletion(prefix_range) + }; + + diagnostic.set_fix(Fix::safe_edit(edit)); } } diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP025.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP025.py.snap index 916977bfed..b9b6bc4da1 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP025.py.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP025.py.snap @@ -281,14 +281,18 @@ UP025.py:27:7: UP025 [*] Remove unicode literals from strings 25 25 | return"Hello" # OK 26 26 | 27 |-f"foo"u"bar" # OK - 27 |+f"foo""bar" # OK + 27 |+f"foo" "bar" # OK 28 28 | f"foo" u"bar" # OK +29 29 | +30 30 | # https://github.com/astral-sh/ruff/issues/18895 UP025.py:28:8: UP025 [*] Remove unicode literals from strings | 27 | f"foo"u"bar" # OK 28 | f"foo" u"bar" # OK | ^^^^^^ UP025 +29 | +30 | # https://github.com/astral-sh/ruff/issues/18895 | = help: Remove unicode prefix @@ -298,3 +302,80 @@ UP025.py:28:8: UP025 [*] Remove unicode literals from strings 27 27 | f"foo"u"bar" # OK 28 |-f"foo" u"bar" # OK 28 |+f"foo" "bar" # OK +29 29 | +30 30 | # https://github.com/astral-sh/ruff/issues/18895 +31 31 | ""u"" + +UP025.py:31:3: UP025 [*] Remove unicode literals from strings + | +30 | # https://github.com/astral-sh/ruff/issues/18895 +31 | ""u"" + | ^^^ UP025 +32 | ""u"hi" +33 | """"""""""""""""""""u"hi" + | + = help: Remove unicode prefix + +ℹ Safe fix +28 28 | f"foo" u"bar" # OK +29 29 | +30 30 | # https://github.com/astral-sh/ruff/issues/18895 +31 |-""u"" + 31 |+"" "" +32 32 | ""u"hi" +33 33 | """"""""""""""""""""u"hi" +34 34 | ""U"helloooo" + +UP025.py:32:3: UP025 [*] Remove unicode literals from strings + | +30 | # https://github.com/astral-sh/ruff/issues/18895 +31 | ""u"" +32 | ""u"hi" + | ^^^^^ UP025 +33 | """"""""""""""""""""u"hi" +34 | ""U"helloooo" + | + = help: Remove unicode prefix + +ℹ Safe fix +29 29 | +30 30 | # https://github.com/astral-sh/ruff/issues/18895 +31 31 | ""u"" +32 |-""u"hi" + 32 |+"" "hi" +33 33 | """"""""""""""""""""u"hi" +34 34 | ""U"helloooo" + +UP025.py:33:21: UP025 [*] Remove unicode literals from strings + | +31 | ""u"" +32 | ""u"hi" +33 | """"""""""""""""""""u"hi" + | ^^^^^ UP025 +34 | ""U"helloooo" + | + = help: Remove unicode prefix + +ℹ Safe fix +30 30 | # https://github.com/astral-sh/ruff/issues/18895 +31 31 | ""u"" +32 32 | ""u"hi" +33 |-""""""""""""""""""""u"hi" + 33 |+"""""""""""""""""""" "hi" +34 34 | ""U"helloooo" + +UP025.py:34:3: UP025 [*] Remove unicode literals from strings + | +32 | ""u"hi" +33 | """"""""""""""""""""u"hi" +34 | ""U"helloooo" + | ^^^^^^^^^^^ UP025 + | + = help: Remove unicode prefix + +ℹ Safe fix +31 31 | ""u"" +32 32 | ""u"hi" +33 33 | """"""""""""""""""""u"hi" +34 |-""U"helloooo" + 34 |+"" "helloooo"