Skip to content

Commit

Permalink
[pydocstyle-D405] Allow using parameters as a sub-section header (#…
Browse files Browse the repository at this point in the history
…9894)

## Summary

This review contains a fix for
[D405](https://docs.astral.sh/ruff/rules/capitalize-section-name/)
(capitalize-section-name)
The problem is that Ruff considers the sub-section header as a normal
section if it has the same name as some section name. For instance, a
function/method has an argument named "parameters". This only applies if
you use Numpy style docstring.

See: [ISSUE](#9806)

The following will not raise D405 after the fix:
```python  
def some_function(parameters: list[str]):
    """A function with a parameters parameter

    Parameters
    ----------

    parameters:
        A list of string parameters
    """
    ...
```


## Test Plan

```bash
cargo test
```

---------

Co-authored-by: Mikko Leppänen <mikko.leppanen@vaisala.com>
Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
  • Loading branch information
3 people committed Feb 9, 2024
1 parent 49fe1b8 commit b4f2882
Show file tree
Hide file tree
Showing 7 changed files with 235 additions and 4 deletions.
43 changes: 43 additions & 0 deletions crates/ruff_linter/resources/test/fixtures/pydocstyle/sections.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
"""
77 changes: 73 additions & 4 deletions crates/ruff_linter/src/docstrings/sections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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: <https://numpydoc.readthedocs.io/en/latest/format.html>
///
/// For Google, see: <https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings>
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> {
Expand Down Expand Up @@ -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;
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,7 @@ sections.py:558:5: D214 [*] Section is over-indented ("Returns")
563 |- Returns:
563 |+ Returns:
564 564 | """
565 565 |
566 566 |


Original file line number Diff line number Diff line change
Expand Up @@ -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


Original file line number Diff line number Diff line change
Expand Up @@ -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


Original file line number Diff line number Diff line change
Expand Up @@ -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:


Original file line number Diff line number Diff line change
Expand Up @@ -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 |


0 comments on commit b4f2882

Please sign in to comment.