diff --git a/crates/ruff_linter/resources/test/fixtures/pydocstyle/D.py b/crates/ruff_linter/resources/test/fixtures/pydocstyle/D.py index d8d9bf5d057a1..724490934ffaf 100644 --- a/crates/ruff_linter/resources/test/fixtures/pydocstyle/D.py +++ b/crates/ruff_linter/resources/test/fixtures/pydocstyle/D.py @@ -663,3 +663,55 @@ def sort_services(self): def newline_after_closing_quote(self): "We enforce a newline after the closing quote for a multi-line docstring \ but continuations shouldn't be considered multi-line" + + + + +def retain_extra_whitespace(): + """Summary. + + This is overindented + And so is this, but it we should preserve the extra space on this line relative + to the one before + """ + + +def retain_extra_whitespace_multiple(): + """Summary. + + This is overindented + And so is this, but it we should preserve the extra space on this line relative + to the one before + This is also overindented + And so is this, but it we should preserve the extra space on this line relative + to the one before + """ + + + +def retain_extra_whitespace_deeper(): + """Summary. + + This is overindented + And so is this, but it we should preserve the extra space on this line relative + to the one before + And the relative indent here should be preserved too + """ + +def retain_extra_whitespace_followed_by_same_offset(): + """Summary. + + This is overindented + And so is this, but it we should preserve the extra space on this line relative + This is overindented + This is overindented + """ + + +def retain_extra_whitespace_not_overindented(): + """Summary. + + This is not overindented + This is overindented, but since one line is not overindented this should not raise + And so is this, but it we should preserve the extra space on this line relative + """ diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/indent.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/indent.rs index 9ba7786b1475d..497dd816e1367 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/indent.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/indent.rs @@ -3,7 +3,7 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::docstrings::{clean_space, leading_space}; use ruff_source_file::NewlineWithTrailingNewline; -use ruff_text_size::Ranged; +use ruff_text_size::{Ranged, TextSize}; use ruff_text_size::{TextLen, TextRange}; use crate::checkers::ast::Checker; @@ -172,6 +172,7 @@ pub(crate) fn indent(checker: &mut Checker, docstring: &Docstring) { let mut has_seen_tab = docstring.indentation.contains('\t'); let mut is_over_indented = true; let mut over_indented_lines = vec![]; + let mut over_indented_offset = TextSize::from(u32::MAX); for i in 0..lines.len() { // First lines and continuations doesn't need any indentation. @@ -217,7 +218,13 @@ pub(crate) fn indent(checker: &mut Checker, docstring: &Docstring) { // the over-indentation status of every line. if i < lines.len() - 1 { if line_indent.len() > docstring.indentation.len() { - over_indented_lines.push(TextRange::at(line.start(), line_indent.text_len())); + over_indented_lines.push(line); + + // Track the _smallest_ offset we see + over_indented_offset = std::cmp::min( + line_indent.text_len() - docstring.indentation.text_len(), + over_indented_offset, + ); } else { is_over_indented = false; } @@ -235,16 +242,21 @@ pub(crate) fn indent(checker: &mut Checker, docstring: &Docstring) { if checker.enabled(Rule::OverIndentation) { // If every line (except the last) is over-indented... if is_over_indented { - for over_indented in over_indented_lines { + for line in over_indented_lines { + let line_indent = leading_space(line); + let indent = clean_space(docstring.indentation); + // We report over-indentation on every line. This isn't great, but // enables fix. let mut diagnostic = - Diagnostic::new(OverIndentation, TextRange::empty(over_indented.start())); - let indent = clean_space(docstring.indentation); + Diagnostic::new(OverIndentation, TextRange::empty(line.start())); let edit = if indent.is_empty() { - Edit::range_deletion(over_indented) + Edit::range_deletion(TextRange::at(line.start(), line_indent.text_len())) } else { - Edit::range_replacement(indent, over_indented) + Edit::range_replacement( + indent.clone(), + TextRange::at(line.start(), indent.text_len() + over_indented_offset), + ) }; diagnostic.set_fix(Fix::safe_edit(edit)); checker.diagnostics.push(diagnostic); 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 0fb50288205a6..caa57c1faa72d 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 @@ -62,4 +62,353 @@ D.py:272:1: D208 [*] Docstring is over-indented 274 274 | """ 275 275 | +D.py:673:1: D208 [*] Docstring is over-indented + | +671 | """Summary. +672 | +673 | This is overindented + | D208 +674 | And so is this, but it we should preserve the extra space on this line relative +675 | to the one before + | + = help: Remove over-indentation + +ℹ Safe fix +670 670 | def retain_extra_whitespace(): +671 671 | """Summary. +672 672 | +673 |- This is overindented + 673 |+ This is overindented +674 674 | And so is this, but it we should preserve the extra space on this line relative +675 675 | to the one before +676 676 | """ + +D.py:674:1: D208 [*] Docstring is over-indented + | +673 | This is overindented +674 | And so is this, but it we should preserve the extra space on this line relative + | D208 +675 | to the one before +676 | """ + | + = help: Remove over-indentation + +ℹ Safe fix +671 671 | """Summary. +672 672 | +673 673 | This is overindented +674 |- And so is this, but it we should preserve the extra space on this line relative + 674 |+ And so is this, but it we should preserve the extra space on this line relative +675 675 | to the one before +676 676 | """ +677 677 | + +D.py:675:1: D208 [*] Docstring is over-indented + | +673 | This is overindented +674 | And so is this, but it we should preserve the extra space on this line relative +675 | to the one before + | D208 +676 | """ + | + = help: Remove over-indentation + +ℹ Safe fix +672 672 | +673 673 | This is overindented +674 674 | And so is this, but it we should preserve the extra space on this line relative +675 |- to the one before + 675 |+ to the one before +676 676 | """ +677 677 | +678 678 | + +D.py:682:1: D208 [*] Docstring is over-indented + | +680 | """Summary. +681 | +682 | This is overindented + | D208 +683 | And so is this, but it we should preserve the extra space on this line relative +684 | to the one before + | + = help: Remove over-indentation + +ℹ Safe fix +679 679 | def retain_extra_whitespace_multiple(): +680 680 | """Summary. +681 681 | +682 |- This is overindented + 682 |+ This is overindented +683 683 | And so is this, but it we should preserve the extra space on this line relative +684 684 | to the one before +685 685 | This is also overindented + +D.py:683:1: D208 [*] Docstring is over-indented + | +682 | This is overindented +683 | And so is this, but it we should preserve the extra space on this line relative + | D208 +684 | to the one before +685 | This is also overindented + | + = help: Remove over-indentation + +ℹ Safe fix +680 680 | """Summary. +681 681 | +682 682 | This is overindented +683 |- And so is this, but it we should preserve the extra space on this line relative + 683 |+ And so is this, but it we should preserve the extra space on this line relative +684 684 | to the one before +685 685 | This is also overindented +686 686 | And so is this, but it we should preserve the extra space on this line relative + +D.py:684:1: D208 [*] Docstring is over-indented + | +682 | This is overindented +683 | And so is this, but it we should preserve the extra space on this line relative +684 | to the one before + | D208 +685 | This is also overindented +686 | And so is this, but it we should preserve the extra space on this line relative + | + = help: Remove over-indentation + +ℹ Safe fix +681 681 | +682 682 | This is overindented +683 683 | And so is this, but it we should preserve the extra space on this line relative +684 |- to the one before + 684 |+ to the one before +685 685 | This is also overindented +686 686 | And so is this, but it we should preserve the extra space on this line relative +687 687 | to the one before + +D.py:685:1: D208 [*] Docstring is over-indented + | +683 | And so is this, but it we should preserve the extra space on this line relative +684 | to the one before +685 | This is also overindented + | D208 +686 | And so is this, but it we should preserve the extra space on this line relative +687 | to the one before + | + = help: Remove over-indentation + +ℹ Safe fix +682 682 | This is overindented +683 683 | And so is this, but it we should preserve the extra space on this line relative +684 684 | to the one before +685 |- This is also overindented + 685 |+ This is also overindented +686 686 | And so is this, but it we should preserve the extra space on this line relative +687 687 | to the one before +688 688 | """ + +D.py:686:1: D208 [*] Docstring is over-indented + | +684 | to the one before +685 | This is also overindented +686 | And so is this, but it we should preserve the extra space on this line relative + | D208 +687 | to the one before +688 | """ + | + = help: Remove over-indentation + +ℹ Safe fix +683 683 | And so is this, but it we should preserve the extra space on this line relative +684 684 | to the one before +685 685 | This is also overindented +686 |- And so is this, but it we should preserve the extra space on this line relative + 686 |+ And so is this, but it we should preserve the extra space on this line relative +687 687 | to the one before +688 688 | """ +689 689 | + +D.py:687:1: D208 [*] Docstring is over-indented + | +685 | This is also overindented +686 | And so is this, but it we should preserve the extra space on this line relative +687 | to the one before + | D208 +688 | """ + | + = help: Remove over-indentation + +ℹ Safe fix +684 684 | to the one before +685 685 | This is also overindented +686 686 | And so is this, but it we should preserve the extra space on this line relative +687 |- to the one before + 687 |+ to the one before +688 688 | """ +689 689 | +690 690 | + +D.py:695:1: D208 [*] Docstring is over-indented + | +693 | """Summary. +694 | +695 | This is overindented + | D208 +696 | And so is this, but it we should preserve the extra space on this line relative +697 | to the one before + | + = help: Remove over-indentation + +ℹ Safe fix +692 692 | def retain_extra_whitespace_deeper(): +693 693 | """Summary. +694 694 | +695 |- This is overindented + 695 |+ This is overindented +696 696 | And so is this, but it we should preserve the extra space on this line relative +697 697 | to the one before +698 698 | And the relative indent here should be preserved too + +D.py:696:1: D208 [*] Docstring is over-indented + | +695 | This is overindented +696 | And so is this, but it we should preserve the extra space on this line relative + | D208 +697 | to the one before +698 | And the relative indent here should be preserved too + | + = help: Remove over-indentation + +ℹ Safe fix +693 693 | """Summary. +694 694 | +695 695 | This is overindented +696 |- And so is this, but it we should preserve the extra space on this line relative + 696 |+ And so is this, but it we should preserve the extra space on this line relative +697 697 | to the one before +698 698 | And the relative indent here should be preserved too +699 699 | """ + +D.py:697:1: D208 [*] Docstring is over-indented + | +695 | This is overindented +696 | And so is this, but it we should preserve the extra space on this line relative +697 | to the one before + | D208 +698 | And the relative indent here should be preserved too +699 | """ + | + = help: Remove over-indentation + +ℹ Safe fix +694 694 | +695 695 | This is overindented +696 696 | And so is this, but it we should preserve the extra space on this line relative +697 |- to the one before + 697 |+ to the one before +698 698 | And the relative indent here should be preserved too +699 699 | """ +700 700 | + +D.py:698:1: D208 [*] Docstring is over-indented + | +696 | And so is this, but it we should preserve the extra space on this line relative +697 | to the one before +698 | And the relative indent here should be preserved too + | D208 +699 | """ + | + = help: Remove over-indentation + +ℹ Safe fix +695 695 | This is overindented +696 696 | And so is this, but it we should preserve the extra space on this line relative +697 697 | to the one before +698 |- And the relative indent here should be preserved too + 698 |+ And the relative indent here should be preserved too +699 699 | """ +700 700 | +701 701 | def retain_extra_whitespace_followed_by_same_offset(): + +D.py:704:1: D208 [*] Docstring is over-indented + | +702 | """Summary. +703 | +704 | This is overindented + | D208 +705 | And so is this, but it we should preserve the extra space on this line relative +706 | This is overindented + | + = help: Remove over-indentation + +ℹ Safe fix +701 701 | def retain_extra_whitespace_followed_by_same_offset(): +702 702 | """Summary. +703 703 | +704 |- This is overindented + 704 |+ This is overindented +705 705 | And so is this, but it we should preserve the extra space on this line relative +706 706 | This is overindented +707 707 | This is overindented + +D.py:705:1: D208 [*] Docstring is over-indented + | +704 | This is overindented +705 | And so is this, but it we should preserve the extra space on this line relative + | D208 +706 | This is overindented +707 | This is overindented + | + = help: Remove over-indentation + +ℹ Safe fix +702 702 | """Summary. +703 703 | +704 704 | This is overindented +705 |- And so is this, but it we should preserve the extra space on this line relative + 705 |+ And so is this, but it we should preserve the extra space on this line relative +706 706 | This is overindented +707 707 | This is overindented +708 708 | """ + +D.py:706:1: D208 [*] Docstring is over-indented + | +704 | This is overindented +705 | And so is this, but it we should preserve the extra space on this line relative +706 | This is overindented + | D208 +707 | This is overindented +708 | """ + | + = help: Remove over-indentation + +ℹ Safe fix +703 703 | +704 704 | This is overindented +705 705 | And so is this, but it we should preserve the extra space on this line relative +706 |- This is overindented + 706 |+ This is overindented +707 707 | This is overindented +708 708 | """ +709 709 | + +D.py:707:1: D208 [*] Docstring is over-indented + | +705 | And so is this, but it we should preserve the extra space on this line relative +706 | This is overindented +707 | This is overindented + | D208 +708 | """ + | + = help: Remove over-indentation + +ℹ Safe fix +704 704 | This is overindented +705 705 | And so is this, but it we should preserve the extra space on this line relative +706 706 | This is overindented +707 |- This is overindented + 707 |+ This is overindented +708 708 | """ +709 709 | +710 710 | + diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D213_D.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D213_D.py.snap index c7d77c92180fb..e0f3516e76c32 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D213_D.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D213_D.py.snap @@ -547,4 +547,136 @@ D.py:615:5: D213 [*] Multi-line docstring summary should start at the second lin 617 618 | """ 618 619 | +D.py:671:5: D213 [*] Multi-line docstring summary should start at the second line + | +670 | def retain_extra_whitespace(): +671 | """Summary. + | _____^ +672 | | +673 | | This is overindented +674 | | And so is this, but it we should preserve the extra space on this line relative +675 | | to the one before +676 | | """ + | |_______^ D213 + | + = help: Insert line break and indentation after opening quotes + +ℹ Safe fix +668 668 | +669 669 | +670 670 | def retain_extra_whitespace(): +671 |- """Summary. + 671 |+ """ + 672 |+ Summary. +672 673 | +673 674 | This is overindented +674 675 | And so is this, but it we should preserve the extra space on this line relative + +D.py:680:5: D213 [*] Multi-line docstring summary should start at the second line + | +679 | def retain_extra_whitespace_multiple(): +680 | """Summary. + | _____^ +681 | | +682 | | This is overindented +683 | | And so is this, but it we should preserve the extra space on this line relative +684 | | to the one before +685 | | This is also overindented +686 | | And so is this, but it we should preserve the extra space on this line relative +687 | | to the one before +688 | | """ + | |_______^ D213 + | + = help: Insert line break and indentation after opening quotes + +ℹ Safe fix +677 677 | +678 678 | +679 679 | def retain_extra_whitespace_multiple(): +680 |- """Summary. + 680 |+ """ + 681 |+ Summary. +681 682 | +682 683 | This is overindented +683 684 | And so is this, but it we should preserve the extra space on this line relative + +D.py:693:5: D213 [*] Multi-line docstring summary should start at the second line + | +692 | def retain_extra_whitespace_deeper(): +693 | """Summary. + | _____^ +694 | | +695 | | This is overindented +696 | | And so is this, but it we should preserve the extra space on this line relative +697 | | to the one before +698 | | And the relative indent here should be preserved too +699 | | """ + | |_______^ D213 +700 | +701 | def retain_extra_whitespace_followed_by_same_offset(): + | + = help: Insert line break and indentation after opening quotes + +ℹ Safe fix +690 690 | +691 691 | +692 692 | def retain_extra_whitespace_deeper(): +693 |- """Summary. + 693 |+ """ + 694 |+ Summary. +694 695 | +695 696 | This is overindented +696 697 | And so is this, but it we should preserve the extra space on this line relative + +D.py:702:5: D213 [*] Multi-line docstring summary should start at the second line + | +701 | def retain_extra_whitespace_followed_by_same_offset(): +702 | """Summary. + | _____^ +703 | | +704 | | This is overindented +705 | | And so is this, but it we should preserve the extra space on this line relative +706 | | This is overindented +707 | | This is overindented +708 | | """ + | |_______^ D213 + | + = help: Insert line break and indentation after opening quotes + +ℹ Safe fix +699 699 | """ +700 700 | +701 701 | def retain_extra_whitespace_followed_by_same_offset(): +702 |- """Summary. + 702 |+ """ + 703 |+ Summary. +703 704 | +704 705 | This is overindented +705 706 | And so is this, but it we should preserve the extra space on this line relative + +D.py:712:5: D213 [*] Multi-line docstring summary should start at the second line + | +711 | def retain_extra_whitespace_not_overindented(): +712 | """Summary. + | _____^ +713 | | +714 | | This is not overindented +715 | | This is overindented, but since one line is not overindented this should not raise +716 | | And so is this, but it we should preserve the extra space on this line relative +717 | | """ + | |_______^ D213 + | + = help: Insert line break and indentation after opening quotes + +ℹ Safe fix +709 709 | +710 710 | +711 711 | def retain_extra_whitespace_not_overindented(): +712 |- """Summary. + 712 |+ """ + 713 |+ Summary. +713 714 | +714 715 | This is not overindented +715 716 | This is overindented, but since one line is not overindented this should not raise + diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D400_D.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D400_D.py.snap index 3bdb8fd614c9a..aacdb3584bcfe 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D400_D.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D400_D.py.snap @@ -326,5 +326,8 @@ D.py:664:5: D400 [*] First line should end with a period 664 664 | "We enforce a newline after the closing quote for a multi-line docstring \ 665 |- but continuations shouldn't be considered multi-line" 665 |+ but continuations shouldn't be considered multi-line." +666 666 | +667 667 | +668 668 | diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D415_D.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D415_D.py.snap index c32b473f83aa7..c563e9541455a 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D415_D.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D415_D.py.snap @@ -308,5 +308,8 @@ D.py:664:5: D415 [*] First line should end with a period, question mark, or excl 664 664 | "We enforce a newline after the closing quote for a multi-line docstring \ 665 |- but continuations shouldn't be considered multi-line" 665 |+ but continuations shouldn't be considered multi-line." +666 666 | +667 667 | +668 668 | diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__preview__D300_D.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__preview__D300_D.py.snap index ee2c307aef3bf..7f14c7fe4c8ca 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__preview__D300_D.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__preview__D300_D.py.snap @@ -196,5 +196,8 @@ D.py:664:5: D300 [*] Use triple double quotes `"""` 665 |- but continuations shouldn't be considered multi-line" 664 |+ """We enforce a newline after the closing quote for a multi-line docstring \ 665 |+ but continuations shouldn't be considered multi-line""" +666 666 | +667 667 | +668 668 |