From eb8d31243716b4004e7d9f7b623dbe125e5d15a5 Mon Sep 17 00:00:00 2001 From: Sambhav Kothari Date: Sun, 15 Dec 2019 20:57:28 +0000 Subject: [PATCH] Fix indentation error while parsing class methods In order to determine the missing arguments, we currently use ast.parse to parse partial source code. This parsing might lead to syntax errors. We catch the syntax error and make the parsing more resilient to errors in the source code. Fixes #437 --- docs/release_notes.rst | 2 ++ src/pydocstyle/checker.py | 15 ++++++++++-- src/tests/test_cases/expected.py | 4 ++-- src/tests/test_cases/sections.py | 40 +++++++++++++++++++++++++++++++- 4 files changed, 56 insertions(+), 5 deletions(-) diff --git a/docs/release_notes.rst b/docs/release_notes.rst index 13c677fb..00466a57 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -15,6 +15,8 @@ Bug Fixes * Update convention support documentation (#386, #393) * Detect inner asynchronous functions for D202 (#467) +* Fix indentation error while parsing class methods (#441). + 5.0.2 - January 8th, 2020 --------------------------- diff --git a/src/pydocstyle/checker.py b/src/pydocstyle/checker.py index f5b34f32..d7739247 100644 --- a/src/pydocstyle/checker.py +++ b/src/pydocstyle/checker.py @@ -1007,9 +1007,20 @@ def is_def_arg_private(arg_name): """Return a boolean indicating if the argument name is private.""" return arg_name.startswith("_") -def get_function_args(function_string): +def get_function_args(function_source): """Return the function arguments given the source-code string.""" - function_arg_node = ast.parse(textwrap.dedent(function_string)).body[0].args + # We are stripping the whitespace from the left of the + # function source. + # This is so that if the docstring has incorrectly + # indented lines, which are at a lower indent than the + # function source, we still dedent the source correctly + # and the AST parser doesn't throw an error. + try: + function_arg_node = ast.parse(function_source.lstrip()).body[0].args + except SyntaxError: + # If we still get a syntax error, we don't want the + # the checker to crash. Instead we just return a blank list. + return [] arg_nodes = function_arg_node.args kwonly_arg_nodes = function_arg_node.kwonlyargs return [arg_node.arg for arg_node in chain(arg_nodes, kwonly_arg_nodes)] diff --git a/src/tests/test_cases/expected.py b/src/tests/test_cases/expected.py index fbe719a7..f131e16a 100644 --- a/src/tests/test_cases/expected.py +++ b/src/tests/test_cases/expected.py @@ -4,13 +4,13 @@ class Expectation: def __init__(self): self.expected = set() - def expect(self, *args, arg_count=0): + def expect(self, *args, arg_count=0, func_name=""): """Decorator that expects a certain PEP 257 violation.""" # The `arg_count` parameter helps the decorator # with functions that have positional arguments. if len(args) == 1: def decorate(f): - self.expected.add((f.__name__, args[0])) + self.expected.add((func_name or f.__name__, args[0])) f(*[None]*arg_count) return f return decorate diff --git a/src/tests/test_cases/sections.py b/src/tests/test_cases/sections.py index 00609386..ad37312f 100644 --- a/src/tests/test_cases/sections.py +++ b/src/tests/test_cases/sections.py @@ -274,6 +274,25 @@ def missing_colon_google_style_section(): # noqa: D406, D407 """ +@expect("D417: Missing argument descriptions in the docstring " + "(argument(s) y are missing descriptions in " + "'bar' docstring)", func_name="bar") +def _test_nested_functions(): + x = 1 + + def bar(y=2): # noqa: D207, D213, D406, D407 + """Nested function test for docstrings. + + Will this work when referencing x? + + Args: + x: Test something +that is broken. + + """ + print(x) + + @expect(_D213) @expect("D417: Missing argument descriptions in the docstring " "(argument(s) y are missing descriptions in " @@ -340,13 +359,15 @@ def test_missing_args_static_method(a, x, y, _test, z=3): # noqa: D213, D407 @expect("D417: Missing argument descriptions in the docstring " "(argument(s) y are missing descriptions in " "'test_missing_numpy_args' docstring)") +@expect("D207: Docstring is under-indented") def test_missing_numpy_args(_private_arg=0, x=1, y=2): # noqa: D406, D407 """Toggle the gizmo. Parameters ---------- x : int - The greatest integer. + The greatest integer in the history \ +of the entire world. """ @@ -425,3 +446,20 @@ def test_mixing_numpy_and_google(danger): # noqa: D213 Zoneeeeee! """ + + +class TestIncorrectIndent: # noqa: D203 + """Test class.""" + + @expect("D417: Missing argument descriptions in the docstring " + "(argument(s) y are missing descriptions in " + "'test_incorrect_indent' docstring)", arg_count=3) + def test_incorrect_indent(self, x=1, y=2): # noqa: D207, D213, D407 + """Reproducing issue #437. + +Testing this incorrectly indented docstring. + + Args: + x: Test argument. + + """