1717pipelines) 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
2426A "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+
101114def _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+
111148def _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