Merge lp:~adiroiban/pocket-lint/1237489-pep8-upstream into lp:pocket-lint

Proposed by Adi Roiban
Status: Merged
Approved by: Curtis Hovey
Approved revision: 504
Merged at revision: 508
Proposed branch: lp:~adiroiban/pocket-lint/1237489-pep8-upstream
Merge into: lp:pocket-lint
Diff against target: 1960 lines (+1/-1944)
2 files modified
pocketlint/contrib/pep8.py (+0/-1943)
pocketlint/formatcheck.py (+1/-1)
To merge this branch: bzr merge lp:~adiroiban/pocket-lint/1237489-pep8-upstream
Reviewer Review Type Date Requested Status
Curtis Hovey code Approve
Review via email: mp+190183@code.launchpad.net

Description of the change

This is the branch which removed the contrib/pep8 and used the default pep8.

I have tried this branch with pep8-1.4.6 in an virtualenv and there were no errors.

Please let me know why if this change is valid.

Thanks!

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

Thank you. I accept this. I believe that pocketlint needs python-pep8 and python3-pep8 installed to work properly in saucy. There will be a delay getting this into the unstable ppa because I have a busy schedule and I am in the the middlw of rewriting the packaging rules

review: Approve (code)
Revision history for this message
Adi Roiban (adiroiban) wrote :

Thanks.

I don't use Ubuntu packages for Python. I prefer virtualenv and pip since this solution is flexible and is not locked to an OS.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== removed file 'pocketlint/contrib/pep8.py'
--- pocketlint/contrib/pep8.py 2013-01-17 21:29:37 +0000
+++ pocketlint/contrib/pep8.py 1970-01-01 00:00:00 +0000
@@ -1,1943 +0,0 @@
1#!/usr/bin/env python
2# pep8.py - Check Python source code formatting, according to PEP 8
3# Copyright (C) 2006-2009 Johann C. Rocholl <johann@rocholl.net>
4# Copyright (C) 2009-2012 Florent Xicluna <florent.xicluna@gmail.com>
5#
6# Permission is hereby granted, free of charge, to any person
7# obtaining a copy of this software and associated documentation files
8# (the "Software"), to deal in the Software without restriction,
9# including without limitation the rights to use, copy, modify, merge,
10# publish, distribute, sublicense, and/or sell copies of the Software,
11# and to permit persons to whom the Software is furnished to do so,
12# subject to the following conditions:
13#
14# The above copyright notice and this permission notice shall be
15# included in all copies or substantial portions of the Software.
16#
17# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
21# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
22# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24# SOFTWARE.
25
26r"""
27Check Python source code formatting, according to PEP 8:
28http://www.python.org/dev/peps/pep-0008/
29
30For usage and a list of options, try this:
31$ python pep8.py -h
32
33This program and its regression test suite live here:
34http://github.com/jcrocholl/pep8
35
36Groups of errors and warnings:
37E errors
38W warnings
39100 indentation
40200 whitespace
41300 blank lines
42400 imports
43500 line length
44600 deprecation
45700 statements
46900 syntax error
47"""
48__version__ = '1.4.1a0'
49
50import os
51import sys
52import re
53import time
54import inspect
55import keyword
56import tokenize
57from optparse import OptionParser
58from fnmatch import fnmatch
59try:
60 from configparser import RawConfigParser
61 from io import TextIOWrapper
62except ImportError:
63 from ConfigParser import RawConfigParser
64
65DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git'
66DEFAULT_IGNORE = 'E226,E24'
67if sys.platform == 'win32':
68 DEFAULT_CONFIG = os.path.expanduser(r'~\.pep8')
69else:
70 DEFAULT_CONFIG = os.path.join(os.getenv('XDG_CONFIG_HOME') or
71 os.path.expanduser('~/.config'), 'pep8')
72PROJECT_CONFIG = ('.pep8', 'tox.ini', 'setup.cfg')
73MAX_LINE_LENGTH = 79
74REPORT_FORMAT = {
75 'default': '%(path)s:%(row)d:%(col)d: %(code)s %(text)s',
76 'pylint': '%(path)s:%(row)d: [%(code)s] %(text)s',
77}
78
79
80SINGLETONS = frozenset(['False', 'None', 'True'])
81KEYWORDS = frozenset(keyword.kwlist + ['print']) - SINGLETONS
82UNARY_OPERATORS = frozenset(['>>', '**', '*', '+', '-'])
83WS_OPTIONAL_OPERATORS = frozenset(['**', '*', '/', '//', '+', '-'])
84WS_NEEDED_OPERATORS = frozenset([
85 '**=', '*=', '/=', '//=', '+=', '-=', '!=', '<>',
86 '%=', '^=', '&=', '|=', '==', '<=', '>=', '<<=', '>>=',
87 '%', '^', '&', '|', '=', '<', '>', '<<'])
88WHITESPACE = frozenset(' \t')
89SKIP_TOKENS = frozenset([tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE,
90 tokenize.INDENT, tokenize.DEDENT])
91BENCHMARK_KEYS = ['directories', 'files', 'logical lines', 'physical lines']
92
93INDENT_REGEX = re.compile(r'([ \t]*)')
94RAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*(,)')
95RERAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*,\s*\w+\s*,\s*\w+')
96SELFTEST_REGEX = re.compile(r'(Okay|[EW]\d{3}):\s(.*)')
97ERRORCODE_REGEX = re.compile(r'[EW]\d{3}')
98DOCSTRING_REGEX = re.compile(r'u?r?["\']')
99EXTRANEOUS_WHITESPACE_REGEX = re.compile(r'[[({] | []}),;:]')
100WHITESPACE_AFTER_COMMA_REGEX = re.compile(r'[,;:]\s*(?: |\t)')
101COMPARE_SINGLETON_REGEX = re.compile(r'([=!]=)\s*(None|False|True)')
102COMPARE_TYPE_REGEX = re.compile(r'([=!]=|is|is\s+not)\s*type(?:s\.(\w+)Type'
103 r'|\(\s*(\(\s*\)|[^)]*[^ )])\s*\))')
104KEYWORD_REGEX = re.compile(r'(?:[^\s]|\b)(\s*)\b(?:%s)\b(\s*)' %
105 r'|'.join(KEYWORDS))
106OPERATOR_REGEX = re.compile(r'(?:[^,\s])(\s*)(?:[-+*/|!<=>%&^]+)(\s*)')
107LAMBDA_REGEX = re.compile(r'\blambda\b')
108HUNK_REGEX = re.compile(r'^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$')
109
110# Work around Python < 2.6 behaviour, which does not generate NL after
111# a comment which is on a line by itself.
112COMMENT_WITH_NL = tokenize.generate_tokens(['#\n'].pop).send(None)[1] == '#\n'
113
114
115##############################################################################
116# Plugins (check functions) for physical lines
117##############################################################################
118
119
120def tabs_or_spaces(physical_line, indent_char):
121 r"""
122 Never mix tabs and spaces.
123
124 The most popular way of indenting Python is with spaces only. The
125 second-most popular way is with tabs only. Code indented with a mixture
126 of tabs and spaces should be converted to using spaces exclusively. When
127 invoking the Python command line interpreter with the -t option, it issues
128 warnings about code that illegally mixes tabs and spaces. When using -tt
129 these warnings become errors. These options are highly recommended!
130
131 Okay: if a == 0:\n a = 1\n b = 1
132 E101: if a == 0:\n a = 1\n\tb = 1
133 """
134 indent = INDENT_REGEX.match(physical_line).group(1)
135 for offset, char in enumerate(indent):
136 if char != indent_char:
137 return offset, "E101 indentation contains mixed spaces and tabs"
138
139
140def tabs_obsolete(physical_line):
141 r"""
142 For new projects, spaces-only are strongly recommended over tabs. Most
143 editors have features that make this easy to do.
144
145 Okay: if True:\n return
146 W191: if True:\n\treturn
147 """
148 indent = INDENT_REGEX.match(physical_line).group(1)
149 if '\t' in indent:
150 return indent.index('\t'), "W191 indentation contains tabs"
151
152
153def trailing_whitespace(physical_line):
154 r"""
155 JCR: Trailing whitespace is superfluous.
156 FBM: Except when it occurs as part of a blank line (i.e. the line is
157 nothing but whitespace). According to Python docs[1] a line with only
158 whitespace is considered a blank line, and is to be ignored. However,
159 matching a blank line to its indentation level avoids mistakenly
160 terminating a multi-line statement (e.g. class declaration) when
161 pasting code into the standard Python interpreter.
162
163 [1] http://docs.python.org/reference/lexical_analysis.html#blank-lines
164
165 The warning returned varies on whether the line itself is blank, for easier
166 filtering for those who want to indent their blank lines.
167
168 Okay: spam(1)\n#
169 W291: spam(1) \n#
170 W293: class Foo(object):\n \n bang = 12
171 """
172 physical_line = physical_line.rstrip('\n') # chr(10), newline
173 physical_line = physical_line.rstrip('\r') # chr(13), carriage return
174 physical_line = physical_line.rstrip('\x0c') # chr(12), form feed, ^L
175 stripped = physical_line.rstrip(' \t\v')
176 if physical_line != stripped:
177 if stripped:
178 return len(stripped), "W291 trailing whitespace"
179 else:
180 return 0, "W293 blank line contains whitespace"
181
182
183def trailing_blank_lines(physical_line, lines, line_number):
184 r"""
185 JCR: Trailing blank lines are superfluous.
186
187 Okay: spam(1)
188 W391: spam(1)\n
189 """
190 if not physical_line.rstrip() and line_number == len(lines):
191 return 0, "W391 blank line at end of file"
192
193
194def missing_newline(physical_line):
195 """
196 JCR: The last line should have a newline.
197
198 Reports warning W292.
199 """
200 if physical_line.rstrip() == physical_line:
201 return len(physical_line), "W292 no newline at end of file"
202
203
204def maximum_line_length(physical_line, max_line_length):
205 """
206 Limit all lines to a maximum of 79 characters.
207
208 There are still many devices around that are limited to 80 character
209 lines; plus, limiting windows to 80 characters makes it possible to have
210 several windows side-by-side. The default wrapping on such devices looks
211 ugly. Therefore, please limit all lines to a maximum of 79 characters.
212 For flowing long blocks of text (docstrings or comments), limiting the
213 length to 72 characters is recommended.
214
215 Reports error E501.
216 """
217 line = physical_line.rstrip()
218 length = len(line)
219 if length > max_line_length:
220 if line.strip().lower().endswith('# nopep8'):
221 return
222 if hasattr(line, 'decode'): # Python 2
223 # The line could contain multi-byte characters
224 try:
225 length = len(line.decode('utf-8'))
226 except UnicodeError:
227 pass
228 if length > max_line_length:
229 return (max_line_length, "E501 line too long "
230 "(%d > %d characters)" % (length, max_line_length))
231
232
233##############################################################################
234# Plugins (check functions) for logical lines
235##############################################################################
236
237
238def blank_lines(logical_line, blank_lines, indent_level, line_number,
239 previous_logical, previous_indent_level):
240 r"""
241 Separate top-level function and class definitions with two blank lines.
242
243 Method definitions inside a class are separated by a single blank line.
244
245 Extra blank lines may be used (sparingly) to separate groups of related
246 functions. Blank lines may be omitted between a bunch of related
247 one-liners (e.g. a set of dummy implementations).
248
249 Use blank lines in functions, sparingly, to indicate logical sections.
250
251 Okay: def a():\n pass\n\n\ndef b():\n pass
252 Okay: def a():\n pass\n\n\n# Foo\n# Bar\n\ndef b():\n pass
253
254 E301: class Foo:\n b = 0\n def bar():\n pass
255 E302: def a():\n pass\n\ndef b(n):\n pass
256 E303: def a():\n pass\n\n\n\ndef b(n):\n pass
257 E303: def a():\n\n\n\n pass
258 E304: @decorator\n\ndef a():\n pass
259 """
260 if line_number == 1:
261 return # Don't expect blank lines before the first line
262 if previous_logical.startswith('@'):
263 if blank_lines:
264 yield 0, "E304 blank lines found after function decorator"
265 elif blank_lines > 2 or (indent_level and blank_lines == 2):
266 yield 0, "E303 too many blank lines (%d)" % blank_lines
267 elif logical_line.startswith(('def ', 'class ', '@')):
268 if indent_level:
269 if not (blank_lines or previous_indent_level < indent_level or
270 DOCSTRING_REGEX.match(previous_logical)):
271 yield 0, "E301 expected 1 blank line, found 0"
272 elif blank_lines != 2:
273 yield 0, "E302 expected 2 blank lines, found %d" % blank_lines
274
275
276def extraneous_whitespace(logical_line):
277 """
278 Avoid extraneous whitespace in the following situations:
279
280 - Immediately inside parentheses, brackets or braces.
281
282 - Immediately before a comma, semicolon, or colon.
283
284 Okay: spam(ham[1], {eggs: 2})
285 E201: spam( ham[1], {eggs: 2})
286 E201: spam(ham[ 1], {eggs: 2})
287 E201: spam(ham[1], { eggs: 2})
288 E202: spam(ham[1], {eggs: 2} )
289 E202: spam(ham[1 ], {eggs: 2})
290 E202: spam(ham[1], {eggs: 2 })
291
292 E203: if x == 4: print x, y; x, y = y , x
293 E203: if x == 4: print x, y ; x, y = y, x
294 E203: if x == 4 : print x, y; x, y = y, x
295 """
296 line = logical_line
297 for match in EXTRANEOUS_WHITESPACE_REGEX.finditer(line):
298 text = match.group()
299 char = text.strip()
300 found = match.start()
301 if text == char + ' ':
302 # assert char in '([{'
303 yield found + 1, "E201 whitespace after '%s'" % char
304 elif line[found - 1] != ',':
305 code = ('E202' if char in '}])' else 'E203') # if char in ',;:'
306 yield found, "%s whitespace before '%s'" % (code, char)
307
308
309def whitespace_around_keywords(logical_line):
310 r"""
311 Avoid extraneous whitespace around keywords.
312
313 Okay: True and False
314 E271: True and False
315 E272: True and False
316 E273: True and\tFalse
317 E274: True\tand False
318 """
319 for match in KEYWORD_REGEX.finditer(logical_line):
320 before, after = match.groups()
321
322 if '\t' in before:
323 yield match.start(1), "E274 tab before keyword"
324 elif len(before) > 1:
325 yield match.start(1), "E272 multiple spaces before keyword"
326
327 if '\t' in after:
328 yield match.start(2), "E273 tab after keyword"
329 elif len(after) > 1:
330 yield match.start(2), "E271 multiple spaces after keyword"
331
332
333def missing_whitespace(logical_line):
334 """
335 JCR: Each comma, semicolon or colon should be followed by whitespace.
336
337 Okay: [a, b]
338 Okay: (3,)
339 Okay: a[1:4]
340 Okay: a[:4]
341 Okay: a[1:]
342 Okay: a[1:4:2]
343 E231: ['a','b']
344 E231: foo(bar,baz)
345 E231: [{'a':'b'}]
346 """
347 line = logical_line
348 for index in range(len(line) - 1):
349 char = line[index]
350 if char in ',;:' and line[index + 1] not in WHITESPACE:
351 before = line[:index]
352 if char == ':' and before.count('[') > before.count(']') and \
353 before.rfind('{') < before.rfind('['):
354 continue # Slice syntax, no space required
355 if char == ',' and line[index + 1] == ')':
356 continue # Allow tuple with only one element: (3,)
357 yield index, "E231 missing whitespace after '%s'" % char
358
359
360def indentation(logical_line, previous_logical, indent_char,
361 indent_level, previous_indent_level):
362 r"""
363 Use 4 spaces per indentation level.
364
365 For really old code that you don't want to mess up, you can continue to
366 use 8-space tabs.
367
368 Okay: a = 1
369 Okay: if a == 0:\n a = 1
370 E111: a = 1
371
372 Okay: for item in items:\n pass
373 E112: for item in items:\npass
374
375 Okay: a = 1\nb = 2
376 E113: a = 1\n b = 2
377 """
378 if indent_char == ' ' and indent_level % 4:
379 yield 0, "E111 indentation is not a multiple of four"
380 indent_expect = previous_logical.endswith(':')
381 if indent_expect and indent_level <= previous_indent_level:
382 yield 0, "E112 expected an indented block"
383 if indent_level > previous_indent_level and not indent_expect:
384 yield 0, "E113 unexpected indentation"
385
386
387def continuation_line_indentation(logical_line, tokens, indent_level, verbose):
388 r"""
389 Continuation lines should align wrapped elements either vertically using
390 Python's implicit line joining inside parentheses, brackets and braces, or
391 using a hanging indent.
392
393 When using a hanging indent the following considerations should be applied:
394
395 - there should be no arguments on the first line, and
396
397 - further indentation should be used to clearly distinguish itself as a
398 continuation line.
399
400 Okay: a = (\n)
401 E123: a = (\n )
402
403 Okay: a = (\n 42)
404 E121: a = (\n 42)
405 E122: a = (\n42)
406 E123: a = (\n 42\n )
407 E124: a = (24,\n 42\n)
408 E125: if (a or\n b):\n pass
409 E126: a = (\n 42)
410 E127: a = (24,\n 42)
411 E128: a = (24,\n 42)
412 """
413 first_row = tokens[0][2][0]
414 nrows = 1 + tokens[-1][2][0] - first_row
415 if nrows == 1:
416 return
417
418 # indent_next tells us whether the next block is indented; assuming
419 # that it is indented by 4 spaces, then we should not allow 4-space
420 # indents on the final continuation line; in turn, some other
421 # indents are allowed to have an extra 4 spaces.
422 indent_next = logical_line.endswith(':')
423
424 row = depth = 0
425 # remember how many brackets were opened on each line
426 parens = [0] * nrows
427 # relative indents of physical lines
428 rel_indent = [0] * nrows
429 # visual indents
430 indent = [indent_level]
431 indent_chances = {}
432 last_indent = tokens[0][2]
433 if verbose >= 3:
434 print(">>> " + tokens[0][4].rstrip())
435
436 for token_type, text, start, end, line in tokens:
437 if line.strip().lower().endswith('# nopep8'):
438 continue
439
440 newline = row < start[0] - first_row
441 if newline:
442 row = start[0] - first_row
443 newline = (not last_token_multiline and
444 token_type not in (tokenize.NL, tokenize.NEWLINE))
445
446 if newline:
447 # this is the beginning of a continuation line.
448 last_indent = start
449 if verbose >= 3:
450 print("... " + line.rstrip())
451
452 # record the initial indent.
453 rel_indent[row] = start[1] - indent_level
454
455 if depth:
456 # a bracket expression in a continuation line.
457 # find the line that it was opened on
458 for open_row in range(row - 1, -1, -1):
459 if parens[open_row]:
460 break
461 else:
462 # an unbracketed continuation line (ie, backslash)
463 open_row = 0
464 hang = rel_indent[row] - rel_indent[open_row]
465 visual_indent = indent_chances.get(start[1])
466
467 if token_type == tokenize.OP and text in ']})':
468 # this line starts with a closing bracket
469 if indent[depth]:
470 if start[1] != indent[depth]:
471 yield (start, 'E124 closing bracket does not match '
472 'visual indentation')
473 elif hang:
474 yield (start, 'E123 closing bracket does not match '
475 'indentation of opening bracket\'s line')
476 elif visual_indent is True:
477 # visual indent is verified
478 if not indent[depth]:
479 indent[depth] = start[1]
480 elif visual_indent in (text, str):
481 # ignore token lined up with matching one from a previous line
482 pass
483 elif indent[depth] and start[1] < indent[depth]:
484 # visual indent is broken
485 yield (start, 'E128 continuation line '
486 'under-indented for visual indent')
487 elif hang == 4 or (indent_next and rel_indent[row] == 8):
488 # hanging indent is verified
489 pass
490 else:
491 # indent is broken
492 if hang <= 0:
493 error = 'E122', 'missing indentation or outdented'
494 elif indent[depth]:
495 error = 'E127', 'over-indented for visual indent'
496 elif hang % 4:
497 error = 'E121', 'indentation is not a multiple of four'
498 else:
499 error = 'E126', 'over-indented for hanging indent'
500 yield start, "%s continuation line %s" % error
501
502 # look for visual indenting
503 if (parens[row] and token_type not in (tokenize.NL, tokenize.COMMENT)
504 and not indent[depth]):
505 indent[depth] = start[1]
506 indent_chances[start[1]] = True
507 if verbose >= 4:
508 print("bracket depth %s indent to %s" % (depth, start[1]))
509 # deal with implicit string concatenation
510 elif (token_type in (tokenize.STRING, tokenize.COMMENT) or
511 text in ('u', 'ur', 'b', 'br')):
512 indent_chances[start[1]] = str
513
514 # keep track of bracket depth
515 if token_type == tokenize.OP:
516 if text in '([{':
517 depth += 1
518 indent.append(0)
519 parens[row] += 1
520 if verbose >= 4:
521 print("bracket depth %s seen, col %s, visual min = %s" %
522 (depth, start[1], indent[depth]))
523 elif text in ')]}' and depth > 0:
524 # parent indents should not be more than this one
525 prev_indent = indent.pop() or last_indent[1]
526 for d in range(depth):
527 if indent[d] > prev_indent:
528 indent[d] = 0
529 for ind in list(indent_chances):
530 if ind >= prev_indent:
531 del indent_chances[ind]
532 depth -= 1
533 if depth:
534 indent_chances[indent[depth]] = True
535 for idx in range(row, -1, -1):
536 if parens[idx]:
537 parens[idx] -= 1
538 break
539 assert len(indent) == depth + 1
540 if start[1] not in indent_chances:
541 # allow to line up tokens
542 indent_chances[start[1]] = text
543
544 last_token_multiline = (start[0] != end[0])
545
546 if indent_next and rel_indent[-1] == 4:
547 yield (last_indent, "E125 continuation line does not distinguish "
548 "itself from next logical line")
549
550
551def whitespace_before_parameters(logical_line, tokens):
552 """
553 Avoid extraneous whitespace in the following situations:
554
555 - Immediately before the open parenthesis that starts the argument
556 list of a function call.
557
558 - Immediately before the open parenthesis that starts an indexing or
559 slicing.
560
561 Okay: spam(1)
562 E211: spam (1)
563
564 Okay: dict['key'] = list[index]
565 E211: dict ['key'] = list[index]
566 E211: dict['key'] = list [index]
567 """
568 prev_type = tokens[0][0]
569 prev_text = tokens[0][1]
570 prev_end = tokens[0][3]
571 for index in range(1, len(tokens)):
572 token_type, text, start, end, line = tokens[index]
573 if (token_type == tokenize.OP and
574 text in '([' and
575 start != prev_end and
576 (prev_type == tokenize.NAME or prev_text in '}])') and
577 # Syntax "class A (B):" is allowed, but avoid it
578 (index < 2 or tokens[index - 2][1] != 'class') and
579 # Allow "return (a.foo for a in range(5))"
580 not keyword.iskeyword(prev_text)):
581 yield prev_end, "E211 whitespace before '%s'" % text
582 prev_type = token_type
583 prev_text = text
584 prev_end = end
585
586
587def whitespace_around_operator(logical_line):
588 r"""
589 Avoid extraneous whitespace in the following situations:
590
591 - More than one space around an assignment (or other) operator to
592 align it with another.
593
594 Okay: a = 12 + 3
595 E221: a = 4 + 5
596 E222: a = 4 + 5
597 E223: a = 4\t+ 5
598 E224: a = 4 +\t5
599 """
600 for match in OPERATOR_REGEX.finditer(logical_line):
601 before, after = match.groups()
602
603 if '\t' in before:
604 yield match.start(1), "E223 tab before operator"
605 elif len(before) > 1:
606 yield match.start(1), "E221 multiple spaces before operator"
607
608 if '\t' in after:
609 yield match.start(2), "E224 tab after operator"
610 elif len(after) > 1:
611 yield match.start(2), "E222 multiple spaces after operator"
612
613
614def missing_whitespace_around_operator(logical_line, tokens):
615 r"""
616 - Always surround these binary operators with a single space on
617 either side: assignment (=), augmented assignment (+=, -= etc.),
618 comparisons (==, <, >, !=, <>, <=, >=, in, not in, is, is not),
619 Booleans (and, or, not).
620
621 - Use spaces around arithmetic operators.
622
623 Okay: i = i + 1
624 Okay: submitted += 1
625 Okay: x = x * 2 - 1
626 Okay: hypot2 = x * x + y * y
627 Okay: c = (a + b) * (a - b)
628 Okay: foo(bar, key='word', *args, **kwargs)
629 Okay: baz(**kwargs)
630 Okay: negative = -1
631 Okay: spam(-1)
632 Okay: alpha[:-i]
633 Okay: if not -5 < x < +5:\n pass
634 Okay: lambda *args, **kw: (args, kw)
635 Okay: z = 2 ** 30
636 Okay: x = x / 2 - 1
637
638 E225: i=i+1
639 E225: submitted +=1
640 E225: c = alpha -4
641 E225: x = x /2 - 1
642 E225: z = x **y
643 E226: c = (a+b) * (a-b)
644 E226: z = 2**30
645 E226: x = x*2 - 1
646 E226: x = x/2 - 1
647 E226: hypot2 = x*x + y*y
648 """
649 parens = 0
650 need_space = False
651 prev_type = tokenize.OP
652 prev_text = prev_end = None
653 for token_type, text, start, end, line in tokens:
654 if token_type in (tokenize.NL, tokenize.NEWLINE, tokenize.ERRORTOKEN):
655 # ERRORTOKEN is triggered by backticks in Python 3
656 continue
657 if text in ('(', 'lambda'):
658 parens += 1
659 elif text == ')':
660 parens -= 1
661 if need_space:
662 if start != prev_end:
663 # Found a (probably) needed space
664 if need_space is not True and not need_space[1]:
665 yield (need_space[0],
666 "E225 missing whitespace around operator")
667 need_space = False
668 elif text == '>' and prev_text in ('<', '-'):
669 # Tolerate the "<>" operator, even if running Python 3
670 # Deal with Python 3's annotated return value "->"
671 pass
672 else:
673 if need_space is True or need_space[1]:
674 # A needed trailing space was not found
675 yield prev_end, "E225 missing whitespace around operator"
676 else:
677 yield (need_space[0],
678 "E226 missing optional whitespace around operator")
679 need_space = False
680 elif token_type == tokenize.OP and prev_end is not None:
681 if text == '=' and parens:
682 # Allow keyword args or defaults: foo(bar=None).
683 pass
684 elif text in WS_NEEDED_OPERATORS:
685 need_space = True
686 elif text in UNARY_OPERATORS:
687 # Check if the operator is being used as a binary operator
688 # Allow unary operators: -123, -x, +1.
689 # Allow argument unpacking: foo(*args, **kwargs).
690 if prev_type == tokenize.OP:
691 binary_usage = (prev_text in '}])')
692 elif prev_type == tokenize.NAME:
693 binary_usage = (prev_text not in KEYWORDS)
694 else:
695 binary_usage = (prev_type not in SKIP_TOKENS)
696
697 if binary_usage:
698 if text in WS_OPTIONAL_OPERATORS:
699 need_space = None
700 else:
701 need_space = True
702 elif text in WS_OPTIONAL_OPERATORS:
703 need_space = None
704
705 if need_space is None:
706 # Surrounding space is optional, but ensure that
707 # trailing space matches opening space
708 need_space = (prev_end, start != prev_end)
709 elif need_space and start == prev_end:
710 # A needed opening space was not found
711 yield prev_end, "E225 missing whitespace around operator"
712 need_space = False
713 prev_type = token_type
714 prev_text = text
715 prev_end = end
716
717
718def whitespace_around_comma(logical_line):
719 r"""
720 Avoid extraneous whitespace in the following situations:
721
722 - More than one space around an assignment (or other) operator to
723 align it with another.
724
725 Note: these checks are disabled by default
726
727 Okay: a = (1, 2)
728 E241: a = (1, 2)
729 E242: a = (1,\t2)
730 """
731 line = logical_line
732 for m in WHITESPACE_AFTER_COMMA_REGEX.finditer(line):
733 found = m.start() + 1
734 if '\t' in m.group():
735 yield found, "E242 tab after '%s'" % m.group()[0]
736 else:
737 yield found, "E241 multiple spaces after '%s'" % m.group()[0]
738
739
740def whitespace_around_named_parameter_equals(logical_line, tokens):
741 """
742 Don't use spaces around the '=' sign when used to indicate a
743 keyword argument or a default parameter value.
744
745 Okay: def complex(real, imag=0.0):
746 Okay: return magic(r=real, i=imag)
747 Okay: boolean(a == b)
748 Okay: boolean(a != b)
749 Okay: boolean(a <= b)
750 Okay: boolean(a >= b)
751
752 E251: def complex(real, imag = 0.0):
753 E251: return magic(r = real, i = imag)
754 """
755 parens = 0
756 no_space = False
757 prev_end = None
758 for token_type, text, start, end, line in tokens:
759 if no_space:
760 no_space = False
761 if start != prev_end:
762 yield (prev_end,
763 "E251 no spaces around keyword / parameter equals")
764 elif token_type == tokenize.OP:
765 if text == '(':
766 parens += 1
767 elif text == ')':
768 parens -= 1
769 elif parens and text == '=':
770 no_space = True
771 if start != prev_end:
772 yield (prev_end,
773 "E251 no spaces around keyword / parameter equals")
774 prev_end = end
775
776
777def whitespace_before_inline_comment(logical_line, tokens):
778 """
779 Separate inline comments by at least two spaces.
780
781 An inline comment is a comment on the same line as a statement. Inline
782 comments should be separated by at least two spaces from the statement.
783 They should start with a # and a single space.
784
785 Okay: x = x + 1 # Increment x
786 Okay: x = x + 1 # Increment x
787 E261: x = x + 1 # Increment x
788 E262: x = x + 1 #Increment x
789 E262: x = x + 1 # Increment x
790 """
791 prev_end = (0, 0)
792 for token_type, text, start, end, line in tokens:
793 if token_type == tokenize.COMMENT:
794 if not line[:start[1]].strip():
795 continue
796 if prev_end[0] == start[0] and start[1] < prev_end[1] + 2:
797 yield (prev_end,
798 "E261 at least two spaces before inline comment")
799 symbol, sp, comment = text.partition(' ')
800 if symbol not in ('#', '#:') or comment[:1].isspace():
801 yield start, "E262 inline comment should start with '# '"
802 elif token_type != tokenize.NL:
803 prev_end = end
804
805
806def imports_on_separate_lines(logical_line):
807 r"""
808 Imports should usually be on separate lines.
809
810 Okay: import os\nimport sys
811 E401: import sys, os
812
813 Okay: from subprocess import Popen, PIPE
814 Okay: from myclas import MyClass
815 Okay: from foo.bar.yourclass import YourClass
816 Okay: import myclass
817 Okay: import foo.bar.yourclass
818 """
819 line = logical_line
820 if line.startswith('import '):
821 found = line.find(',')
822 if -1 < found and ';' not in line[:found]:
823 yield found, "E401 multiple imports on one line"
824
825
826def compound_statements(logical_line):
827 r"""
828 Compound statements (multiple statements on the same line) are
829 generally discouraged.
830
831 While sometimes it's okay to put an if/for/while with a small body
832 on the same line, never do this for multi-clause statements. Also
833 avoid folding such long lines!
834
835 Okay: if foo == 'blah':\n do_blah_thing()
836 Okay: do_one()
837 Okay: do_two()
838 Okay: do_three()
839
840 E701: if foo == 'blah': do_blah_thing()
841 E701: for x in lst: total += x
842 E701: while t < 10: t = delay()
843 E701: if foo == 'blah': do_blah_thing()
844 E701: else: do_non_blah_thing()
845 E701: try: something()
846 E701: finally: cleanup()
847 E701: if foo == 'blah': one(); two(); three()
848
849 E702: do_one(); do_two(); do_three()
850 E703: do_four(); # useless semicolon
851 """
852 line = logical_line
853 last_char = len(line) - 1
854 found = line.find(':')
855 if -1 < found < last_char:
856 before = line[:found]
857 if (before.count('{') <= before.count('}') and # {'a': 1} (dict)
858 before.count('[') <= before.count(']') and # [1:2] (slice)
859 before.count('(') <= before.count(')') and # (Python 3 annotation)
860 not LAMBDA_REGEX.search(before)): # lambda x: x
861 yield found, "E701 multiple statements on one line (colon)"
862 found = line.find(';')
863 if -1 < found:
864 if found < last_char:
865 yield found, "E702 multiple statements on one line (semicolon)"
866 else:
867 yield found, "E703 statement ends with a semicolon"
868
869
870def explicit_line_join(logical_line, tokens):
871 r"""
872 Avoid explicit line join between brackets.
873
874 The preferred way of wrapping long lines is by using Python's implied line
875 continuation inside parentheses, brackets and braces. Long lines can be
876 broken over multiple lines by wrapping expressions in parentheses. These
877 should be used in preference to using a backslash for line continuation.
878
879 E502: aaa = [123, \\n 123]
880 E502: aaa = ("bbb " \\n "ccc")
881
882 Okay: aaa = [123,\n 123]
883 Okay: aaa = ("bbb "\n "ccc")
884 Okay: aaa = "bbb " \\n "ccc"
885 """
886 prev_start = prev_end = parens = 0
887 for token_type, text, start, end, line in tokens:
888 if start[0] != prev_start and parens and backslash:
889 yield backslash, "E502 the backslash is redundant between brackets"
890 if end[0] != prev_end:
891 if line.rstrip('\r\n').endswith('\\'):
892 backslash = (end[0], len(line.splitlines()[-1]) - 1)
893 else:
894 backslash = None
895 prev_start = prev_end = end[0]
896 else:
897 prev_start = start[0]
898 if token_type == tokenize.OP:
899 if text in '([{':
900 parens += 1
901 elif text in ')]}':
902 parens -= 1
903
904
905def comparison_to_singleton(logical_line):
906 """
907 Comparisons to singletons like None should always be done
908 with "is" or "is not", never the equality operators.
909
910 Okay: if arg is not None:
911 E711: if arg != None:
912 E712: if arg == True:
913
914 Also, beware of writing if x when you really mean if x is not None --
915 e.g. when testing whether a variable or argument that defaults to None was
916 set to some other value. The other value might have a type (such as a
917 container) that could be false in a boolean context!
918 """
919 match = COMPARE_SINGLETON_REGEX.search(logical_line)
920 if match:
921 same = (match.group(1) == '==')
922 singleton = match.group(2)
923 msg = "'if cond is %s:'" % (('' if same else 'not ') + singleton)
924 if singleton in ('None',):
925 code = 'E711'
926 else:
927 code = 'E712'
928 nonzero = ((singleton == 'True' and same) or
929 (singleton == 'False' and not same))
930 msg += " or 'if %scond:'" % ('' if nonzero else 'not ')
931 yield match.start(1), ("%s comparison to %s should be %s" %
932 (code, singleton, msg))
933
934
935def comparison_type(logical_line):
936 """
937 Object type comparisons should always use isinstance() instead of
938 comparing types directly.
939
940 Okay: if isinstance(obj, int):
941 E721: if type(obj) is type(1):
942
943 When checking if an object is a string, keep in mind that it might be a
944 unicode string too! In Python 2.3, str and unicode have a common base
945 class, basestring, so you can do:
946
947 Okay: if isinstance(obj, basestring):
948 Okay: if type(a1) is type(b1):
949 """
950 match = COMPARE_TYPE_REGEX.search(logical_line)
951 if match:
952 inst = match.group(3)
953 if inst and isidentifier(inst) and inst not in SINGLETONS:
954 return # Allow comparison for types which are not obvious
955 yield match.start(1), "E721 do not compare types, use 'isinstance()'"
956
957
958def python_3000_has_key(logical_line):
959 r"""
960 The {}.has_key() method is removed in the Python 3.
961 Use the 'in' operation instead.
962
963 Okay: if "alph" in d:\n print d["alph"]
964 W601: assert d.has_key('alph')
965 """
966 pos = logical_line.find('.has_key(')
967 if pos > -1:
968 yield pos, "W601 .has_key() is deprecated, use 'in'"
969
970
971def python_3000_raise_comma(logical_line):
972 """
973 When raising an exception, use "raise ValueError('message')"
974 instead of the older form "raise ValueError, 'message'".
975
976 The paren-using form is preferred because when the exception arguments
977 are long or include string formatting, you don't need to use line
978 continuation characters thanks to the containing parentheses. The older
979 form is removed in Python 3.
980
981 Okay: raise DummyError("Message")
982 W602: raise DummyError, "Message"
983 """
984 match = RAISE_COMMA_REGEX.match(logical_line)
985 if match and not RERAISE_COMMA_REGEX.match(logical_line):
986 yield match.start(1), "W602 deprecated form of raising exception"
987
988
989def python_3000_not_equal(logical_line):
990 """
991 != can also be written <>, but this is an obsolete usage kept for
992 backwards compatibility only. New code should always use !=.
993 The older syntax is removed in Python 3.
994
995 Okay: if a != 'no':
996 W603: if a <> 'no':
997 """
998 pos = logical_line.find('<>')
999 if pos > -1:
1000 yield pos, "W603 '<>' is deprecated, use '!='"
1001
1002
1003def python_3000_backticks(logical_line):
1004 """
1005 Backticks are removed in Python 3.
1006 Use repr() instead.
1007
1008 Okay: val = repr(1 + 2)
1009 W604: val = `1 + 2`
1010 """
1011 pos = logical_line.find('`')
1012 if pos > -1:
1013 yield pos, "W604 backticks are deprecated, use 'repr()'"
1014
1015
1016##############################################################################
1017# Helper functions
1018##############################################################################
1019
1020
1021if '' == ''.encode():
1022 # Python 2: implicit encoding.
1023 def readlines(filename):
1024 f = open(filename)
1025 try:
1026 return f.readlines()
1027 finally:
1028 f.close()
1029
1030 isidentifier = re.compile(r'[a-zA-Z_]\w*').match
1031 stdin_get_value = sys.stdin.read
1032else:
1033 # Python 3
1034 def readlines(filename):
1035 f = open(filename, 'rb')
1036 try:
1037 coding, lines = tokenize.detect_encoding(f.readline)
1038 f = TextIOWrapper(f, coding, line_buffering=True)
1039 return [l.decode(coding) for l in lines] + f.readlines()
1040 except (LookupError, SyntaxError, UnicodeError):
1041 f.close()
1042 # Fall back if files are improperly declared
1043 f = open(filename, encoding='latin-1')
1044 return f.readlines()
1045 finally:
1046 f.close()
1047
1048 isidentifier = str.isidentifier
1049
1050 def stdin_get_value():
1051 return TextIOWrapper(sys.stdin.buffer, errors='ignore').read()
1052readlines.__doc__ = " Read the source code."
1053
1054
1055def expand_indent(line):
1056 r"""
1057 Return the amount of indentation.
1058 Tabs are expanded to the next multiple of 8.
1059
1060 >>> expand_indent(' ')
1061 4
1062 >>> expand_indent('\t')
1063 8
1064 >>> expand_indent(' \t')
1065 8
1066 >>> expand_indent(' \t')
1067 8
1068 >>> expand_indent(' \t')
1069 16
1070 """
1071 if '\t' not in line:
1072 return len(line) - len(line.lstrip())
1073 result = 0
1074 for char in line:
1075 if char == '\t':
1076 result = result // 8 * 8 + 8
1077 elif char == ' ':
1078 result += 1
1079 else:
1080 break
1081 return result
1082
1083
1084def mute_string(text):
1085 """
1086 Replace contents with 'xxx' to prevent syntax matching.
1087
1088 >>> mute_string('"abc"')
1089 '"xxx"'
1090 >>> mute_string("'''abc'''")
1091 "'''xxx'''"
1092 >>> mute_string("r'abc'")
1093 "r'xxx'"
1094 """
1095 # String modifiers (e.g. u or r)
1096 start = text.index(text[-1]) + 1
1097 end = len(text) - 1
1098 # Triple quotes
1099 if text[-3:] in ('"""', "'''"):
1100 start += 2
1101 end -= 2
1102 return text[:start] + 'x' * (end - start) + text[end:]
1103
1104
1105def parse_udiff(diff, patterns=None, parent='.'):
1106 """Return a dictionary of matching lines."""
1107 # For each file of the diff, the entry key is the filename,
1108 # and the value is a set of row numbers to consider.
1109 rv = {}
1110 path = nrows = None
1111 for line in diff.splitlines():
1112 if nrows:
1113 if line[:1] != '-':
1114 nrows -= 1
1115 continue
1116 if line[:3] == '@@ ':
1117 hunk_match = HUNK_REGEX.match(line)
1118 row, nrows = [int(g or '1') for g in hunk_match.groups()]
1119 rv[path].update(range(row, row + nrows))
1120 elif line[:3] == '+++':
1121 path = line[4:].split('\t', 1)[0]
1122 if path[:2] == 'b/':
1123 path = path[2:]
1124 rv[path] = set()
1125 return dict([(os.path.join(parent, path), rows)
1126 for (path, rows) in rv.items()
1127 if rows and filename_match(path, patterns)])
1128
1129
1130def filename_match(filename, patterns, default=True):
1131 """
1132 Check if patterns contains a pattern that matches filename.
1133 If patterns is unspecified, this always returns True.
1134 """
1135 if not patterns:
1136 return default
1137 return any(fnmatch(filename, pattern) for pattern in patterns)
1138
1139
1140##############################################################################
1141# Framework to run all checks
1142##############################################################################
1143
1144
1145def find_checks(argument_name):
1146 """
1147 Find all globally visible functions where the first argument name
1148 starts with argument_name.
1149 """
1150 for name, function in globals().items():
1151 if not inspect.isfunction(function):
1152 continue
1153 args = inspect.getargspec(function)[0]
1154 if args and args[0].startswith(argument_name):
1155 codes = ERRORCODE_REGEX.findall(function.__doc__ or '')
1156 yield name, codes, function, args
1157
1158
1159class Checker(object):
1160 """
1161 Load a Python source file, tokenize it, check coding style.
1162 """
1163
1164 def __init__(self, filename=None, lines=None,
1165 options=None, report=None, **kwargs):
1166 if options is None:
1167 options = StyleGuide(kwargs).options
1168 else:
1169 assert not kwargs
1170 self._io_error = None
1171 self._physical_checks = options.physical_checks
1172 self._logical_checks = options.logical_checks
1173 self.max_line_length = options.max_line_length
1174 self.verbose = options.verbose
1175 self.filename = filename
1176 if filename is None:
1177 self.filename = 'stdin'
1178 self.lines = lines or []
1179 elif filename == '-':
1180 self.filename = 'stdin'
1181 self.lines = stdin_get_value().splitlines(True)
1182 elif lines is None:
1183 try:
1184 self.lines = readlines(filename)
1185 except IOError:
1186 exc_type, exc = sys.exc_info()[:2]
1187 self._io_error = '%s: %s' % (exc_type.__name__, exc)
1188 self.lines = []
1189 else:
1190 self.lines = lines
1191 self.report = report or options.report
1192 self.report_error = self.report.error
1193
1194 def readline(self):
1195 """
1196 Get the next line from the input buffer.
1197 """
1198 self.line_number += 1
1199 if self.line_number > len(self.lines):
1200 return ''
1201 return self.lines[self.line_number - 1]
1202
1203 def readline_check_physical(self):
1204 """
1205 Check and return the next physical line. This method can be
1206 used to feed tokenize.generate_tokens.
1207 """
1208 line = self.readline()
1209 if line:
1210 self.check_physical(line)
1211 return line
1212
1213 def run_check(self, check, argument_names):
1214 """
1215 Run a check plugin.
1216 """
1217 arguments = []
1218 for name in argument_names:
1219 arguments.append(getattr(self, name))
1220 return check(*arguments)
1221
1222 def check_physical(self, line):
1223 """
1224 Run all physical checks on a raw input line.
1225 """
1226 self.physical_line = line
1227 if self.indent_char is None and line[:1] in WHITESPACE:
1228 self.indent_char = line[0]
1229 for name, check, argument_names in self._physical_checks:
1230 result = self.run_check(check, argument_names)
1231 if result is not None:
1232 offset, text = result
1233 self.report_error(self.line_number, offset, text, check)
1234
1235 def build_tokens_line(self):
1236 """
1237 Build a logical line from tokens.
1238 """
1239 self.mapping = []
1240 logical = []
1241 length = 0
1242 previous = None
1243 for token in self.tokens:
1244 token_type, text = token[0:2]
1245 if token_type in SKIP_TOKENS:
1246 continue
1247 if token_type == tokenize.STRING:
1248 text = mute_string(text)
1249 if previous:
1250 end_row, end = previous[3]
1251 start_row, start = token[2]
1252 if end_row != start_row: # different row
1253 prev_text = self.lines[end_row - 1][end - 1]
1254 if prev_text == ',' or (prev_text not in '{[('
1255 and text not in '}])'):
1256 logical.append(' ')
1257 length += 1
1258 elif end != start: # different column
1259 fill = self.lines[end_row - 1][end:start]
1260 logical.append(fill)
1261 length += len(fill)
1262 self.mapping.append((length, token))
1263 logical.append(text)
1264 length += len(text)
1265 previous = token
1266 self.logical_line = ''.join(logical)
1267 # With Python 2, if the line ends with '\r\r\n' the assertion fails
1268 # assert self.logical_line.strip() == self.logical_line
1269
1270 def check_logical(self):
1271 """
1272 Build a line from tokens and run all logical checks on it.
1273 """
1274 self.build_tokens_line()
1275 self.report.increment_logical_line()
1276 first_line = self.lines[self.mapping[0][1][2][0] - 1]
1277 indent = first_line[:self.mapping[0][1][2][1]]
1278 self.previous_indent_level = self.indent_level
1279 self.indent_level = expand_indent(indent)
1280 if self.verbose >= 2:
1281 print(self.logical_line[:80].rstrip())
1282 for name, check, argument_names in self._logical_checks:
1283 if self.verbose >= 4:
1284 print(' ' + name)
1285 for result in self.run_check(check, argument_names):
1286 offset, text = result
1287 if isinstance(offset, tuple):
1288 orig_number, orig_offset = offset
1289 else:
1290 for token_offset, token in self.mapping:
1291 if offset >= token_offset:
1292 orig_number = token[2][0]
1293 orig_offset = (token[2][1] + offset - token_offset)
1294 self.report_error(orig_number, orig_offset, text, check)
1295 self.previous_logical = self.logical_line
1296
1297 def generate_tokens(self):
1298 if self._io_error:
1299 self.report_error(1, 0, 'E902 %s' % self._io_error, readlines)
1300 tokengen = tokenize.generate_tokens(self.readline_check_physical)
1301 try:
1302 for token in tokengen:
1303 yield token
1304 except (SyntaxError, tokenize.TokenError):
1305 exc_type, exc = sys.exc_info()[:2]
1306 offset = exc.args[1]
1307 if len(offset) > 2:
1308 offset = offset[1:3]
1309 self.report_error(offset[0], offset[1],
1310 'E901 %s: %s' % (exc_type.__name__, exc.args[0]),
1311 self.generate_tokens)
1312 generate_tokens.__doc__ = " Check if the syntax is valid."
1313
1314 def check_all(self, expected=None, line_offset=0):
1315 """
1316 Run all checks on the input file.
1317 """
1318 self.report.init_file(self.filename, self.lines, expected, line_offset)
1319 self.line_number = 0
1320 self.indent_char = None
1321 self.indent_level = 0
1322 self.previous_logical = ''
1323 self.tokens = []
1324 self.blank_lines = blank_lines_before_comment = 0
1325 parens = 0
1326 for token in self.generate_tokens():
1327 self.tokens.append(token)
1328 token_type, text = token[0:2]
1329 if self.verbose >= 3:
1330 if token[2][0] == token[3][0]:
1331 pos = '[%s:%s]' % (token[2][1] or '', token[3][1])
1332 else:
1333 pos = 'l.%s' % token[3][0]
1334 print('l.%s\t%s\t%s\t%r' %
1335 (token[2][0], pos, tokenize.tok_name[token[0]], text))
1336 if token_type == tokenize.OP:
1337 if text in '([{':
1338 parens += 1
1339 elif text in '}])':
1340 parens -= 1
1341 elif not parens:
1342 if token_type == tokenize.NEWLINE:
1343 if self.blank_lines < blank_lines_before_comment:
1344 self.blank_lines = blank_lines_before_comment
1345 self.check_logical()
1346 self.tokens = []
1347 self.blank_lines = blank_lines_before_comment = 0
1348 elif token_type == tokenize.NL:
1349 if len(self.tokens) == 1:
1350 # The physical line contains only this token.
1351 self.blank_lines += 1
1352 self.tokens = []
1353 elif token_type == tokenize.COMMENT and len(self.tokens) == 1:
1354 if blank_lines_before_comment < self.blank_lines:
1355 blank_lines_before_comment = self.blank_lines
1356 self.blank_lines = 0
1357 if COMMENT_WITH_NL:
1358 # The comment also ends a physical line
1359 self.tokens = []
1360 return self.report.get_file_results()
1361
1362
1363class BaseReport(object):
1364 """Collect the results of the checks."""
1365 print_filename = False
1366
1367 def __init__(self, options):
1368 self._benchmark_keys = options.benchmark_keys
1369 self._ignore_code = options.ignore_code
1370 # Results
1371 self.elapsed = 0
1372 self.total_errors = 0
1373 self.counters = dict.fromkeys(self._benchmark_keys, 0)
1374 self.messages = {}
1375
1376 def start(self):
1377 """Start the timer."""
1378 self._start_time = time.time()
1379
1380 def stop(self):
1381 """Stop the timer."""
1382 self.elapsed = time.time() - self._start_time
1383
1384 def init_file(self, filename, lines, expected, line_offset):
1385 """Signal a new file."""
1386 self.filename = filename
1387 self.lines = lines
1388 self.expected = expected or ()
1389 self.line_offset = line_offset
1390 self.file_errors = 0
1391 self.counters['files'] += 1
1392 self.counters['physical lines'] += len(lines)
1393
1394 def increment_logical_line(self):
1395 """Signal a new logical line."""
1396 self.counters['logical lines'] += 1
1397
1398 def error(self, line_number, offset, text, check):
1399 """Report an error, according to options."""
1400 code = text[:4]
1401 if self._ignore_code(code):
1402 return
1403 if code in self.counters:
1404 self.counters[code] += 1
1405 else:
1406 self.counters[code] = 1
1407 self.messages[code] = text[5:]
1408 # Don't care about expected errors or warnings
1409 if code in self.expected:
1410 return
1411 if self.print_filename and not self.file_errors:
1412 print(self.filename)
1413 self.file_errors += 1
1414 self.total_errors += 1
1415 return code
1416
1417 def get_file_results(self):
1418 """Return the count of errors and warnings for this file."""
1419 return self.file_errors
1420
1421 def get_count(self, prefix=''):
1422 """Return the total count of errors and warnings."""
1423 return sum([self.counters[key]
1424 for key in self.messages if key.startswith(prefix)])
1425
1426 def get_statistics(self, prefix=''):
1427 """
1428 Get statistics for message codes that start with the prefix.
1429
1430 prefix='' matches all errors and warnings
1431 prefix='E' matches all errors
1432 prefix='W' matches all warnings
1433 prefix='E4' matches all errors that have to do with imports
1434 """
1435 return ['%-7s %s %s' % (self.counters[key], key, self.messages[key])
1436 for key in sorted(self.messages) if key.startswith(prefix)]
1437
1438 def print_statistics(self, prefix=''):
1439 """Print overall statistics (number of errors and warnings)."""
1440 for line in self.get_statistics(prefix):
1441 print(line)
1442
1443 def print_benchmark(self):
1444 """Print benchmark numbers."""
1445 print('%-7.2f %s' % (self.elapsed, 'seconds elapsed'))
1446 if self.elapsed:
1447 for key in self._benchmark_keys:
1448 print('%-7d %s per second (%d total)' %
1449 (self.counters[key] / self.elapsed, key,
1450 self.counters[key]))
1451
1452
1453class FileReport(BaseReport):
1454 """Collect the results of the checks and print only the filenames."""
1455 print_filename = True
1456
1457
1458class StandardReport(BaseReport):
1459 """Collect and print the results of the checks."""
1460
1461 def __init__(self, options):
1462 super(StandardReport, self).__init__(options)
1463 self._fmt = REPORT_FORMAT.get(options.format.lower(),
1464 options.format)
1465 self._repeat = options.repeat
1466 self._show_source = options.show_source
1467 self._show_pep8 = options.show_pep8
1468
1469 def error(self, line_number, offset, text, check):
1470 """
1471 Report an error, according to options.
1472 """
1473 code = super(StandardReport, self).error(line_number, offset,
1474 text, check)
1475 if code and (self.counters[code] == 1 or self._repeat):
1476 print(self._fmt % {
1477 'path': self.filename,
1478 'row': self.line_offset + line_number, 'col': offset + 1,
1479 'code': code, 'text': text[5:],
1480 })
1481 if self._show_source:
1482 if line_number > len(self.lines):
1483 line = ''
1484 else:
1485 line = self.lines[line_number - 1]
1486 print(line.rstrip())
1487 print(' ' * offset + '^')
1488 if self._show_pep8:
1489 print(check.__doc__.lstrip('\n').rstrip())
1490 return code
1491
1492
1493class DiffReport(StandardReport):
1494 """Collect and print the results for the changed lines only."""
1495
1496 def __init__(self, options):
1497 super(DiffReport, self).__init__(options)
1498 self._selected = options.selected_lines
1499
1500 def error(self, line_number, offset, text, check):
1501 if line_number not in self._selected[self.filename]:
1502 return
1503 return super(DiffReport, self).error(line_number, offset, text, check)
1504
1505
1506class TestReport(StandardReport):
1507 """Collect the results for the tests."""
1508
1509 def __init__(self, options):
1510 options.benchmark_keys += ['test cases', 'failed tests']
1511 super(TestReport, self).__init__(options)
1512 self._verbose = options.verbose
1513
1514 def get_file_results(self):
1515 # Check if the expected errors were found
1516 label = '%s:%s:1' % (self.filename, self.line_offset)
1517 codes = sorted(self.expected)
1518 for code in codes:
1519 if not self.counters.get(code):
1520 self.file_errors += 1
1521 self.total_errors += 1
1522 print('%s: error %s not found' % (label, code))
1523 if self._verbose and not self.file_errors:
1524 print('%s: passed (%s)' %
1525 (label, ' '.join(codes) or 'Okay'))
1526 self.counters['test cases'] += 1
1527 if self.file_errors:
1528 self.counters['failed tests'] += 1
1529 # Reset counters
1530 for key in set(self.counters) - set(self._benchmark_keys):
1531 del self.counters[key]
1532 self.messages = {}
1533 return self.file_errors
1534
1535 def print_results(self):
1536 results = ("%(physical lines)d lines tested: %(files)d files, "
1537 "%(test cases)d test cases%%s." % self.counters)
1538 if self.total_errors:
1539 print(results % ", %s failures" % self.total_errors)
1540 else:
1541 print(results % "")
1542 print("Test failed." if self.total_errors else "Test passed.")
1543
1544
1545class StyleGuide(object):
1546 """Initialize a PEP-8 instance with few options."""
1547
1548 def __init__(self, *args, **kwargs):
1549 # build options from the command line
1550 parse_argv = kwargs.pop('parse_argv', False)
1551 config_file = kwargs.pop('config_file', None)
1552 options, self.paths = process_options(parse_argv=parse_argv,
1553 config_file=config_file)
1554 if args or kwargs:
1555 # build options from dict
1556 options_dict = dict(*args, **kwargs)
1557 options.__dict__.update(options_dict)
1558 if 'paths' in options_dict:
1559 self.paths = options_dict['paths']
1560
1561 self.runner = self.input_file
1562 self.options = options
1563
1564 if not options.reporter:
1565 options.reporter = BaseReport if options.quiet else StandardReport
1566
1567 for index, value in enumerate(options.exclude):
1568 options.exclude[index] = value.rstrip('/')
1569 # Ignore all checks which are not explicitly selected
1570 options.select = tuple(options.select or ())
1571 options.ignore = tuple(options.ignore or options.select and ('',))
1572 options.benchmark_keys = BENCHMARK_KEYS[:]
1573 options.ignore_code = self.ignore_code
1574 options.physical_checks = self.get_checks('physical_line')
1575 options.logical_checks = self.get_checks('logical_line')
1576 self.init_report()
1577
1578 def init_report(self, reporter=None):
1579 """Initialize the report instance."""
1580 self.options.report = (reporter or self.options.reporter)(self.options)
1581 return self.options.report
1582
1583 def check_files(self, paths=None):
1584 """Run all checks on the paths."""
1585 if paths is None:
1586 paths = self.paths
1587 report = self.options.report
1588 runner = self.runner
1589 report.start()
1590 for path in paths:
1591 if os.path.isdir(path):
1592 self.input_dir(path)
1593 elif not self.excluded(path):
1594 runner(path)
1595 report.stop()
1596 return report
1597
1598 def input_file(self, filename, lines=None, expected=None, line_offset=0):
1599 """Run all checks on a Python source file."""
1600 if self.options.verbose:
1601 print('checking %s' % filename)
1602 fchecker = Checker(filename, lines=lines, options=self.options)
1603 return fchecker.check_all(expected=expected, line_offset=line_offset)
1604
1605 def input_dir(self, dirname):
1606 """Check all files in this directory and all subdirectories."""
1607 dirname = dirname.rstrip('/')
1608 if self.excluded(dirname):
1609 return 0
1610 counters = self.options.report.counters
1611 verbose = self.options.verbose
1612 filepatterns = self.options.filename
1613 runner = self.runner
1614 for root, dirs, files in os.walk(dirname):
1615 if verbose:
1616 print('directory ' + root)
1617 counters['directories'] += 1
1618 for subdir in sorted(dirs):
1619 if self.excluded(os.path.join(root, subdir)):
1620 dirs.remove(subdir)
1621 for filename in sorted(files):
1622 # contain a pattern that matches?
1623 if ((filename_match(filename, filepatterns) and
1624 not self.excluded(filename))):
1625 runner(os.path.join(root, filename))
1626
1627 def excluded(self, filename):
1628 """
1629 Check if options.exclude contains a pattern that matches filename.
1630 """
1631 basename = os.path.basename(filename)
1632 return any((filename_match(filename, self.options.exclude,
1633 default=False),
1634 filename_match(basename, self.options.exclude,
1635 default=False)))
1636
1637 def ignore_code(self, code):
1638 """
1639 Check if the error code should be ignored.
1640
1641 If 'options.select' contains a prefix of the error code,
1642 return False. Else, if 'options.ignore' contains a prefix of
1643 the error code, return True.
1644 """
1645 return (code.startswith(self.options.ignore) and
1646 not code.startswith(self.options.select))
1647
1648 def get_checks(self, argument_name):
1649 """
1650 Find all globally visible functions where the first argument name
1651 starts with argument_name and which contain selected tests.
1652 """
1653 checks = []
1654 for name, codes, function, args in find_checks(argument_name):
1655 if any(not (code and self.ignore_code(code)) for code in codes):
1656 checks.append((name, function, args))
1657 return sorted(checks)
1658
1659
1660def init_tests(pep8style):
1661 """
1662 Initialize testing framework.
1663
1664 A test file can provide many tests. Each test starts with a
1665 declaration. This declaration is a single line starting with '#:'.
1666 It declares codes of expected failures, separated by spaces or 'Okay'
1667 if no failure is expected.
1668 If the file does not contain such declaration, it should pass all
1669 tests. If the declaration is empty, following lines are not checked,
1670 until next declaration.
1671
1672 Examples:
1673
1674 * Only E224 and W701 are expected: #: E224 W701
1675 * Following example is conform: #: Okay
1676 * Don't check these lines: #:
1677 """
1678 report = pep8style.init_report(TestReport)
1679 runner = pep8style.input_file
1680
1681 def run_tests(filename):
1682 """Run all the tests from a file."""
1683 lines = readlines(filename) + ['#:\n']
1684 line_offset = 0
1685 codes = ['Okay']
1686 testcase = []
1687 count_files = report.counters['files']
1688 for index, line in enumerate(lines):
1689 if not line.startswith('#:'):
1690 if codes:
1691 # Collect the lines of the test case
1692 testcase.append(line)
1693 continue
1694 if codes and index:
1695 codes = [c for c in codes if c != 'Okay']
1696 # Run the checker
1697 runner(filename, testcase, expected=codes,
1698 line_offset=line_offset)
1699 # output the real line numbers
1700 line_offset = index + 1
1701 # configure the expected errors
1702 codes = line.split()[1:]
1703 # empty the test case buffer
1704 del testcase[:]
1705 report.counters['files'] = count_files + 1
1706 return report.counters['failed tests']
1707
1708 pep8style.runner = run_tests
1709
1710
1711def selftest(options):
1712 """
1713 Test all check functions with test cases in docstrings.
1714 """
1715 count_failed = count_all = 0
1716 report = BaseReport(options)
1717 counters = report.counters
1718 checks = options.physical_checks + options.logical_checks
1719 for name, check, argument_names in checks:
1720 for line in check.__doc__.splitlines():
1721 line = line.lstrip()
1722 match = SELFTEST_REGEX.match(line)
1723 if match is None:
1724 continue
1725 code, source = match.groups()
1726 lines = [part.replace(r'\t', '\t') + '\n'
1727 for part in source.split(r'\n')]
1728 checker = Checker(lines=lines, options=options, report=report)
1729 checker.check_all()
1730 error = None
1731 if code == 'Okay':
1732 if len(counters) > len(options.benchmark_keys):
1733 codes = [key for key in counters
1734 if key not in options.benchmark_keys]
1735 error = "incorrectly found %s" % ', '.join(codes)
1736 elif not counters.get(code):
1737 error = "failed to find %s" % code
1738 # Keep showing errors for multiple tests
1739 for key in set(counters) - set(options.benchmark_keys):
1740 del counters[key]
1741 report.messages = {}
1742 count_all += 1
1743 if not error:
1744 if options.verbose:
1745 print("%s: %s" % (code, source))
1746 else:
1747 count_failed += 1
1748 print("%s: %s:" % (__file__, error))
1749 for line in checker.lines:
1750 print(line.rstrip())
1751 return count_failed, count_all
1752
1753
1754def read_config(options, args, arglist, parser):
1755 """Read both user configuration and local configuration."""
1756 config = RawConfigParser()
1757
1758 user_conf = options.config
1759 if user_conf and os.path.isfile(user_conf):
1760 if options.verbose:
1761 print('user configuration: %s' % user_conf)
1762 config.read(user_conf)
1763
1764 parent = tail = args and os.path.abspath(os.path.commonprefix(args))
1765 while tail:
1766 for name in PROJECT_CONFIG:
1767 local_conf = os.path.join(parent, name)
1768 if os.path.isfile(local_conf):
1769 break
1770 else:
1771 parent, tail = os.path.split(parent)
1772 continue
1773 if options.verbose:
1774 print('local configuration: %s' % local_conf)
1775 config.read(local_conf)
1776 break
1777
1778 if config.has_section('pep8'):
1779 option_list = dict([(o.dest, o.type or o.action)
1780 for o in parser.option_list])
1781
1782 # First, read the default values
1783 new_options, _ = parser.parse_args([])
1784
1785 # Second, parse the configuration
1786 for opt in config.options('pep8'):
1787 if options.verbose > 1:
1788 print(' %s = %s' % (opt, config.get('pep8', opt)))
1789 if opt.replace('_', '-') not in parser.config_options:
1790 print('Unknown option: \'%s\'\n not in [%s]' %
1791 (opt, ' '.join(parser.config_options)))
1792 sys.exit(1)
1793 normalized_opt = opt.replace('-', '_')
1794 opt_type = option_list[normalized_opt]
1795 if opt_type in ('int', 'count'):
1796 value = config.getint('pep8', opt)
1797 elif opt_type == 'string':
1798 value = config.get('pep8', opt)
1799 else:
1800 assert opt_type in ('store_true', 'store_false')
1801 value = config.getboolean('pep8', opt)
1802 setattr(new_options, normalized_opt, value)
1803
1804 # Third, overwrite with the command-line options
1805 options, _ = parser.parse_args(arglist, values=new_options)
1806
1807 return options
1808
1809
1810def process_options(arglist=None, parse_argv=False, config_file=None):
1811 """Process options passed either via arglist or via command line args."""
1812 if not arglist and not parse_argv:
1813 # Don't read the command line if the module is used as a library.
1814 arglist = []
1815 if config_file is True:
1816 config_file = DEFAULT_CONFIG
1817 parser = OptionParser(version=__version__,
1818 usage="%prog [options] input ...")
1819 parser.config_options = [
1820 'exclude', 'filename', 'select', 'ignore', 'max-line-length', 'count',
1821 'format', 'quiet', 'show-pep8', 'show-source', 'statistics', 'verbose']
1822 parser.add_option('-v', '--verbose', default=0, action='count',
1823 help="print status messages, or debug with -vv")
1824 parser.add_option('-q', '--quiet', default=0, action='count',
1825 help="report only file names, or nothing with -qq")
1826 parser.add_option('-r', '--repeat', default=True, action='store_true',
1827 help="(obsolete) show all occurrences of the same error")
1828 parser.add_option('--first', action='store_false', dest='repeat',
1829 help="show first occurrence of each error")
1830 parser.add_option('--exclude', metavar='patterns', default=DEFAULT_EXCLUDE,
1831 help="exclude files or directories which match these "
1832 "comma separated patterns (default: %default)")
1833 parser.add_option('--filename', metavar='patterns', default='*.py',
1834 help="when parsing directories, only check filenames "
1835 "matching these comma separated patterns "
1836 "(default: %default)")
1837 parser.add_option('--select', metavar='errors', default='',
1838 help="select errors and warnings (e.g. E,W6)")
1839 parser.add_option('--ignore', metavar='errors', default='',
1840 help="skip errors and warnings (e.g. E4,W)")
1841 parser.add_option('--show-source', action='store_true',
1842 help="show source code for each error")
1843 parser.add_option('--show-pep8', action='store_true',
1844 help="show text of PEP 8 for each error "
1845 "(implies --first)")
1846 parser.add_option('--statistics', action='store_true',
1847 help="count errors and warnings")
1848 parser.add_option('--count', action='store_true',
1849 help="print total number of errors and warnings "
1850 "to standard error and set exit code to 1 if "
1851 "total is not null")
1852 parser.add_option('--max-line-length', type='int', metavar='n',
1853 default=MAX_LINE_LENGTH,
1854 help="set maximum allowed line length "
1855 "(default: %default)")
1856 parser.add_option('--format', metavar='format', default='default',
1857 help="set the error format [default|pylint|<custom>]")
1858 parser.add_option('--diff', action='store_true',
1859 help="report only lines changed according to the "
1860 "unified diff received on STDIN")
1861 group = parser.add_option_group("Testing Options")
1862 group.add_option('--testsuite', metavar='dir',
1863 help="run regression tests from dir")
1864 group.add_option('--doctest', action='store_true',
1865 help="run doctest on myself")
1866 group.add_option('--benchmark', action='store_true',
1867 help="measure processing speed")
1868 group = parser.add_option_group("Configuration", description=(
1869 "The project options are read from the [pep8] section of the tox.ini "
1870 "file or the setup.cfg file located in any parent folder of the "
1871 "path(s) being processed. Allowed options are: %s." %
1872 ', '.join(parser.config_options)))
1873 group.add_option('--config', metavar='path', default=config_file,
1874 help="user config file location (default: %default)")
1875
1876 options, args = parser.parse_args(arglist)
1877 options.reporter = None
1878
1879 if options.testsuite:
1880 args.append(options.testsuite)
1881 elif not options.doctest:
1882 if parse_argv and not args:
1883 if options.diff or any(os.path.exists(name)
1884 for name in PROJECT_CONFIG):
1885 args = ['.']
1886 else:
1887 parser.error('input not specified')
1888 options = read_config(options, args, arglist, parser)
1889 options.reporter = parse_argv and options.quiet == 1 and FileReport
1890
1891 if options.filename:
1892 options.filename = options.filename.split(',')
1893 options.exclude = options.exclude.split(',')
1894 if options.select:
1895 options.select = options.select.split(',')
1896 if options.ignore:
1897 options.ignore = options.ignore.split(',')
1898 elif not (options.select or
1899 options.testsuite or options.doctest) and DEFAULT_IGNORE:
1900 # The default choice: ignore controversial checks
1901 # (for doctest and testsuite, all checks are required)
1902 options.ignore = DEFAULT_IGNORE.split(',')
1903
1904 if options.diff:
1905 options.reporter = DiffReport
1906 stdin = stdin_get_value()
1907 options.selected_lines = parse_udiff(stdin, options.filename, args[0])
1908 args = sorted(options.selected_lines)
1909
1910 return options, args
1911
1912
1913def _main():
1914 """Parse options and run checks on Python source."""
1915 pep8style = StyleGuide(parse_argv=True, config_file=True)
1916 options = pep8style.options
1917 if options.doctest:
1918 import doctest
1919 fail_d, done_d = doctest.testmod(report=False, verbose=options.verbose)
1920 fail_s, done_s = selftest(options)
1921 count_failed = fail_s + fail_d
1922 if not options.quiet:
1923 count_passed = done_d + done_s - count_failed
1924 print("%d passed and %d failed." % (count_passed, count_failed))
1925 print("Test failed." if count_failed else "Test passed.")
1926 if count_failed:
1927 sys.exit(1)
1928 if options.testsuite:
1929 init_tests(pep8style)
1930 report = pep8style.check_files()
1931 if options.statistics:
1932 report.print_statistics()
1933 if options.benchmark:
1934 report.print_benchmark()
1935 if options.testsuite and not options.quiet:
1936 report.print_results()
1937 if report.total_errors:
1938 if options.count:
1939 sys.stderr.write(str(report.total_errors) + '\n')
1940 sys.exit(1)
1941
1942if __name__ == '__main__':
1943 _main()
19440
=== modified file 'pocketlint/formatcheck.py'
--- pocketlint/formatcheck.py 2013-08-12 13:28:25 +0000
+++ pocketlint/formatcheck.py 2013-10-09 16:44:43 +0000
@@ -67,7 +67,7 @@
67 css_report_handler,67 css_report_handler,
68 Reporter,68 Reporter,
69)69)
70import pocketlint.contrib.pep8 as pep870import pep8
71from pocketlint.contrib.cssccc import CSSCodingConventionChecker71from pocketlint.contrib.cssccc import CSSCodingConventionChecker
72try:72try:
73 from pyflakes.checker import Checker as PyFlakesChecker73 from pyflakes.checker import Checker as PyFlakesChecker

Subscribers

People subscribed via source and target branches

to all changes: