Skip to content
Open
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
2 changes: 1 addition & 1 deletion src/docformatter/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@
# (\S*) matches any non-whitespace character between zero and infinity times.
# >? matches the character > between zero and one times.
URL_REGEX = (
rf"(__ |`{{2}}|`\w[\w :#\n]*[.|\.\. _?[\w. :]+|')?<?"
rf"(__ |`{{2}}|`\w[\w :#\n]*|\.\. _[\w. :]+|')?<?"
rf"({URL_PATTERNS}):(\//)?(\S*)>?"
)

Expand Down
32 changes: 32 additions & 0 deletions src/docformatter/patterns/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,38 @@ def do_find_field_lists(
for _field in re.finditer(SPHINX_REGEX, text)
]
_wrap_parameters = True
elif style == "google":
_field_idx = [
(_field.start(0), _field.end(0))
for _field in re.finditer(GOOGLE_REGEX, text, re.MULTILINE)
]
_wrap_parameters = False # Don't wrap Google-style field lists
elif style == "numpy":
_field_idx = [
(_field.start(0), _field.end(0))
for _field in re.finditer(NUMPY_REGEX, text, re.MULTILINE)
]
_wrap_parameters = False # Don't wrap NumPy-style field lists

# If no field lists were found for the current style, check for field lists
# from other styles and preserve them as-is (don't wrap).
if not _field_idx:
# Check for Google-style field lists (e.g., "Args:", "Returns:").
# Use a more specific pattern that only matches known Google section names.
google_sections = r'^ *(Args|Arguments|Attributes|Example|Examples|Note|Notes|' \
r'See Also|References|Returns|Return|Raises|Raise|Yields|Yield|' \
r'Warns|Warning|Warnings|Receives|Receive|Other Parameters):$'
google_matches = list(re.finditer(google_sections, text, re.MULTILINE))
if google_matches:
_field_idx = [(_field.start(0), _field.end(0)) for _field in google_matches]
_wrap_parameters = False

# If still nothing, check for NumPy-style field lists
if not _field_idx:
numpy_matches = list(re.finditer(NUMPY_REGEX, text, re.MULTILINE))
if numpy_matches:
_field_idx = [(_field.start(0), _field.end(0)) for _field in numpy_matches]
_wrap_parameters = False

return _field_idx, _wrap_parameters

Expand Down
72 changes: 60 additions & 12 deletions src/docformatter/patterns/lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,25 +82,73 @@ def is_type_of_list(

if is_field_list(text, style):
return False

return any(
(

# Check for definition list pattern (term followed by indented definition)
# This is a conservative check that only triggers for terms with special markup
for i, line in enumerate(split_lines):
# Skip empty lines and lines ending with ':' or starting with '<' (URLs)
if not line.strip() or line.rstrip().endswith(':') or line.strip().startswith('<'):
continue
# Check if next line exists and is indented more than current line
if i < len(split_lines) - 1:
next_line = split_lines[i + 1]
# If current line has content and next line is indented, it might be a definition list
if line.strip() and next_line.startswith(' ') and next_line.strip():
# Additional check: current line shouldn't start with common list markers
if not (line.strip().startswith(('*', '-', '+')) or
line.strip()[0:2].rstrip().isdigit()):
# Skip if this looks like an inline link continuation:
# Line has backtick but doesn't end with >`_ and next line starts with <
if ('`' in line and not line.rstrip().endswith('>`_') and
next_line.strip().startswith('<')):
continue
# Only consider it a definition list if the term has special markup like ``term``
# This is a conservative check to avoid false positives
if '``' in line:
return True

# Check for various list patterns
for line in split_lines:
# Always check for non-field-list patterns
if (
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)
or is_inline_math(line)
or is_alembic_header(line)
)
for line in split_lines
)
or is_user_defined_field_list(line)
):
return True

# For field list patterns from other styles:
# - When using epytext or sphinx (field-based styles), do NOT treat
# section-based styles (Google/NumPy) as lists to skip. Instead, return
# False so that do_split_description can wrap the description while
# preserving the field sections.
# - When using numpy or google (section-based styles), check for all field
# list patterns to maintain backward compatibility.
if style in ("numpy", "google"):
# For numpy and google styles, check all field list patterns
if (
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)
):
return True
elif style in ("epytext", "sphinx"):
# For field-based styles, only check for OTHER field-based styles
if style != "epytext" and is_epytext_field_list(line):
return True
if style != "sphinx" and is_sphinx_field_list(line):
return True
# Do NOT check for Google/NumPy patterns - they'll be preserved by
# do_split_description

return False


def is_bullet_list(line: str) -> Union[Match[str], None]:
Expand Down
22 changes: 20 additions & 2 deletions src/docformatter/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ def do_split_description(
_url_idx,
)

if not _url_idx and not (_field_idx and _wrap_fields):
if not _url_idx and not _field_idx:
return description_to_list(
text,
indentation,
Expand All @@ -314,7 +314,7 @@ def do_split_description(
wrap_length,
)

if _field_idx:
if _field_idx and _wrap_fields:
_lines, _text_idx = _wrappers.do_wrap_field_lists(
text,
_field_idx,
Expand All @@ -323,6 +323,24 @@ def do_split_description(
indentation,
wrap_length,
)
elif _field_idx and not _wrap_fields:
# Field lists were found but should not be wrapped (e.g., Google/NumPy style
# when using a different style). Wrap the text before the first field list,
# then preserve the rest as-is.
_lines.extend(
description_to_list(
text[_text_idx : _field_idx[0][0]],
indentation,
wrap_length,
)
)
# Add the field list section as-is, preserving original formatting.
# The text has already been reindented by do_wrap_description, so we
# just preserve the lines as they are.
_field_section = text[_field_idx[0][0]:].splitlines()
for line in _field_section:
_lines.append(line if line.strip() else "")
_text_idx = len(text)
else:
# Finally, add everything after the last URL or field list directive.
_lines += _wrappers.do_close_description(text, _text_idx, indentation)
Expand Down
7 changes: 7 additions & 0 deletions src/docformatter/wrappers/description.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ def do_wrap_description( # noqa: PLR0913
):
return text

# When force_wrap is True, wrap everything as regular text without special
# handling for field lists.
if force_wrap:
return indentation + "\n".join(
_strings.description_to_list(text, indentation, wrap_length)
).strip()

lines = _strings.do_split_description(text, indentation, wrap_length, style)

return indentation + "\n".join(lines).strip()
Expand Down
3 changes: 2 additions & 1 deletion tests/_data/string_files/description_wrappers.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ This is a long description from a docstring that will contain an heuristic list.
Item 3
"""
expected = """
This is a long description from a docstring that will contain an heuristic list. The description shouldn't get wrapped at all.
This is a long description from a docstring that will contain an
heuristic list. The description shouldn't get wrapped at all.

Example:
Item one
Expand Down
2 changes: 1 addition & 1 deletion tests/_data/string_files/list_patterns.toml
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ Parameters
"""
strict = false
style = "sphinx"
expected = true
expected = false # Changed from true: NumPy sections in Sphinx docs should be preserved, not skip wrapping

[is_google_list_numpy_style]
instring = """\
Expand Down
10 changes: 3 additions & 7 deletions tests/test_docformatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,15 +478,11 @@ def test_ignore_already_wrapped_link(
See issue #150.
"""
assert '''\
@@ -1,6 +1,7 @@
def foo():
"""Description from issue #150 that was being improperly wrapped.
@@ -3,4 +3,5 @@

- The text file can be retrieved via the Chrome plugin `Get
- Cookies.txt <https://chrome.google.com/webstore/detail/get-
The text file can be retrieved via the Chrome plugin `Get
Cookies.txt <https://chrome.google.com/webstore/detail/get-
- cookiestxt/bgaddhkoddajcdgocldbbfleckgcbcid>` while browsing."""
+ The text file can be retrieved via the Chrome plugin
+ `Get Cookies.txt <https://chrome.google.com/webstore/detail/get-
+ cookiestxt/bgaddhkoddajcdgocldbbfleckgcbcid>` while browsing.
+ """
''' == "\n".join(
Expand Down