From 5d269b76ecb52e4fc0064ff2b8c239a04b74bbf7 Mon Sep 17 00:00:00 2001 From: Dash Date: Fri, 6 Nov 2026 22:05:02 +0100 Subject: [PATCH] common: give items no empty descriptions An item with nothing after its colon, such as ReST ":param x:", got an empty string as its description, while one with no colon at all got None. Every meta now takes None for an empty description, so users test for one value only, in every style. Compose loses the difference the two had, so it writes what reads back as None: a typed item with no description as its type field alone, as before for a None one; an untyped one, or one whose field gives its own type, such as ReST ":param int x:", as its field; and a Google raises type alone only when it has no trailing space, which the colon after it keeps. --- CHANGELOG.md | 1 + docstring_parser/common.py | 5 +-- docstring_parser/epydoc.py | 8 +++-- docstring_parser/google.py | 10 ++++-- docstring_parser/rest.py | 7 ++-- .../tests/cases/auto/google_01.txt | 1 - docstring_parser/tests/cases/auto/rest_01.txt | 1 - .../flask.app.Flask.finalize_request.txt | 1 - ...lask.app.Flask.raise_routing_exception.txt | 2 -- .../flask/flask.helpers.get_root_path.txt | 1 - .../cases/epydoc/empty_description_01.txt | 36 +++++++++++++++++++ .../cases/fork/rest_auto_attribute_01.txt | 1 - .../cases/fork/rest_parser_attributes_02.txt | 3 -- .../cases/fork/rest_parser_attributes_03.txt | 3 -- .../cases/google/empty_description_01.txt | 31 ++++++++++++++++ .../tests/cases/google/empty_example_01.txt | 1 - .../tests/cases/google/meta_newlines_01.txt | 1 - .../tests/cases/google/meta_newlines_02.txt | 1 - .../tests/cases/google/meta_newlines_03.txt | 1 - .../tests/cases/google/meta_newlines_04.txt | 1 - .../tests/cases/google/meta_newlines_05.txt | 1 - .../tests/cases/google/meta_newlines_06.txt | 1 - .../cases/numpydoc/empty_description_01.txt | 36 +++++++++++++++++++ .../cases/rest/attribute_directive_02.txt | 1 - .../tests/cases/rest/empty_description_01.txt | 36 +++++++++++++++++++ 25 files changed, 160 insertions(+), 31 deletions(-) create mode 100644 docstring_parser/tests/cases/epydoc/empty_description_01.txt create mode 100644 docstring_parser/tests/cases/google/empty_description_01.txt create mode 100644 docstring_parser/tests/cases/numpydoc/empty_description_01.txt create mode 100644 docstring_parser/tests/cases/rest/empty_description_01.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 51a66fc7..2e8cf3de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - General: **Breaking:** Leave attributes out of `Docstring.params`, and list them in the new `Docstring.attrs` - General: **Breaking:** Leave yields out of `Docstring.returns` and `many_returns`; read them from the new `yields` and `many_yields` - General: **Breaking:** Make `is_optional` of a parsed param always a bool, `False` instead of `None` when it has no type +- General: **Breaking:** Give an item with no description `None` as its description, never `""` - Google, Numpydoc: **Breaking:** Remove customising parsing by overriding internals such as `titles_re`, `title_pattern`, `_build_meta` and `KV_REGEX`; use `SectionSpec` instead - Google, Numpydoc: **Breaking:** Split examples as doctest reads them: an example's `description` is the text before its snippet, and its `snippet` holds the `>>>` lines, their `...` continuations and the expected output; text after the last snippet is an example with no snippet - General: Parse docstrings with `\r\n` or `\r` line endings the same as with `\n`, on every Python version diff --git a/docstring_parser/common.py b/docstring_parser/common.py index beb38f49..572b7f32 100644 --- a/docstring_parser/common.py +++ b/docstring_parser/common.py @@ -188,7 +188,8 @@ class DocstringMeta: :param arg: description :raises ValueError: if something happens - :ivar description: the item's text + :ivar description: the item's text, or ``None`` if it has none; never + an empty string :ivar kind: what the item documents. Each class sets it, so that the class and the kind cannot disagree. :ivar key: the field or section key it was written with, such as @@ -211,7 +212,7 @@ def __init__( name: T.Optional[str] = None, ) -> None: """Initialize self.""" - self.description = description + self.description = description or None self.kind = Kind.TEXT self.key = key self.name = name diff --git a/docstring_parser/epydoc.py b/docstring_parser/epydoc.py index e084aae3..f7953704 100644 --- a/docstring_parser/epydoc.py +++ b/docstring_parser/epydoc.py @@ -373,15 +373,17 @@ def process_desc(desc: "T.Optional[str]", is_type: bool) -> str: if meta.is_generator else ("return", "rtype") ) - if meta.description is None: - # A type field alone keeps the name it was read from. + # A type field alone keeps the name it was read from, and is + # written for a return with a type and no description. + alone = meta.description is None and bool(meta.type_name) + if alone: type_key = _own_key(meta, type_key) else: arg_key = _own_key(meta, arg_key) if meta.type_name: text = f"@{type_key}:" + process_desc(meta.type_name, True) parts.append(text) - if meta.description is not None: + if not alone: text = f"@{arg_key}:" + process_desc(meta.description, False) parts.append(text) elif isinstance(meta, DocstringRaises): diff --git a/docstring_parser/google.py b/docstring_parser/google.py index a4ea686e..1a06b240 100644 --- a/docstring_parser/google.py +++ b/docstring_parser/google.py @@ -691,9 +691,13 @@ def process_one( head = "" if isinstance(one, DocstringRaises): - if one.type_name and one.description is None: - # A type alone reads back with no description, where a - # colon after it gives an empty one. + if ( + one.type_name + and one.description is None + and one.type_name == one.type_name.rstrip() + ): + # A type alone reads back with no description, as does one + # with a colon after it, which keeps the spaces before it. parts.append(indent + one.type_name) return if ( diff --git a/docstring_parser/rest.py b/docstring_parser/rest.py index 9d13a299..fab3d6b8 100644 --- a/docstring_parser/rest.py +++ b/docstring_parser/rest.py @@ -625,10 +625,13 @@ def process_desc(desc: "T.Optional[str]") -> str: # own, as its value. parts.append(_attribute_directive(meta, indent)) elif meta.description is None and ( - type_name or meta.key in ("type", "vartype") + meta.key in ("type", "vartype") + or (type_name and meta.key is None) ): # A type field alone, which reads back as a param, or an - # attribute, with no description, even with no type. + # attribute, with no description, even with no type. A + # field of its own kind, such as ``:param int x:``, also + # has none. head = f"{_type_key(meta)} {meta.arg_name}" parts.append( _type_field(indent, head, type_name) diff --git a/docstring_parser/tests/cases/auto/google_01.txt b/docstring_parser/tests/cases/auto/google_01.txt index 19d00b45..d37722f2 100644 --- a/docstring_parser/tests/cases/auto/google_01.txt +++ b/docstring_parser/tests/cases/auto/google_01.txt @@ -44,7 +44,6 @@ style: google type_name: 'int' is_optional: False - DocstringParam - description: '' kind: param key: 'param' arg_name: 'yay' diff --git a/docstring_parser/tests/cases/auto/rest_01.txt b/docstring_parser/tests/cases/auto/rest_01.txt index 5b3665e1..1ce62a08 100644 --- a/docstring_parser/tests/cases/auto/rest_01.txt +++ b/docstring_parser/tests/cases/auto/rest_01.txt @@ -40,7 +40,6 @@ style: rest type_name: 'int' is_optional: False - DocstringParam - description: '' kind: param key: 'param' arg_name: 'yay' diff --git a/docstring_parser/tests/cases/corpus/flask/flask.app.Flask.finalize_request.txt b/docstring_parser/tests/cases/corpus/flask/flask.app.Flask.finalize_request.txt index 12180b9a..152b0d26 100644 --- a/docstring_parser/tests/cases/corpus/flask/flask.app.Flask.finalize_request.txt +++ b/docstring_parser/tests/cases/corpus/flask/flask.app.Flask.finalize_request.txt @@ -27,7 +27,6 @@ blank_after_short_description: False blank_after_long_description: True style: rest - DocstringMeta - description: '' kind: text key: 'internal' === detect diff --git a/docstring_parser/tests/cases/corpus/flask/flask.app.Flask.raise_routing_exception.txt b/docstring_parser/tests/cases/corpus/flask/flask.app.Flask.raise_routing_exception.txt index b40bde5b..1afb7ea4 100644 --- a/docstring_parser/tests/cases/corpus/flask/flask.app.Flask.raise_routing_exception.txt +++ b/docstring_parser/tests/cases/corpus/flask/flask.app.Flask.raise_routing_exception.txt @@ -31,12 +31,10 @@ blank_after_short_description: True blank_after_long_description: True style: rest - DocstringMeta - description: '' kind: text key: 'meta' name: 'private' - DocstringMeta - description: '' kind: text key: 'internal' === detect diff --git a/docstring_parser/tests/cases/corpus/flask/flask.helpers.get_root_path.txt b/docstring_parser/tests/cases/corpus/flask/flask.helpers.get_root_path.txt index c8e96070..c63abf3d 100644 --- a/docstring_parser/tests/cases/corpus/flask/flask.helpers.get_root_path.txt +++ b/docstring_parser/tests/cases/corpus/flask/flask.helpers.get_root_path.txt @@ -19,7 +19,6 @@ blank_after_short_description: False blank_after_long_description: True style: rest - DocstringMeta - description: '' kind: text key: 'meta' name: 'private' diff --git a/docstring_parser/tests/cases/epydoc/empty_description_01.txt b/docstring_parser/tests/cases/epydoc/empty_description_01.txt new file mode 100644 index 00000000..e29f5113 --- /dev/null +++ b/docstring_parser/tests/cases/epydoc/empty_description_01.txt @@ -0,0 +1,36 @@ +# Test that items with nothing after their colon have no description. +=== input +Short. + +@param x: +@raise ValueError: +@return: +@note: +=== parse epydoc +short_description: 'Short.' +blank_after_short_description: True +blank_after_long_description: False +style: epydoc +- DocstringParam + kind: param + key: 'param' + arg_name: 'x' + is_optional: False +- DocstringRaises + kind: raises + key: 'raise' + type_name: 'ValueError' +- DocstringReturns + kind: returns + key: 'return' + is_generator: False +- DocstringMeta + kind: text + key: 'note' +=== compose epydoc +Short. + +@param x: +@raise ValueError: +@return: +@note: diff --git a/docstring_parser/tests/cases/fork/rest_auto_attribute_01.txt b/docstring_parser/tests/cases/fork/rest_auto_attribute_01.txt index f3aa8386..ca13a745 100644 --- a/docstring_parser/tests/cases/fork/rest_auto_attribute_01.txt +++ b/docstring_parser/tests/cases/fork/rest_auto_attribute_01.txt @@ -49,7 +49,6 @@ style: rest type_name: 'int' is_optional: False - DocstringParam - description: '' kind: param key: 'param' arg_name: 'yay' diff --git a/docstring_parser/tests/cases/fork/rest_parser_attributes_02.txt b/docstring_parser/tests/cases/fork/rest_parser_attributes_02.txt index 392374e7..70abd31c 100644 --- a/docstring_parser/tests/cases/fork/rest_parser_attributes_02.txt +++ b/docstring_parser/tests/cases/fork/rest_parser_attributes_02.txt @@ -34,7 +34,6 @@ blank_after_short_description: True blank_after_long_description: True style: rest - DocstringParam - description: '' kind: attribute key: 'attribute' arg_name: 'attr_1' @@ -54,13 +53,11 @@ style: rest arg_name: 'attr_3' is_optional: False - DocstringParam - description: '' kind: attribute key: 'attribute' arg_name: 'attr_4' is_optional: False - DocstringParam - description: '' kind: attribute key: 'attribute' arg_name: 'attr_5' diff --git a/docstring_parser/tests/cases/fork/rest_parser_attributes_03.txt b/docstring_parser/tests/cases/fork/rest_parser_attributes_03.txt index 45071a30..e6af6cc3 100644 --- a/docstring_parser/tests/cases/fork/rest_parser_attributes_03.txt +++ b/docstring_parser/tests/cases/fork/rest_parser_attributes_03.txt @@ -41,7 +41,6 @@ blank_after_short_description: True blank_after_long_description: False style: rest - DocstringParam - description: '' kind: attribute key: 'attribute' arg_name: 'attr_1' @@ -79,13 +78,11 @@ style: rest type_name: 'bool' is_optional: False - DocstringParam - description: '' kind: attribute key: 'attribute' arg_name: 'attr_4' is_optional: False - DocstringParam - description: '' kind: attribute key: 'attribute' arg_name: 'attr_5' diff --git a/docstring_parser/tests/cases/google/empty_description_01.txt b/docstring_parser/tests/cases/google/empty_description_01.txt new file mode 100644 index 00000000..50f3ce02 --- /dev/null +++ b/docstring_parser/tests/cases/google/empty_description_01.txt @@ -0,0 +1,31 @@ +# Test that items with nothing after their colon have no description. +=== input +Short. + +Args: + x: + +Raises: + ValueError: +=== parse google +short_description: 'Short.' +blank_after_short_description: True +blank_after_long_description: False +style: google +- DocstringParam + kind: param + key: 'param' + arg_name: 'x' + is_optional: False +- DocstringRaises + kind: raises + key: 'raises' + type_name: 'ValueError' +=== compose google +Short. + +Args: + x: + +Raises: + ValueError diff --git a/docstring_parser/tests/cases/google/empty_example_01.txt b/docstring_parser/tests/cases/google/empty_example_01.txt index 1044559a..07e1ee25 100644 --- a/docstring_parser/tests/cases/google/empty_example_01.txt +++ b/docstring_parser/tests/cases/google/empty_example_01.txt @@ -13,7 +13,6 @@ blank_after_short_description: True blank_after_long_description: False style: google - DocstringExample - description: '' kind: examples key: 'examples' - DocstringRaises diff --git a/docstring_parser/tests/cases/google/meta_newlines_01.txt b/docstring_parser/tests/cases/google/meta_newlines_01.txt index e6b789d8..b099991c 100644 --- a/docstring_parser/tests/cases/google/meta_newlines_01.txt +++ b/docstring_parser/tests/cases/google/meta_newlines_01.txt @@ -11,7 +11,6 @@ blank_after_short_description: False blank_after_long_description: False style: google - DocstringParam - description: '' kind: param key: 'param' arg_name: 'asd' diff --git a/docstring_parser/tests/cases/google/meta_newlines_02.txt b/docstring_parser/tests/cases/google/meta_newlines_02.txt index 772aec2e..f94751f6 100644 --- a/docstring_parser/tests/cases/google/meta_newlines_02.txt +++ b/docstring_parser/tests/cases/google/meta_newlines_02.txt @@ -13,7 +13,6 @@ blank_after_short_description: False blank_after_long_description: False style: google - DocstringParam - description: '' kind: param key: 'param' arg_name: 'asd' diff --git a/docstring_parser/tests/cases/google/meta_newlines_03.txt b/docstring_parser/tests/cases/google/meta_newlines_03.txt index 33c104d1..727429b4 100644 --- a/docstring_parser/tests/cases/google/meta_newlines_03.txt +++ b/docstring_parser/tests/cases/google/meta_newlines_03.txt @@ -16,7 +16,6 @@ blank_after_short_description: False blank_after_long_description: False style: google - DocstringParam - description: '' kind: param key: 'param' arg_name: 'asd' diff --git a/docstring_parser/tests/cases/google/meta_newlines_04.txt b/docstring_parser/tests/cases/google/meta_newlines_04.txt index b2ba4c4a..5524928b 100644 --- a/docstring_parser/tests/cases/google/meta_newlines_04.txt +++ b/docstring_parser/tests/cases/google/meta_newlines_04.txt @@ -17,7 +17,6 @@ blank_after_short_description: True blank_after_long_description: False style: google - DocstringParam - description: '' kind: param key: 'param' arg_name: 'asd' diff --git a/docstring_parser/tests/cases/google/meta_newlines_05.txt b/docstring_parser/tests/cases/google/meta_newlines_05.txt index fef8c55a..c7554f9d 100644 --- a/docstring_parser/tests/cases/google/meta_newlines_05.txt +++ b/docstring_parser/tests/cases/google/meta_newlines_05.txt @@ -18,7 +18,6 @@ blank_after_short_description: True blank_after_long_description: True style: google - DocstringParam - description: '' kind: param key: 'param' arg_name: 'asd' diff --git a/docstring_parser/tests/cases/google/meta_newlines_06.txt b/docstring_parser/tests/cases/google/meta_newlines_06.txt index 4725eb93..851b3e3a 100644 --- a/docstring_parser/tests/cases/google/meta_newlines_06.txt +++ b/docstring_parser/tests/cases/google/meta_newlines_06.txt @@ -9,7 +9,6 @@ blank_after_short_description: False blank_after_long_description: False style: google - DocstringParam - description: '' kind: param key: 'param' arg_name: 'asd' diff --git a/docstring_parser/tests/cases/numpydoc/empty_description_01.txt b/docstring_parser/tests/cases/numpydoc/empty_description_01.txt new file mode 100644 index 00000000..55696285 --- /dev/null +++ b/docstring_parser/tests/cases/numpydoc/empty_description_01.txt @@ -0,0 +1,36 @@ +# Test that items with nothing under them have no description. +=== input +Short. + +Parameters +---------- +x : int + +Raises +------ +ValueError +=== parse numpydoc +short_description: 'Short.' +blank_after_short_description: True +blank_after_long_description: False +style: numpydoc +- DocstringParam + kind: param + key: 'param' + arg_name: 'x' + type_name: 'int' + is_optional: False +- DocstringRaises + kind: raises + key: 'raises' + type_name: 'ValueError' +=== compose numpydoc +Short. + +Parameters +---------- +x : int + +Raises +------ +ValueError diff --git a/docstring_parser/tests/cases/rest/attribute_directive_02.txt b/docstring_parser/tests/cases/rest/attribute_directive_02.txt index 01f057f8..ac8468c5 100644 --- a/docstring_parser/tests/cases/rest/attribute_directive_02.txt +++ b/docstring_parser/tests/cases/rest/attribute_directive_02.txt @@ -41,7 +41,6 @@ style: rest arg_name: 'a' is_optional: False - DocstringParam - description: '' kind: attribute key: 'attribute' arg_name: 'y' diff --git a/docstring_parser/tests/cases/rest/empty_description_01.txt b/docstring_parser/tests/cases/rest/empty_description_01.txt new file mode 100644 index 00000000..f7d49c64 --- /dev/null +++ b/docstring_parser/tests/cases/rest/empty_description_01.txt @@ -0,0 +1,36 @@ +# Test that items with nothing after their colon have no description. +=== input +Short. + +:param x: +:raises ValueError: +:returns: +:note: +=== parse rest +short_description: 'Short.' +blank_after_short_description: True +blank_after_long_description: False +style: rest +- DocstringParam + kind: param + key: 'param' + arg_name: 'x' + is_optional: False +- DocstringRaises + kind: raises + key: 'raises' + type_name: 'ValueError' +- DocstringReturns + kind: returns + key: 'returns' + is_generator: False +- DocstringMeta + kind: text + key: 'note' +=== compose rest +Short. + +:param x: +:raises ValueError : +:returns: +:note: