diff --git a/crates/ruff/resources/test/fixtures/pydocstyle/sections.py b/crates/ruff/resources/test/fixtures/pydocstyle/sections.py index 4bd805065c40b..fef7f2fba15c3 100644 --- a/crates/ruff/resources/test/fixtures/pydocstyle/sections.py +++ b/crates/ruff/resources/test/fixtures/pydocstyle/sections.py @@ -513,3 +513,19 @@ def implicit_string_concatenation(): A value of some sort. """"Extra content" + + +def replace_equals_with_dash(): + """Equal length equals should be replaced with dashes. + + Parameters + ========== + """ + + +def replace_equals_with_dash2(): + """Here, the length of equals is not the same. + + Parameters + =========== + """ diff --git a/crates/ruff/src/rules/pydocstyle/rules/sections.rs b/crates/ruff/src/rules/pydocstyle/rules/sections.rs index 91b94e20333ec..5f7be37f5b641 100644 --- a/crates/ruff/src/rules/pydocstyle/rules/sections.rs +++ b/crates/ruff/src/rules/pydocstyle/rules/sections.rs @@ -13,7 +13,7 @@ use ruff_python_ast::docstrings::{clean_space, leading_space}; use ruff_python_ast::identifier::Identifier; use ruff_python_semantic::analyze::visibility::is_staticmethod; use ruff_python_semantic::{Definition, Member, MemberKind}; -use ruff_python_whitespace::NewlineWithTrailingNewline; +use ruff_python_whitespace::{NewlineWithTrailingNewline, PythonWhitespace}; use ruff_textwrap::dedent; use crate::checkers::ast::Checker; @@ -488,6 +488,10 @@ fn blanks_and_section_underline( } } } else { + let equal_line_found = non_blank_line + .chars() + .all(|char| char.is_whitespace() || char == '='); + if checker.enabled(Rule::DashedUnderlineAfterSection) { let mut diagnostic = Diagnostic::new( DashedUnderlineAfterSection { @@ -503,10 +507,23 @@ fn blanks_and_section_underline( clean_space(docstring.indentation), "-".repeat(context.section_name().len()), ); - diagnostic.set_fix(Fix::automatic(Edit::insertion( - content, - context.summary_range().end(), - ))); + if equal_line_found + && non_blank_line.trim_whitespace().len() == context.section_name().len() + { + // If an existing underline is an equal sign line of the appropriate length, + // replace it with a dashed line. + diagnostic.set_fix(Fix::automatic(Edit::replacement( + content, + context.summary_range().end(), + non_blank_line.end(), + ))); + } else { + // Otherwise, insert a dashed line after the section header. + diagnostic.set_fix(Fix::automatic(Edit::insertion( + content, + context.summary_range().end(), + ))); + } } checker.diagnostics.push(diagnostic); } diff --git a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D407_sections.py.snap b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D407_sections.py.snap index a0fe542d21286..f0eac751331cb 100644 --- a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D407_sections.py.snap +++ b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D407_sections.py.snap @@ -453,4 +453,48 @@ sections.py:499:9: D407 [*] Missing dashed underline after section ("Args") 505 506 | 506 507 | """ +sections.py:519:5: D407 [*] Missing dashed underline after section ("Parameters") + | +518 | def replace_equals_with_dash(): +519 | """Equal length equals should be replaced with dashes. + | _____^ +520 | | +521 | | Parameters +522 | | ========== +523 | | """ + | |_______^ D407 + | + = help: Add dashed line under "Parameters" + +ℹ Fix +519 519 | """Equal length equals should be replaced with dashes. +520 520 | +521 521 | Parameters +522 |- ========== + 522 |+ ---------- +523 523 | """ +524 524 | +525 525 | + +sections.py:527:5: D407 [*] Missing dashed underline after section ("Parameters") + | +526 | def replace_equals_with_dash2(): +527 | """Here, the length of equals is not the same. + | _____^ +528 | | +529 | | Parameters +530 | | =========== +531 | | """ + | |_______^ D407 + | + = help: Add dashed line under "Parameters" + +ℹ Fix +527 527 | """Here, the length of equals is not the same. +528 528 | +529 529 | Parameters + 530 |+ ---------- +530 531 | =========== +531 532 | """ +