diff --git a/docs/release_notes.rst b/docs/release_notes.rst index 5ab5e11..e5ec725 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -4,6 +4,13 @@ Release Notes **pydocstyle** version numbers follow the `Semantic Versioning `_ specification. +next +---- + +Bug Fixes + +* No longer emit D401 for sections at the start of docstrings (#556). + 6.3.0 - January 17th, 2023 -------------------------- diff --git a/src/pydocstyle/checker.py b/src/pydocstyle/checker.py index 2441749..2304412 100644 --- a/src/pydocstyle/checker.py +++ b/src/pydocstyle/checker.py @@ -521,6 +521,9 @@ def check_imperative_mood(self, function, docstring): # def context "Returns the pathname ...". """ + ctxs = list(self._get_section_contexts_autodetect(docstring)) + if ctxs and ctxs[0].is_docstring_start: + return if ( docstring and not function.is_test @@ -604,6 +607,16 @@ def check_starts_with_this(self, function, docstring): if first_word.lower() == 'this': return violations.D404() + @staticmethod + def _is_at_docstring_start(context): + """Return whether a `SectionContext` occurs at the start of a docstring.""" + return context.original_index == 1 and context.previous_line in [ + '"', + "'", + '"""', + "'''", + ] + @staticmethod def _is_docstring_section(context): """Check if the suspected context is really a section header. @@ -656,7 +669,9 @@ def _is_docstring_section(context): ) prev_line_looks_like_end_of_paragraph = ( - prev_line_ends_with_punctuation or is_blank(context.previous_line) + prev_line_ends_with_punctuation + or is_blank(context.previous_line) + or context.is_docstring_start ) return ( @@ -766,7 +781,10 @@ def _check_common_section( else: yield violations.D410(capitalized_section) - if not is_blank(context.previous_line): + if ( + not is_blank(context.previous_line) + and not context.is_docstring_start + ): yield violations.D411(capitalized_section) yield from cls._check_blanks_and_section_underline( @@ -1004,6 +1022,7 @@ def _suspected_as_section(_line): 'line', 'following_lines', 'original_index', + 'is_docstring_start', 'is_last_section', ), ) @@ -1019,15 +1038,18 @@ def _suspected_as_section(_line): lines[i + 1 :], i, False, + False, ) for i in suspected_section_indices ) - - # Now that we have manageable objects - rule out false positives. contexts = ( - c for c in contexts if ConventionChecker._is_docstring_section(c) + c._replace(is_docstring_start=cls._is_at_docstring_start(c)) + for c in contexts ) + # Now that we have manageable objects - rule out false positives. + contexts = (c for c in contexts if cls._is_docstring_section(c)) + # Now we shall trim the `following lines` field to only reach the # next section name. for a, b in pairwise(contexts, None): @@ -1039,6 +1061,7 @@ def _suspected_as_section(_line): a.line, lines[a.original_index + 1 : end], a.original_index, + a.is_docstring_start, b is None, ) diff --git a/src/tests/test_cases/sections.py b/src/tests/test_cases/sections.py index 5bf9a7b..c2f3466 100644 --- a/src/tests/test_cases/sections.py +++ b/src/tests/test_cases/sections.py @@ -6,6 +6,7 @@ expect = expectation.expect +_D212 = 'D212: Multi-line docstring summary should start at the first line' _D213 = 'D213: Multi-line docstring summary should start at the second line' _D400 = "D400: First line should end with a period (not '!')" @@ -191,6 +192,21 @@ def section_name_in_first_line(): # noqa: D416 """ +@expect(_D212) +@expect("D400: First line should end with a period (not 's')") +@expect("D415: First line should end with a period, question " + "mark, or exclamation point (not 's')") +@expect("D205: 1 blank line required between summary line and description " + "(found 0)") +def section_name_in_first_nonblank_line(): # noqa: D416 + """ + Returns + ------- + A value of some sort. + + """ + + @expect(_D213) @expect("D405: Section name should be properly capitalized " "('Short Summary', not 'Short summary')")