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.
@@ -854,10 +864,38 @@ def _check_args_section(docstring, definition, context):
854864 * The section documents all function arguments (D417)
855865 except `self` or `cls` if it is a method.
856866
867+ Documentation for each arg should start at the same indentation
868+ level. For example, in this case x and y are distinguishable::
869+
870+ Args:
871+ x: Lorem ipsum dolor sit amet
872+ y: Ut enim ad minim veniam
873+
874+ In the case below, we only recognize x as a documented parameter
875+ because the rest of the content is indented as if it belongs
876+ to the description for x::
877+
878+ Args:
879+ x: Lorem ipsum dolor sit amet
880+ y: Ut enim ad minim veniam
857881 """
858882 docstring_args = set ()
859- for line in context .following_lines :
860- match = ConventionChecker .GOOGLE_ARGS_REGEX .match (line )
883+ # normalize leading whitespace
884+ args_content = dedent ("\n " .join (context .following_lines )).strip ()
885+
886+ args_sections = []
887+ for line in args_content .splitlines (keepends = True ):
888+ if not line [:1 ].isspace ():
889+ # This line is the start of documentation for the next
890+ # parameter because it doesn't start with any whitespace.
891+ args_sections .append (line )
892+ else :
893+ # This is a continuation of documentation for the last
894+ # parameter because it does start with whitespace.
895+ args_sections [- 1 ] += line
896+
897+ for section in args_sections :
898+ match = ConventionChecker .GOOGLE_ARGS_REGEX .match (section )
861899 if match :
862900 docstring_args .add (match .group (1 ))
863901 yield from ConventionChecker ._check_missing_args (
0 commit comments