mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-03 15:15:33 +00:00
[pydocstyle] Escaped docstring in docstring (D301 ) (#12192)
<!-- Thank you for contributing to Ruff! 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? - Does this pull request include references to any relevant issues? --> ## Summary <!-- What's the purpose of the change? What does it do, and why? --> This PR updates D301 rule to allow inclduing escaped docstring, e.g. `\"""Foo.\"""` or `\"\"\"Bar.\"\"\"`, within a docstring. Related issue: #12152 ## Test Plan Add more test cases to D301.py and update the snapshot file. <!-- How was it tested? -->
This commit is contained in:
parent
fa5b19d4b6
commit
0ba7fc63d0
3 changed files with 143 additions and 15 deletions
|
@ -35,3 +35,67 @@ def make_unique_pod_id(pod_id: str) -> str | None:
|
||||||
|
|
||||||
def shouldnt_add_raw_here2():
|
def shouldnt_add_raw_here2():
|
||||||
u"Sum\\mary."
|
u"Sum\\mary."
|
||||||
|
|
||||||
|
|
||||||
|
def shouldnt_add_raw_for_double_quote_docstring_contains_docstring():
|
||||||
|
"""
|
||||||
|
This docstring contains another double-quote docstring.
|
||||||
|
|
||||||
|
def foo():
|
||||||
|
\"\"\"Foo.\"\"\"
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def shouldnt_add_raw_for_double_quote_docstring_contains_docstring2():
|
||||||
|
"""
|
||||||
|
This docstring contains another double-quote docstring.
|
||||||
|
|
||||||
|
def bar():
|
||||||
|
\"""Bar.\"""
|
||||||
|
|
||||||
|
More content here.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def shouldnt_add_raw_for_single_quote_docstring_contains_docstring():
|
||||||
|
'''
|
||||||
|
This docstring contains another single-quote docstring.
|
||||||
|
|
||||||
|
def foo():
|
||||||
|
\'\'\'Foo.\'\'\'
|
||||||
|
|
||||||
|
More content here.
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
def shouldnt_add_raw_for_single_quote_docstring_contains_docstring2():
|
||||||
|
'''
|
||||||
|
This docstring contains another single-quote docstring.
|
||||||
|
|
||||||
|
def bar():
|
||||||
|
\'''Bar.\'''
|
||||||
|
|
||||||
|
More content here.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def shouldnt_add_raw_for_docstring_contains_escaped_double_triple_quotes():
|
||||||
|
"""
|
||||||
|
Escaped triple quote \""" or \"\"\".
|
||||||
|
"""
|
||||||
|
|
||||||
|
def shouldnt_add_raw_for_docstring_contains_escaped_single_triple_quotes():
|
||||||
|
'''
|
||||||
|
Escaped triple quote \''' or \'\'\'.
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
def should_add_raw_for_single_double_quote_escape():
|
||||||
|
"""
|
||||||
|
This is single quote escape \".
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def should_add_raw_for_single_single_quote_escape():
|
||||||
|
'''
|
||||||
|
This is single quote escape \'.
|
||||||
|
'''
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
use memchr::memchr_iter;
|
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
|
@ -69,20 +67,47 @@ pub(crate) fn backslashes(checker: &mut Checker, docstring: &Docstring) {
|
||||||
// Docstring contains at least one backslash.
|
// Docstring contains at least one backslash.
|
||||||
let body = docstring.body();
|
let body = docstring.body();
|
||||||
let bytes = body.as_bytes();
|
let bytes = body.as_bytes();
|
||||||
if memchr_iter(b'\\', bytes).any(|position| {
|
let mut offset = 0;
|
||||||
let escaped_char = bytes.get(position.saturating_add(1));
|
while let Some(position) = memchr::memchr(b'\\', &bytes[offset..]) {
|
||||||
// Allow continuations (backslashes followed by newlines) and Unicode escapes.
|
if position + offset + 1 >= body.len() {
|
||||||
!matches!(escaped_char, Some(b'\r' | b'\n' | b'u' | b'U' | b'N'))
|
break;
|
||||||
}) {
|
|
||||||
let mut diagnostic = Diagnostic::new(EscapeSequenceInDocstring, docstring.range());
|
|
||||||
|
|
||||||
if !docstring.leading_quote().contains(['u', 'U']) {
|
|
||||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
|
|
||||||
"r".to_owned() + docstring.contents,
|
|
||||||
docstring.range(),
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
checker.diagnostics.push(diagnostic);
|
let after_escape = &body[position + offset + 1..];
|
||||||
|
|
||||||
|
// End of Docstring.
|
||||||
|
let Some(escaped_char) = &after_escape.chars().next() else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
if matches!(escaped_char, '"' | '\'') {
|
||||||
|
// If the next three characters are equal to """, it indicates an escaped docstring pattern.
|
||||||
|
if after_escape.starts_with("\"\"\"") || after_escape.starts_with("\'\'\'") {
|
||||||
|
offset += position + 3;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// If the next three characters are equal to "\"\", it indicates an escaped docstring pattern.
|
||||||
|
if after_escape.starts_with("\"\\\"\\\"") || after_escape.starts_with("\'\\\'\\\'") {
|
||||||
|
offset += position + 5;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += position + escaped_char.len_utf8();
|
||||||
|
|
||||||
|
// Only allow continuations (backslashes followed by newlines) and Unicode escapes.
|
||||||
|
if !matches!(*escaped_char, '\r' | '\n' | 'u' | 'U' | 'N') {
|
||||||
|
let mut diagnostic = Diagnostic::new(EscapeSequenceInDocstring, docstring.range());
|
||||||
|
|
||||||
|
if !docstring.leading_quote().contains(['u', 'U']) {
|
||||||
|
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
|
||||||
|
"r".to_owned() + docstring.contents,
|
||||||
|
docstring.range(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
checker.diagnostics.push(diagnostic);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,4 +25,43 @@ D301.py:37:5: D301 Use `r"""` if any backslashes in a docstring
|
||||||
|
|
|
|
||||||
= help: Add `r` prefix
|
= help: Add `r` prefix
|
||||||
|
|
||||||
|
D301.py:93:5: D301 [*] Use `r"""` if any backslashes in a docstring
|
||||||
|
|
|
||||||
|
92 | def should_add_raw_for_single_double_quote_escape():
|
||||||
|
93 | """
|
||||||
|
| _____^
|
||||||
|
94 | | This is single quote escape \".
|
||||||
|
95 | | """
|
||||||
|
| |_______^ D301
|
||||||
|
|
|
||||||
|
= help: Add `r` prefix
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
90 90 |
|
||||||
|
91 91 |
|
||||||
|
92 92 | def should_add_raw_for_single_double_quote_escape():
|
||||||
|
93 |- """
|
||||||
|
93 |+ r"""
|
||||||
|
94 94 | This is single quote escape \".
|
||||||
|
95 95 | """
|
||||||
|
96 96 |
|
||||||
|
|
||||||
|
D301.py:99:5: D301 [*] Use `r"""` if any backslashes in a docstring
|
||||||
|
|
|
||||||
|
98 | def should_add_raw_for_single_single_quote_escape():
|
||||||
|
99 | '''
|
||||||
|
| _____^
|
||||||
|
100 | | This is single quote escape \'.
|
||||||
|
101 | | '''
|
||||||
|
| |_______^ D301
|
||||||
|
|
|
||||||
|
= help: Add `r` prefix
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
96 96 |
|
||||||
|
97 97 |
|
||||||
|
98 98 | def should_add_raw_for_single_single_quote_escape():
|
||||||
|
99 |- '''
|
||||||
|
99 |+ r'''
|
||||||
|
100 100 | This is single quote escape \'.
|
||||||
|
101 101 | '''
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue