diff --git a/crates/ruff_linter/resources/test/fixtures/pydocstyle/D.py b/crates/ruff_linter/resources/test/fixtures/pydocstyle/D.py index 617231c5c8..e5b1b15c9c 100644 --- a/crates/ruff_linter/resources/test/fixtures/pydocstyle/D.py +++ b/crates/ruff_linter/resources/test/fixtures/pydocstyle/D.py @@ -722,3 +722,10 @@ def inconsistent_indent_byte_size():     Returns: """ + + +def line_continuation_chars():\ + + """No fix should be offered for D201/D202 because of the line continuation chars."""\ + + ... diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/blank_before_after_function.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/blank_before_after_function.rs index a3f1ab0efd..bf124a12d1 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/blank_before_after_function.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/blank_before_after_function.rs @@ -10,7 +10,7 @@ use ruff_text_size::TextRange; use crate::checkers::ast::Checker; use crate::docstrings::Docstring; use crate::registry::Rule; -use crate::{AlwaysFixableViolation, Edit, Fix}; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for docstrings on functions that are separated by one or more blank @@ -42,15 +42,17 @@ pub(crate) struct BlankLineBeforeFunction { num_lines: usize, } -impl AlwaysFixableViolation for BlankLineBeforeFunction { +impl Violation for BlankLineBeforeFunction { + const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; + #[derive_message_formats] fn message(&self) -> String { let BlankLineBeforeFunction { num_lines } = self; format!("No blank lines allowed before function docstring (found {num_lines})") } - fn fix_title(&self) -> String { - "Remove blank line(s) before function docstring".to_string() + fn fix_title(&self) -> Option { + Some("Remove blank line(s) before function docstring".to_string()) } } @@ -86,15 +88,17 @@ pub(crate) struct BlankLineAfterFunction { num_lines: usize, } -impl AlwaysFixableViolation for BlankLineAfterFunction { +impl Violation for BlankLineAfterFunction { + const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; + #[derive_message_formats] fn message(&self) -> String { let BlankLineAfterFunction { num_lines } = self; format!("No blank lines allowed after function docstring (found {num_lines})") } - fn fix_title(&self) -> String { - "Remove blank line(s) after function docstring".to_string() + fn fix_title(&self) -> Option { + Some("Remove blank line(s) after function docstring".to_string()) } } @@ -115,12 +119,14 @@ pub(crate) fn blank_before_after_function(checker: &Checker, docstring: &Docstri let mut lines = UniversalNewlineIterator::with_offset(before, function.start()).rev(); let mut blank_lines_before = 0usize; let mut blank_lines_start = lines.next().map(|l| l.end()).unwrap_or_default(); + let mut start_is_line_continuation = false; for line in lines { if line.trim().is_empty() { blank_lines_before += 1; blank_lines_start = line.start(); } else { + start_is_line_continuation = line.ends_with('\\'); break; } } @@ -132,11 +138,14 @@ pub(crate) fn blank_before_after_function(checker: &Checker, docstring: &Docstri }, docstring.range(), ); - // Delete the blank line before the docstring. - diagnostic.set_fix(Fix::safe_edit(Edit::deletion( - blank_lines_start, - docstring.line_start(), - ))); + // Do not offer fix if a \ would cause it to be a syntax error + if !start_is_line_continuation { + // Delete the blank line before the docstring. + diagnostic.set_fix(Fix::safe_edit(Edit::deletion( + blank_lines_start, + docstring.line_start(), + ))); + } } } @@ -156,7 +165,9 @@ pub(crate) fn blank_before_after_function(checker: &Checker, docstring: &Docstri // Count the number of blank lines after the docstring. let mut blank_lines_after = 0usize; let mut lines = UniversalNewlineIterator::with_offset(after, docstring.end()).peekable(); - let first_line_end = lines.next().map(|l| l.end()).unwrap_or_default(); + let first_line = lines.next(); + let first_line_line_continuation = first_line.as_ref().is_some_and(|l| l.ends_with('\\')); + let first_line_end = first_line.map(|l| l.end()).unwrap_or_default(); let mut blank_lines_end = first_line_end; while let Some(line) = lines.peek() { @@ -185,11 +196,14 @@ pub(crate) fn blank_before_after_function(checker: &Checker, docstring: &Docstri }, docstring.range(), ); - // Delete the blank line after the docstring. - diagnostic.set_fix(Fix::safe_edit(Edit::deletion( - first_line_end, - blank_lines_end, - ))); + // Do not offer fix if a \ would cause it to be a syntax error + if !first_line_line_continuation { + // Delete the blank line after the docstring. + diagnostic.set_fix(Fix::safe_edit(Edit::deletion( + first_line_end, + blank_lines_end, + ))); + } } } } diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D201_D.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D201_D.py.snap index b22486d55a..98a7e7e11e 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D201_D.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D201_D.py.snap @@ -82,3 +82,14 @@ D.py:568:5: D201 [*] No blank lines allowed before function docstring (found 1) 568 567 | """Trailing and leading space. 569 568 | 570 569 | More content. + +D.py:729:5: D201 No blank lines allowed before function docstring (found 1) + | +727 | def line_continuation_chars():\ +728 | +729 | """No fix should be offered for D201/D202 because of the line continuation chars."""\ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D201 +730 | +731 | ... + | + = help: Remove blank line(s) before function docstring diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D202_D.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D202_D.py.snap index 8852a6a690..d234ae1d62 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D202_D.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D202_D.py.snap @@ -85,4 +85,15 @@ D.py:568:5: D202 [*] No blank lines allowed after function docstring (found 1) 572 |- 573 572 | pass 574 573 | -575 574 | +575 574 | + +D.py:729:5: D202 No blank lines allowed after function docstring (found 1) + | +727 | def line_continuation_chars():\ +728 | +729 | """No fix should be offered for D201/D202 because of the line continuation chars."""\ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D202 +730 | +731 | ... + | + = help: Remove blank line(s) after function docstring diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D208_D.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D208_D.py.snap index c3ba66a565..acf0b91d6c 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D208_D.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D208_D.py.snap @@ -428,3 +428,5 @@ D.py:723:1: D208 [*] Docstring is over-indented 723 |-     Returns: 723 |+ Returns: 724 724 | """ +725 725 | +726 726 |