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.
This commit is contained in:
Zanie Blue 2023-11-16 22:11:07 -06:00 committed by GitHub
parent 4c86b155f2
commit bd99175fea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 561 additions and 7 deletions

View file

@ -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);

View file

@ -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 |

View file

@ -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

View file

@ -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 |

View file

@ -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 |

View file

@ -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 |