Skip to content

Commit 1b57c61

Browse files
committed
fix: add multi-line window creation for section header detection
1 parent d9ace1f commit 1b57c61

File tree

3 files changed

+78
-3
lines changed

3 files changed

+78
-3
lines changed

src/docformatter/patterns/lists.py

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
# SOFTWARE.
2727
"""This module provides docformatter's list pattern recognition functions."""
2828

29-
3029
# Standard Library Imports
3130
import re
3231
from re import Match
@@ -53,6 +52,42 @@
5352
from .misc import is_inline_math, is_literal_block
5453

5554

55+
def _create_multiline_windows(lines: list[str], window_size: int = 3) -> list[str]:
56+
r"""Create overlapping windows of consecutive lines.
57+
58+
This allows pattern matching against multi-line constructs like
59+
NumPy section headers (e.g., "Parameters\\n----------") and reST
60+
section headers (e.g., "Title\\n=====").
61+
62+
Parameters
63+
----------
64+
lines : list[str]
65+
The list of individual lines.
66+
window_size : int
67+
Number of consecutive lines to join (default 3).
68+
69+
Returns
70+
-------
71+
list[str]
72+
List of multi-line strings, each containing window_size consecutive
73+
lines joined with newlines.
74+
75+
Notes
76+
-----
77+
Example:
78+
lines = ['A', 'B', 'C', 'D']
79+
_create_multiline_windows(lines, 2)
80+
# Returns: ['A\\nB', 'B\\nC', 'C\\nD']
81+
"""
82+
if len(lines) < window_size:
83+
return ["\n".join(lines)] if lines else []
84+
85+
return [
86+
"\n".join(lines[i : i + window_size])
87+
for i in range(len(lines) - window_size + 1)
88+
]
89+
90+
5691
def is_type_of_list(
5792
text: str,
5893
strict: bool,
@@ -83,16 +118,22 @@ def is_type_of_list(
83118
if is_field_list(text, style):
84119
return False
85120

121+
# Check for multi-line patterns (section headers) first.
122+
# These require looking at consecutive lines together.
123+
multiline_windows = _create_multiline_windows(split_lines, window_size=2)
124+
for window in multiline_windows:
125+
if is_rest_section_header(window) or is_numpy_section_header(window):
126+
return True
127+
128+
# Check single-line patterns.
86129
return any(
87130
(
88131
is_bullet_list(line)
89132
or is_enumerated_list(line)
90-
or is_rest_section_header(line)
91133
or is_option_list(line)
92134
or is_epytext_field_list(line)
93135
or is_sphinx_field_list(line)
94136
or is_numpy_field_list(line)
95-
or is_numpy_section_header(line)
96137
or is_google_field_list(line)
97138
or is_user_defined_field_list(line)
98139
or is_literal_block(line)

tests/_data/string_files/list_patterns.toml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,3 +190,35 @@ This is a description.
190190
strict = false
191191
style = "epytext"
192192
expected = false
193+
194+
[is_numpy_section_in_docstring_issue_338] # See GitHub issue #338
195+
instring = """Do a number of things.
196+
197+
Parameters
198+
----------
199+
n
200+
How many things to do.
201+
colors
202+
True to paint the things in bright colors, False to keep them
203+
dull and grey.
204+
205+
Returns
206+
-------
207+
int
208+
How many things were actually done.
209+
"""
210+
strict = false
211+
style = "numpy"
212+
expected = true
213+
214+
[is_rest_section_in_docstring]
215+
instring = """This is a description.
216+
217+
Section Title
218+
=============
219+
220+
Some content under the section.
221+
"""
222+
strict = false
223+
style = "numpy"
224+
expected = true

tests/patterns/test_list_patterns.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@
7373
"is_type_of_list_alembic_header",
7474
"is_epytext_field_list",
7575
"is_sphinx_field_list",
76+
"is_numpy_section_in_docstring_issue_338",
77+
"is_rest_section_in_docstring",
7678
],
7779
)
7880
def test_is_type_of_list(test_key):

0 commit comments

Comments
 (0)