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

Commit 98623fe

Browse files
committed
Cleaner triage based on error code
1 parent 5b69acd commit 98623fe

File tree

4 files changed

+58
-64
lines changed

4 files changed

+58
-64
lines changed

docs/release_notes.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ New Features
1616
* Added D404 - First word of the docstring should not be "This". It is turned
1717
off by default (#183).
1818

19-
* Added the ability to ignore specific function and method doctstrings with
20-
comments:
19+
* Added the ability to ignore specific function and method docstrings with
20+
inline comments:
2121

2222
1. "# noqa" skips all checks.
2323

docs/snippets/per_file.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
``pydocstyle`` inline commenting to skip specific checks on specific
2-
functions or methods. The supported comments that can be added are:
1+
``pydocstyle`` supports inline commenting to skip specific checks on
2+
specific functions or methods. The supported comments that can be added are:
33

44
1. ``"# noqa"`` skips all checks.
55

docs/usage.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Configuration Files
1919
.. include:: snippets/config.rst
2020

2121

22-
Per-file configuration
23-
^^^^^^^^^^^^^^^^^^^^^^
22+
In-file configuration
23+
^^^^^^^^^^^^^^^^^^^^^
2424

2525
.. include:: snippets/per_file.rst

src/pydocstyle.py

Lines changed: 52 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,9 @@ class Value(object):
9696

9797
def __init__(self, *args):
9898
if len(self._fields) != len(args):
99-
raise ValueError('got %s arguments for %s fields for %s: %s'
100-
% (len(args), len(self._fields),
101-
self.__class__.__name__, self._fields))
99+
raise ValueError('got {0} arguments for {1} fields for {2}: {3}'
100+
.format(len(args), len(self._fields),
101+
self.__class__.__name__, self._fields))
102102
vars(self).update(zip(self._fields, args))
103103

104104
def __hash__(self):
@@ -116,7 +116,7 @@ def __repr__(self):
116116
class Definition(Value):
117117

118118
_fields = ('name', '_source', 'start', 'end', 'decorators', 'docstring',
119-
'children', 'parent', 'skips')
119+
'children', 'parent', 'skipped_error_codes')
120120

121121
_human = property(lambda self: humanize(type(self).__name__))
122122
kind = property(lambda self: self._human.split()[-1])
@@ -145,15 +145,16 @@ def is_empty_or_comment(line):
145145

146146
def __str__(self):
147147
out = 'in %s %s `%s`' % (self._publicity, self._human, self.name)
148-
if self.skips:
149-
out += ' (skipping %s)' % self.skips
148+
if self.skipped_error_codes:
149+
out += ' (skipping {0})'.format(self.skipped_error_codes)
150150
return out
151151

152152

153153
class Module(Definition):
154154

155155
_fields = ('name', '_source', 'start', 'end', 'decorators', 'docstring',
156-
'children', 'parent', '_all', 'future_imports', 'skips')
156+
'children', 'parent', '_all', 'future_imports',
157+
'skipped_error_codes')
157158
is_public = True
158159
_nest = staticmethod(lambda s: {'def': Function, 'class': Class}[s])
159160
module = property(lambda self: self)
@@ -469,13 +470,8 @@ def parse_definition(self, class_):
469470
self.leapfrog(tk.OP, value=":")
470471
else:
471472
self.consume(tk.OP)
472-
skips = ''
473473
if self.current.kind in (tk.NEWLINE, tk.COMMENT):
474-
if self.current.kind == tk.COMMENT:
475-
if 'noqa: ' in self.current.value:
476-
skips = ''.join(self.current.value.split('noqa: ')[1:])
477-
elif self.current.value.startswith('# noqa'):
478-
skips = 'all'
474+
skipped_error_codes = self.parse_skip_comment()
479475
self.leapfrog(tk.INDENT)
480476
assert self.current.kind != tk.INDENT
481477
docstring = self.parse_docstring()
@@ -486,20 +482,33 @@ def parse_definition(self, class_):
486482
log.debug("finished parsing nested definitions for '%s'", name)
487483
end = self.line - 1
488484
else: # one-liner definition
485+
skipped_error_codes = ''
489486
docstring = self.parse_docstring()
490487
decorators = [] # TODO
491488
children = []
492489
end = self.line
493490
self.leapfrog(tk.NEWLINE)
494491
definition = class_(name, self.source, start, end,
495-
decorators, docstring, children, None, skips)
492+
decorators, docstring, children, None,
493+
skipped_error_codes)
496494
for child in definition.children:
497495
child.parent = definition
498496
log.debug("finished parsing %s '%s'. Next token is %r (%s)",
499497
class_.__name__, name, self.current.kind,
500498
self.current.value)
501499
return definition
502500

501+
def parse_skip_comment(self):
502+
"""Parse a definition comment for noqa skips."""
503+
skipped_error_codes = ''
504+
if self.current.kind == tk.COMMENT:
505+
if 'noqa: ' in self.current.value:
506+
skipped_error_codes = ''.join(
507+
self.current.value.split('noqa: ')[1:])
508+
elif self.current.value.startswith('# noqa'):
509+
skipped_error_codes = 'all'
510+
return skipped_error_codes
511+
503512
def check_current(self, kind=None, value=None):
504513
msg = textwrap.dedent("""
505514
Unexpected token at line {self.line}:
@@ -1413,13 +1422,14 @@ def check_source(self, source, filename):
14131422
for check in self.checks:
14141423
terminate = False
14151424
if isinstance(definition, check._check_for):
1416-
if definition.skips != 'all':
1425+
if definition.skipped_error_codes != 'all':
14171426
error = check(None, definition, definition.docstring)
14181427
else:
14191428
error = None
14201429
errors = error if hasattr(error, '__iter__') else [error]
14211430
for error in errors:
1422-
if error is not None:
1431+
if error is not None and error.code not in \
1432+
definition.skipped_error_codes:
14231433
partition = check.__doc__.partition('.\n')
14241434
message, _, explanation = partition
14251435
error.set_context(explanation=explanation,
@@ -1457,10 +1467,7 @@ def check_docstring_missing(self, definition, docstring):
14571467
Method: (lambda: D105() if is_magic(definition.name)
14581468
else D102()),
14591469
Function: D103, NestedFunction: D103, Package: D104}
1460-
code = codes[type(definition)]
1461-
if code.__name__ in definition.skips:
1462-
return
1463-
return code()
1470+
return codes[type(definition)]()
14641471

14651472
@check_for(Definition)
14661473
def check_one_liners(self, definition, docstring):
@@ -1470,8 +1477,6 @@ def check_one_liners(self, definition, docstring):
14701477
This looks better for one-liners.
14711478
14721479
"""
1473-
if 'D200' in definition.skips:
1474-
return
14751480
if docstring:
14761481
lines = ast.literal_eval(docstring).split('\n')
14771482
if len(lines) > 1:
@@ -1493,11 +1498,9 @@ def check_no_blank_before(self, function, docstring): # def
14931498
blanks_before_count = sum(takewhile(bool, reversed(blanks_before)))
14941499
blanks_after_count = sum(takewhile(bool, blanks_after))
14951500
if blanks_before_count != 0:
1496-
if 'D201' not in function.skips:
1497-
yield D201(blanks_before_count)
1501+
yield D201(blanks_before_count)
14981502
if not all(blanks_after) and blanks_after_count != 0:
1499-
if 'D202' not in function.skips:
1500-
yield D202(blanks_after_count)
1503+
yield D202(blanks_after_count)
15011504

15021505
@check_for(Class)
15031506
def check_blank_before_after_class(self, class_, docstring):
@@ -1525,12 +1528,11 @@ def check_blank_before_after_class(self, class_, docstring):
15251528
blanks_after = list(map(is_blank, after.split('\n')[1:]))
15261529
blanks_before_count = sum(takewhile(bool, reversed(blanks_before)))
15271530
blanks_after_count = sum(takewhile(bool, blanks_after))
1528-
if 'D211' not in class_.skips and blanks_before_count != 0:
1531+
if blanks_before_count != 0:
15291532
yield D211(blanks_before_count)
1530-
if 'D203' not in class_.skips and blanks_before_count != 1:
1533+
if blanks_before_count != 1:
15311534
yield D203(blanks_before_count)
1532-
if 'D204' not in class_.skips and (not all(blanks_after) and
1533-
blanks_after_count != 1):
1535+
if not all(blanks_after) and blanks_after_count != 1:
15341536
yield D204(blanks_after_count)
15351537

15361538
@check_for(Definition)
@@ -1545,8 +1547,6 @@ def check_blank_after_summary(self, definition, docstring):
15451547
15461548
"""
15471549
if docstring:
1548-
if 'D205' in definition.skips:
1549-
return
15501550
lines = ast.literal_eval(docstring).strip().split('\n')
15511551
if len(lines) > 1:
15521552
post_summary_blanks = list(map(is_blank, lines[1:]))
@@ -1569,16 +1569,13 @@ def check_indent(self, definition, docstring):
15691569
if len(lines) > 1:
15701570
lines = lines[1:] # First line does not need indent.
15711571
indents = [leading_space(l) for l in lines if not is_blank(l)]
1572-
if 'D206' not in definition.skips:
1573-
if set(' \t') == set(''.join(indents) + indent):
1574-
yield D206()
1575-
if 'D208' not in definition.skips:
1576-
if (len(indents) > 1 and min(indents[:-1]) > indent or
1577-
indents[-1] > indent):
1578-
yield D208()
1579-
if 'D207' not in definition.skips:
1580-
if min(indents) < indent:
1581-
yield D207()
1572+
if set(' \t') == set(''.join(indents) + indent):
1573+
yield D206()
1574+
if (len(indents) > 1 and min(indents[:-1]) > indent or
1575+
indents[-1] > indent):
1576+
yield D208()
1577+
if min(indents) < indent:
1578+
yield D207()
15821579

15831580
@check_for(Definition)
15841581
def check_newline_after_last_paragraph(self, definition, docstring):
@@ -1588,7 +1585,7 @@ def check_newline_after_last_paragraph(self, definition, docstring):
15881585
quotes on a line by themselves.
15891586
15901587
"""
1591-
if docstring and 'D209' not in definition.skips:
1588+
if docstring:
15921589
lines = [l for l in ast.literal_eval(docstring).split('\n')
15931590
if not is_blank(l)]
15941591
if len(lines) > 1:
@@ -1598,7 +1595,7 @@ def check_newline_after_last_paragraph(self, definition, docstring):
15981595
@check_for(Definition)
15991596
def check_surrounding_whitespaces(self, definition, docstring):
16001597
"""D210: No whitespaces allowed surrounding docstring text."""
1601-
if docstring and 'D210' not in definition.skips:
1598+
if docstring:
16021599
lines = ast.literal_eval(docstring).split('\n')
16031600
if lines[0].startswith(' ') or \
16041601
len(lines) == 1 and lines[0].endswith(' '):
@@ -1624,11 +1621,9 @@ def check_multi_line_summary_start(self, definition, docstring):
16241621
if len(lines) > 1:
16251622
first = docstring.split("\n")[0].strip().lower()
16261623
if first in start_triple:
1627-
if 'D212' not in definition.skips:
1628-
return D212()
1624+
return D212()
16291625
else:
1630-
if 'D213' not in definition.skips:
1631-
return D213()
1626+
return D213()
16321627

16331628
@check_for(Definition)
16341629
def check_triple_double_quotes(self, definition, docstring):
@@ -1643,7 +1638,7 @@ def check_triple_double_quotes(self, definition, docstring):
16431638
""" quotes in its body.
16441639
16451640
'''
1646-
if docstring and 'D300' not in definition.skips:
1641+
if docstring:
16471642
opening = docstring[:5].lower()
16481643
if '"""' in ast.literal_eval(docstring) and opening.startswith(
16491644
("'''", "r'''", "u'''", "ur'''")):
@@ -1665,8 +1660,8 @@ def check_backslashes(self, definition, docstring):
16651660
'''
16661661
# Just check that docstring is raw, check_triple_double_quotes
16671662
# ensures the correct quotes.
1668-
if docstring and 'D301' not in definition.skips and \
1669-
'\\' in docstring and not docstring.startswith(('r', 'ur')):
1663+
if docstring and '\\' in docstring and not docstring.startswith(
1664+
('r', 'ur')):
16701665
return D301()
16711666

16721667
@check_for(Definition)
@@ -1681,8 +1676,7 @@ def check_unicode_docstring(self, definition, docstring):
16811676

16821677
# Just check that docstring is unicode, check_triple_double_quotes
16831678
# ensures the correct quotes.
1684-
if docstring and sys.version_info[0] <= 2 and \
1685-
'D302' not in definition.skips:
1679+
if docstring and sys.version_info[0] <= 2:
16861680
if not is_ascii(docstring) and not docstring.startswith(
16871681
('u', 'ur')):
16881682
return D302()
@@ -1694,7 +1688,7 @@ def check_ends_with_period(self, definition, docstring):
16941688
The [first line of a] docstring is a phrase ending in a period.
16951689
16961690
"""
1697-
if docstring and 'D400' not in definition.skips:
1691+
if docstring:
16981692
summary_line = ast.literal_eval(docstring).strip().split('\n')[0]
16991693
if not summary_line.endswith('.'):
17001694
return D400(summary_line[-1])
@@ -1708,7 +1702,7 @@ def check_imperative_mood(self, function, docstring): # def context
17081702
"Returns the pathname ...".
17091703
17101704
"""
1711-
if docstring and 'D401' not in function.skips:
1705+
if docstring:
17121706
stripped = ast.literal_eval(docstring).strip()
17131707
if stripped:
17141708
first_word = stripped.split()[0]
@@ -1723,7 +1717,7 @@ def check_no_signature(self, function, docstring): # def context
17231717
function/method parameters (which can be obtained by introspection).
17241718
17251719
"""
1726-
if docstring and 'D402' not in function.skips:
1720+
if docstring:
17271721
first_line = ast.literal_eval(docstring).strip().split('\n')[0]
17281722
if function.name + '(' in first_line.replace(' ', ''):
17291723
return D402()
@@ -1735,7 +1729,7 @@ def check_capitalized(self, function, docstring):
17351729
The [first line of a] docstring is a phrase ending in a period.
17361730
17371731
"""
1738-
if docstring and 'D403' not in function.skips:
1732+
if docstring:
17391733
first_word = ast.literal_eval(docstring).split()[0]
17401734
if first_word == first_word.upper():
17411735
return
@@ -1753,7 +1747,7 @@ def check_starts_with_this(self, function, docstring):
17531747
with "This class is [..]" or "This module contains [..]".
17541748
17551749
"""
1756-
if docstring and 'D404' not in function.skips:
1750+
if docstring:
17571751
first_word = ast.literal_eval(docstring).split()[0]
17581752
if first_word.lower() == 'this':
17591753
return D404()

0 commit comments

Comments
 (0)