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

Commit 174b330

Browse files
committed
#230 - Fixing false D410 recognition and adding D412
1 parent 238141e commit 174b330

File tree

4 files changed

+86
-23
lines changed

4 files changed

+86
-23
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).
2425

2526
Bug Fixes
2627

src/pydocstyle/checker.py

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

@@ -34,6 +34,16 @@ def decorator(f):
3434
return decorator
3535

3636

37+
def pairwise(iterable, default_value):
38+
"""Return pairs of items from `iterable`.
39+
40+
pairwise([1, 2, 3], default_value=None) -> (1, 2) (2, 3), (3, None)
41+
"""
42+
a, b = tee(iterable)
43+
_ = next(b, default_value)
44+
return izip_longest(a, b, fillvalue=default_value)
45+
46+
3747
class ConventionChecker(object):
3848
"""Checker for PEP 257 and numpy conventions.
3949
@@ -462,15 +472,15 @@ def _is_a_docstring_section(context):
462472

463473
@classmethod
464474
def _check_section_underline(cls, section_name, context, indentation):
465-
"""D4{07,08,09,10}, D215: Section underline checks.
475+
"""D4{07,08,09,12}, D215: Section underline checks.
466476
467477
Check for correct formatting for docstring sections. Checks that:
468478
* The line that follows the section name contains
469479
dashes (D40{7,8}).
470480
* The amount of dashes is equal to the length of the section
471481
name (D409).
472-
* The line that follows the section header (with or without dashes)
473-
is empty (D410).
482+
* The section's content does not begin in the line that follows
483+
the section header (D412).
474484
* The indentation of the dashed line is equal to the docstring's
475485
indentation (D215).
476486
"""
@@ -486,8 +496,8 @@ def _check_section_underline(cls, section_name, context, indentation):
486496

487497
if not dash_line_found:
488498
yield violations.D407(section_name)
489-
if next_non_empty_line_offset == 0:
490-
yield violations.D410(section_name)
499+
if next_non_empty_line_offset > 0:
500+
yield violations.D412(section_name)
491501
else:
492502
if next_non_empty_line_offset > 0:
493503
yield violations.D408(section_name)
@@ -498,22 +508,24 @@ def _check_section_underline(cls, section_name, context, indentation):
498508
section_name,
499509
len(dash_line.strip()))
500510

501-
line_after_dashes = \
502-
context.following_lines[next_non_empty_line_offset + 1]
503-
if not is_blank(line_after_dashes):
504-
yield violations.D410(section_name)
505-
506511
if leading_space(dash_line) > indentation:
507512
yield violations.D215(section_name)
508513

514+
if next_non_empty_line_offset + 1 < len(context.following_lines):
515+
line_after_dashes = \
516+
context.following_lines[next_non_empty_line_offset + 1]
517+
if is_blank(line_after_dashes):
518+
yield violations.D412(section_name)
519+
509520
@classmethod
510521
def _check_section(cls, docstring, definition, context):
511-
"""D4{05,06,11}, D214: Section name checks.
522+
"""D4{05,06,10,11}, D214: Section name checks.
512523
513524
Check for valid section names. Checks that:
514525
* The section name is properly capitalized (D405).
515526
* The section is not over-indented (D214).
516527
* The section name has no superfluous suffix to it (D406).
528+
* There's a blank line after the section (D410).
517529
* There's a blank line before the section (D411).
518530
519531
Also yields all the errors from `_check_section_underline`.
@@ -532,6 +544,10 @@ def _check_section(cls, docstring, definition, context):
532544
if suffix:
533545
yield violations.D406(capitalized_section, context.line.strip())
534546

547+
if (not context.following_lines or
548+
not is_blank(context.following_lines[-1])):
549+
yield violations.D410(capitalized_section)
550+
535551
if not is_blank(context.previous_line):
536552
yield violations.D411(capitalized_section)
537553

@@ -549,13 +565,12 @@ def check_docstring_sections(self, definition, docstring):
549565
550566
Short Summary
551567
-------------
552-
553568
This is my summary.
554569
555570
Returns
556571
-------
557-
558572
None.
573+
559574
'''
560575
561576
Section names appear in `SECTION_NAMES`.
@@ -580,18 +595,32 @@ def _suspected_as_section(_line):
580595
SectionContext = namedtuple('SectionContext', ('section_name',
581596
'previous_line',
582597
'line',
583-
'following_lines'))
598+
'following_lines',
599+
'original_index'))
584600

601+
# First - create a list of possible contexts. Note that the
602+
# `following_linex` member is until the end of the docstring.
585603
contexts = (SectionContext(self._get_leading_words(lines[i].strip()),
586604
lines[i - 1],
587605
lines[i],
588-
lines[i + 1:])
606+
lines[i + 1:],
607+
i)
589608
for i in suspected_section_indices)
590609

591-
for ctx in contexts:
592-
if self._is_a_docstring_section(ctx):
593-
for err in self._check_section(docstring, definition, ctx):
594-
yield err
610+
# Now that we have manageable objects - rule out false positives.
611+
contexts = (c for c in contexts if self._is_a_docstring_section(c))
612+
613+
# Now we shall trim the `following lines` field to only reach the
614+
# next section name.
615+
for a, b in pairwise(contexts, None):
616+
end = -1 if b is None else b.original_index
617+
new_ctx = SectionContext(a.section_name,
618+
a.previous_line,
619+
a.line,
620+
lines[a.original_index + 1:end],
621+
a.original_index)
622+
for err in self._check_section(docstring, definition, new_ctx):
623+
yield err
595624

596625

597626
parse = Parser()

src/pydocstyle/violations.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ 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}')
226228

227229

228230
class AttrDict(dict):

src/tests/test_cases/sections.py

Lines changed: 33 additions & 2 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,10 +41,21 @@ def no_underline():
3941
"""Valid headline.
4042
4143
Returns
44+
A value of some sort.
4245
4346
"""
4447

4548

49+
@expect(_D213)
50+
@expect("D407: Missing dashed underline after section ('Returns')")
51+
@expect("D410: Missing blank line after section ('Returns')")
52+
def no_underline():
53+
"""Valid headline.
54+
55+
Returns
56+
"""
57+
58+
4659
@expect(_D213)
4760
@expect("D408: Section underline should be in the line following the "
4861
"section's name ('Returns')")
@@ -52,6 +65,7 @@ def blank_line_before_underline():
5265
Returns
5366
5467
-------
68+
A value of some sort.
5569
5670
"""
5771

@@ -64,6 +78,7 @@ def bad_underline_length():
6478
6579
Returns
6680
--
81+
A value of some sort.
6782
6883
"""
6984

@@ -75,7 +90,7 @@ def no_blank_line_after_section():
7590
7691
Returns
7792
-------
78-
A whole lot of values.
93+
A value of some sort.
7994
"""
8095

8196

@@ -87,6 +102,7 @@ def no_blank_line_before_section():
87102
The function's description.
88103
Returns
89104
-------
105+
A value of some sort.
90106
91107
"""
92108

@@ -98,6 +114,7 @@ def section_overindented():
98114
99115
Returns
100116
-------
117+
A value of some sort.
101118
102119
"""
103120

@@ -109,10 +126,22 @@ def section_underline_overindented():
109126
110127
Returns
111128
-------
129+
A value of some sort.
112130
113131
"""
114132

115133

134+
@expect(_D213)
135+
@expect("D215: Section underline is over-indented (in section 'Returns')")
136+
@expect("D410: Missing blank line after section ('Returns')")
137+
def section_underline_overindented_and_contentless():
138+
"""Valid headline.
139+
140+
Returns
141+
-------
142+
"""
143+
144+
116145
@expect(_D213)
117146
def ignore_non_actual_section():
118147
"""Valid headline.
@@ -132,21 +161,23 @@ def ignore_non_actual_section():
132161
def section_name_in_first_line():
133162
"""Returns
134163
-------
164+
A value of some sort.
135165
136166
"""
137167

138168

139169
@expect(_D213)
140170
@expect("D405: Section name should be properly capitalized "
141171
"('Short Summary', not 'Short summary')")
172+
@expect("D412: Section content should be in the line following its header "
173+
"('Short Summary')")
142174
@expect("D409: Section underline should match the length of its name "
143175
"(Expected 7 dashes in section 'Returns', got 6)")
144176
@expect("D410: Missing blank line after section ('Returns')")
145177
@expect("D411: Missing blank line before section ('Raises')")
146178
@expect("D406: Section name should end with a newline "
147179
"('Raises', not 'Raises:')")
148180
@expect("D407: Missing dashed underline after section ('Raises')")
149-
@expect("D410: Missing blank line after section ('Raises')")
150181
def multiple_sections():
151182
"""Valid headline.
152183

0 commit comments

Comments
 (0)