diff --git a/crates/ruff/resources/test/fixtures/pydocstyle/D.py b/crates/ruff/resources/test/fixtures/pydocstyle/D.py index 23a74c381f..0c803cc647 100644 --- a/crates/ruff/resources/test/fixtures/pydocstyle/D.py +++ b/crates/ruff/resources/test/fixtures/pydocstyle/D.py @@ -639,3 +639,8 @@ def starts_with_space_then_this(): class SameLine: """This is a docstring on the same line""" def same_line(): """This is a docstring on the same line""" + + +def single_line_docstring_with_an_escaped_backslash(): + "\ + " diff --git a/crates/ruff/src/rules/pydocstyle/rules/ends_with_period.rs b/crates/ruff/src/rules/pydocstyle/rules/ends_with_period.rs index 65cea763f0..57c2b63fd0 100644 --- a/crates/ruff/src/rules/pydocstyle/rules/ends_with_period.rs +++ b/crates/ruff/src/rules/pydocstyle/rules/ends_with_period.rs @@ -90,13 +90,21 @@ pub(crate) fn ends_with_period(checker: &mut Checker, docstring: &Docstring) { let line = lines.nth(index).unwrap(); let trimmed = line.trim_end(); + if trimmed.ends_with('\\') { + // Ignore the edge case whether a single quoted string is multiple lines through an + // escape (https://github.com/astral-sh/ruff/issues/7139). Single quote docstrings are + // flagged by D300. + // ```python + // "\ + // " + // ``` + return; + } + if !trimmed.ends_with('.') { let mut diagnostic = Diagnostic::new(EndsInPeriod, docstring.range()); // Best-effort autofix: avoid adding a period after other punctuation marks. - if checker.patch(diagnostic.kind.rule()) - && !trimmed.ends_with(':') - && !trimmed.ends_with(';') - { + if checker.patch(diagnostic.kind.rule()) && !trimmed.ends_with([':', ';']) { diagnostic.set_fix(Fix::suggested(Edit::insertion( ".".to_string(), line.start() + trimmed.text_len(), diff --git a/crates/ruff/src/rules/pydocstyle/rules/ends_with_punctuation.rs b/crates/ruff/src/rules/pydocstyle/rules/ends_with_punctuation.rs index 39b42105d5..05ec17ddb1 100644 --- a/crates/ruff/src/rules/pydocstyle/rules/ends_with_punctuation.rs +++ b/crates/ruff/src/rules/pydocstyle/rules/ends_with_punctuation.rs @@ -89,6 +89,17 @@ pub(crate) fn ends_with_punctuation(checker: &mut Checker, docstring: &Docstring let line = lines.next().unwrap(); let trimmed = line.trim_end(); + if trimmed.ends_with('\\') { + // Ignore the edge case whether a single quoted string is multiple lines through an + // escape (https://github.com/astral-sh/ruff/issues/7139). Single quote docstrings are + // flagged by D300. + // ```python + // "\ + // " + // ``` + return; + } + if !trimmed.ends_with(['.', '!', '?']) { let mut diagnostic = Diagnostic::new(EndsInPunctuation, docstring.range()); // Best-effort autofix: avoid adding a period after other punctuation marks. diff --git a/crates/ruff/src/rules/pydocstyle/rules/multi_line_summary_start.rs b/crates/ruff/src/rules/pydocstyle/rules/multi_line_summary_start.rs index f78caf5987..27c5804928 100644 --- a/crates/ruff/src/rules/pydocstyle/rules/multi_line_summary_start.rs +++ b/crates/ruff/src/rules/pydocstyle/rules/multi_line_summary_start.rs @@ -151,6 +151,15 @@ pub(crate) fn multi_line_summary_start(checker: &mut Checker, docstring: &Docstr } checker.diagnostics.push(diagnostic); } + } else if first_line.as_str().ends_with('\\') { + // Ignore the edge case whether a single quoted string is multiple lines through an + // escape (https://github.com/astral-sh/ruff/issues/7139). Single quote docstrings are + // flagged by D300. + // ```python + // "\ + // " + // ``` + return; } else { if checker.enabled(Rule::MultiLineSummarySecondLine) { let mut diagnostic = Diagnostic::new(MultiLineSummarySecondLine, docstring.range()); diff --git a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D200_D.py.snap b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D200_D.py.snap index 0adf91a9b6..cd7a63bd6f 100644 --- a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D200_D.py.snap +++ b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D200_D.py.snap @@ -97,4 +97,14 @@ D.py:624:5: D200 One-line docstring should fit on one line | = help: Reformat to one line +D.py:645:5: D200 One-line docstring should fit on one line + | +644 | def single_line_docstring_with_an_escaped_backslash(): +645 | "\ + | _____^ +646 | | " + | |_____^ D200 + | + = help: Reformat to one line + diff --git a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D300_D.py.snap b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D300_D.py.snap index a65966e43e..159d72bade 100644 --- a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D300_D.py.snap +++ b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D300_D.py.snap @@ -41,4 +41,13 @@ D.py:328:5: D300 Use triple double quotes `"""` | ^^^^^^^^^^^^ D300 | +D.py:645:5: D300 Use triple double quotes `"""` + | +644 | def single_line_docstring_with_an_escaped_backslash(): +645 | "\ + | _____^ +646 | | " + | |_____^ D300 + | + diff --git a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D400_D.py.snap b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D400_D.py.snap index 3b92b97fd1..45481187d8 100644 --- a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D400_D.py.snap +++ b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D400_D.py.snap @@ -289,6 +289,7 @@ D.py:639:17: D400 [*] First line should end with a period 639 |+class SameLine: """This is a docstring on the same line.""" 640 640 | 641 641 | def same_line(): """This is a docstring on the same line""" +642 642 | D.py:641:18: D400 [*] First line should end with a period | @@ -305,5 +306,8 @@ D.py:641:18: D400 [*] First line should end with a period 640 640 | 641 |-def same_line(): """This is a docstring on the same line""" 641 |+def same_line(): """This is a docstring on the same line.""" +642 642 | +643 643 | +644 644 | def single_line_docstring_with_an_escaped_backslash(): diff --git a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D415_D.py.snap b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D415_D.py.snap index 73725b09cf..a5630beb76 100644 --- a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D415_D.py.snap +++ b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D415_D.py.snap @@ -271,6 +271,7 @@ D.py:639:17: D415 [*] First line should end with a period, question mark, or exc 639 |+class SameLine: """This is a docstring on the same line.""" 640 640 | 641 641 | def same_line(): """This is a docstring on the same line""" +642 642 | D.py:641:18: D415 [*] First line should end with a period, question mark, or exclamation point | @@ -287,5 +288,8 @@ D.py:641:18: D415 [*] First line should end with a period, question mark, or exc 640 640 | 641 |-def same_line(): """This is a docstring on the same line""" 641 |+def same_line(): """This is a docstring on the same line.""" +642 642 | +643 643 | +644 644 | def single_line_docstring_with_an_escaped_backslash():