From 3b744d180e5ee7add0553780d0a92b5f87f14f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Thu, 7 Apr 2022 10:20:56 +0200 Subject: [PATCH] Handle asterisks better in Sphinx and Google style docstrings --- ChangeLog | 4 ++ doc/whatsnew/2.13.rst | 6 ++ pylint/extensions/_check_docs_utils.py | 7 ++- pylint/extensions/docparams.py | 56 ++++++++++--------- .../missing_param_doc_required_Google.py | 28 +++++++++- .../missing_param_doc_required_Google.txt | 46 +++++++-------- .../missing_param_doc_required_Numpy.py | 19 +++++++ .../missing_param_doc_required_Sphinx.py | 36 ++++++++++++ .../missing_param_doc_required_Sphinx.txt | 18 +++--- 9 files changed, 161 insertions(+), 59 deletions(-) diff --git a/ChangeLog b/ChangeLog index 5ab05d5779..52007414b8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -85,7 +85,11 @@ What's New in Pylint 2.13.6? ============================ Release date: TBA +* Asterisks are no longer required in Sphinx and Google style parameter documentation + for ``missing-param-doc`` and are parsed correctly. + Closes #5815 + Closes #5406 What's New in Pylint 2.13.5? diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 73e898d243..3479baf6f8 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -199,6 +199,12 @@ Other Changes Closes #5614 +* Asterisks are no longer required in Sphinx and Google style parameter documentation + for ``missing-param-doc`` and are parsed correctly. + + Closes #5815 + Closes #5406 + * Use the ``tomli`` package instead of ``toml`` to parse ``.toml`` files. Closes #5885 diff --git a/pylint/extensions/_check_docs_utils.py b/pylint/extensions/_check_docs_utils.py index 4853162c0f..0e2774bda2 100644 --- a/pylint/extensions/_check_docs_utils.py +++ b/pylint/extensions/_check_docs_utils.py @@ -269,7 +269,7 @@ class SphinxDocstring(Docstring): \s+ )? - ((\\\*{{1,2}}\w+)|(\w+)) # Parameter name with potential asterisks + ((\\\*{{0,2}}\w+)|(\w+)) # Parameter name with potential asterisks \s* # whitespace : # final colon """ @@ -469,7 +469,7 @@ class GoogleDocstring(Docstring): re_param_line = re.compile( rf""" - \s* (\*{{0,2}}\w+) # identifier potentially with asterisks + \s* ((?:\\?\*{{0,2}})?\w+) # identifier potentially with asterisks \s* ( [(] {re_multiple_type} (?:,\s+optional)? @@ -647,6 +647,9 @@ def match_param_docs(self): continue param_name = match.group(1) + # Remove escape characters necessary for asterisks + param_name = param_name.replace("\\", "") + param_type = match.group(2) param_desc = match.group(3) diff --git a/pylint/extensions/docparams.py b/pylint/extensions/docparams.py index 7b88fc42ee..0a28dd0045 100644 --- a/pylint/extensions/docparams.py +++ b/pylint/extensions/docparams.py @@ -4,7 +4,7 @@ """Pylint plugin for checking in Sphinx, Google, or Numpy style docstrings.""" import re -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Optional, Set import astroid from astroid import nodes @@ -369,33 +369,36 @@ def visit_yieldfrom(self, node: nodes.YieldFrom) -> None: def _compare_missing_args( self, - found_argument_names, - message_id, - not_needed_names, - expected_argument_names, - warning_node, - ): + found_argument_names: Set[str], + message_id: str, + not_needed_names: Set[str], + expected_argument_names: Set[str], + warning_node: nodes.NodeNG, + ) -> None: """Compare the found argument names with the expected ones and generate a message if there are arguments missing. :param found_argument_names: argument names found in the docstring - :type found_argument_names: set :param message_id: pylint message id - :type message_id: str :param not_needed_names: names that may be omitted - :type not_needed_names: set :param expected_argument_names: Expected argument names - :type expected_argument_names: set :param warning_node: The node to be analyzed - :type warning_node: :class:`astroid.scoped_nodes.Node` """ - missing_argument_names = ( + potential_missing_argument_names = ( expected_argument_names - found_argument_names ) - not_needed_names + + # Handle variadic and keyword args without asterisks + missing_argument_names = set() + for name in potential_missing_argument_names: + if name.replace("*", "") in found_argument_names: + continue + missing_argument_names.add(name) + if missing_argument_names: self.add_message( message_id, @@ -405,32 +408,35 @@ def _compare_missing_args( def _compare_different_args( self, - found_argument_names, - message_id, - not_needed_names, - expected_argument_names, - warning_node, - ): + found_argument_names: Set[str], + message_id: str, + not_needed_names: Set[str], + expected_argument_names: Set[str], + warning_node: nodes.NodeNG, + ) -> None: """Compare the found argument names with the expected ones and generate a message if there are extra arguments found. :param found_argument_names: argument names found in the docstring - :type found_argument_names: set :param message_id: pylint message id - :type message_id: str :param not_needed_names: names that may be omitted - :type not_needed_names: set :param expected_argument_names: Expected argument names - :type expected_argument_names: set :param warning_node: The node to be analyzed - :type warning_node: :class:`astroid.scoped_nodes.Node` """ + # Handle variadic and keyword args without asterisks + modified_expected_argument_names: Set[str] = set() + for name in expected_argument_names: + if name.replace("*", "") in found_argument_names: + modified_expected_argument_names.add(name.replace("*", "")) + else: + modified_expected_argument_names.add(name) + differing_argument_names = ( - (expected_argument_names ^ found_argument_names) + (modified_expected_argument_names ^ found_argument_names) - not_needed_names - expected_argument_names ) diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.py b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.py index 6e291ce835..18fbaf0490 100644 --- a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.py +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.py @@ -103,7 +103,9 @@ def test_non_builtin_annotations_in_google_docstring( """ -def test_non_builtin_annotations_for_returntype_in_google_docstring(bottomleft: Point, topright: Point) -> Point: +def test_non_builtin_annotations_for_returntype_in_google_docstring( + bottomleft: Point, topright: Point +) -> Point: """Example of a function with missing Google style parameter documentation in the docstring. Args: @@ -324,6 +326,30 @@ def test_finds_kwargs_without_type_google(named_arg, **kwargs): return named_arg +def test_finds_kwargs_without_asterisk_google(named_arg, **kwargs): + """The docstring + + Args: + named_arg (object): Returned + kwargs: Keyword arguments + + Returns: + object or None: Maybe named_arg + """ + if kwargs: + return named_arg + + +def test_finds_escaped_args_google(value: int, *args: Any) -> None: + """This is myfunc. + + Args: + \\*args: this is args + value: this is value + """ + print(*args, value) + + def test_finds_args_with_xref_type_google(named_arg, **kwargs): """The docstring diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.txt b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.txt index c0daa6ee21..578d8a8c32 100644 --- a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.txt +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.txt @@ -1,26 +1,26 @@ missing-param-doc:24:0:24:48:test_missing_func_params_in_google_docstring:"""y"" missing in parameter documentation":UNDEFINED missing-type-doc:24:0:24:48:test_missing_func_params_in_google_docstring:"""x, y"" missing in parameter type documentation":UNDEFINED missing-type-doc:80:0:80:73:test_missing_func_params_with_partial_annotations_in_google_docstring:"""x"" missing in parameter type documentation":UNDEFINED -differing-param-doc:129:0:129:65:test_func_params_and_wrong_keyword_params_in_google_docstring:"""these"" differing in parameter documentation":UNDEFINED -differing-type-doc:129:0:129:65:test_func_params_and_wrong_keyword_params_in_google_docstring:"""these"" differing in parameter type documentation":UNDEFINED -missing-param-doc:129:0:129:65:test_func_params_and_wrong_keyword_params_in_google_docstring:"""that"" missing in parameter documentation":UNDEFINED -missing-type-doc:129:0:129:65:test_func_params_and_wrong_keyword_params_in_google_docstring:"""that"" missing in parameter type documentation":UNDEFINED -missing-param-doc:146:4:146:54:Foo.test_missing_method_params_in_google_docstring:"""y"" missing in parameter documentation":UNDEFINED -missing-type-doc:146:4:146:54:Foo.test_missing_method_params_in_google_docstring:"""x, y"" missing in parameter type documentation":UNDEFINED -differing-param-doc:177:0:177:58:test_wrong_name_of_func_params_in_google_docstring_one:"""xarg1, zarg1"" differing in parameter documentation":UNDEFINED -differing-type-doc:177:0:177:58:test_wrong_name_of_func_params_in_google_docstring_one:"""xarg1, zarg1"" differing in parameter type documentation":UNDEFINED -missing-param-doc:177:0:177:58:test_wrong_name_of_func_params_in_google_docstring_one:"""xarg, zarg"" missing in parameter documentation":UNDEFINED -missing-type-doc:177:0:177:58:test_wrong_name_of_func_params_in_google_docstring_one:"""xarg, zarg"" missing in parameter type documentation":UNDEFINED -differing-param-doc:192:0:192:58:test_wrong_name_of_func_params_in_google_docstring_two:"""yarg1"" differing in parameter documentation":UNDEFINED -differing-type-doc:192:0:192:58:test_wrong_name_of_func_params_in_google_docstring_two:"""yarg1"" differing in parameter type documentation":UNDEFINED -missing-param-doc:219:0:219:14:ClassFoo:"""x"" missing in parameter documentation":UNDEFINED -missing-type-doc:219:0:219:14:ClassFoo:"""x, y"" missing in parameter type documentation":UNDEFINED -missing-param-doc:237:4:237:16:ClassFoo.__init__:"""x"" missing in parameter documentation":UNDEFINED -missing-type-doc:237:4:237:16:ClassFoo.__init__:"""x, y"" missing in parameter type documentation":UNDEFINED -missing-param-doc:249:0:249:14:ClassFoo:"""x"" missing in parameter documentation":UNDEFINED -missing-type-doc:249:0:249:14:ClassFoo:"""x, y"" missing in parameter type documentation":UNDEFINED -multiple-constructor-doc:249:0:249:14:ClassFoo:"""ClassFoo"" has constructor parameters documented in class and __init__":UNDEFINED -missing-param-doc:263:4:263:16:ClassFoo.__init__:"""x"" missing in parameter documentation":UNDEFINED -missing-type-doc:263:4:263:16:ClassFoo.__init__:"""x, y"" missing in parameter type documentation":UNDEFINED -missing-param-doc:273:0:273:34:test_warns_missing_args_google:"""*args"" missing in parameter documentation":UNDEFINED -missing-param-doc:286:0:286:36:test_warns_missing_kwargs_google:"""**kwargs"" missing in parameter documentation":UNDEFINED +differing-param-doc:131:0:131:65:test_func_params_and_wrong_keyword_params_in_google_docstring:"""these"" differing in parameter documentation":UNDEFINED +differing-type-doc:131:0:131:65:test_func_params_and_wrong_keyword_params_in_google_docstring:"""these"" differing in parameter type documentation":UNDEFINED +missing-param-doc:131:0:131:65:test_func_params_and_wrong_keyword_params_in_google_docstring:"""that"" missing in parameter documentation":UNDEFINED +missing-type-doc:131:0:131:65:test_func_params_and_wrong_keyword_params_in_google_docstring:"""that"" missing in parameter type documentation":UNDEFINED +missing-param-doc:148:4:148:54:Foo.test_missing_method_params_in_google_docstring:"""y"" missing in parameter documentation":UNDEFINED +missing-type-doc:148:4:148:54:Foo.test_missing_method_params_in_google_docstring:"""x, y"" missing in parameter type documentation":UNDEFINED +differing-param-doc:179:0:179:58:test_wrong_name_of_func_params_in_google_docstring_one:"""xarg1, zarg1"" differing in parameter documentation":UNDEFINED +differing-type-doc:179:0:179:58:test_wrong_name_of_func_params_in_google_docstring_one:"""xarg1, zarg1"" differing in parameter type documentation":UNDEFINED +missing-param-doc:179:0:179:58:test_wrong_name_of_func_params_in_google_docstring_one:"""xarg, zarg"" missing in parameter documentation":UNDEFINED +missing-type-doc:179:0:179:58:test_wrong_name_of_func_params_in_google_docstring_one:"""xarg, zarg"" missing in parameter type documentation":UNDEFINED +differing-param-doc:194:0:194:58:test_wrong_name_of_func_params_in_google_docstring_two:"""yarg1"" differing in parameter documentation":UNDEFINED +differing-type-doc:194:0:194:58:test_wrong_name_of_func_params_in_google_docstring_two:"""yarg1"" differing in parameter type documentation":UNDEFINED +missing-param-doc:221:0:221:14:ClassFoo:"""x"" missing in parameter documentation":UNDEFINED +missing-type-doc:221:0:221:14:ClassFoo:"""x, y"" missing in parameter type documentation":UNDEFINED +missing-param-doc:239:4:239:16:ClassFoo.__init__:"""x"" missing in parameter documentation":UNDEFINED +missing-type-doc:239:4:239:16:ClassFoo.__init__:"""x, y"" missing in parameter type documentation":UNDEFINED +missing-param-doc:251:0:251:14:ClassFoo:"""x"" missing in parameter documentation":UNDEFINED +missing-type-doc:251:0:251:14:ClassFoo:"""x, y"" missing in parameter type documentation":UNDEFINED +multiple-constructor-doc:251:0:251:14:ClassFoo:"""ClassFoo"" has constructor parameters documented in class and __init__":UNDEFINED +missing-param-doc:265:4:265:16:ClassFoo.__init__:"""x"" missing in parameter documentation":UNDEFINED +missing-type-doc:265:4:265:16:ClassFoo.__init__:"""x, y"" missing in parameter type documentation":UNDEFINED +missing-param-doc:275:0:275:34:test_warns_missing_args_google:"""*args"" missing in parameter documentation":UNDEFINED +missing-param-doc:288:0:288:36:test_warns_missing_kwargs_google:"""**kwargs"" missing in parameter documentation":UNDEFINED diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Numpy.py b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Numpy.py index e780ac0f5c..c47d03c316 100644 --- a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Numpy.py +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Numpy.py @@ -294,6 +294,25 @@ def test_finds_kwargs_without_type_numpy(named_arg, **kwargs): return named_arg +def test_finds_kwargs_without_asterisk_numpy(named_arg, **kwargs): + """The docstring + + Args + ---- + named_arg : object + Returned + kwargs : + Keyword arguments + + Returns + ------- + object or None + Maybe named_arg + """ + if kwargs: + return named_arg + + def my_func( named_arg_one, named_arg_two, diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Sphinx.py b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Sphinx.py index 942de6e6e3..b1028c749c 100644 --- a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Sphinx.py +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Sphinx.py @@ -253,6 +253,42 @@ def test_finds_kwargs_without_type_sphinx( # [inconsistent-return-statements] return named_arg +def test_finds_args_without_type_sphinx( # [inconsistent-return-statements] + named_arg, *args +): + r"""The Sphinx docstring + We can leave the asterisk out. + + :param named_arg: Returned + :type named_arg: object + + :param args: Optional arguments + + :returns: Maybe named_arg + :rtype: object or None + """ + if args: + return named_arg + + +def test_finds_kwargs_without_type_sphinx( # [inconsistent-return-statements] + named_arg, **kwargs +): + r"""The Sphinx docstring + We can leave the asterisk out. + + :param named_arg: Returned + :type named_arg: object + + :param kwargs: Keyword arguments + + :returns: Maybe named_arg + :rtype: object or None + """ + if kwargs: + return named_arg + + class Foo: """test_finds_missing_raises_from_setter_sphinx Example of a setter having missing raises documentation in diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Sphinx.txt b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Sphinx.txt index dcc77f48ed..e7e1a55495 100644 --- a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Sphinx.txt +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Sphinx.txt @@ -27,11 +27,13 @@ inconsistent-return-statements:201:0:201:41:test_finds_kwargs_without_type_sphin missing-param-doc:201:0:201:41:test_finds_kwargs_without_type_sphinx:"""**kwargs"" missing in parameter documentation":UNDEFINED inconsistent-return-statements:218:0:218:39:test_finds_args_without_type_sphinx:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED inconsistent-return-statements:237:0:237:41:test_finds_kwargs_without_type_sphinx:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED -missing-raises-doc:263:4:263:11:Foo.foo:"""AttributeError"" not documented as being raised":UNDEFINED -unreachable:289:8:289:17:Foo.foo:Unreachable code:UNDEFINED -missing-param-doc:292:4:292:11:Foo.foo:"""value"" missing in parameter documentation":UNDEFINED -missing-raises-doc:292:4:292:11:Foo.foo:"""AttributeError"" not documented as being raised":UNDEFINED -missing-type-doc:292:4:292:11:Foo.foo:"""value"" missing in parameter type documentation":UNDEFINED -unreachable:328:8:328:17:Foo.foo:Unreachable code:UNDEFINED -useless-param-doc:332:4:332:55:Foo.test_useless_docs_ignored_argument_names_sphinx:"""_, _ignored"" useless ignored parameter documentation":UNDEFINED -useless-type-doc:332:4:332:55:Foo.test_useless_docs_ignored_argument_names_sphinx:"""_"" useless ignored parameter type documentation":UNDEFINED +inconsistent-return-statements:256:0:256:39:test_finds_args_without_type_sphinx:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +inconsistent-return-statements:274:0:274:41:test_finds_kwargs_without_type_sphinx:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +missing-raises-doc:299:4:299:11:Foo.foo:"""AttributeError"" not documented as being raised":UNDEFINED +unreachable:325:8:325:17:Foo.foo:Unreachable code:UNDEFINED +missing-param-doc:328:4:328:11:Foo.foo:"""value"" missing in parameter documentation":UNDEFINED +missing-raises-doc:328:4:328:11:Foo.foo:"""AttributeError"" not documented as being raised":UNDEFINED +missing-type-doc:328:4:328:11:Foo.foo:"""value"" missing in parameter type documentation":UNDEFINED +unreachable:364:8:364:17:Foo.foo:Unreachable code:UNDEFINED +useless-param-doc:368:4:368:55:Foo.test_useless_docs_ignored_argument_names_sphinx:"""_, _ignored"" useless ignored parameter documentation":UNDEFINED +useless-type-doc:368:4:368:55:Foo.test_useless_docs_ignored_argument_names_sphinx:"""_"" useless ignored parameter type documentation":UNDEFINED