Skip to content
This repository was archived by the owner on Nov 3, 2023. It is now read-only.

Commit 2f25e0e

Browse files
committed
Add support for D417 missing argument docstrings
pydocstyle now supports checking for missing function arguments from docstrings.
1 parent f7cabca commit 2f25e0e

File tree

4 files changed

+50
-4
lines changed

4 files changed

+50
-4
lines changed

src/pydocstyle/checker.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import string
55
import sys
66
import tokenize as tk
7-
from itertools import takewhile
7+
from itertools import takewhile, chain
88
from re import compile as re
99
from collections import namedtuple
1010

@@ -668,6 +668,26 @@ def _check_numpy_section(cls, docstring, definition, context):
668668
if suffix:
669669
yield violations.D406(capitalized_section, context.line.strip())
670670

671+
@staticmethod
672+
def _check_args_section(docstring, definition, context):
673+
"""D417: `Args` section checks.
674+
675+
Check for a valid `Args` or `Argument` section. Checks that:
676+
* The section documents all function arguments (D417)
677+
except `self` or `cls` if it is a method.
678+
679+
"""
680+
if definition.kind == 'function':
681+
function_pos_args = get_function_args(definition.source)
682+
docstring_args = set()
683+
for line in context.following_lines:
684+
match = ConventionChecker.GOOGLE_ARGS_REGEX.match(line)
685+
if match:
686+
docstring_args.add(match.group(1))
687+
missing_args = function_pos_args - docstring_args
688+
if missing_args:
689+
yield violations.D417(", ".join(missing_args), definition.name)
690+
671691
@classmethod
672692
def _check_google_section(cls, docstring, definition, context):
673693
"""D416: Google-style section name checks.
@@ -690,6 +710,9 @@ def _check_google_section(cls, docstring, definition, context):
690710
if suffix != ":":
691711
yield violations.D416(capitalized_section + ":", context.line.strip())
692712

713+
if capitalized_section in ("Args", "Arguments"):
714+
yield from cls._check_args_section(docstring, definition, context)
715+
693716

694717
@staticmethod
695718
def _get_section_contexts(lines, valid_section_names):
@@ -804,7 +827,6 @@ def check_docstring_sections(self, definition, docstring):
804827
lines = docstring.split("\n")
805828
if len(lines) < 2:
806829
return
807-
808830
yield from self._check_numpy_sections(lines, definition, docstring)
809831
yield from self._check_google_sections(lines, definition, docstring)
810832

@@ -887,3 +909,11 @@ def get_leading_words(line):
887909
result = re("[\w ]+").match(line.strip())
888910
if result is not None:
889911
return result.group()
912+
913+
914+
def get_function_args(function_string):
915+
"""Return the function arguments given the source-code string."""
916+
function_arg_node = ast.parse(function_string).body[0].args
917+
arg_nodes = function_arg_node.args
918+
kwonly_arg_nodes = function_arg_node.kwonlyargs
919+
return set(arg_node.arg for arg_node in chain(arg_nodes, kwonly_arg_nodes))

src/pydocstyle/violations.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,8 @@ def to_rst(cls) -> str:
251251
'mark, or exclamation point', 'not {0!r}')
252252
D416 = D4xx.create_error('D416', 'Section name should end with a semicolon',
253253
'{0!r}, not {1!r}')
254+
D417 = D4xx.create_error('D417', 'Missing arguments in the function docstring',
255+
'argument(s) {0!r} missing in function {1!r} docstring')
254256

255257
class AttrDict(dict):
256258
def __getattr__(self, item: str) -> Any:
@@ -263,9 +265,9 @@ def __getattr__(self, item: str) -> Any:
263265
conventions = AttrDict({
264266
'pep257': all_errors - {'D203', 'D212', 'D213', 'D214', 'D215', 'D404',
265267
'D405', 'D406', 'D407', 'D408', 'D409', 'D410',
266-
'D411', 'D415', 'D416'},
268+
'D411', 'D415', 'D416', 'D417'},
267269
'numpy': all_errors - {'D107', 'D203', 'D212', 'D213', 'D402', 'D413',
268-
'D415', 'D416'},
270+
'D415', 'D416', 'D417'},
269271
'google': all_errors - {'D203', 'D204', 'D213', 'D215', 'D400', 'D401',
270272
'D404', 'D406', 'D407', 'D408', 'D409'}
271273
})

src/tests/test_cases/sections.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,3 +272,16 @@ def missing_colon_google_style_section(): # noqa: D406, D407
272272
note: A random string.
273273
274274
"""
275+
276+
277+
@expect(_D213)
278+
@expect("D417: Missing arguments in the function docstring "
279+
"(argument(s) 'y' missing in function "
280+
"'test_missing_args' docstring)")
281+
def test_missing_args(x=1, y=2): # noqa: D407, D407
282+
"""Toggle the gizmo.
283+
284+
Args:
285+
x (int): The greatest integer.
286+
287+
"""

src/tests/test_integration.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,7 @@ def __init__(self):
664664
assert 'D412' in out
665665
assert 'D413' in out
666666
assert 'D414' in out
667+
assert 'D417' in out
667668

668669

669670
def test_config_file_inheritance(env):

0 commit comments

Comments
 (0)