Skip to content
Draft
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
47 changes: 44 additions & 3 deletions src/docformatter/patterns/lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
# SOFTWARE.
"""This module provides docformatter's list pattern recognition functions."""


# Standard Library Imports
import re
from re import Match
Expand All @@ -53,6 +52,42 @@
from .misc import is_inline_math, is_literal_block


def _create_multiline_windows(lines: list[str], window_size: int = 3) -> list[str]:
r"""Create overlapping windows of consecutive lines.

This allows pattern matching against multi-line constructs like
NumPy section headers (e.g., "Parameters\\n----------") and reST
section headers (e.g., "Title\\n=====").

Parameters
----------
lines : list[str]
The list of individual lines.
window_size : int
Number of consecutive lines to join (default 3).

Returns
-------
list[str]
List of multi-line strings, each containing window_size consecutive
lines joined with newlines.

Notes
-----
Example:
lines = ['A', 'B', 'C', 'D']
_create_multiline_windows(lines, 2)
# Returns: ['A\\nB', 'B\\nC', 'C\\nD']
"""
if len(lines) < window_size:
return ["\n".join(lines)] if lines else []

return [
"\n".join(lines[i : i + window_size])
for i in range(len(lines) - window_size + 1)
]


def is_type_of_list(
text: str,
strict: bool,
Expand Down Expand Up @@ -83,16 +118,22 @@ def is_type_of_list(
if is_field_list(text, style):
return False

# Check for multi-line patterns (section headers) first.
# These require looking at consecutive lines together.
multiline_windows = _create_multiline_windows(split_lines, window_size=2)
for window in multiline_windows:
if is_rest_section_header(window) or is_numpy_section_header(window):
return True

# Check single-line patterns.
return any(
(
is_bullet_list(line)
or is_enumerated_list(line)
or is_rest_section_header(line)
or is_option_list(line)
or is_epytext_field_list(line)
or is_sphinx_field_list(line)
or is_numpy_field_list(line)
or is_numpy_section_header(line)
or is_google_field_list(line)
or is_user_defined_field_list(line)
or is_literal_block(line)
Expand Down
32 changes: 32 additions & 0 deletions tests/_data/string_files/list_patterns.toml
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,35 @@ This is a description.
strict = false
style = "epytext"
expected = false

[is_numpy_section_in_docstring_issue_338] # See GitHub issue #338
instring = """Do a number of things.

Parameters
----------
n
How many things to do.
colors
True to paint the things in bright colors, False to keep them
dull and grey.

Returns
-------
int
How many things were actually done.
"""
strict = false
style = "numpy"
expected = true

[is_rest_section_in_docstring]
instring = """This is a description.

Section Title
=============

Some content under the section.
"""
strict = false
style = "numpy"
expected = true
2 changes: 2 additions & 0 deletions tests/patterns/test_list_patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@
"is_type_of_list_alembic_header",
"is_epytext_field_list",
"is_sphinx_field_list",
"is_numpy_section_in_docstring_issue_338",
"is_rest_section_in_docstring",
],
)
def test_is_type_of_list(test_key):
Expand Down