diff --git a/crates/ruff_linter/resources/test/fixtures/pydocstyle/sections.py b/crates/ruff_linter/resources/test/fixtures/pydocstyle/sections.py index 4a50617d26be7..ab0b02132b265 100644 --- a/crates/ruff_linter/resources/test/fixtures/pydocstyle/sections.py +++ b/crates/ruff_linter/resources/test/fixtures/pydocstyle/sections.py @@ -562,3 +562,46 @@ def titlecase_sub_section_header(): Returns: """ + + +def test_method_should_be_correctly_capitalized(parameters: list[str], other_parameters: dict[str, str]): # noqa: D213 + """Test parameters and attributes sections are capitalized correctly. + + Parameters + ---------- + parameters: + A list of string parameters + other_parameters: + A dictionary of string attributes + + Other Parameters + ---------- + other_parameters: + A dictionary of string attributes + parameters: + A list of string parameters + + """ + + +def test_lowercase_sub_section_header_should_be_valid(parameters: list[str], value: int): # noqa: D213 + """Test that lower case subsection header is valid even if it has the same name as section kind. + + Parameters: + ---------- + parameters: + A list of string parameters + value: + Some value + """ + + +def test_lowercase_sub_section_header_different_kind(returns: int): + """Test that lower case subsection header is valid even if it is of a different kind. + + Parameters + -‐----------------- + returns: + some value + + """ diff --git a/crates/ruff_linter/src/docstrings/sections.rs b/crates/ruff_linter/src/docstrings/sections.rs index 04dfb08e214eb..a6560084ff48b 100644 --- a/crates/ruff_linter/src/docstrings/sections.rs +++ b/crates/ruff_linter/src/docstrings/sections.rs @@ -130,6 +130,34 @@ impl SectionKind { Self::Yields => "Yields", } } + + /// Returns `true` if a section can contain subsections, as in: + /// ```python + /// Yields + /// ------ + /// int + /// Description of the anonymous integer return value. + /// ``` + /// + /// For NumPy, see: + /// + /// For Google, see: + pub(crate) fn has_subsections(self) -> bool { + matches!( + self, + Self::Args + | Self::Arguments + | Self::OtherArgs + | Self::OtherParameters + | Self::OtherParams + | Self::Parameters + | Self::Raises + | Self::Returns + | Self::SeeAlso + | Self::Warns + | Self::Yields + ) + } } pub(crate) struct SectionContexts<'a> { @@ -462,13 +490,54 @@ fn is_docstring_section( // args: The arguments to the function. // """ // ``` + // Or `parameters` in: + // ```python + // def func(parameters: tuple[int]): + // """Toggle the gizmo. + // + // Parameters: + // ----- + // parameters: + // The arguments to the function. + // """ + // ``` // However, if the header is an _exact_ match (like `Returns:`, as opposed to `returns:`), then // continue to treat it as a section header. - if let Some(previous_section) = previous_section { - if previous_section.indent_size < indent_size { + if section_kind.has_subsections() { + if let Some(previous_section) = previous_section { let verbatim = &line[TextRange::at(indent_size, section_name_size)]; - if section_kind.as_str() != verbatim { - return false; + + // If the section is more deeply indented, assume it's a subsection, as in: + // ```python + // def func(args: tuple[int]): + // """Toggle the gizmo. + // + // Args: + // args: The arguments to the function. + // """ + // ``` + if previous_section.indent_size < indent_size { + if section_kind.as_str() != verbatim { + return false; + } + } + + // If the section isn't underlined, and isn't title-cased, assume it's a subsection, + // as in: + // ```python + // def func(parameters: tuple[int]): + // """Toggle the gizmo. + // + // Parameters: + // ----- + // parameters: + // The arguments to the function. + // """ + // ``` + if !next_line_is_underline && verbatim.chars().next().is_some_and(char::is_lowercase) { + if section_kind.as_str() != verbatim { + return false; + } } } } diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D214_sections.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D214_sections.py.snap index 3fd044b8d8514..d8ce888327b5c 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D214_sections.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D214_sections.py.snap @@ -49,5 +49,7 @@ sections.py:558:5: D214 [*] Section is over-indented ("Returns") 563 |- Returns: 563 |+ Returns: 564 564 | """ +565 565 | +566 566 | diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D406_sections.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D406_sections.py.snap index d996d6fe54aa4..14530459e76bd 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D406_sections.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D406_sections.py.snap @@ -61,4 +61,31 @@ sections.py:216:5: D406 [*] Section name should end with a newline ("Raises") 229 229 | 230 230 | """ +sections.py:588:5: D406 [*] Section name should end with a newline ("Parameters") + | +587 | def test_lowercase_sub_section_header_should_be_valid(parameters: list[str], value: int): # noqa: D213 +588 | """Test that lower case subsection header is valid even if it has the same name as section kind. + | _____^ +589 | | +590 | | Parameters: +591 | | ---------- +592 | | parameters: +593 | | A list of string parameters +594 | | value: +595 | | Some value +596 | | """ + | |_______^ D406 + | + = help: Add newline after "Parameters" + +ℹ Safe fix +587 587 | def test_lowercase_sub_section_header_should_be_valid(parameters: list[str], value: int): # noqa: D213 +588 588 | """Test that lower case subsection header is valid even if it has the same name as section kind. +589 589 | +590 |- Parameters: + 590 |+ Parameters +591 591 | ---------- +592 592 | parameters: +593 593 | A list of string parameters + diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D407_sections.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D407_sections.py.snap index 53e527de24b33..95efe19803e69 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D407_sections.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D407_sections.py.snap @@ -567,5 +567,32 @@ sections.py:558:5: D407 [*] Missing dashed underline after section ("Returns") 563 563 | Returns: 564 |+ ------- 564 565 | """ +565 566 | +566 567 | + +sections.py:600:4: D407 [*] Missing dashed underline after section ("Parameters") + | +599 | def test_lowercase_sub_section_header_different_kind(returns: int): +600 | """Test that lower case subsection header is valid even if it is of a different kind. + | ____^ +601 | | +602 | | Parameters +603 | | -‐----------------- +604 | | returns: +605 | | some value +606 | | +607 | | """ + | |______^ D407 + | + = help: Add dashed line under "Parameters" + +ℹ Safe fix +600 600 | """Test that lower case subsection header is valid even if it is of a different kind. +601 601 | +602 602 | Parameters + 603 |+ ---------- +603 604 | -‐----------------- +604 605 | returns: +605 606 | some value diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D409_sections.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D409_sections.py.snap index ce559c70a0dc2..881b5a0f0f099 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D409_sections.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D409_sections.py.snap @@ -61,4 +61,39 @@ sections.py:216:5: D409 [*] Section underline should match the length of its nam 227 227 | Raises: 228 228 | My attention. +sections.py:568:5: D409 [*] Section underline should match the length of its name ("Other Parameters") + | +567 | def test_method_should_be_correctly_capitalized(parameters: list[str], other_parameters: dict[str, str]): # noqa: D213 +568 | """Test parameters and attributes sections are capitalized correctly. + | _____^ +569 | | +570 | | Parameters +571 | | ---------- +572 | | parameters: +573 | | A list of string parameters +574 | | other_parameters: +575 | | A dictionary of string attributes +576 | | +577 | | Other Parameters +578 | | ---------- +579 | | other_parameters: +580 | | A dictionary of string attributes +581 | | parameters: +582 | | A list of string parameters +583 | | +584 | | """ + | |_______^ D409 + | + = help: Adjust underline length to match "Other Parameters" + +ℹ Safe fix +575 575 | A dictionary of string attributes +576 576 | +577 577 | Other Parameters +578 |- ---------- + 578 |+ ---------------- +579 579 | other_parameters: +580 580 | A dictionary of string attributes +581 581 | parameters: + diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D413_sections.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D413_sections.py.snap index f2bf6474708dc..0d1bcec87a0bc 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D413_sections.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D413_sections.py.snap @@ -161,5 +161,33 @@ sections.py:558:5: D413 [*] Missing blank line after last section ("Returns") 563 563 | Returns: 564 |+ 564 565 | """ +565 566 | +566 567 | + +sections.py:588:5: D413 [*] Missing blank line after last section ("Parameters") + | +587 | def test_lowercase_sub_section_header_should_be_valid(parameters: list[str], value: int): # noqa: D213 +588 | """Test that lower case subsection header is valid even if it has the same name as section kind. + | _____^ +589 | | +590 | | Parameters: +591 | | ---------- +592 | | parameters: +593 | | A list of string parameters +594 | | value: +595 | | Some value +596 | | """ + | |_______^ D413 + | + = help: Add blank line after "Parameters" + +ℹ Safe fix +593 593 | A list of string parameters +594 594 | value: +595 595 | Some value + 596 |+ +596 597 | """ +597 598 | +598 599 |