Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Consider space-only lines to be empty #220

Merged
merged 4 commits into from
Jan 14, 2024
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
4 changes: 0 additions & 4 deletions docs/docstrings.md
Original file line number Diff line number Diff line change
Expand Up @@ -832,10 +832,6 @@ The parser accepts a few options:
These flags are used to alter the behavior of [doctest][] when testing docstrings,
and should not be visible in your docs. Default: true.
- `warn_unknown_params`: Warn about parameters documented in docstrings that do not appear in the signature. Default: true.
- `allow_section_blank_line`: Allow blank lines in sections' content.
When false, a blank line finishes the current section.
When true, single blank lines are kept as part of the section.
You can terminate sections with double blank lines. Default: false.

#### Attributes

Expand Down
10 changes: 5 additions & 5 deletions src/griffe/docstrings/google.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,11 @@ def _read_block_items(docstring: Docstring, *, offset: int, **options: Any) -> I
while new_offset < len(lines):
line = lines[new_offset]

if line.startswith(indent * 2 * " "):
if _is_empty_line(line):
# empty line: preserve it in the current item
current_item[1].append("")

elif line.startswith(indent * 2 * " "):
# continuation line
current_item[1].append(line[indent * 2 :])

Expand All @@ -125,10 +129,6 @@ def _read_block_items(docstring: Docstring, *, offset: int, **options: Any) -> I
f"should be {indent} * 2 = {indent*2} spaces, not {cont_indent}",
)

elif _is_empty_line(line):
# empty line: preserve it in the current item
current_item[1].append("")

elif line.startswith(indent * " "):
# indent equal to initial one: new item
items.append(current_item)
Expand Down
37 changes: 8 additions & 29 deletions src/griffe/docstrings/numpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ def _read_block_items(
docstring: Docstring,
*,
offset: int,
allow_section_blank_line: bool,
**options: Any, # noqa: ARG001
) -> tuple[list[list[str]], int]:
lines = docstring.lines
Expand All @@ -109,8 +108,6 @@ def _read_block_items(
while _is_empty_line(lines[new_offset]):
new_offset += 1

previous_was_empty = False

# start processing first item
current_item = [lines[new_offset]]
new_offset += 1
Expand All @@ -119,10 +116,13 @@ def _read_block_items(
while new_offset < len(lines):
line = lines[new_offset]

if line.startswith(4 * " "):
if _is_empty_line(line):
# empty line: preserve it in the current item
current_item.append("")

elif line.startswith(4 * " "):
# continuation line
current_item.append(line[4:])
previous_was_empty = False

elif line.startswith(" "):
# indent between initial and continuation: append but warn
Expand All @@ -134,30 +134,14 @@ def _read_block_items(
f"Confusing indentation for continuation line {new_offset+1} in docstring, "
f"should be 4 spaces, not {cont_indent}",
)
previous_was_empty = False

elif _is_empty_line(line):
# two line breaks indicate the start of a new section
if previous_was_empty:
break

# empty line: preserve it in the current item
current_item.append("")
previous_was_empty = True

else:
# preserve original behavior, that a single line break between block
# items triggers a new section
if not allow_section_blank_line and previous_was_empty:
break

elif new_offset + 1 < len(lines) and _is_dash_line(lines[new_offset + 1]):
# detect the start of a new section
if new_offset + 1 < len(lines) and lines[new_offset + 1].startswith("---"):
break
break

else:
items.append(current_item)
current_item = [line]
previous_was_empty = False

new_offset += 1

Expand Down Expand Up @@ -758,7 +742,6 @@ def parse(
*,
ignore_init_summary: bool = False,
trim_doctest_flags: bool = True,
allow_section_blank_line: bool = False,
warn_unknown_params: bool = True,
**options: Any,
) -> list[DocstringSection]:
Expand All @@ -771,9 +754,6 @@ def parse(
docstring: The docstring to parse.
ignore_init_summary: Whether to ignore the summary in `__init__` methods' docstrings.
trim_doctest_flags: Whether to remove doctest flags from Python example blocks.
allow_section_blank_line: Whether to continue a section if there's an empty line
between items in a formatted block, like Parameters or Returns.
If True, you can still create a new section using two empty lines.
warn_unknown_params: Warn about documented parameters not appearing in the signature.
**options: Additional parsing options.

Expand All @@ -789,7 +769,6 @@ def parse(
options = {
"trim_doctest_flags": trim_doctest_flags,
"ignore_init_summary": ignore_init_summary,
"allow_section_blank_line": allow_section_blank_line,
"warn_unknown_params": warn_unknown_params,
**options,
}
Expand Down
48 changes: 41 additions & 7 deletions tests/test_docstrings/test_numpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,8 @@ def test_empty_indented_lines_in_section_with_items(parse_numpy: ParserType) ->
"""
docstring = "Returns\n-------\nonly_item : type\n Description.\n \n \n\nSomething."
sections, _ = parse_numpy(docstring)
assert len(sections) == 2
assert len(sections[0].value) == 1

# allow_section_blank_line requires at least 2 newlines to create a new section
sections2, _ = parse_numpy(docstring, allow_section_blank_line=True)
assert len(sections2) == 1
assert len(sections2[0].value) == 2
assert len(sections) == 1
assert len(sections[0].value) == 2


def test_doubly_indented_lines_in_section_items(parse_numpy: ParserType) -> None:
Expand Down Expand Up @@ -711,6 +706,23 @@ def test_examples_section_as_last(parse_numpy: ParserType) -> None:
assert sections[1].kind is DocstringSectionKind.examples


def test_blank_lines_in_section(parse_numpy: ParserType) -> None:
"""Support blank lines in the middle of sections.

Parameters:
parse_numpy: Fixture parser.
"""
docstring = """
Examples
--------
Line 1.

Line 2.
"""
sections, _ = parse_numpy(docstring)
assert len(sections) == 1


# =============================================================================================
# Attributes sections
def test_retrieve_attributes_annotation_from_parent(parse_numpy: ParserType) -> None:
Expand Down Expand Up @@ -863,6 +875,28 @@ def test_detect_optional_flag(parse_numpy: ParserType) -> None:
assert sections[0].value[2].default == "b''"


@pytest.mark.parametrize("newlines", [1, 2, 3])
def test_blank_lines_in_item_descriptions(parse_numpy: ParserType, newlines: int) -> None:
"""Support blank lines in the middle of item descriptions.

Parameters:
parse_numpy: Fixture parser.
newlines: Number of new lines between item summary and its body.
"""
nl = "\n"
nlindent = "\n" + " " * 12
docstring = f"""
Parameters
----------
a : str
Summary.{nlindent * newlines}Body.
"""
sections, _ = parse_numpy(docstring)
assert len(sections) == 1
assert sections[0].value[0].annotation == "str"
assert sections[0].value[0].description == f"Summary.{nl * newlines}Body."


# =============================================================================================
# Yields sections
@pytest.mark.parametrize(
Expand Down
2 changes: 1 addition & 1 deletion tests/test_inspector.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

from __future__ import annotations

import sys
from pathlib import Path

import pytest

from griffe.agents.inspector import inspect
import sys
from griffe.tests import temporary_inspected_module, temporary_pypackage


Expand Down