Skip to content

Commit e2d0ad0

Browse files
dluiscostaTomAugspurger
authored andcommitted
DOC: require Return section only if return is not None nor commentary (#25008)
* Return section only required if at least one return is not None nor commentary
1 parent 3099773 commit e2d0ad0

File tree

2 files changed

+62
-3
lines changed

2 files changed

+62
-3
lines changed

scripts/tests/test_validate_docstrings.py

+22-1
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,27 @@ def good_imports(self):
231231
"""
232232
pass
233233

234+
def no_returns(self):
235+
"""
236+
Say hello and have no returns.
237+
"""
238+
pass
239+
240+
def empty_returns(self):
241+
"""
242+
Say hello and always return None.
243+
244+
Since this function never returns a value, this
245+
docstring doesn't need a return section.
246+
"""
247+
def say_hello():
248+
return "Hello World!"
249+
say_hello()
250+
if True:
251+
return
252+
else:
253+
return None
254+
234255

235256
class BadGenericDocStrings(object):
236257
"""Everything here has a bad docstring
@@ -785,7 +806,7 @@ def test_good_class(self, capsys):
785806

786807
@pytest.mark.parametrize("func", [
787808
'plot', 'sample', 'random_letters', 'sample_values', 'head', 'head1',
788-
'contains', 'mode', 'good_imports'])
809+
'contains', 'mode', 'good_imports', 'no_returns', 'empty_returns'])
789810
def test_good_functions(self, capsys, func):
790811
errors = validate_one(self._import_path(
791812
klass='GoodDocStrings', func=func))['errors']

scripts/validate_docstrings.py

+40-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import importlib
2727
import doctest
2828
import tempfile
29+
import ast
30+
import textwrap
2931

3032
import flake8.main.application
3133

@@ -490,9 +492,45 @@ def yields(self):
490492
@property
491493
def method_source(self):
492494
try:
493-
return inspect.getsource(self.obj)
495+
source = inspect.getsource(self.obj)
494496
except TypeError:
495497
return ''
498+
return textwrap.dedent(source)
499+
500+
@property
501+
def method_returns_something(self):
502+
'''
503+
Check if the docstrings method can return something.
504+
505+
Bare returns, returns valued None and returns from nested functions are
506+
disconsidered.
507+
508+
Returns
509+
-------
510+
bool
511+
Whether the docstrings method can return something.
512+
'''
513+
514+
def get_returns_not_on_nested_functions(node):
515+
returns = [node] if isinstance(node, ast.Return) else []
516+
for child in ast.iter_child_nodes(node):
517+
# Ignore nested functions and its subtrees.
518+
if not isinstance(child, ast.FunctionDef):
519+
child_returns = get_returns_not_on_nested_functions(child)
520+
returns.extend(child_returns)
521+
return returns
522+
523+
tree = ast.parse(self.method_source).body
524+
if tree:
525+
returns = get_returns_not_on_nested_functions(tree[0])
526+
return_values = [r.value for r in returns]
527+
# Replace NameConstant nodes valued None for None.
528+
for i, v in enumerate(return_values):
529+
if isinstance(v, ast.NameConstant) and v.value is None:
530+
return_values[i] = None
531+
return any(return_values)
532+
else:
533+
return False
496534

497535
@property
498536
def first_line_ends_in_dot(self):
@@ -691,7 +729,7 @@ def get_validation_data(doc):
691729

692730
if doc.is_function_or_method:
693731
if not doc.returns:
694-
if 'return' in doc.method_source:
732+
if doc.method_returns_something:
695733
errs.append(error('RT01'))
696734
else:
697735
if len(doc.returns) == 1 and doc.returns[0][1]:

0 commit comments

Comments
 (0)