diff --git a/docs/release_notes.rst b/docs/release_notes.rst index b70aca01..5da4b27d 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -10,6 +10,7 @@ Current Development Version New Features +* Allow for hanging indent when documenting args in Google style. (#449) * Add support for `property_decorators` config to ignore D401. * Add support for Python 3.10 (#554). * Replace D10X errors with D419 if docstring exists but is empty (#559). diff --git a/src/pydocstyle/checker.py b/src/pydocstyle/checker.py index 5c5522e7..0b456444 100644 --- a/src/pydocstyle/checker.py +++ b/src/pydocstyle/checker.py @@ -6,6 +6,7 @@ from collections import namedtuple from itertools import chain, takewhile from re import compile as re +from textwrap import dedent from . import violations from .config import IllegalConfiguration @@ -122,6 +123,8 @@ class ConventionChecker: r"\s*" # Followed by a colon r":" + # Might have a new line and leading whitespace + r"\n?\s*" # Followed by 1 or more characters - which is the docstring for the parameter ".+" ) @@ -843,10 +846,38 @@ def _check_args_section(docstring, definition, context): * The section documents all function arguments (D417) except `self` or `cls` if it is a method. + Documentation for each arg should start at the same indentation + level. For example, in this case x and y are distinguishable:: + + Args: + x: Lorem ipsum dolor sit amet + y: Ut enim ad minim veniam + + In the case below, we only recognize x as a documented parameter + because the rest of the content is indented as if it belongs + to the description for x:: + + Args: + x: Lorem ipsum dolor sit amet + y: Ut enim ad minim veniam """ docstring_args = set() - for line in context.following_lines: - match = ConventionChecker.GOOGLE_ARGS_REGEX.match(line) + # normalize leading whitespace + args_content = dedent("\n".join(context.following_lines)).strip() + + args_sections = [] + for line in args_content.splitlines(keepends=True): + if not line[:1].isspace(): + # This line is the start of documentation for the next + # parameter because it doesn't start with any whitespace. + args_sections.append(line) + else: + # This is a continuation of documentation for the last + # parameter because it does start with whitespace. + args_sections[-1] += line + + for section in args_sections: + match = ConventionChecker.GOOGLE_ARGS_REGEX.match(section) if match: docstring_args.add(match.group(1)) yield from ConventionChecker._check_missing_args( diff --git a/src/tests/test_cases/sections.py b/src/tests/test_cases/sections.py index d671102b..b4932606 100644 --- a/src/tests/test_cases/sections.py +++ b/src/tests/test_cases/sections.py @@ -367,10 +367,7 @@ def test_missing_docstring(a, b): # noqa: D213, D407 """ @staticmethod - @expect("D417: Missing argument descriptions in the docstring " - "(argument(s) skip, verbose are missing descriptions in " - "'test_missing_docstring_another' docstring)", arg_count=2) - def test_missing_docstring_another(skip, verbose): # noqa: D213, D407 + def test_hanging_indent(skip, verbose): # noqa: D213, D407 """Do stuff. Args: