diff --git a/crates/ruff_linter/resources/test/fixtures/pydocstyle/D301.py b/crates/ruff_linter/resources/test/fixtures/pydocstyle/D301.py index 1e6c8eef078ae..6af8362ad8dcf 100644 --- a/crates/ruff_linter/resources/test/fixtures/pydocstyle/D301.py +++ b/crates/ruff_linter/resources/test/fixtures/pydocstyle/D301.py @@ -10,6 +10,10 @@ def double_quotes_backslash_uppercase(): R"""Sum\\mary.""" +def shouldnt_add_raw_here(): + "Ruff \U000026a1" + + def make_unique_pod_id(pod_id: str) -> str | None: r""" Generate a unique Pod name. diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/backslashes.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/backslashes.rs index 91cc414c3bdc3..0c02b457a642f 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/backslashes.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/backslashes.rs @@ -1,6 +1,6 @@ use memchr::memchr_iter; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_text_size::Ranged; @@ -46,18 +46,21 @@ use crate::docstrings::Docstring; #[violation] pub struct EscapeSequenceInDocstring; -impl Violation for EscapeSequenceInDocstring { +impl AlwaysFixableViolation for EscapeSequenceInDocstring { #[derive_message_formats] fn message(&self) -> String { format!(r#"Use `r"""` if any backslashes in a docstring"#) } + + fn fix_title(&self) -> String { + format!(r#"Add `r` prefix"#) + } } /// D301 pub(crate) fn backslashes(checker: &mut Checker, docstring: &Docstring) { // Docstring is already raw. - let contents = docstring.contents; - if contents.starts_with('r') || contents.starts_with("ur") { + if docstring.leading_quote().contains(['r', 'R']) { return; } @@ -67,11 +70,15 @@ pub(crate) fn backslashes(checker: &mut Checker, docstring: &Docstring) { if memchr_iter(b'\\', bytes).any(|position| { let escaped_char = bytes.get(position.saturating_add(1)); // Allow continuations (backslashes followed by newlines) and Unicode escapes. - !matches!(escaped_char, Some(b'\r' | b'\n' | b'u' | b'N')) + !matches!(escaped_char, Some(b'\r' | b'\n' | b'u' | b'U' | b'N')) }) { - checker.diagnostics.push(Diagnostic::new( - EscapeSequenceInDocstring, + let mut diagnostic = Diagnostic::new(EscapeSequenceInDocstring, docstring.range()); + + diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( + "r".to_owned() + docstring.contents, docstring.range(), - )); + ))); + + checker.diagnostics.push(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D301_D.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D301_D.py.snap index 9c2880c067cbe..640d797182a99 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D301_D.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D301_D.py.snap @@ -1,28 +1,23 @@ --- source: crates/ruff_linter/src/rules/pydocstyle/mod.rs --- -D.py:328:5: D301 Use `r"""` if any backslashes in a docstring - | -326 | @expect('D301: Use r""" if any backslashes in a docstring') -327 | def single_quotes_raw_uppercase_backslash(): -328 | R'Sum\mary.' - | ^^^^^^^^^^^^ D301 - | - -D.py:333:5: D301 Use `r"""` if any backslashes in a docstring +D.py:333:5: D301 [*] Use `r"""` if any backslashes in a docstring | 331 | @expect('D301: Use r""" if any backslashes in a docstring') 332 | def double_quotes_backslash(): 333 | """Sum\\mary.""" | ^^^^^^^^^^^^^^^^ D301 | + = help: Add `r` prefix -D.py:338:5: D301 Use `r"""` if any backslashes in a docstring - | -336 | @expect('D301: Use r""" if any backslashes in a docstring') -337 | def double_quotes_backslash_uppercase(): -338 | R"""Sum\\mary.""" - | ^^^^^^^^^^^^^^^^^ D301 - | +ℹ Suggested fix +330 330 | +331 331 | @expect('D301: Use r""" if any backslashes in a docstring') +332 332 | def double_quotes_backslash(): +333 |- """Sum\\mary.""" + 333 |+ r"""Sum\\mary.""" +334 334 | +335 335 | +336 336 | @expect('D301: Use r""" if any backslashes in a docstring') diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D301_D301.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D301_D301.py.snap index 5ee1cedd052d7..f64cbcd59ad57 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D301_D301.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D301_D301.py.snap @@ -1,18 +1,20 @@ --- source: crates/ruff_linter/src/rules/pydocstyle/mod.rs --- -D301.py:2:5: D301 Use `r"""` if any backslashes in a docstring +D301.py:2:5: D301 [*] Use `r"""` if any backslashes in a docstring | 1 | def double_quotes_backslash(): 2 | """Sum\\mary.""" | ^^^^^^^^^^^^^^^^ D301 | + = help: Add `r` prefix -D301.py:10:5: D301 Use `r"""` if any backslashes in a docstring - | - 9 | def double_quotes_backslash_uppercase(): -10 | R"""Sum\\mary.""" - | ^^^^^^^^^^^^^^^^^ D301 - | +ℹ Suggested fix +1 1 | def double_quotes_backslash(): +2 |- """Sum\\mary.""" + 2 |+ r"""Sum\\mary.""" +3 3 | +4 4 | +5 5 | def double_quotes_backslash_raw():