diff --git a/ChangeLog b/ChangeLog index 90810665fa..8b1e97508f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -103,6 +103,11 @@ Release date: TBA Closes #3733 +* ``mising-param-doc`` now correctly handles Numpy parameter documentation without + explicit typing + + Closes #5222 + * Update ``literal-comparison``` checker to ignore tuple literals Closes #3031 diff --git a/doc/whatsnew/2.12.rst b/doc/whatsnew/2.12.rst index 0f16e4bdd2..627a5eb071 100644 --- a/doc/whatsnew/2.12.rst +++ b/doc/whatsnew/2.12.rst @@ -123,3 +123,8 @@ Other Changes keyword parameters Closes #3733 + +* ``mising-param-doc`` now correctly handles Numpy parameter documentation without + explicit typing + + Closes #5222 diff --git a/pylint/extensions/_check_docs_utils.py b/pylint/extensions/_check_docs_utils.py index 52f9e470cc..e94ffc33a5 100644 --- a/pylint/extensions/_check_docs_utils.py +++ b/pylint/extensions/_check_docs_utils.py @@ -23,7 +23,7 @@ """Utility methods for docstring checking.""" import re -from typing import List +from typing import List, Set, Tuple import astroid from astroid import nodes @@ -731,9 +731,8 @@ class NumpyDocstring(GoogleDocstring): re_param_line = re.compile( fr""" - \s* (\*{{0,2}}\w+) # identifier with potential asterisks - \s* : - \s* (?:({GoogleDocstring.re_multiple_type})(?:,\s+optional)?)? # optional type declaration + \s* (\*{{0,2}}\w+)(\s?(:|\n)) # identifier with potential asterisks + \s* (?:({GoogleDocstring.re_multiple_type})(?:,\s+optional)?\n)? # optional type declaration \s* (.*) # optional description """, re.X | re.S, @@ -772,6 +771,38 @@ class NumpyDocstring(GoogleDocstring): supports_yields = True + def match_param_docs(self) -> Tuple[Set[str], Set[str]]: + """Matches parameter documentation section to parameter documentation rules""" + params_with_doc = set() + params_with_type = set() + + entries = self._parse_section(self.re_param_section) + print(entries) + entries.extend(self._parse_section(self.re_keyword_param_section)) + for entry in entries: + match = self.re_param_line.match(entry) + if not match: + continue + + # check if parameter has description only + re_only_desc = re.match(r"\\s* (\\*{0,2}\\w+)\\s*:?\n", entry) + if re_only_desc: + param_name = match.group(1) + param_desc = match.group(2) + param_type = None + else: + param_name = match.group(1) + param_type = match.group(3) + param_desc = match.group(4) + + if param_type: + params_with_type.add(param_name) + + if param_desc: + params_with_doc.add(param_name) + + return params_with_doc, params_with_type + @staticmethod def min_section_indent(section_match): return len(section_match.group(1)) diff --git a/tests/extensions/test_check_docs.py b/tests/extensions/test_check_docs.py index a637da1241..5fdbc0ee3c 100644 --- a/tests/extensions/test_check_docs.py +++ b/tests/extensions/test_check_docs.py @@ -292,35 +292,6 @@ def function_foo(x, y, z): ): self.checker.visit_functiondef(node) - @set_config(default_docstring_type="numpy") - def test_no_type_func_params_in_numpy_docstring(self) -> None: - """Example of a function with NumPy style parameter without type annotation - documentation in the docstring - """ - node = astroid.extract_node( - """ - def func(arg1: bool, arg2: bool): - '''Return args. - - Parameters - ---------- - arg1 : bool - arg1 - - arg2 - arg2 - - Returns - ---------- - bool - bool - ''' - return arg1, arg2 - """ - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - @set_config(accept_no_param_doc=True) def test_tolerate_no_param_documentation_at_all(self) -> None: """Example of a function with no parameter documentation at all @@ -1367,13 +1338,17 @@ def my_func(named_arg, **kwargs): def test_finds_args_without_type_numpy(self) -> None: node = astroid.extract_node( ''' - def my_func(named_arg, *args): + def my_func(named_arg, typed_arg: bool, untyped_arg, *args): """The docstring Args ---- named_arg : object Returned + typed_arg + Other argument without numpy type annotation + untyped_arg + Other argument without any type annotation *args : Optional Arguments @@ -1386,7 +1361,9 @@ def my_func(named_arg, *args): return named_arg ''' ) - with self.assertNoMessages(): + with self.assertAddsMessages( + MessageTest(msg_id="missing-type-doc", node=node, args=("untyped_arg",)) + ): self.checker.visit_functiondef(node) def test_finds_args_with_xref_type_google(self) -> None: