diff --git a/docs/docstrings.md b/docs/docstrings.md index a831481a..3f2d1282 100644 --- a/docs/docstrings.md +++ b/docs/docstrings.md @@ -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 diff --git a/src/griffe/docstrings/google.py b/src/griffe/docstrings/google.py index c8feee9b..e7a16ad7 100644 --- a/src/griffe/docstrings/google.py +++ b/src/griffe/docstrings/google.py @@ -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 :]) @@ -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) diff --git a/src/griffe/docstrings/numpy.py b/src/griffe/docstrings/numpy.py index e9d7b892..8fe956cf 100644 --- a/src/griffe/docstrings/numpy.py +++ b/src/griffe/docstrings/numpy.py @@ -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 @@ -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 @@ -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 @@ -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 @@ -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]: @@ -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. @@ -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, } diff --git a/tests/test_docstrings/test_numpy.py b/tests/test_docstrings/test_numpy.py index 11e7ccab..81232d20 100644 --- a/tests/test_docstrings/test_numpy.py +++ b/tests/test_docstrings/test_numpy.py @@ -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: @@ -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: @@ -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( diff --git a/tests/test_inspector.py b/tests/test_inspector.py index 77629671..cc0dd871 100644 --- a/tests/test_inspector.py +++ b/tests/test_inspector.py @@ -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