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

Commit 774be90

Browse files
committed
#230 - CR fixes + more error codes!
1 parent 450fe4d commit 774be90

File tree

5 files changed

+94
-48
lines changed

5 files changed

+94
-48
lines changed

src/pydocstyle/checker.py

Lines changed: 43 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,15 @@
44
import string
55
import sys
66
import tokenize as tk
7-
from itertools import takewhile, tee
7+
from itertools import takewhile
88
from re import compile as re
99
from collections import namedtuple
1010

1111
from . import violations
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
16-
17-
try:
18-
from itertools import zip_longest
19-
except ImportError:
20-
from itertools import izip_longest as zip_longest
15+
from .utils import log, is_blank, pairwise
2116

2217

2318
__all__ = ('check', )
@@ -39,16 +34,6 @@ def decorator(f):
3934
return decorator
4035

4136

42-
def pairwise(iterable, default_value):
43-
"""Return pairs of items from `iterable`.
44-
45-
pairwise([1, 2, 3], default_value=None) -> (1, 2) (2, 3), (3, None)
46-
"""
47-
a, b = tee(iterable)
48-
_ = next(b, default_value)
49-
return zip_longest(a, b, fillvalue=default_value)
50-
51-
5237
class ConventionChecker(object):
5338
"""Checker for PEP 257 and numpy conventions.
5439
@@ -489,48 +474,62 @@ def _check_section_underline(cls, section_name, context, indentation):
489474
* The indentation of the dashed line is equal to the docstring's
490475
indentation (D215).
491476
"""
492-
dash_line_found = False
493-
next_non_empty_line_offset = 0
477+
blank_lines_after_header = 0
494478

495479
for line in context.following_lines:
496-
line_set = ''.join(set(line.strip()))
497-
if not is_blank(line_set):
498-
dash_line_found = line_set == '-'
480+
if not is_blank(line):
499481
break
500-
next_non_empty_line_offset += 1
482+
blank_lines_after_header += 1
483+
else:
484+
# There are only blank lines after the header.
485+
yield violations.D407(section_name)
486+
return
487+
488+
non_empty_line = context.following_lines[blank_lines_after_header]
489+
dash_line_found = ''.join(set(non_empty_line.strip())) == '-'
501490

502491
if not dash_line_found:
503492
yield violations.D407(section_name)
504-
if next_non_empty_line_offset > 0:
493+
if blank_lines_after_header > 0:
505494
yield violations.D412(section_name)
506495
else:
507-
if next_non_empty_line_offset > 0:
496+
if blank_lines_after_header > 0:
508497
yield violations.D408(section_name)
509498

510-
dash_line = context.following_lines[next_non_empty_line_offset]
511-
if dash_line.strip() != "-" * len(section_name):
499+
if non_empty_line.strip() != "-" * len(section_name):
512500
yield violations.D409(len(section_name),
513501
section_name,
514-
len(dash_line.strip()))
502+
len(non_empty_line.strip()))
515503

516-
if leading_space(dash_line) > indentation:
504+
if leading_space(non_empty_line) > indentation:
517505
yield violations.D215(section_name)
518506

519-
if next_non_empty_line_offset + 1 < len(context.following_lines):
507+
line_after_dashes_index = blank_lines_after_header + 1
508+
# If the line index after the dashes is in range (perhaps we have
509+
# a header + underline followed by another section header).
510+
if line_after_dashes_index < len(context.following_lines):
520511
line_after_dashes = \
521-
context.following_lines[next_non_empty_line_offset + 1]
512+
context.following_lines[line_after_dashes_index]
522513
if is_blank(line_after_dashes):
523-
yield violations.D412(section_name)
514+
rest_of_lines = \
515+
context.following_lines[line_after_dashes_index:]
516+
if not is_blank(''.join(rest_of_lines)):
517+
yield violations.D412(section_name)
518+
else:
519+
yield violations.D414(section_name)
520+
else:
521+
yield violations.D414(section_name)
522+
524523

525524
@classmethod
526525
def _check_section(cls, docstring, definition, context):
527-
"""D4{05,06,10,11}, D214: Section name checks.
526+
"""D4{05,06,10,11,13}, D214: Section name checks.
528527
529528
Check for valid section names. Checks that:
530529
* The section name is properly capitalized (D405).
531530
* The section is not over-indented (D214).
532531
* The section name has no superfluous suffix to it (D406).
533-
* There's a blank line after the section (D410).
532+
* There's a blank line after the section (D410, D413).
534533
* There's a blank line before the section (D411).
535534
536535
Also yields all the errors from `_check_section_underline`.
@@ -551,7 +550,10 @@ def _check_section(cls, docstring, definition, context):
551550

552551
if (not context.following_lines or
553552
not is_blank(context.following_lines[-1])):
554-
yield violations.D410(capitalized_section)
553+
if context.is_last_section:
554+
yield violations.D413(capitalized_section)
555+
else:
556+
yield violations.D410(capitalized_section)
555557

556558
if not is_blank(context.previous_line):
557559
yield violations.D411(capitalized_section)
@@ -601,15 +603,17 @@ def _suspected_as_section(_line):
601603
'previous_line',
602604
'line',
603605
'following_lines',
604-
'original_index'))
606+
'original_index',
607+
'is_last_section'))
605608

606609
# First - create a list of possible contexts. Note that the
607610
# `following_linex` member is until the end of the docstring.
608611
contexts = (SectionContext(self._get_leading_words(lines[i].strip()),
609612
lines[i - 1],
610613
lines[i],
611614
lines[i + 1:],
612-
i)
615+
i,
616+
False)
613617
for i in suspected_section_indices)
614618

615619
# Now that we have manageable objects - rule out false positives.
@@ -623,7 +627,8 @@ def _suspected_as_section(_line):
623627
a.previous_line,
624628
a.line,
625629
lines[a.original_index + 1:end],
626-
a.original_index)
630+
a.original_index,
631+
b is None)
627632
for err in self._check_section(docstring, definition, new_ctx):
628633
yield err
629634

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 & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,11 @@ def to_rst(cls):
223223
'Expected {0!r} dashes in section {1!r}, got {2!r}')
224224
D410 = D4xx.create_error('D410', 'Missing blank line after section', '{0!r}')
225225
D411 = D4xx.create_error('D411', 'Missing blank line before section', '{0!r}')
226-
D412 = D4xx.create_error('D412', 'Section content should be in the line '
227-
'following its header', '{0!r}')
226+
D412 = D4xx.create_error('D412', 'No blank lines allowed between a section '
227+
'header and its content', '{0!r}')
228+
D413 = D4xx.create_error('D413', 'Missing blank line after last section',
229+
'{0!r}')
230+
D414 = D4xx.create_error('D414', 'Section has no content', '{0!r}')
228231

229232

230233
class AttrDict(dict):
@@ -237,5 +240,5 @@ def __getattr__(self, item):
237240
'pep257': all_errors - {'D203', 'D212', 'D213', 'D214', 'D215', 'D404',
238241
'D405', 'D406', 'D407', 'D408', 'D409', 'D410',
239242
'D411'},
240-
'numpy': all_errors - {'D203', 'D212', 'D213', 'D402'}
243+
'numpy': all_errors - {'D203', 'D212', 'D213', 'D402', 'D413'}
241244
})

src/tests/test_cases/sections.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,31 @@ def no_underline():
4848

4949
@expect(_D213)
5050
@expect("D407: Missing dashed underline after section ('Returns')")
51-
@expect("D410: Missing blank line after section ('Returns')")
5251
def no_underline():
5352
"""Valid headline.
5453
5554
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.
75+
5676
"""
5777

5878

@@ -84,8 +104,8 @@ def bad_underline_length():
84104

85105

86106
@expect(_D213)
87-
@expect("D410: Missing blank line after section ('Returns')")
88-
def no_blank_line_after_section():
107+
@expect("D413: Missing blank line after last section ('Returns')")
108+
def no_blank_line_after_last_section():
89109
"""Valid headline.
90110
91111
Returns
@@ -133,7 +153,8 @@ def section_underline_overindented():
133153

134154
@expect(_D213)
135155
@expect("D215: Section underline is over-indented (in section 'Returns')")
136-
@expect("D410: Missing blank line after section ('Returns')")
156+
@expect("D413: Missing blank line after last section ('Returns')")
157+
@expect("D414: Section has no content ('Returns')")
137158
def section_underline_overindented_and_contentless():
138159
"""Valid headline.
139160
@@ -169,8 +190,8 @@ def section_name_in_first_line():
169190
@expect(_D213)
170191
@expect("D405: Section name should be properly capitalized "
171192
"('Short Summary', not 'Short summary')")
172-
@expect("D412: Section content should be in the line following its header "
173-
"('Short Summary')")
193+
@expect("D412: No blank lines allowed between a section header and its "
194+
"content ('Short Summary')")
174195
@expect("D409: Section underline should match the length of its name "
175196
"(Expected 7 dashes in section 'Returns', got 6)")
176197
@expect("D410: Missing blank line after section ('Returns')")

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)