Skip to content

Commit f6139a8

Browse files
committed
Implement W505 - Max Doc Length check
1 parent 11ba4cc commit f6139a8

File tree

6 files changed

+109
-36
lines changed

6 files changed

+109
-36
lines changed

docs/intro.rst

+11-6
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ Quick help is available on the command line::
157157
--count print total number of errors and warnings to standard
158158
error and set exit code to 1 if total is not null
159159
--max-line-length=n set maximum allowed line length (default: 79)
160+
--max-doc-length=n set maximum allowed doc line length and perform these
161+
checks (unchecked if not set)
160162
--hang-closing hang closing bracket instead of matching indentation of
161163
opening bracket's line
162164
--format=format set the error format [default|pylint|<custom>]
@@ -169,9 +171,9 @@ Quick help is available on the command line::
169171
Configuration:
170172
The project options are read from the [pycodestyle] section of the
171173
tox.ini file or the setup.cfg file located in any parent folder of the
172-
path(s) being processed. Allowed options are: exclude, filename, select,
173-
ignore, max-line-length, hang-closing, count, format, quiet, show-pep8,
174-
show-source, statistics, verbose.
174+
path(s) being processed. Allowed options are: exclude, filename,
175+
select, ignore, max-line-length, max-doc-length, hang-closing, count,
176+
format, quiet, show-pep8, show-source, statistics, verbose.
175177

176178
--config=path user config file location
177179
(default: ~/.config/pycodestyle)
@@ -406,6 +408,8 @@ This is the current list of error and warning codes:
406408
+------------+----------------------------------------------------------------------+
407409
| W504 (*)   | line break after binary operator                         |
408410
+------------+----------------------------------------------------------------------+
411+
| W505 (\*^) | doc line too long (82 > 79 characters) |
412+
+------------+----------------------------------------------------------------------+
409413
+------------+----------------------------------------------------------------------+
410414
| **W6** | *Deprecation warning* |
411415
+------------+----------------------------------------------------------------------+
@@ -423,14 +427,15 @@ This is the current list of error and warning codes:
423427
+------------+----------------------------------------------------------------------+
424428

425429

426-
**(*)** In the default configuration, the checks **E121**, **E123**, **E126**,
427-
**E133**, **E226**, **E241**, **E242**, **E704**, **W503** and **W504** are ignored
430+
**(*)** In the default configuration, the checks **E121**, **E123**, **E126**, **E133**,
431+
**E226**, **E241**, **E242**, **E704**, **W503**, **W504** and **W505** are ignored
428432
because they are not rules unanimously accepted, and `PEP 8`_ does not enforce them.
429433
Please note that if the option **--ignore=errors** is used,
430434
the default configuration will be overridden and ignore only the check(s) you skip.
431435
The check **W503** is mutually exclusive with check **W504**.
432436
The check **E133** is mutually exclusive with check **E123**. Use switch
433-
``--hang-closing`` to report **E133** instead of **E123**.
437+
``--hang-closing`` to report **E133** instead of **E123**. Use switch
438+
``--max-doc-length=n`` to report **W505**.
434439

435440
**(^)** These checks can be disabled at the line level using the ``# noqa``
436441
special comment. This possibility should be reserved for special cases.

pycodestyle.py

+65-4
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation.
102102
# Methods and nested class and function.
103103
'method': 1,
104104
}
105+
MAX_DOC_LENGTH = 72
105106
REPORT_FORMAT = {
106107
'default': '%(path)s:%(row)d:%(col)d: %(code)s %(text)s',
107108
'pylint': '%(path)s:%(row)d: [%(code)s] %(text)s',
@@ -1585,9 +1586,64 @@ def python_3000_async_await_keywords(logical_line, tokens):
15851586
)
15861587

15871588

1588-
##############################################################################
1589+
########################################################################
1590+
@register_check
1591+
def maximum_doc_length(logical_line, max_doc_length, noqa, tokens):
1592+
r"""Limit all doc lines to a maximum of 72 characters.
1593+
1594+
For flowing long blocks of text (docstrings or comments), limiting
1595+
the length to 72 characters is recommended.
1596+
1597+
Reports warning W505
1598+
"""
1599+
if max_doc_length is None or noqa:
1600+
return
1601+
1602+
prev_token = None
1603+
skip_lines = set()
1604+
# Skip lines that
1605+
for token_type, text, start, end, line in tokens:
1606+
if token_type not in SKIP_COMMENTS.union([tokenize.STRING]):
1607+
skip_lines.add(line)
1608+
1609+
for token_type, text, start, end, line in tokens:
1610+
# Skip lines that aren't pure strings
1611+
if token_type == tokenize.STRING and skip_lines:
1612+
continue
1613+
if token_type in (tokenize.STRING, tokenize.COMMENT):
1614+
# Only check comment-only lines
1615+
if prev_token is None or prev_token in SKIP_TOKENS:
1616+
lines = line.splitlines()
1617+
for line_num, physical_line in enumerate(lines):
1618+
if hasattr(physical_line, 'decode'): # Python 2
1619+
# The line could contain multi-byte characters
1620+
try:
1621+
physical_line = physical_line.decode('utf-8')
1622+
except UnicodeError:
1623+
pass
1624+
if start[0] + line_num == 1 and line.startswith('#!'):
1625+
return
1626+
length = len(physical_line)
1627+
chunks = physical_line.split()
1628+
if token_type == tokenize.COMMENT:
1629+
if (len(chunks) == 2 and
1630+
length - len(chunks[-1]) < MAX_DOC_LENGTH):
1631+
continue
1632+
if len(chunks) == 1 and line_num + 1 < len(lines):
1633+
if (len(chunks) == 1 and
1634+
length - len(chunks[-1]) < MAX_DOC_LENGTH):
1635+
continue
1636+
if length > max_doc_length:
1637+
doc_error = (start[0] + line_num, max_doc_length)
1638+
yield (doc_error, "W505 doc line too long "
1639+
"(%d > %d characters)"
1640+
% (length, max_doc_length))
1641+
prev_token = token_type
1642+
1643+
1644+
########################################################################
15891645
# Helper functions
1590-
##############################################################################
1646+
########################################################################
15911647

15921648

15931649
if sys.version_info < (3,):
@@ -1758,6 +1814,7 @@ def __init__(self, filename=None, lines=None,
17581814
self._logical_checks = options.logical_checks
17591815
self._ast_checks = options.ast_checks
17601816
self.max_line_length = options.max_line_length
1817+
self.max_doc_length = options.max_doc_length
17611818
self.multiline = False # in a multiline string?
17621819
self.hang_closing = options.hang_closing
17631820
self.verbose = options.verbose
@@ -2324,8 +2381,8 @@ def get_parser(prog='pycodestyle', version=__version__):
23242381
usage="%prog [options] input ...")
23252382
parser.config_options = [
23262383
'exclude', 'filename', 'select', 'ignore', 'max-line-length',
2327-
'hang-closing', 'count', 'format', 'quiet', 'show-pep8',
2328-
'show-source', 'statistics', 'verbose']
2384+
'max-doc-length', 'hang-closing', 'count', 'format', 'quiet',
2385+
'show-pep8', 'show-source', 'statistics', 'verbose']
23292386
parser.add_option('-v', '--verbose', default=0, action='count',
23302387
help="print status messages, or debug with -vv")
23312388
parser.add_option('-q', '--quiet', default=0, action='count',
@@ -2361,6 +2418,10 @@ def get_parser(prog='pycodestyle', version=__version__):
23612418
default=MAX_LINE_LENGTH,
23622419
help="set maximum allowed line length "
23632420
"(default: %default)")
2421+
parser.add_option('--max-doc-length', type='int', metavar='n',
2422+
default=None,
2423+
help="set maximum allowed doc line length and perform "
2424+
"these checks (unchecked if not set)")
23642425
parser.add_option('--hang-closing', action='store_true',
23652426
help="hang closing bracket instead of matching "
23662427
"indentation of opening bracket's line")

testsuite/E26.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ def oof():
5050
#foo not parsed
5151
"""
5252

53-
###########################################################################
54-
# A SEPARATOR #
55-
###########################################################################
53+
####################################################################
54+
# A SEPARATOR #
55+
####################################################################
5656

57-
# ####################################################################### #
58-
# ########################## another separator ########################## #
59-
# ####################################################################### #
57+
# ################################################################ #
58+
# ####################### another separator ###################### #
59+
# ################################################################ #

testsuite/E50.py

+11-10
Original file line numberDiff line numberDiff line change
@@ -62,27 +62,27 @@
6262
#: E501 E225 E226
6363
very_long_identifiers=and_terrible_whitespace_habits(are_no_excuse+for_long_lines)
6464
#
65-
#: E501
65+
#: E501 W505
6666
'''multiline string
6767
with a long long long long long long long long long long long long long long long long line
6868
'''
69-
#: E501
69+
#: E501 W505
7070
'''same thing, but this time without a terminal newline in the string
7171
long long long long long long long long long long long long long long long long line'''
7272
#
7373
# issue 224 (unavoidable long lines in docstrings)
7474
#: Okay
7575
"""
7676
I'm some great documentation. Because I'm some great documentation, I'm
77-
going to give you a reference to some valuable information about some API
78-
that I'm calling:
77+
going to give you a reference to some valuable information about some
78+
API that I'm calling:
7979
8080
http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx
8181
"""
82-
#: E501
82+
#: E501 W505
8383
"""
8484
longnospaceslongnospaceslongnospaceslongnospaceslongnospaceslongnospaceslongnospaceslongnospaces"""
85-
#: E501
85+
#: E501 W505
8686
# Regression test for #622
8787
def foo():
8888
"""Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis pulvinar vitae
@@ -92,19 +92,20 @@ def foo():
9292
This
9393
almost_empty_line
9494
"""
95-
#: E501
95+
#: E501 W505
9696
"""
9797
This
9898
almost_empty_line
9999
"""
100-
#: E501
100+
#: E501 W505
101101
# A basic comment
102102
# with a long long long long long long long long long long long long long long long long line
103103

104104
#
105105
#: Okay
106106
# I'm some great comment. Because I'm so great, I'm going to give you a
107-
# reference to some valuable information about some API that I'm calling:
107+
# reference to some valuable information about some API that I'm
108+
# calling:
108109
#
109110
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx
110111

@@ -118,7 +119,7 @@ def foo():
118119
# almost_empty_line
119120

120121
#
121-
#: E501
122+
#: E501 W505
122123
# This
123124
# almost_empty_line
124125

testsuite/test_api.py

+6
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,12 @@ def parse_argv(argstring):
209209
self.assertEqual(options.select, ('E24',))
210210
self.assertEqual(options.ignore, ('',))
211211

212+
options = parse_argv('--max-doc-length=72').options
213+
self.assertEqual(options.max_doc_length, 72)
214+
215+
options = parse_argv('').options
216+
self.assertEqual(options.max_doc_length, None)
217+
212218
pep8style = pycodestyle.StyleGuide(paths=[E11])
213219
self.assertFalse(pep8style.ignore_code('E112'))
214220
self.assertFalse(pep8style.ignore_code('W191'))

testsuite/utf-8.py

+10-10
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,34 @@
22

33
# Some random text with multi-byte characters (utf-8 encoded)
44
#
5-
# Εδώ μάτσο κειμένων τη, τρόπο πιθανό διευθυντές ώρα μη. Νέων απλό παράγει ροή
6-
# κι, το επί δεδομένη καθορίζουν. Πάντως ζητήσεις περιβάλλοντος ένα με, τη
7-
# ξέχασε αρπάζεις φαινόμενο όλη. Τρέξει εσφαλμένη χρησιμοποίησέ νέα τι. Θα όρο
5+
# Εδώ μάτσο κειμένων τη, τρόπο πιθανό διευθυντές ώρα μη. Νέων απλό π ροή
6+
# κι, το επί δεδομένη καθορίζουν. Πάντως ζητήσεις περιβάλλοντος ένα με,
7+
# ξέχασε αρπάζεις φαινόμενο όλη. Τρέξει εσφαλμένη χρησιμοποίησέ νέα τι.
88
# πετάνε φακέλους, άρα με διακοπής λαμβάνουν εφαμοργής. Λες κι μειώσει
99
# καθυστερεί.
1010

1111
# 79 narrow chars
12-
# 01 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 [79]
12+
# 01 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3[79]
1313

1414
# 78 narrow chars (Na) + 1 wide char (W)
15-
# 01 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8情
15+
# 01 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4
1616

1717
# 3 narrow chars (Na) + 40 wide chars (W)
1818
# 情 情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情
1919

20-
# 3 narrow chars (Na) + 76 wide chars (W)
21-
# 情 情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情
20+
# 3 narrow chars (Na) + 69 wide chars (W)
21+
# 情 情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情
2222

2323
#
24-
#: E501
24+
#: E501 W505
2525
# 80 narrow chars (Na)
2626
# 01 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 [80]
2727
#
28-
#: E501
28+
#: E501 W505
2929
# 78 narrow chars (Na) + 2 wide char (W)
3030
# 01 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8情情
3131
#
32-
#: E501
32+
#: E501 W505
3333
# 3 narrow chars (Na) + 77 wide chars (W)
3434
# 情 情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情
3535
#

0 commit comments

Comments
 (0)