| OLD | NEW |
| 1 # Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). | 1 # Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). |
| 2 # | 2 # |
| 3 # This program is free software; you can redistribute it and/or modify it under | 3 # This program is free software; you can redistribute it and/or modify it under |
| 4 # the terms of the GNU General Public License as published by the Free Software | 4 # the terms of the GNU General Public License as published by the Free Software |
| 5 # Foundation; either version 2 of the License, or (at your option) any later | 5 # Foundation; either version 2 of the License, or (at your option) any later |
| 6 # version. | 6 # version. |
| 7 # | 7 # |
| 8 # This program is distributed in the hope that it will be useful, but WITHOUT | 8 # This program is distributed in the hope that it will be useful, but WITHOUT |
| 9 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | 9 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 10 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | 10 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
| 11 # | 11 # |
| 12 # You should have received a copy of the GNU General Public License along with | 12 # You should have received a copy of the GNU General Public License along with |
| 13 # this program; if not, write to the Free Software Foundation, Inc., | 13 # this program; if not, write to the Free Software Foundation, Inc., |
| 14 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | 14 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| 15 """Python code format's checker. | 15 """Python code format's checker. |
| 16 | 16 |
| 17 By default try to follow Guido's style guide : | 17 By default try to follow Guido's style guide : |
| 18 | 18 |
| 19 http://www.python.org/doc/essays/styleguide.html | 19 http://www.python.org/doc/essays/styleguide.html |
| 20 | 20 |
| 21 Some parts of the process_token method is based from The Tab Nanny std module. | 21 Some parts of the process_token method is based from The Tab Nanny std module. |
| 22 """ | 22 """ |
| 23 | 23 |
| 24 import keyword | 24 import keyword |
| 25 import sys | 25 import sys |
| 26 import tokenize | 26 import tokenize |
| 27 from functools import reduce # pylint: disable=redefined-builtin |
| 27 | 28 |
| 28 if not hasattr(tokenize, 'NL'): | 29 import six |
| 29 raise ValueError("tokenize.NL doesn't exist -- tokenize module too old") | 30 from six.moves import zip, map, filter # pylint: disable=redefined-builtin |
| 30 | 31 |
| 31 from astroid import nodes | 32 from astroid import nodes |
| 32 | 33 |
| 33 from pylint.interfaces import ITokenChecker, IAstroidChecker, IRawChecker | 34 from pylint.interfaces import ITokenChecker, IAstroidChecker, IRawChecker |
| 34 from pylint.checkers import BaseTokenChecker | 35 from pylint.checkers import BaseTokenChecker |
| 35 from pylint.checkers.utils import check_messages | 36 from pylint.checkers.utils import check_messages |
| 36 from pylint.utils import WarningScope, OPTION_RGX | 37 from pylint.utils import WarningScope, OPTION_RGX |
| 37 | 38 |
| 38 _CONTINUATION_BLOCK_OPENERS = ['elif', 'except', 'for', 'if', 'while', 'def', 'c
lass'] | 39 _CONTINUATION_BLOCK_OPENERS = ['elif', 'except', 'for', 'if', 'while', 'def', 'c
lass'] |
| 39 _KEYWORD_TOKENS = ['assert', 'del', 'elif', 'except', 'for', 'if', 'in', 'not', | 40 _KEYWORD_TOKENS = ['assert', 'del', 'elif', 'except', 'for', 'if', 'in', 'not', |
| (...skipping 18 matching lines...) Expand all Loading... |
| 58 | 59 |
| 59 # Whitespace checking config constants | 60 # Whitespace checking config constants |
| 60 _DICT_SEPARATOR = 'dict-separator' | 61 _DICT_SEPARATOR = 'dict-separator' |
| 61 _TRAILING_COMMA = 'trailing-comma' | 62 _TRAILING_COMMA = 'trailing-comma' |
| 62 _NO_SPACE_CHECK_CHOICES = [_TRAILING_COMMA, _DICT_SEPARATOR] | 63 _NO_SPACE_CHECK_CHOICES = [_TRAILING_COMMA, _DICT_SEPARATOR] |
| 63 | 64 |
| 64 MSGS = { | 65 MSGS = { |
| 65 'C0301': ('Line too long (%s/%s)', | 66 'C0301': ('Line too long (%s/%s)', |
| 66 'line-too-long', | 67 'line-too-long', |
| 67 'Used when a line is longer than a given number of characters.'), | 68 'Used when a line is longer than a given number of characters.'), |
| 68 'C0302': ('Too many lines in module (%s)', # was W0302 | 69 'C0302': ('Too many lines in module (%s/%s)', # was W0302 |
| 69 'too-many-lines', | 70 'too-many-lines', |
| 70 'Used when a module has too much lines, reducing its readability.' | 71 'Used when a module has too much lines, reducing its readability.' |
| 71 ), | 72 ), |
| 72 'C0303': ('Trailing whitespace', | 73 'C0303': ('Trailing whitespace', |
| 73 'trailing-whitespace', | 74 'trailing-whitespace', |
| 74 'Used when there is whitespace between the end of a line and the ' | 75 'Used when there is whitespace between the end of a line and the ' |
| 75 'newline.'), | 76 'newline.'), |
| 76 'C0304': ('Final newline missing', | 77 'C0304': ('Final newline missing', |
| 77 'missing-final-newline', | 78 'missing-final-newline', |
| 78 'Used when the last line in a file is missing a newline.'), | 79 'Used when the last line in a file is missing a newline.'), |
| (...skipping 19 matching lines...) Expand all Loading... |
| 98 'superfluous-parens', | 99 'superfluous-parens', |
| 99 'Used when a single item in parentheses follows an if, for, or ' | 100 'Used when a single item in parentheses follows an if, for, or ' |
| 100 'other keyword.'), | 101 'other keyword.'), |
| 101 'C0326': ('%s space %s %s %s\n%s', | 102 'C0326': ('%s space %s %s %s\n%s', |
| 102 'bad-whitespace', | 103 'bad-whitespace', |
| 103 ('Used when a wrong number of spaces is used around an operator, ' | 104 ('Used when a wrong number of spaces is used around an operator, ' |
| 104 'bracket or block opener.'), | 105 'bracket or block opener.'), |
| 105 {'old_names': [('C0323', 'no-space-after-operator'), | 106 {'old_names': [('C0323', 'no-space-after-operator'), |
| 106 ('C0324', 'no-space-after-comma'), | 107 ('C0324', 'no-space-after-comma'), |
| 107 ('C0322', 'no-space-before-operator')]}), | 108 ('C0322', 'no-space-before-operator')]}), |
| 108 'W0331': ('Use of the <> operator', | |
| 109 'old-ne-operator', | |
| 110 'Used when the deprecated "<>" operator is used instead ' | |
| 111 'of "!=".', | |
| 112 {'maxversion': (3, 0)}), | |
| 113 'W0332': ('Use of "l" as long integer identifier', | 109 'W0332': ('Use of "l" as long integer identifier', |
| 114 'lowercase-l-suffix', | 110 'lowercase-l-suffix', |
| 115 'Used when a lower case "l" is used to mark a long integer. You ' | 111 'Used when a lower case "l" is used to mark a long integer. You ' |
| 116 'should use a upper case "L" since the letter "l" looks too much ' | 112 'should use a upper case "L" since the letter "l" looks too much ' |
| 117 'like the digit "1"', | 113 'like the digit "1"', |
| 118 {'maxversion': (3, 0)}), | 114 {'maxversion': (3, 0)}), |
| 119 'W0333': ('Use of the `` operator', | 115 'C0327': ('Mixed line endings LF and CRLF', |
| 120 'backtick', | 116 'mixed-line-endings', |
| 121 'Used when the deprecated "``" (backtick) operator is used ' | 117 'Used when there are mixed (LF and CRLF) newline signs in a file.'
), |
| 122 'instead of the str() function.', | 118 'C0328': ('Unexpected line ending format. There is \'%s\' while it should be
\'%s\'.', |
| 123 {'scope': WarningScope.NODE, 'maxversion': (3, 0)}), | 119 'unexpected-line-ending-format', |
| 120 'Used when there is different newline than expected.'), |
| 124 } | 121 } |
| 125 | 122 |
| 126 | 123 |
| 127 def _underline_token(token): | 124 def _underline_token(token): |
| 128 length = token[3][1] - token[2][1] | 125 length = token[3][1] - token[2][1] |
| 129 offset = token[2][1] | 126 offset = token[2][1] |
| 130 return token[4] + (' ' * offset) + ('^' * length) | 127 return token[4] + (' ' * offset) + ('^' * length) |
| 131 | 128 |
| 132 | 129 |
| 133 def _column_distance(token1, token2): | 130 def _column_distance(token1, token2): |
| (...skipping 195 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 329 position, | 326 position, |
| 330 _Offsets(indentation + self._continuation_size, indentation), | 327 _Offsets(indentation + self._continuation_size, indentation), |
| 331 _BeforeBlockOffsets(indentation + self._continuation_size, | 328 _BeforeBlockOffsets(indentation + self._continuation_size, |
| 332 indentation + self._continuation_size * 2)) | 329 indentation + self._continuation_size * 2)) |
| 333 elif bracket == ':': | 330 elif bracket == ':': |
| 334 # If the dict key was on the same line as the open brace, the new | 331 # If the dict key was on the same line as the open brace, the new |
| 335 # correct indent should be relative to the key instead of the | 332 # correct indent should be relative to the key instead of the |
| 336 # current indent level | 333 # current indent level |
| 337 paren_align = self._cont_stack[-1].valid_outdent_offsets | 334 paren_align = self._cont_stack[-1].valid_outdent_offsets |
| 338 next_align = self._cont_stack[-1].valid_continuation_offsets.copy() | 335 next_align = self._cont_stack[-1].valid_continuation_offsets.copy() |
| 339 next_align[next_align.keys()[0] + self._continuation_size] = True | 336 next_align_keys = list(next_align.keys()) |
| 337 next_align[next_align_keys[0] + self._continuation_size] = True |
| 340 # Note that the continuation of | 338 # Note that the continuation of |
| 341 # d = { | 339 # d = { |
| 342 # 'a': 'b' | 340 # 'a': 'b' |
| 343 # 'c' | 341 # 'c' |
| 344 # } | 342 # } |
| 345 # is handled by the special-casing for hanging continued string inde
nts. | 343 # is handled by the special-casing for hanging continued string inde
nts. |
| 346 return _ContinuedIndent(HANGING_DICT_VALUE, bracket, position, paren
_align, next_align) | 344 return _ContinuedIndent(HANGING_DICT_VALUE, bracket, position, paren
_align, next_align) |
| 347 else: | 345 else: |
| 348 return _ContinuedIndent( | 346 return _ContinuedIndent( |
| 349 HANGING, | 347 HANGING, |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 394 else: | 392 else: |
| 395 self._cont_stack.append( | 393 self._cont_stack.append( |
| 396 self._continuation_inside_bracket(token, position)) | 394 self._continuation_inside_bracket(token, position)) |
| 397 | 395 |
| 398 | 396 |
| 399 class FormatChecker(BaseTokenChecker): | 397 class FormatChecker(BaseTokenChecker): |
| 400 """checks for : | 398 """checks for : |
| 401 * unauthorized constructions | 399 * unauthorized constructions |
| 402 * strict indentation | 400 * strict indentation |
| 403 * line length | 401 * line length |
| 404 * use of <> instead of != | |
| 405 """ | 402 """ |
| 406 | 403 |
| 407 __implements__ = (ITokenChecker, IAstroidChecker, IRawChecker) | 404 __implements__ = (ITokenChecker, IAstroidChecker, IRawChecker) |
| 408 | 405 |
| 409 # configuration section name | 406 # configuration section name |
| 410 name = 'format' | 407 name = 'format' |
| 411 # messages | 408 # messages |
| 412 msgs = MSGS | 409 msgs = MSGS |
| 413 # configuration options | 410 # configuration options |
| 414 # for available dict keys/values see the optik parser 'add_option' method | 411 # for available dict keys/values see the optik parser 'add_option' method |
| 415 options = (('max-line-length', | 412 options = (('max-line-length', |
| 416 {'default' : 80, 'type' : "int", 'metavar' : '<int>', | 413 {'default' : 100, 'type' : "int", 'metavar' : '<int>', |
| 417 'help' : 'Maximum number of characters on a single line.'}), | 414 'help' : 'Maximum number of characters on a single line.'}), |
| 418 ('ignore-long-lines', | 415 ('ignore-long-lines', |
| 419 {'type': 'regexp', 'metavar': '<regexp>', | 416 {'type': 'regexp', 'metavar': '<regexp>', |
| 420 'default': r'^\s*(# )?<?https?://\S+>?$', | 417 'default': r'^\s*(# )?<?https?://\S+>?$', |
| 421 'help': ('Regexp for a line that is allowed to be longer than ' | 418 'help': ('Regexp for a line that is allowed to be longer than ' |
| 422 'the limit.')}), | 419 'the limit.')}), |
| 423 ('single-line-if-stmt', | 420 ('single-line-if-stmt', |
| 424 {'default': False, 'type' : 'yn', 'metavar' : '<y_or_n>', | 421 {'default': False, 'type' : 'yn', 'metavar' : '<y_or_n>', |
| 425 'help' : ('Allow the body of an if to be on the same ' | 422 'help' : ('Allow the body of an if to be on the same ' |
| 426 'line as the test if there is no else.')}), | 423 'line as the test if there is no else.')}), |
| 427 ('no-space-check', | 424 ('no-space-check', |
| 428 {'default': ','.join(_NO_SPACE_CHECK_CHOICES), | 425 {'default': ','.join(_NO_SPACE_CHECK_CHOICES), |
| 429 'type': 'multiple_choice', | 426 'type': 'multiple_choice', |
| 430 'choices': _NO_SPACE_CHECK_CHOICES, | 427 'choices': _NO_SPACE_CHECK_CHOICES, |
| 431 'help': ('List of optional constructs for which whitespace ' | 428 'help': ('List of optional constructs for which whitespace ' |
| 432 'checking is disabled')}), | 429 'checking is disabled')}), |
| 433 ('max-module-lines', | 430 ('max-module-lines', |
| 434 {'default' : 1000, 'type' : 'int', 'metavar' : '<int>', | 431 {'default' : 1000, 'type' : 'int', 'metavar' : '<int>', |
| 435 'help': 'Maximum number of lines in a module'} | 432 'help': 'Maximum number of lines in a module'} |
| 436 ), | 433 ), |
| 437 ('indent-string', | 434 ('indent-string', |
| 438 {'default' : ' ', 'type' : "string", 'metavar' : '<string>', | 435 {'default' : ' ', 'type' : "string", 'metavar' : '<string>', |
| 439 'help' : 'String used as indentation unit. This is usually ' | 436 'help' : 'String used as indentation unit. This is usually ' |
| 440 '" " (4 spaces) or "\\t" (1 tab).'}), | 437 '" " (4 spaces) or "\\t" (1 tab).'}), |
| 441 ('indent-after-paren', | 438 ('indent-after-paren', |
| 442 {'type': 'int', 'metavar': '<int>', 'default': 4, | 439 {'type': 'int', 'metavar': '<int>', 'default': 4, |
| 443 'help': 'Number of spaces of indent required inside a hanging ' | 440 'help': 'Number of spaces of indent required inside a hanging ' |
| 444 ' or continued line.'}), | 441 ' or continued line.'}), |
| 442 ('expected-line-ending-format', |
| 443 {'type': 'choice', 'metavar': '<empty or LF or CRLF>', 'default'
: '', |
| 444 'choices': ['', 'LF', 'CRLF'], |
| 445 'help': 'Expected format of line ending, e.g. empty (any line e
nding), LF or CRLF.'}), |
| 445 ) | 446 ) |
| 446 | 447 |
| 447 def __init__(self, linter=None): | 448 def __init__(self, linter=None): |
| 448 BaseTokenChecker.__init__(self, linter) | 449 BaseTokenChecker.__init__(self, linter) |
| 449 self._lines = None | 450 self._lines = None |
| 450 self._visited_lines = None | 451 self._visited_lines = None |
| 451 self._bracket_stack = [None] | 452 self._bracket_stack = [None] |
| 452 | 453 |
| 453 def _pop_token(self): | 454 def _pop_token(self): |
| 454 self._bracket_stack.pop() | 455 self._bracket_stack.pop() |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 489 if self._inside_brackets(':') and tokens[start][1] == 'for': | 490 if self._inside_brackets(':') and tokens[start][1] == 'for': |
| 490 self._pop_token() | 491 self._pop_token() |
| 491 if tokens[start+1][1] != '(': | 492 if tokens[start+1][1] != '(': |
| 492 return | 493 return |
| 493 | 494 |
| 494 found_and_or = False | 495 found_and_or = False |
| 495 depth = 0 | 496 depth = 0 |
| 496 keyword_token = tokens[start][1] | 497 keyword_token = tokens[start][1] |
| 497 line_num = tokens[start][2][0] | 498 line_num = tokens[start][2][0] |
| 498 | 499 |
| 499 for i in xrange(start, len(tokens) - 1): | 500 for i in range(start, len(tokens) - 1): |
| 500 token = tokens[i] | 501 token = tokens[i] |
| 501 | 502 |
| 502 # If we hit a newline, then assume any parens were for continuation. | 503 # If we hit a newline, then assume any parens were for continuation. |
| 503 if token[0] == tokenize.NL: | 504 if token[0] == tokenize.NL: |
| 504 return | 505 return |
| 505 | 506 |
| 506 if token[1] == '(': | 507 if token[1] == '(': |
| 507 depth += 1 | 508 depth += 1 |
| 508 elif token[1] == ')': | 509 elif token[1] == ')': |
| 509 depth -= 1 | 510 depth -= 1 |
| (...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 615 self._check_space(tokens, i, (_MUST, _MUST)) | 616 self._check_space(tokens, i, (_MUST, _MUST)) |
| 616 | 617 |
| 617 def _check_space(self, tokens, i, policies): | 618 def _check_space(self, tokens, i, policies): |
| 618 def _policy_string(policy): | 619 def _policy_string(policy): |
| 619 if policy == _MUST: | 620 if policy == _MUST: |
| 620 return 'Exactly one', 'required' | 621 return 'Exactly one', 'required' |
| 621 else: | 622 else: |
| 622 return 'No', 'allowed' | 623 return 'No', 'allowed' |
| 623 | 624 |
| 624 def _name_construct(token): | 625 def _name_construct(token): |
| 625 if tokens[i][1] == ',': | 626 if token[1] == ',': |
| 626 return 'comma' | 627 return 'comma' |
| 627 elif tokens[i][1] == ':': | 628 elif token[1] == ':': |
| 628 return ':' | 629 return ':' |
| 629 elif tokens[i][1] in '()[]{}': | 630 elif token[1] in '()[]{}': |
| 630 return 'bracket' | 631 return 'bracket' |
| 631 elif tokens[i][1] in ('<', '>', '<=', '>=', '!=', '=='): | 632 elif token[1] in ('<', '>', '<=', '>=', '!=', '=='): |
| 632 return 'comparison' | 633 return 'comparison' |
| 633 else: | 634 else: |
| 634 if self._inside_brackets('('): | 635 if self._inside_brackets('('): |
| 635 return 'keyword argument assignment' | 636 return 'keyword argument assignment' |
| 636 else: | 637 else: |
| 637 return 'assignment' | 638 return 'assignment' |
| 638 | 639 |
| 639 good_space = [True, True] | 640 good_space = [True, True] |
| 640 pairs = [(tokens[i-1], tokens[i]), (tokens[i], tokens[i+1])] | 641 token = tokens[i] |
| 642 pairs = [(tokens[i-1], token), (token, tokens[i+1])] |
| 641 | 643 |
| 642 for other_idx, (policy, token_pair) in enumerate(zip(policies, pairs)): | 644 for other_idx, (policy, token_pair) in enumerate(zip(policies, pairs)): |
| 643 if token_pair[other_idx][0] in _EOL or policy == _IGNORE: | 645 if token_pair[other_idx][0] in _EOL or policy == _IGNORE: |
| 644 continue | 646 continue |
| 645 | 647 |
| 646 distance = _column_distance(*token_pair) | 648 distance = _column_distance(*token_pair) |
| 647 if distance is None: | 649 if distance is None: |
| 648 continue | 650 continue |
| 649 good_space[other_idx] = ( | 651 good_space[other_idx] = ( |
| 650 (policy == _MUST and distance == 1) or | 652 (policy == _MUST and distance == 1) or |
| 651 (policy == _MUST_NOT and distance == 0)) | 653 (policy == _MUST_NOT and distance == 0)) |
| 652 | 654 |
| 653 warnings = [] | 655 warnings = [] |
| 654 if not any(good_space) and policies[0] == policies[1]: | 656 if not any(good_space) and policies[0] == policies[1]: |
| 655 warnings.append((policies[0], 'around')) | 657 warnings.append((policies[0], 'around')) |
| 656 else: | 658 else: |
| 657 for ok, policy, position in zip(good_space, policies, ('before', 'af
ter')): | 659 for ok, policy, position in zip(good_space, policies, ('before', 'af
ter')): |
| 658 if not ok: | 660 if not ok: |
| 659 warnings.append((policy, position)) | 661 warnings.append((policy, position)) |
| 660 for policy, position in warnings: | 662 for policy, position in warnings: |
| 661 construct = _name_construct(tokens[i]) | 663 construct = _name_construct(token) |
| 662 count, state = _policy_string(policy) | 664 count, state = _policy_string(policy) |
| 663 self.add_message('bad-whitespace', line=tokens[i][2][0], | 665 self.add_message('bad-whitespace', line=token[2][0], |
| 664 args=(count, state, position, construct, | 666 args=(count, state, position, construct, |
| 665 _underline_token(tokens[i]))) | 667 _underline_token(token))) |
| 666 | 668 |
| 667 def _inside_brackets(self, left): | 669 def _inside_brackets(self, left): |
| 668 return self._bracket_stack[-1] == left | 670 return self._bracket_stack[-1] == left |
| 669 | 671 |
| 670 def _handle_old_ne_operator(self, tokens, i): | |
| 671 if tokens[i][1] == '<>': | |
| 672 self.add_message('old-ne-operator', line=tokens[i][2][0]) | |
| 673 | |
| 674 def _prepare_token_dispatcher(self): | 672 def _prepare_token_dispatcher(self): |
| 675 raw = [ | 673 raw = [ |
| 676 (_KEYWORD_TOKENS, | 674 (_KEYWORD_TOKENS, |
| 677 self._check_keyword_parentheses), | 675 self._check_keyword_parentheses), |
| 678 | 676 |
| 679 (_OPENING_BRACKETS, self._opening_bracket), | 677 (_OPENING_BRACKETS, self._opening_bracket), |
| 680 | 678 |
| 681 (_CLOSING_BRACKETS, self._closing_bracket), | 679 (_CLOSING_BRACKETS, self._closing_bracket), |
| 682 | 680 |
| 683 (['='], self._check_equals_spacing), | 681 (['='], self._check_equals_spacing), |
| 684 | 682 |
| 685 (_SPACED_OPERATORS, self._check_surrounded_by_space), | 683 (_SPACED_OPERATORS, self._check_surrounded_by_space), |
| 686 | 684 |
| 687 ([','], self._handle_comma), | 685 ([','], self._handle_comma), |
| 688 | 686 |
| 689 ([':'], self._handle_colon), | 687 ([':'], self._handle_colon), |
| 690 | 688 |
| 691 (['lambda'], self._open_lambda), | 689 (['lambda'], self._open_lambda), |
| 692 | 690 |
| 693 (['<>'], self._handle_old_ne_operator), | |
| 694 ] | 691 ] |
| 695 | 692 |
| 696 dispatch = {} | 693 dispatch = {} |
| 697 for tokens, handler in raw: | 694 for tokens, handler in raw: |
| 698 for token in tokens: | 695 for token in tokens: |
| 699 dispatch[token] = handler | 696 dispatch[token] = handler |
| 700 return dispatch | 697 return dispatch |
| 701 | 698 |
| 702 def process_tokens(self, tokens): | 699 def process_tokens(self, tokens): |
| 703 """process tokens and search for : | 700 """process tokens and search for : |
| 704 | 701 |
| 705 _ non strict indentation (i.e. not always using the <indent> parameter
as | 702 _ non strict indentation (i.e. not always using the <indent> parameter
as |
| 706 indent unit) | 703 indent unit) |
| 707 _ too long lines (i.e. longer than <max_chars>) | 704 _ too long lines (i.e. longer than <max_chars>) |
| 708 _ optionally bad construct (if given, bad_construct must be a compiled | 705 _ optionally bad construct (if given, bad_construct must be a compiled |
| 709 regular expression). | 706 regular expression). |
| 710 """ | 707 """ |
| 711 self._bracket_stack = [None] | 708 self._bracket_stack = [None] |
| 712 indents = [0] | 709 indents = [0] |
| 713 check_equal = False | 710 check_equal = False |
| 714 line_num = 0 | 711 line_num = 0 |
| 715 self._lines = {} | 712 self._lines = {} |
| 716 self._visited_lines = {} | 713 self._visited_lines = {} |
| 717 token_handlers = self._prepare_token_dispatcher() | 714 token_handlers = self._prepare_token_dispatcher() |
| 715 self._last_line_ending = None |
| 718 | 716 |
| 719 self._current_line = ContinuedLineState(tokens, self.config) | 717 self._current_line = ContinuedLineState(tokens, self.config) |
| 720 for idx, (tok_type, token, start, _, line) in enumerate(tokens): | 718 for idx, (tok_type, token, start, _, line) in enumerate(tokens): |
| 721 if start[0] != line_num: | 719 if start[0] != line_num: |
| 722 line_num = start[0] | 720 line_num = start[0] |
| 723 # A tokenizer oddity: if an indented line contains a multi-line | 721 # A tokenizer oddity: if an indented line contains a multi-line |
| 724 # docstring, the line member of the INDENT token does not contai
n | 722 # docstring, the line member of the INDENT token does not contai
n |
| 725 # the full line; therefore we check the next token on the line. | 723 # the full line; therefore we check the next token on the line. |
| 726 if tok_type == tokenize.INDENT: | 724 if tok_type == tokenize.INDENT: |
| 727 self.new_line(TokenWrapper(tokens), idx-1, idx+1) | 725 self.new_line(TokenWrapper(tokens), idx-1, idx+1) |
| 728 else: | 726 else: |
| 729 self.new_line(TokenWrapper(tokens), idx-1, idx) | 727 self.new_line(TokenWrapper(tokens), idx-1, idx) |
| 730 | 728 |
| 731 if tok_type == tokenize.NEWLINE: | 729 if tok_type == tokenize.NEWLINE: |
| 732 # a program statement, or ENDMARKER, will eventually follow, | 730 # a program statement, or ENDMARKER, will eventually follow, |
| 733 # after some (possibly empty) run of tokens of the form | 731 # after some (possibly empty) run of tokens of the form |
| 734 # (NL | COMMENT)* (INDENT | DEDENT+)? | 732 # (NL | COMMENT)* (INDENT | DEDENT+)? |
| 735 # If an INDENT appears, setting check_equal is wrong, and will | 733 # If an INDENT appears, setting check_equal is wrong, and will |
| 736 # be undone when we see the INDENT. | 734 # be undone when we see the INDENT. |
| 737 check_equal = True | 735 check_equal = True |
| 738 self._process_retained_warnings(TokenWrapper(tokens), idx) | 736 self._process_retained_warnings(TokenWrapper(tokens), idx) |
| 739 self._current_line.next_logical_line() | 737 self._current_line.next_logical_line() |
| 738 self._check_line_ending(token, line_num) |
| 740 elif tok_type == tokenize.INDENT: | 739 elif tok_type == tokenize.INDENT: |
| 741 check_equal = False | 740 check_equal = False |
| 742 self.check_indent_level(token, indents[-1]+1, line_num) | 741 self.check_indent_level(token, indents[-1]+1, line_num) |
| 743 indents.append(indents[-1]+1) | 742 indents.append(indents[-1]+1) |
| 744 elif tok_type == tokenize.DEDENT: | 743 elif tok_type == tokenize.DEDENT: |
| 745 # there's nothing we need to check here! what's important is | 744 # there's nothing we need to check here! what's important is |
| 746 # that when the run of DEDENTs ends, the indentation of the | 745 # that when the run of DEDENTs ends, the indentation of the |
| 747 # program statement (or ENDMARKER) that triggered the run is | 746 # program statement (or ENDMARKER) that triggered the run is |
| 748 # equal to what's left at the top of the indents stack | 747 # equal to what's left at the top of the indents stack |
| 749 check_equal = True | 748 check_equal = True |
| (...skipping 19 matching lines...) Expand all Loading... |
| 769 | 768 |
| 770 try: | 769 try: |
| 771 handler = token_handlers[token] | 770 handler = token_handlers[token] |
| 772 except KeyError: | 771 except KeyError: |
| 773 pass | 772 pass |
| 774 else: | 773 else: |
| 775 handler(tokens, idx) | 774 handler(tokens, idx) |
| 776 | 775 |
| 777 line_num -= 1 # to be ok with "wc -l" | 776 line_num -= 1 # to be ok with "wc -l" |
| 778 if line_num > self.config.max_module_lines: | 777 if line_num > self.config.max_module_lines: |
| 779 self.add_message('too-many-lines', args=line_num, line=1) | 778 # Get the line where the too-many-lines (or its message id) |
| 779 # was disabled or default to 1. |
| 780 symbol = self.linter.msgs_store.check_message_id('too-many-lines') |
| 781 names = (symbol.msgid, 'too-many-lines') |
| 782 line = next(filter(None, |
| 783 map(self.linter._pragma_lineno.get, names)), 1) |
| 784 self.add_message('too-many-lines', |
| 785 args=(line_num, self.config.max_module_lines), |
| 786 line=line) |
| 787 |
| 788 def _check_line_ending(self, line_ending, line_num): |
| 789 # check if line endings are mixed |
| 790 if self._last_line_ending is not None: |
| 791 if line_ending != self._last_line_ending: |
| 792 self.add_message('mixed-line-endings', line=line_num) |
| 793 |
| 794 self._last_line_ending = line_ending |
| 795 |
| 796 # check if line ending is as expected |
| 797 expected = self.config.expected_line_ending_format |
| 798 if expected: |
| 799 line_ending = reduce(lambda x, y: x + y if x != y else x, line_endin
g, "") # reduce multiple \n\n\n\n to one \n |
| 800 line_ending = 'LF' if line_ending == '\n' else 'CRLF' |
| 801 if line_ending != expected: |
| 802 self.add_message('unexpected-line-ending-format', args=(line_end
ing, expected), line=line_num) |
| 803 |
| 780 | 804 |
| 781 def _process_retained_warnings(self, tokens, current_pos): | 805 def _process_retained_warnings(self, tokens, current_pos): |
| 782 single_line_block_stmt = not _last_token_on_line_is(tokens, current_pos,
':') | 806 single_line_block_stmt = not _last_token_on_line_is(tokens, current_pos,
':') |
| 783 | 807 |
| 784 for indent_pos, state, offsets in self._current_line.retained_warnings: | 808 for indent_pos, state, offsets in self._current_line.retained_warnings: |
| 785 block_type = offsets[tokens.start_col(indent_pos)] | 809 block_type = offsets[tokens.start_col(indent_pos)] |
| 786 hints = dict((k, v) for k, v in offsets.iteritems() | 810 hints = dict((k, v) for k, v in six.iteritems(offsets) |
| 787 if v != block_type) | 811 if v != block_type) |
| 788 if single_line_block_stmt and block_type == WITH_BODY: | 812 if single_line_block_stmt and block_type == WITH_BODY: |
| 789 self._add_continuation_message(state, hints, tokens, indent_pos) | 813 self._add_continuation_message(state, hints, tokens, indent_pos) |
| 790 elif not single_line_block_stmt and block_type == SINGLE_LINE: | 814 elif not single_line_block_stmt and block_type == SINGLE_LINE: |
| 791 self._add_continuation_message(state, hints, tokens, indent_pos) | 815 self._add_continuation_message(state, hints, tokens, indent_pos) |
| 792 | 816 |
| 793 def _check_continued_indentation(self, tokens, next_idx): | 817 def _check_continued_indentation(self, tokens, next_idx): |
| 794 def same_token_around_nl(token_type): | 818 def same_token_around_nl(token_type): |
| 795 return (tokens.type(next_idx) == token_type and | 819 return (tokens.type(next_idx) == token_type and |
| 796 tokens.type(next_idx-2) == token_type) | 820 tokens.type(next_idx-2) == token_type) |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 850 self._check_multi_statement_line(node, line) | 874 self._check_multi_statement_line(node, line) |
| 851 return | 875 return |
| 852 if line in self._visited_lines: | 876 if line in self._visited_lines: |
| 853 return | 877 return |
| 854 try: | 878 try: |
| 855 tolineno = node.blockstart_tolineno | 879 tolineno = node.blockstart_tolineno |
| 856 except AttributeError: | 880 except AttributeError: |
| 857 tolineno = node.tolineno | 881 tolineno = node.tolineno |
| 858 assert tolineno, node | 882 assert tolineno, node |
| 859 lines = [] | 883 lines = [] |
| 860 for line in xrange(line, tolineno + 1): | 884 for line in range(line, tolineno + 1): |
| 861 self._visited_lines[line] = 1 | 885 self._visited_lines[line] = 1 |
| 862 try: | 886 try: |
| 863 lines.append(self._lines[line].rstrip()) | 887 lines.append(self._lines[line].rstrip()) |
| 864 except KeyError: | 888 except KeyError: |
| 865 lines.append('') | 889 lines.append('') |
| 866 | 890 |
| 867 def _check_multi_statement_line(self, node, line): | 891 def _check_multi_statement_line(self, node, line): |
| 868 """Check for lines containing multiple statements.""" | 892 """Check for lines containing multiple statements.""" |
| 869 # Do not warn about multiple nested context managers | 893 # Do not warn about multiple nested context managers |
| 870 # in with statements. | 894 # in with statements. |
| 871 if isinstance(node, nodes.With): | 895 if isinstance(node, nodes.With): |
| 872 return | 896 return |
| 873 # For try... except... finally..., the two nodes | 897 # For try... except... finally..., the two nodes |
| 874 # appear to be on the same line due to how the AST is built. | 898 # appear to be on the same line due to how the AST is built. |
| 875 if (isinstance(node, nodes.TryExcept) and | 899 if (isinstance(node, nodes.TryExcept) and |
| 876 isinstance(node.parent, nodes.TryFinally)): | 900 isinstance(node.parent, nodes.TryFinally)): |
| 877 return | 901 return |
| 878 if (isinstance(node.parent, nodes.If) and not node.parent.orelse | 902 if (isinstance(node.parent, nodes.If) and not node.parent.orelse |
| 879 and self.config.single_line_if_stmt): | 903 and self.config.single_line_if_stmt): |
| 880 return | 904 return |
| 881 self.add_message('multiple-statements', node=node) | 905 self.add_message('multiple-statements', node=node) |
| 882 self._visited_lines[line] = 2 | 906 self._visited_lines[line] = 2 |
| 883 | 907 |
| 884 @check_messages('backtick') | |
| 885 def visit_backquote(self, node): | |
| 886 self.add_message('backtick', node=node) | |
| 887 | |
| 888 def check_lines(self, lines, i): | 908 def check_lines(self, lines, i): |
| 889 """check lines have less than a maximum number of characters | 909 """check lines have less than a maximum number of characters |
| 890 """ | 910 """ |
| 891 max_chars = self.config.max_line_length | 911 max_chars = self.config.max_line_length |
| 892 ignore_long_line = self.config.ignore_long_lines | 912 ignore_long_line = self.config.ignore_long_lines |
| 893 | 913 |
| 894 for line in lines.splitlines(True): | 914 for line in lines.splitlines(True): |
| 895 if not line.endswith('\n'): | 915 if not line.endswith('\n'): |
| 896 self.add_message('missing-final-newline', line=i) | 916 self.add_message('missing-final-newline', line=i) |
| 897 else: | 917 else: |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 935 if indent[0] == '\t': | 955 if indent[0] == '\t': |
| 936 i_type = 'tabs' | 956 i_type = 'tabs' |
| 937 self.add_message('bad-indentation', line=line_num, | 957 self.add_message('bad-indentation', line=line_num, |
| 938 args=(level * unit_size + len(suppl), i_type, | 958 args=(level * unit_size + len(suppl), i_type, |
| 939 expected * unit_size)) | 959 expected * unit_size)) |
| 940 | 960 |
| 941 | 961 |
| 942 def register(linter): | 962 def register(linter): |
| 943 """required method to auto register this checker """ | 963 """required method to auto register this checker """ |
| 944 linter.register_checker(FormatChecker(linter)) | 964 linter.register_checker(FormatChecker(linter)) |
| OLD | NEW |