Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""A module-level docstring with a Sphinx directive containing section-like content.

.. code-block:: yaml

references:
- ref: Bibliographic citation in your favorite format.
refType: open literature

This is more text after the directive.
"""


def func():
"""A function-level docstring with a Sphinx directive.

Examples:
This is an example.

.. code-block:: python

returns = "not a section"
notes = "also not a section"

Returns:
None
"""


def func2():
"""A function-level docstring with single-colon directive (invalid RST but still common).

.. code-block: yaml

references:
- ref: Some reference.

More text.
"""


def func3():
"""A function-level docstring with nested directives.

.. note::

.. code-block:: python

warnings = "not a section"
Comment thread
ntBre marked this conversation as resolved.

Returns:
None
"""


def func4():
"""A function-level docstring where a real section follows a directive.

.. code-block:: python

example = "code"

Notes:
This IS a real section and should still be detected.
"""
# Regression test for nested directive state.
def func5():
"""A nested directive whose outer body contains section-like content.

.. note::

.. code-block:: yaml

references:
- ref: Some reference.

references:
Still part of the note body, not a section.
"""
26 changes: 23 additions & 3 deletions crates/ruff_linter/src/docstrings/sections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,31 @@ impl<'a> SectionContexts<'a> {
// Skip the first line, which is the summary.
let mut previous_line = lines.next();

// Track only the outermost RST directive. Nested directives remain in its body
// until a non-blank line dedents to the outermost indentation.
// See: https://github.com/astral-sh/ruff/issues/23562
let mut directive_indent = None;

while let Some(line) = lines.next() {
if let Some(section_kind) = suspected_as_section(&line, style) {
let indent = leading_space(&line);
let indent_size = indent.text_len();
let indent = leading_space(&line);
let indent_size = indent.text_len();

if let Some(active_indent) = directive_indent
&& (line.trim().is_empty() || indent_size > active_indent)
{
previous_line = Some(line);
continue;
}

directive_indent = None;

if line.trim_start().starts_with(".. ") {
directive_indent = Some(indent_size);
previous_line = Some(line);
continue;
}

if let Some(section_kind) = suspected_as_section(&line, style) {
let section_name = leading_words(&line);
let section_name_size = section_name.text_len();

Expand Down
6 changes: 6 additions & 0 deletions crates/ruff_linter/src/rules/pydocstyle/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ mod tests {
#[test_case(Rule::MissingSectionNameColon, Path::new("D.py"))]
#[test_case(Rule::OverindentedSection, Path::new("sections.py"))]
#[test_case(Rule::OverindentedSection, Path::new("D214_module.py"))]
#[test_case(Rule::OverindentedSection, Path::new("sphinx_directive.py"))]
#[test_case(Rule::NonCapitalizedSectionName, Path::new("sphinx_directive.py"))]
#[test_case(
Rule::MissingBlankLineAfterLastSection,
Path::new("sphinx_directive.py")
)]
#[test_case(Rule::OverindentedSectionUnderline, Path::new("D215.py"))]
#[test_case(Rule::MissingSectionUnderlineAfterName, Path::new("sections.py"))]
#[test_case(Rule::MismatchedSectionUnderlineLength, Path::new("sections.py"))]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D413 [*] Missing blank line after last section ("Returns")
--> sphinx_directive.py:24:5
|
22 | notes = "also not a section"
23 |
24 | Returns:
| ^^^^^^^
25 | None
26 | """
|
help: Add blank line after "Returns"
|
25 | None
26 +
27 | """
|

D413 [*] Missing blank line after last section ("Returns")
--> sphinx_directive.py:50:5
|
48 | warnings = "not a section"
49 |
50 | Returns:
| ^^^^^^^
51 | None
52 | """
|
help: Add blank line after "Returns"
|
51 | None
52 +
53 | """
|

D413 [*] Missing blank line after last section ("Notes")
--> sphinx_directive.py:62:5
|
60 | example = "code"
61 |
62 | Notes:
| ^^^^^
63 | This IS a real section and should still be detected.
64 | """
|
help: Add blank line after "Notes"
|
63 | This IS a real section and should still be detected.
64 +
65 | """
|
Loading