66from collections import namedtuple
77from itertools import chain , takewhile
88from re import compile as re
9+ from textwrap import dedent
910
1011from . import violations
1112from .config import IllegalConfiguration
@@ -122,6 +123,8 @@ class ConventionChecker:
122123 r"\s*"
123124 # Followed by a colon
124125 r":"
126+ # Might have a new line and leading whitespace
127+ r"\n?\s*"
125128 # Followed by 1 or more characters - which is the docstring for the parameter
126129 ".+"
127130 )
@@ -196,12 +199,7 @@ def check_docstring_missing(self, definition, docstring):
196199 with a single underscore.
197200
198201 """
199- if (
200- not docstring
201- and definition .is_public
202- or docstring
203- and is_blank (ast .literal_eval (docstring ))
204- ):
202+ if not docstring and definition .is_public :
205203 codes = {
206204 Module : violations .D100 ,
207205 Class : violations .D101 ,
@@ -227,6 +225,18 @@ def check_docstring_missing(self, definition, docstring):
227225 }
228226 return codes [type (definition )]()
229227
228+ @check_for (Definition , terminal = True )
229+ def check_docstring_empty (self , definition , docstring ):
230+ """D419: Docstring is empty.
231+
232+ If the user provided a docstring but it was empty, it is like they never provided one.
233+
234+ NOTE: This used to report as D10X errors.
235+
236+ """
237+ if docstring and is_blank (ast .literal_eval (docstring )):
238+ return violations .D419 ()
239+
230240 @check_for (Definition )
231241 def check_one_liners (self , definition , docstring ):
232242 """D200: One-liner docstrings should fit on one line with quotes.
@@ -836,10 +846,38 @@ def _check_args_section(docstring, definition, context):
836846 * The section documents all function arguments (D417)
837847 except `self` or `cls` if it is a method.
838848
849+ Documentation for each arg should start at the same indentation
850+ level. For example, in this case x and y are distinguishable::
851+
852+ Args:
853+ x: Lorem ipsum dolor sit amet
854+ y: Ut enim ad minim veniam
855+
856+ In the case below, we only recognize x as a documented parameter
857+ because the rest of the content is indented as if it belongs
858+ to the description for x::
859+
860+ Args:
861+ x: Lorem ipsum dolor sit amet
862+ y: Ut enim ad minim veniam
839863 """
840864 docstring_args = set ()
841- for line in context .following_lines :
842- match = ConventionChecker .GOOGLE_ARGS_REGEX .match (line )
865+ # normalize leading whitespace
866+ args_content = dedent ("\n " .join (context .following_lines )).strip ()
867+
868+ args_sections = []
869+ for line in args_content .splitlines (keepends = True ):
870+ if not line [:1 ].isspace ():
871+ # This line is the start of documentation for the next
872+ # parameter because it doesn't start with any whitespace.
873+ args_sections .append (line )
874+ else :
875+ # This is a continuation of documentation for the last
876+ # parameter because it does start with whitespace.
877+ args_sections [- 1 ] += line
878+
879+ for section in args_sections :
880+ match = ConventionChecker .GOOGLE_ARGS_REGEX .match (section )
843881 if match :
844882 docstring_args .add (match .group (1 ))
845883 yield from ConventionChecker ._check_missing_args (
0 commit comments