Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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,64 @@
"""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.
"""
31 changes: 31 additions & 0 deletions crates/ruff_linter/src/docstrings/sections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,38 @@ impl<'a> SectionContexts<'a> {
// Skip the first line, which is the summary.
let mut previous_line = lines.next();

// Track the indentation of an active RST directive (e.g., `.. code-block:: python`).
// Lines indented deeper than this are the directive body and should be skipped
// from section detection. See: https://github.com/astral-sh/ruff/issues/23562
let mut directive_indent: Option<TextSize> = None;

while let Some(line) = lines.next() {
let trimmed = line.trim_start();

// Detect RST directive start (lines like `.. code-block:: yaml`)
if trimmed.starts_with(".. ") {
directive_indent = Some(leading_space(&line).text_len());
previous_line = Some(line);
continue;
}

// If inside a directive body, skip indented lines (the directive body)
if let Some(dir_indent) = directive_indent {
if line.trim().is_empty() {
// Blank lines don't end directive bodies
previous_line = Some(line);
continue;
}
let current_indent = leading_space(&line).text_len();
if current_indent > dir_indent {
// Still inside the directive body
previous_line = Some(line);
continue;
}
// Indentation returned to or above directive level — we've exited
directive_indent = None;
}

if let Some(section_kind) = suspected_as_section(&line, style) {
let indent = leading_space(&line);
let indent_size = indent.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,57 @@
---
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"
23 |
24 | Returns:
25 | None
26 +
27 | """
28 |
29 |

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"
49 |
50 | Returns:
51 | None
52 +
53 | """
54 |
55 |

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"
61 |
62 | Notes:
63 | This IS a real section and should still be detected.
64 +
65 | """
Loading