-
Notifications
You must be signed in to change notification settings - Fork 41
Optionally test for missing docstrings #33
base: master
Are you sure you want to change the base?
Changes from all commits
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 |
---|---|---|
|
@@ -34,6 +34,8 @@ | |
ExcessReturnError, | ||
ExcessVariableError, | ||
ExcessYieldError, | ||
MissingDocstringForPublicMethod, | ||
MissingDocstringForPublicFunction, | ||
MissingParameterError, | ||
MissingRaiseError, | ||
MissingReturnError, | ||
|
@@ -64,7 +66,8 @@ def __init__(self, | |
config=Configuration( | ||
ignore=[], | ||
message_template=None, | ||
style=DocstringStyle.GOOGLE | ||
style=DocstringStyle.GOOGLE, | ||
raise_on_missing_docstrings=False | ||
), | ||
raise_errors=False | ||
): | ||
|
@@ -93,8 +96,8 @@ def run_checks(self, function): | |
|
||
""" | ||
self.function = function | ||
if function.docstring is not None: | ||
try: | ||
try: | ||
if function.docstring is not None: | ||
if self.config.style == DocstringStyle.GOOGLE: | ||
self.docstring = Docstring.from_google( | ||
function.docstring, | ||
|
@@ -113,24 +116,35 @@ def run_checks(self, function): | |
self._check_yield() | ||
self._check_raises() | ||
self._sorted = False | ||
except ParserException as exception: | ||
# If a syntax exception was raised, we may still | ||
# want to ignore it, if we place a noqa statement. | ||
if ( | ||
SYNTAX_NOQA.search(function.docstring) | ||
or EXPLICIT_GLOBAL_NOQA.search(function.docstring) | ||
or BARE_NOQA.search(function.docstring) | ||
): | ||
return | ||
if self.raise_errors: | ||
raise | ||
# If not docstring is given, and raise on missing is true, and its not | ||
# a private function, add an error. | ||
elif self.config.raise_on_missing_docstrings and not function.name.startswith("_"): | ||
cls = (MissingDocstringForPublicMethod | ||
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. This would probably be more understandable if we moved the if-else to the top level, like
That will make it more maintainable as well. (Say, if we decide we need to pass more information for functions, or if we change the |
||
if function.is_method else MissingDocstringForPublicFunction) | ||
self.errors.append( | ||
exception.style_error( | ||
cls( | ||
self.function.function, | ||
message=str(exception), | ||
line_numbers=exception.line_numbers, | ||
), | ||
line_numbers=(-1, -1), | ||
) | ||
) | ||
except ParserException as exception: | ||
# If a syntax exception was raised, we may still | ||
# want to ignore it, if we place a noqa statement. | ||
if ( | ||
SYNTAX_NOQA.search(function.docstring) | ||
or EXPLICIT_GLOBAL_NOQA.search(function.docstring) | ||
or BARE_NOQA.search(function.docstring) | ||
): | ||
return | ||
if self.raise_errors: | ||
raise | ||
self.errors.append( | ||
exception.style_error( | ||
self.function.function, | ||
message=str(exception), | ||
line_numbers=exception.line_numbers, | ||
), | ||
) | ||
|
||
def _check_parameter_types(self): | ||
# type: () -> None | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,8 @@ | |
MissingRaiseError, | ||
MissingReturnError, | ||
MissingYieldError, | ||
MissingDocstringForPublicFunction, | ||
MissingDocstringForPublicMethod, | ||
ParameterTypeMismatchError, | ||
ReturnTypeMismatchError, | ||
) | ||
|
@@ -30,7 +32,8 @@ def setUp(self): | |
self.config = Configuration( | ||
ignore=[], | ||
message_template=None, | ||
style=DocstringStyle.SPHINX | ||
style=DocstringStyle.SPHINX, | ||
raise_on_missing_docstrings=False | ||
) | ||
|
||
def test_missing_parameter(self): | ||
|
@@ -644,3 +647,80 @@ def test_global_noqa_works_for_syntax_errors(self): | |
]) | ||
for variant in ['# noqa: *', '# noqa: S001', '# noqa']: | ||
self.has_no_errors(program.format(variant)) | ||
|
||
self.config = Configuration( | ||
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. Remove this, or comment on its purpose. (It's a little confusing here.) |
||
ignore=[], | ||
message_template=None, | ||
style=DocstringStyle.SPHINX, | ||
raise_on_missing_docstrings=False | ||
) | ||
|
||
def test_check_for_missing_docstrings(self): | ||
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. This could probably be broken up into multiple unit tests. If it's in one unit tests because of the hassle of performing configuration, etc., then you could put it into its own TestCase and have a setUp method. If it's broken up, it will make failing tests easier to diagnose. If we somehow broke the check for missing docstrings on functions but not methods, for example, then the method's name would immediately let us know what is wrong. |
||
"""Make sure we capture missing parameters.""" | ||
program = '\n'.join([ | ||
# Function without a docstring. | ||
'def foo():', | ||
' pass', | ||
# Function with a docstring. | ||
'def bar():', | ||
' """"docstring"""', | ||
' pass', | ||
# private function without a docstring. | ||
'def _foo():', | ||
' pass', | ||
'class FooBar:', | ||
# Method with a docstring. | ||
' def bar_method(self):', | ||
' """docstring"""', | ||
' pass', | ||
# Method without a docstring. | ||
' def foo_method(self):', | ||
' pass', | ||
# Private method without a docstring. | ||
' def _foo_method(self):', | ||
' pass', | ||
]) | ||
tree = ast.parse(program) | ||
functions = get_function_descriptions(tree) | ||
|
||
# No errors if raise_on_missing_docstrings is False. | ||
checker = IntegrityChecker( | ||
Configuration( | ||
ignore=[], | ||
message_template=None, | ||
style=DocstringStyle.GOOGLE, | ||
raise_on_missing_docstrings=False | ||
) | ||
) | ||
for f in functions: | ||
checker.run_checks(f) | ||
errors = checker.errors | ||
self.assertEqual(len(errors), 0) | ||
|
||
# But two errors if True. | ||
checker = IntegrityChecker( | ||
Configuration( | ||
ignore=[], | ||
message_template=None, | ||
style=DocstringStyle.GOOGLE, | ||
raise_on_missing_docstrings=True | ||
) | ||
) | ||
for f in functions: | ||
checker.run_checks(f) | ||
errors = checker.errors | ||
self.assertEqual(len(errors), 2) | ||
|
||
# Somehow the functions are parsed methods -> functions. | ||
self.assertTrue(isinstance( | ||
errors[0], MissingDocstringForPublicMethod)) | ||
self.assertTrue(isinstance( | ||
errors[1], MissingDocstringForPublicFunction)) | ||
self.assertEqual(errors[0].general_message, | ||
'Missing docstring for public method') | ||
self.assertEqual(errors[0].terse_message, | ||
'foo_method') | ||
self.assertEqual(errors[1].general_message, | ||
'Missing docstring for public function') | ||
self.assertEqual(errors[1].terse_message, | ||
'foo') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you move the try-block back inside of the conditional? The else-branch can't raise a ParserException, so there's no need for it to be included in the try-block.