-
-
Notifications
You must be signed in to change notification settings - Fork 18.5k
require Return section only if return is not None nor commentary #25008
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 34 commits
575abc4
4b6406f
b204f11
0a76a3a
d35ec84
e7d9bed
2cdd367
0e25d93
d5d9800
073a6b6
0143b7c
76188dd
2051ea6
ff17a79
bafc25d
172115a
d5e0de8
0e61d2c
4c89beb
b04110a
4b01092
0928ba6
57fdb1e
00d5fdb
0a0c05e
6ab2534
504ea10
17737d1
fe23351
783eb39
2c41bd7
6e53215
2c60e4d
11b827b
3e93cbe
1660060
be6c906
0b361de
2ebfa33
65b0f56
bbf39d0
2c66c89
d99edb3
07a4b01
4d6ec7e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -229,6 +229,27 @@ def good_imports(self): | |
""" | ||
pass | ||
|
||
def no_returns(self): | ||
""" | ||
Say hello and have no returns. | ||
""" | ||
print("Hello World!") | ||
|
||
def empty_returns(self): | ||
""" | ||
Say hello and always return None. | ||
|
||
Since this function never returns a value, this | ||
docstring doesn't need a return section. | ||
""" | ||
def say_hello(): | ||
return "Hello World!" | ||
say_hello() | ||
if True: | ||
return | ||
else: | ||
return None | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you add a test for a property that returns something (without the Returns section, which is the correct). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. such a test was already present before this PR:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it is negative test, not like the one being commented here, so it's inside the BadReturns class |
||
|
||
|
||
class BadGenericDocStrings(object): | ||
"""Everything here has a bad docstring | ||
|
@@ -594,7 +615,10 @@ def return_not_documented(self): | |
""" | ||
Lacks section for Returns | ||
""" | ||
return "Hello world!" | ||
if False: | ||
return None | ||
else: | ||
return True | ||
|
||
def yield_not_documented(self): | ||
""" | ||
|
@@ -783,7 +807,7 @@ def test_good_class(self, capsys): | |
|
||
@pytest.mark.parametrize("func", [ | ||
'plot', 'sample', 'random_letters', 'sample_values', 'head', 'head1', | ||
'contains', 'mode', 'good_imports']) | ||
'contains', 'mode', 'good_imports', 'no_returns', 'empty_returns']) | ||
def test_good_functions(self, capsys, func): | ||
errors = validate_one(self._import_path( | ||
klass='GoodDocStrings', func=func))['errors'] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,7 @@ | |
import importlib | ||
import doctest | ||
import tempfile | ||
import ast | ||
TomAugspurger marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
import flake8.main.application | ||
|
||
|
@@ -490,10 +491,41 @@ def yields(self): | |
@property | ||
def method_source(self): | ||
try: | ||
return inspect.getsource(self.obj) | ||
source = inspect.getsource(self.obj) | ||
dluiscosta marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# Remove common indentation. | ||
first_spaces = re.match(' +', source) | ||
if first_spaces: | ||
first_spaces = first_spaces.group(0) | ||
source = re.sub('^' + first_spaces, '', source) | ||
source = re.sub(r'\n' + first_spaces, r'\n', source) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is happening here is that, when
Getting the source of B will return
with the previously present indentation kept. If we feed this directly to An easy fix is to apply
Which solves the previous problem. The thing is, on more complex cases, this approach isn't enough. Consider the real example:
By applying
This leaves the To avoid this problem, the present solution removes the same number os whitespaces from each line, as if the function was declared at an otherwise empty file:
That said, I'm open to suggestions if someone can think of a simpler way to solve the described problem. |
||
return source | ||
except TypeError: | ||
return '' | ||
|
||
@property | ||
def method_returns(self): | ||
dluiscosta marked this conversation as resolved.
Show resolved
Hide resolved
|
||
tree = ast.parse(self.method_source).body | ||
if tree: | ||
root = tree[0] | ||
|
||
def gather_returns(node): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm is there any way to simplify what is going on here? Is there no way to extract the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In our case, there isn't. The AST provides the
Without context, there is no way to tell if a return node is inside a nested inner function or not, which is needed for one of the requirements raised by @datapythonista. I explain this a bit further here.
dluiscosta marked this conversation as resolved.
Show resolved
Hide resolved
|
||
gathered = [node] if isinstance(node, ast.Return) else [] | ||
for child in ast.iter_child_nodes(node): | ||
# Ignore nested functions and its subtrees. | ||
if not isinstance(child, ast.FunctionDef): | ||
gathered.extend(gather_returns(child)) | ||
return gathered | ||
# Walk the tree recursively and gather the return nodes. | ||
return_values = [r.value for r in gather_returns(root)] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As opposing to using The reason for that is to respect cases like the following:
In this case, there is a inner function which returns a string, but the function being evaluated itself always return None, thus shouldn't have a Returns section on the docstring. If I read all the nodes indiscriminately, as happens with |
||
|
||
# Replace NameConstant nodes valued None for None. | ||
for i, v in enumerate(return_values): | ||
if isinstance(v, ast.NameConstant) and v.value is None: | ||
return_values[i] = None | ||
return return_values | ||
else: | ||
return [] | ||
|
||
@property | ||
def first_line_ends_in_dot(self): | ||
if self.doc: | ||
|
@@ -691,7 +723,7 @@ def get_validation_data(doc): | |
|
||
if doc.is_function_or_method: | ||
if not doc.returns: | ||
if 'return' in doc.method_source: | ||
if any(ret is not None for ret in doc.method_returns): | ||
errs.append(error('RT01')) | ||
else: | ||
if len(doc.returns) == 1 and doc.returns[0][1]: | ||
|
Uh oh!
There was an error while loading. Please reload this page.