From bd99175fea3f804b1106d94969611d4efaa29944 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 16 Nov 2023 22:11:07 -0600 Subject: [PATCH] Update `D208` to preserve indentation offsets when fixing overindented lines (#8699) Closes https://github.com/astral-sh/ruff/issues/8695 We track the smallest offset seen for overindented lines then only reduce the indentation of the lines that far to preserve indentation in other lines. This rule's behavior now matches our formatter, which is nice. We may want to gate this with preview. --- .../resources/test/fixtures/pydocstyle/D.py | 52 +++ .../src/rules/pydocstyle/rules/indent.rs | 26 +- ...__rules__pydocstyle__tests__D208_D.py.snap | 349 ++++++++++++++++++ ...__rules__pydocstyle__tests__D213_D.py.snap | 132 +++++++ ...__rules__pydocstyle__tests__D400_D.py.snap | 3 + ...__rules__pydocstyle__tests__D415_D.py.snap | 3 + ...pydocstyle__tests__preview__D300_D.py.snap | 3 + 7 files changed, 561 insertions(+), 7 deletions(-) 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 |