Skip to content

Commit 89542ff

Browse files
committed
Handle *args and **kwargs, and show the problematic arguments.
1 parent e0df1ae commit 89542ff

File tree

3 files changed

+127
-34
lines changed

3 files changed

+127
-34
lines changed

flake8_params/__init__.py

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
# 3rd party
3434
import flake8_helper
3535

36-
__all__ = ("Plugin", "Visitor", "get_decorator_names", "check_params")
36+
__all__ = ("Plugin", "Visitor", "get_decorator_names", "check_params", "get_docstring_args", "get_signature_args")
3737

3838
__author__ = "Dominic Davis-Foster"
3939
__copyright__ = "2025 Dominic Davis-Foster"
@@ -42,8 +42,8 @@
4242
__email__ = "dominic@davis-foster.co.uk"
4343

4444
PRM001 = "PRM001 Docstring parameters in wrong order."
45-
PRM002 = "PRM002 Missing parameters in docstring."
46-
PRM003 = "PRM003 Extra parameters in docstring."
45+
PRM002 = "PRM002 Missing parameters in docstring"
46+
PRM003 = "PRM003 Extra parameters in docstring"
4747
# TODO: class-specific codes?
4848

4949
deco_allowed_attr_names = {
@@ -124,14 +124,58 @@ def check_params(
124124
return PRM001
125125
elif signature_set - docstring_set:
126126
# Extras in signature
127-
return PRM002
127+
return PRM002 + ": " + ' '.join(sorted(signature_set - docstring_set))
128128
elif docstring_set - signature_set:
129129
# Extras in docstrings
130-
return PRM003
130+
return PRM003 + ": " + ' '.join(sorted(docstring_set - signature_set))
131131

132132
return None # pragma: no cover
133133

134134

135+
def get_signature_args(function: Union[ast.FunctionDef, ast.AsyncFunctionDef]) -> Iterator[str]:
136+
"""
137+
Extract arguments from the function signature.
138+
139+
:param function:
140+
141+
:rtype:
142+
143+
..versionadded:: 0.2.0
144+
"""
145+
146+
for arg in function.args.posonlyargs:
147+
yield arg.arg
148+
149+
for arg in function.args.args:
150+
yield arg.arg
151+
152+
if function.args.vararg:
153+
yield '*' + function.args.vararg.arg
154+
155+
for arg in function.args.kwonlyargs:
156+
yield arg.arg
157+
158+
if function.args.kwarg:
159+
yield "**" + function.args.kwarg.arg
160+
161+
162+
def get_docstring_args(docstring: str) -> Iterator[str]:
163+
"""
164+
Extract arguments from the docstring.
165+
166+
:param docstring:
167+
168+
:rtype:
169+
170+
..versionadded:: 0.2.0
171+
"""
172+
173+
for line in docstring.split('\n'):
174+
line = line.strip()
175+
if line.startswith(":param"):
176+
yield line[6:].split(':', 1)[0].strip().replace(r"\*", '*')
177+
178+
135179
class Visitor(flake8_helper.Visitor):
136180
"""
137181
AST node visitor for identifying mismatches between function signatures and docstring params.
@@ -154,14 +198,8 @@ def _visit_function(self, node: Union[ast.FunctionDef, ast.AsyncFunctionDef]) ->
154198
self.generic_visit(node)
155199
return
156200

157-
docstring_args = []
158-
for line in docstring.split('\n'):
159-
line = line.strip()
160-
if line.startswith(":param"):
161-
docstring_args.append(line[6:].split(':', 1)[0].strip())
162-
163-
signature_args = [a.arg for a in node.args.args]
164-
201+
docstring_args = list(get_docstring_args(docstring))
202+
signature_args = list(get_signature_args(node))
165203
decorators = list(get_decorator_names(node))
166204

167205
error = check_params(signature_args, docstring_args, decorators)
@@ -184,20 +222,15 @@ def visit_ClassDef(self, node: ast.ClassDef) -> None: # noqa: D102
184222
self.generic_visit(node)
185223
return
186224

187-
docstring_args = []
188-
for line in docstring.split('\n'):
189-
line = line.strip()
190-
if line.startswith(":param"):
191-
docstring_args.append(line[6:].split(':', 1)[0].strip())
192-
225+
docstring_args = list(get_docstring_args(docstring))
193226
decorators = list(get_decorator_names(node))
194227

195228
signature_args = []
196229
functions_in_body: List[ast.FunctionDef] = [n for n in node.body if isinstance(n, ast.FunctionDef)]
197230

198231
for function in functions_in_body:
199232
if function.name == "__init__":
200-
signature_args = [a.arg for a in function.args.args]
233+
signature_args = list(get_signature_args(function))
201234
break
202235
else:
203236
# No __init__; maybe it comes from a base class.

tests/example_code.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,3 +287,61 @@ async def missing_in_signature_with_classmethod(cls, foo, baz):
287287

288288
async def no_docstring_async(foo, bar, baz):
289289
pass
290+
291+
292+
def missing_args(foo, bar, *baz):
293+
"""
294+
Does something.
295+
296+
:param foo:
297+
:param bar:
298+
"""
299+
300+
301+
def has_args(foo, bar, *baz):
302+
r"""
303+
Does something.
304+
305+
:param foo:
306+
:param bar:
307+
:param \*baz:
308+
"""
309+
310+
311+
def missing_kwargs(foo, bar, **baz):
312+
"""
313+
Does something.
314+
315+
:param foo:
316+
:param bar:
317+
"""
318+
319+
320+
def has_kwargs(foo, bar, **baz):
321+
r"""
322+
Does something.
323+
324+
:param foo:
325+
:param bar:
326+
:param \*\*baz:
327+
"""
328+
329+
330+
def keyword_only(foo, bar, *, baz=None):
331+
"""
332+
Does something.
333+
334+
:param foo:
335+
:param bar:
336+
:param baz:
337+
"""
338+
339+
340+
def positional_only(foo, /, bar, baz):
341+
"""
342+
Does something.
343+
344+
:param foo:
345+
:param bar:
346+
:param baz:
347+
"""
Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
1-
1:0: PRM002 Missing parameters in docstring.
2-
20:0: PRM002 Missing parameters in docstring.
1+
1:0: PRM002 Missing parameters in docstring: baz
2+
20:0: PRM002 Missing parameters in docstring: foo
33
29:0: PRM001 Docstring parameters in wrong order.
4-
39:0: PRM003 Extra parameters in docstring.
5-
49:0: PRM003 Extra parameters in docstring.
6-
59:0: PRM002 Missing parameters in docstring.
7-
81:1: PRM002 Missing parameters in docstring.
8-
90:1: PRM003 Extra parameters in docstring.
9-
112:0: PRM003 Extra parameters in docstring.
4+
39:0: PRM003 Extra parameters in docstring: baz
5+
49:0: PRM003 Extra parameters in docstring: baz
6+
59:0: PRM002 Missing parameters in docstring: baz
7+
81:1: PRM002 Missing parameters in docstring: bar
8+
90:1: PRM003 Extra parameters in docstring: bar
9+
112:0: PRM003 Extra parameters in docstring: baz
1010
125:0: PRM001 Docstring parameters in wrong order.
11-
195:0: PRM002 Missing parameters in docstring.
12-
214:0: PRM002 Missing parameters in docstring.
11+
195:0: PRM002 Missing parameters in docstring: baz
12+
214:0: PRM002 Missing parameters in docstring: foo
1313
223:0: PRM001 Docstring parameters in wrong order.
14-
233:0: PRM003 Extra parameters in docstring.
15-
243:0: PRM003 Extra parameters in docstring.
16-
269:1: PRM002 Missing parameters in docstring.
17-
278:1: PRM003 Extra parameters in docstring.
14+
233:0: PRM003 Extra parameters in docstring: baz
15+
243:0: PRM003 Extra parameters in docstring: baz
16+
269:1: PRM002 Missing parameters in docstring: bar
17+
278:1: PRM003 Extra parameters in docstring: bar
18+
292:0: PRM002 Missing parameters in docstring: *baz
19+
311:0: PRM002 Missing parameters in docstring: **baz

0 commit comments

Comments
 (0)