Skip to content

Commit d7b2686

Browse files
committed
feat: check for returns, too.
1 parent 959c411 commit d7b2686

1 file changed

Lines changed: 52 additions & 5 deletions

File tree

utils/check_forward_call_docstrings.py

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
pipelines) match the method's docstring exactly:
1818
1919
* every signature argument has an entry in the ``Args:`` /
20-
``Arguments:`` / ``Parameters:`` section, and
20+
``Arguments:`` / ``Parameters:`` section,
2121
* every documented argument still exists in the signature
22-
(stale entries from removed/renamed args are flagged).
22+
(stale entries from removed/renamed args are flagged), and
23+
* when the method has a non-``None`` return annotation, the docstring has
24+
a ``Returns:`` / ``Return:`` / ``Yields:`` section.
2325
2426
A "main" class is detected via its base classes — models inherit from
2527
``ModelMixin`` and pipelines inherit from ``DiffusionPipeline``. Only methods
@@ -98,6 +100,17 @@ def _find_method(class_def: ast.ClassDef, method_name: str) -> ast.FunctionDef |
98100
return None
99101

100102

103+
def _docstring_node(func: ast.FunctionDef | ast.AsyncFunctionDef) -> ast.Expr | None:
104+
if (
105+
func.body
106+
and isinstance(func.body[0], ast.Expr)
107+
and isinstance(func.body[0].value, ast.Constant)
108+
and isinstance(func.body[0].value.value, str)
109+
):
110+
return func.body[0]
111+
return None
112+
113+
101114
def _signature_arg_names(func: ast.FunctionDef | ast.AsyncFunctionDef) -> list[str]:
102115
args = func.args
103116
collected: list[str] = []
@@ -108,6 +121,30 @@ def _signature_arg_names(func: ast.FunctionDef | ast.AsyncFunctionDef) -> list[s
108121
return collected
109122

110123

124+
def _has_meaningful_return(func: ast.FunctionDef | ast.AsyncFunctionDef) -> bool:
125+
"""True iff the method has a return annotation other than ``None`` or ``NoReturn``."""
126+
ret = func.returns
127+
if ret is None: # no annotation at all
128+
return False
129+
if isinstance(ret, ast.Constant) and ret.value is None: # `-> None`
130+
return False
131+
# `-> NoReturn` or `-> typing.NoReturn`
132+
if isinstance(ret, ast.Name) and ret.id == "NoReturn":
133+
return False
134+
if isinstance(ret, ast.Attribute) and ret.attr == "NoReturn":
135+
return False
136+
return True
137+
138+
139+
def _has_returns_section(docstring: str | None) -> bool:
140+
if not docstring:
141+
return False
142+
for line in docstring.splitlines():
143+
if line.strip() in {"Returns:", "Return:", "Yields:", "Yield:"}:
144+
return True
145+
return False
146+
147+
111148
def _extract_documented_args(docstring: str | None) -> set[str]:
112149
"""Extract argument names listed in an Args/Arguments/Parameters section.
113150
@@ -185,10 +222,9 @@ def check_file(path: Path, kind: str) -> list[str]:
185222
if method is None:
186223
continue
187224
sig_args = _signature_arg_names(method)
188-
if not sig_args:
189-
continue
190225
sig_set = set(sig_args)
191-
documented = _extract_documented_args(ast.get_docstring(method))
226+
docstring_text = ast.get_docstring(method)
227+
documented = _extract_documented_args(docstring_text)
192228
missing = [a for a in sig_args if a not in documented]
193229
stale = sorted(documented - sig_set)
194230
if missing:
@@ -201,6 +237,17 @@ def check_file(path: Path, kind: str) -> list[str]:
201237
f"{rel}:{method.lineno}: {node.name}.{method_name} documents "
202238
f"argument(s) not in the signature: {', '.join(stale)}"
203239
)
240+
if _has_meaningful_return(method) and not _has_returns_section(docstring_text):
241+
return_repr = ast.unparse(method.returns)
242+
ds = _docstring_node(method)
243+
if ds is None:
244+
where = " (method has no docstring)"
245+
else:
246+
where = f' (add it just above the closing """ on line {ds.end_lineno})'
247+
errors.append(
248+
f"{rel}:{method.lineno}: {node.name}.{method_name} returns "
249+
f"`{return_repr}` but the docstring has no Returns: section{where}"
250+
)
204251
return errors
205252

206253

0 commit comments

Comments
 (0)