Skip to content
This repository was archived by the owner on Nov 3, 2023. It is now read-only.

Commit d12c92a

Browse files
committed
Merge branch 'master' of github.com:PyCQA/pydocstyle into imperative-mood-heuristic-2
2 parents 8cc935c + 07ada6a commit d12c92a

File tree

6 files changed

+151
-38
lines changed

6 files changed

+151
-38
lines changed

docs/release_notes.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ New Features
2020
* Decorator-based skipping via ``--ignore-decorators`` has been added (#204).
2121
* Support for using pycodestyle style wildcards has been added (#72, #209).
2222
* Superfluous opening quotes are now reported as part of D300 (#166, #225).
23-
* Support for ``numpy`` conventions verification has been added (#129).
23+
* Support for ``numpy`` conventions verification has been added (#129, #226).
24+
* Fixed a false-positive recognition of `D410` and added `D412` (#230, #233).
2425

2526
Bug Fixes
2627

src/pydocstyle/checker.py

Lines changed: 69 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from .config import IllegalConfiguration
1313
from .parser import (Package, Module, Class, NestedClass, Definition, AllError,
1414
Method, Function, NestedFunction, Parser, StringIO)
15-
from .utils import log, is_blank
15+
from .utils import log, is_blank, pairwise
1616
from .wordlists import IMPERATIVE_VERBS, IMPERATIVE_BLACKLIST, stem
1717

1818

@@ -472,58 +472,73 @@ def _is_a_docstring_section(context):
472472

473473
@classmethod
474474
def _check_section_underline(cls, section_name, context, indentation):
475-
"""D4{07,08,09,10}, D215: Section underline checks.
475+
"""D4{07,08,09,12}, D215: Section underline checks.
476476
477477
Check for correct formatting for docstring sections. Checks that:
478478
* The line that follows the section name contains
479479
dashes (D40{7,8}).
480480
* The amount of dashes is equal to the length of the section
481481
name (D409).
482-
* The line that follows the section header (with or without dashes)
483-
is empty (D410).
482+
* The section's content does not begin in the line that follows
483+
the section header (D412).
484484
* The indentation of the dashed line is equal to the docstring's
485485
indentation (D215).
486486
"""
487-
dash_line_found = False
488-
next_non_empty_line_offset = 0
487+
blank_lines_after_header = 0
489488

490489
for line in context.following_lines:
491-
line_set = ''.join(set(line.strip()))
492-
if not is_blank(line_set):
493-
dash_line_found = line_set == '-'
490+
if not is_blank(line):
494491
break
495-
next_non_empty_line_offset += 1
492+
blank_lines_after_header += 1
493+
else:
494+
# There are only blank lines after the header.
495+
yield violations.D407(section_name)
496+
return
497+
498+
non_empty_line = context.following_lines[blank_lines_after_header]
499+
dash_line_found = ''.join(set(non_empty_line.strip())) == '-'
496500

497501
if not dash_line_found:
498502
yield violations.D407(section_name)
499-
if next_non_empty_line_offset == 0:
500-
yield violations.D410(section_name)
503+
if blank_lines_after_header > 0:
504+
yield violations.D412(section_name)
501505
else:
502-
if next_non_empty_line_offset > 0:
506+
if blank_lines_after_header > 0:
503507
yield violations.D408(section_name)
504508

505-
dash_line = context.following_lines[next_non_empty_line_offset]
506-
if dash_line.strip() != "-" * len(section_name):
509+
if non_empty_line.strip() != "-" * len(section_name):
507510
yield violations.D409(len(section_name),
508511
section_name,
509-
len(dash_line.strip()))
512+
len(non_empty_line.strip()))
510513

511-
line_after_dashes = \
512-
context.following_lines[next_non_empty_line_offset + 1]
513-
if not is_blank(line_after_dashes):
514-
yield violations.D410(section_name)
515-
516-
if leading_space(dash_line) > indentation:
514+
if leading_space(non_empty_line) > indentation:
517515
yield violations.D215(section_name)
518516

517+
line_after_dashes_index = blank_lines_after_header + 1
518+
# If the line index after the dashes is in range (perhaps we have
519+
# a header + underline followed by another section header).
520+
if line_after_dashes_index < len(context.following_lines):
521+
line_after_dashes = \
522+
context.following_lines[line_after_dashes_index]
523+
if is_blank(line_after_dashes):
524+
rest_of_lines = \
525+
context.following_lines[line_after_dashes_index:]
526+
if not is_blank(''.join(rest_of_lines)):
527+
yield violations.D412(section_name)
528+
else:
529+
yield violations.D414(section_name)
530+
else:
531+
yield violations.D414(section_name)
532+
519533
@classmethod
520534
def _check_section(cls, docstring, definition, context):
521-
"""D4{05,06,11}, D214: Section name checks.
535+
"""D4{05,06,10,11,13}, D214: Section name checks.
522536
523537
Check for valid section names. Checks that:
524538
* The section name is properly capitalized (D405).
525539
* The section is not over-indented (D214).
526540
* The section name has no superfluous suffix to it (D406).
541+
* There's a blank line after the section (D410, D413).
527542
* There's a blank line before the section (D411).
528543
529544
Also yields all the errors from `_check_section_underline`.
@@ -542,6 +557,13 @@ def _check_section(cls, docstring, definition, context):
542557
if suffix:
543558
yield violations.D406(capitalized_section, context.line.strip())
544559

560+
if (not context.following_lines or
561+
not is_blank(context.following_lines[-1])):
562+
if context.is_last_section:
563+
yield violations.D413(capitalized_section)
564+
else:
565+
yield violations.D410(capitalized_section)
566+
545567
if not is_blank(context.previous_line):
546568
yield violations.D411(capitalized_section)
547569

@@ -559,13 +581,12 @@ def check_docstring_sections(self, definition, docstring):
559581
560582
Short Summary
561583
-------------
562-
563584
This is my summary.
564585
565586
Returns
566587
-------
567-
568588
None.
589+
569590
'''
570591
571592
Section names appear in `SECTION_NAMES`.
@@ -590,18 +611,35 @@ def _suspected_as_section(_line):
590611
SectionContext = namedtuple('SectionContext', ('section_name',
591612
'previous_line',
592613
'line',
593-
'following_lines'))
614+
'following_lines',
615+
'original_index',
616+
'is_last_section'))
594617

618+
# First - create a list of possible contexts. Note that the
619+
# `following_linex` member is until the end of the docstring.
595620
contexts = (SectionContext(self._get_leading_words(lines[i].strip()),
596621
lines[i - 1],
597622
lines[i],
598-
lines[i + 1:])
623+
lines[i + 1:],
624+
i,
625+
False)
599626
for i in suspected_section_indices)
600627

601-
for ctx in contexts:
602-
if self._is_a_docstring_section(ctx):
603-
for err in self._check_section(docstring, definition, ctx):
604-
yield err
628+
# Now that we have manageable objects - rule out false positives.
629+
contexts = (c for c in contexts if self._is_a_docstring_section(c))
630+
631+
# Now we shall trim the `following lines` field to only reach the
632+
# next section name.
633+
for a, b in pairwise(contexts, None):
634+
end = -1 if b is None else b.original_index
635+
new_ctx = SectionContext(a.section_name,
636+
a.previous_line,
637+
a.line,
638+
lines[a.original_index + 1:end],
639+
a.original_index,
640+
b is None)
641+
for err in self._check_section(docstring, definition, new_ctx):
642+
yield err
605643

606644

607645
parse = Parser()

src/pydocstyle/utils.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
"""General shared utilities."""
22
import logging
3+
from itertools import tee
4+
try:
5+
from itertools import zip_longest
6+
except ImportError:
7+
from itertools import izip_longest as zip_longest
38

49

510
__version__ = '2.0.0rc'
@@ -9,3 +14,13 @@
914
def is_blank(string):
1015
"""Return True iff the string contains only whitespaces."""
1116
return not string.strip()
17+
18+
19+
def pairwise(iterable, default_value):
20+
"""Return pairs of items from `iterable`.
21+
22+
pairwise([1, 2, 3], default_value=None) -> (1, 2) (2, 3), (3, None)
23+
"""
24+
a, b = tee(iterable)
25+
_ = next(b, default_value)
26+
return zip_longest(a, b, fillvalue=default_value)

src/pydocstyle/violations.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,11 @@ def to_rst(cls):
225225
'Expected {0!r} dashes in section {1!r}, got {2!r}')
226226
D410 = D4xx.create_error('D410', 'Missing blank line after section', '{0!r}')
227227
D411 = D4xx.create_error('D411', 'Missing blank line before section', '{0!r}')
228+
D412 = D4xx.create_error('D412', 'No blank lines allowed between a section '
229+
'header and its content', '{0!r}')
230+
D413 = D4xx.create_error('D413', 'Missing blank line after last section',
231+
'{0!r}')
232+
D414 = D4xx.create_error('D414', 'Section has no content', '{0!r}')
228233

229234

230235
class AttrDict(dict):
@@ -237,5 +242,5 @@ def __getattr__(self, item):
237242
'pep257': all_errors - {'D203', 'D212', 'D213', 'D214', 'D215', 'D404',
238243
'D405', 'D406', 'D407', 'D408', 'D409', 'D410',
239244
'D411'},
240-
'numpy': all_errors - {'D203', 'D212', 'D213', 'D402'}
245+
'numpy': all_errors - {'D203', 'D212', 'D213', 'D402', 'D413'}
241246
})

src/tests/test_cases/sections.py

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ def not_capitalized():
1717
1818
returns
1919
-------
20+
A value of some sort.
2021
2122
"""
2223

@@ -29,6 +30,7 @@ def superfluous_suffix():
2930
3031
Returns:
3132
-------
33+
A value of some sort.
3234
3335
"""
3436

@@ -39,6 +41,37 @@ def no_underline():
3941
"""Toggle the gizmo.
4042
4143
Returns
44+
A value of some sort.
45+
46+
"""
47+
48+
49+
@expect(_D213)
50+
@expect("D407: Missing dashed underline after section ('Returns')")
51+
def no_underline():
52+
"""Valid headline.
53+
54+
Returns
55+
56+
"""
57+
58+
59+
@expect(_D213)
60+
@expect("D410: Missing blank line after section ('Returns')")
61+
@expect("D414: Section has no content ('Returns')")
62+
@expect("D411: Missing blank line before section ('Yields')")
63+
@expect("D414: Section has no content ('Yields')")
64+
def consecutive_sections():
65+
"""Valid headline.
66+
67+
Returns
68+
-------
69+
Yields
70+
------
71+
72+
Raises
73+
------
74+
Questions.
4275
4376
"""
4477

@@ -52,6 +85,7 @@ def blank_line_before_underline():
5285
Returns
5386
5487
-------
88+
A value of some sort.
5589
5690
"""
5791

@@ -64,18 +98,19 @@ def bad_underline_length():
6498
6599
Returns
66100
--
101+
A value of some sort.
67102
68103
"""
69104

70105

71106
@expect(_D213)
72-
@expect("D410: Missing blank line after section ('Returns')")
73-
def no_blank_line_after_section():
107+
@expect("D413: Missing blank line after last section ('Returns')")
108+
def no_blank_line_after_last_section():
74109
"""Toggle the gizmo.
75110
76111
Returns
77112
-------
78-
A whole lot of values.
113+
A value of some sort.
79114
"""
80115

81116

@@ -87,6 +122,7 @@ def no_blank_line_before_section():
87122
The function's description.
88123
Returns
89124
-------
125+
A value of some sort.
90126
91127
"""
92128

@@ -98,6 +134,7 @@ def section_overindented():
98134
99135
Returns
100136
-------
137+
A value of some sort.
101138
102139
"""
103140

@@ -109,10 +146,23 @@ def section_underline_overindented():
109146
110147
Returns
111148
-------
149+
A value of some sort.
112150
113151
"""
114152

115153

154+
@expect(_D213)
155+
@expect("D215: Section underline is over-indented (in section 'Returns')")
156+
@expect("D413: Missing blank line after last section ('Returns')")
157+
@expect("D414: Section has no content ('Returns')")
158+
def section_underline_overindented_and_contentless():
159+
"""Valid headline.
160+
161+
Returns
162+
-------
163+
"""
164+
165+
116166
@expect(_D213)
117167
def ignore_non_actual_section():
118168
"""Toggle the gizmo.
@@ -132,21 +182,23 @@ def ignore_non_actual_section():
132182
def section_name_in_first_line():
133183
"""Returns
134184
-------
185+
A value of some sort.
135186
136187
"""
137188

138189

139190
@expect(_D213)
140191
@expect("D405: Section name should be properly capitalized "
141192
"('Short Summary', not 'Short summary')")
193+
@expect("D412: No blank lines allowed between a section header and its "
194+
"content ('Short Summary')")
142195
@expect("D409: Section underline should match the length of its name "
143196
"(Expected 7 dashes in section 'Returns', got 6)")
144197
@expect("D410: Missing blank line after section ('Returns')")
145198
@expect("D411: Missing blank line before section ('Raises')")
146199
@expect("D406: Section name should end with a newline "
147200
"('Raises', not 'Raises:')")
148201
@expect("D407: Missing dashed underline after section ('Raises')")
149-
@expect("D410: Missing blank line after section ('Raises')")
150202
def multiple_sections():
151203
"""Toggle the gizmo.
152204

src/tests/test_integration.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -521,7 +521,9 @@ class Foo(object):
521521
assert 'D215' in out
522522
assert 'D405' in out
523523
assert 'D409' in out
524-
assert 'D410' in out
524+
assert 'D414' in out
525+
assert 'D410' not in out
526+
assert 'D413' not in out
525527

526528

527529
def test_config_file_inheritance(env):

0 commit comments

Comments
 (0)