| OLD | NEW |
| 1 # Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). | 1 # Copyright (c) 2003-2010 Sylvain Thenault (thenault@gmail.com). |
| 2 # | 2 # Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE). |
| 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 # 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 re, sys |
| 25 import sys | |
| 26 import tokenize | 25 import tokenize |
| 27 | |
| 28 if not hasattr(tokenize, 'NL'): | 26 if not hasattr(tokenize, 'NL'): |
| 29 raise ValueError("tokenize.NL doesn't exist -- tokenize module too old") | 27 raise ValueError("tokenize.NL doesn't exist -- tokenize module too old") |
| 30 | 28 |
| 31 from astroid import nodes | 29 from logilab.common.textutils import pretty_match |
| 30 from logilab.astng import nodes |
| 32 | 31 |
| 33 from pylint.interfaces import ITokenChecker, IAstroidChecker, IRawChecker | 32 from pylint.interfaces import IRawChecker, IASTNGChecker |
| 34 from pylint.checkers import BaseTokenChecker | 33 from pylint.checkers import BaseRawChecker |
| 35 from pylint.checkers.utils import check_messages | 34 from pylint.checkers.utils import check_messages |
| 36 from pylint.utils import WarningScope, OPTION_RGX | |
| 37 | |
| 38 _CONTINUATION_BLOCK_OPENERS = ['elif', 'except', 'for', 'if', 'while', 'def', 'c
lass'] | |
| 39 _KEYWORD_TOKENS = ['assert', 'del', 'elif', 'except', 'for', 'if', 'in', 'not', | |
| 40 'raise', 'return', 'while', 'yield'] | |
| 41 if sys.version_info < (3, 0): | |
| 42 _KEYWORD_TOKENS.append('print') | |
| 43 | |
| 44 _SPACED_OPERATORS = ['==', '<', '>', '!=', '<>', '<=', '>=', | |
| 45 '+=', '-=', '*=', '**=', '/=', '//=', '&=', '|=', '^=', | |
| 46 '%=', '>>=', '<<='] | |
| 47 _OPENING_BRACKETS = ['(', '[', '{'] | |
| 48 _CLOSING_BRACKETS = [')', ']', '}'] | |
| 49 _TAB_LENGTH = 8 | |
| 50 | |
| 51 _EOL = frozenset([tokenize.NEWLINE, tokenize.NL, tokenize.COMMENT]) | |
| 52 _JUNK_TOKENS = (tokenize.COMMENT, tokenize.NL) | |
| 53 | |
| 54 # Whitespace checking policy constants | |
| 55 _MUST = 0 | |
| 56 _MUST_NOT = 1 | |
| 57 _IGNORE = 2 | |
| 58 | |
| 59 # Whitespace checking config constants | |
| 60 _DICT_SEPARATOR = 'dict-separator' | |
| 61 _TRAILING_COMMA = 'trailing-comma' | |
| 62 _NO_SPACE_CHECK_CHOICES = [_TRAILING_COMMA, _DICT_SEPARATOR] | |
| 63 | 35 |
| 64 MSGS = { | 36 MSGS = { |
| 65 'C0301': ('Line too long (%s/%s)', | 37 'C0301': ('Line too long (%s/%s)', |
| 66 'line-too-long', | |
| 67 'Used when a line is longer than a given number of characters.'), | 38 'Used when a line is longer than a given number of characters.'), |
| 68 'C0302': ('Too many lines in module (%s)', # was W0302 | 39 'C0302': ('Too many lines in module (%s)', # was W0302 |
| 69 'too-many-lines', | |
| 70 'Used when a module has too much lines, reducing its readability.' | 40 'Used when a module has too much lines, reducing its readability.' |
| 71 ), | 41 ), |
| 72 'C0303': ('Trailing whitespace', | 42 |
| 73 'trailing-whitespace', | |
| 74 'Used when there is whitespace between the end of a line and the ' | |
| 75 'newline.'), | |
| 76 'C0304': ('Final newline missing', | |
| 77 'missing-final-newline', | |
| 78 'Used when the last line in a file is missing a newline.'), | |
| 79 'W0311': ('Bad indentation. Found %s %s, expected %s', | 43 'W0311': ('Bad indentation. Found %s %s, expected %s', |
| 80 'bad-indentation', | |
| 81 'Used when an unexpected number of indentation\'s tabulations or ' | 44 'Used when an unexpected number of indentation\'s tabulations or ' |
| 82 'spaces has been found.'), | 45 'spaces has been found.'), |
| 83 'C0330': ('Wrong %s indentation%s.\n%s%s', | |
| 84 'bad-continuation', | |
| 85 'TODO'), | |
| 86 'W0312': ('Found indentation with %ss instead of %ss', | 46 'W0312': ('Found indentation with %ss instead of %ss', |
| 87 'mixed-indentation', | |
| 88 'Used when there are some mixed tabs and spaces in a module.'), | 47 'Used when there are some mixed tabs and spaces in a module.'), |
| 89 'W0301': ('Unnecessary semicolon', # was W0106 | 48 'W0301': ('Unnecessary semicolon', # was W0106 |
| 90 'unnecessary-semicolon', | |
| 91 'Used when a statement is ended by a semi-colon (";"), which \ | 49 'Used when a statement is ended by a semi-colon (";"), which \ |
| 92 isn\'t necessary (that\'s python, not C ;).'), | 50 isn\'t necessary (that\'s python, not C ;).'), |
| 93 'C0321': ('More than one statement on a single line', | 51 'C0321': ('More than one statement on a single line', |
| 94 'multiple-statements', | 52 'Used when more than on statement are found on the same line.'), |
| 95 'Used when more than on statement are found on the same line.', | 53 'C0322': ('Operator not preceded by a space\n%s', |
| 96 {'scope': WarningScope.NODE}), | 54 'Used when one of the following operator (!= | <= | == | >= | < ' |
| 97 'C0325' : ('Unnecessary parens after %r keyword', | 55 '| > | = | \+= | -= | \*= | /= | %) is not preceded by a space.'), |
| 98 'superfluous-parens', | 56 'C0323': ('Operator not followed by a space\n%s', |
| 99 'Used when a single item in parentheses follows an if, for, or ' | 57 'Used when one of the following operator (!= | <= | == | >= | < ' |
| 100 'other keyword.'), | 58 '| > | = | \+= | -= | \*= | /= | %) is not followed by a space.'), |
| 101 'C0326': ('%s space %s %s %s\n%s', | 59 'C0324': ('Comma not followed by a space\n%s', |
| 102 'bad-whitespace', | 60 'Used when a comma (",") is not followed by a space.'), |
| 103 ('Used when a wrong number of spaces is used around an operator, ' | 61 } |
| 104 'bracket or block opener.'), | 62 |
| 105 {'old_names': [('C0323', 'no-space-after-operator'), | 63 if sys.version_info < (3, 0): |
| 106 ('C0324', 'no-space-after-comma'), | 64 |
| 107 ('C0322', 'no-space-before-operator')]}), | 65 MSGS.update({ |
| 108 'W0331': ('Use of the <> operator', | 66 'W0331': ('Use of the <> operator', |
| 109 'old-ne-operator', | 67 'Used when the deprecated "<>" operator is used instead \ |
| 110 'Used when the deprecated "<>" operator is used instead ' | 68 of "!=".'), |
| 111 'of "!=".', | 69 'W0332': ('Use l as long integer identifier', |
| 112 {'maxversion': (3, 0)}), | |
| 113 'W0332': ('Use of "l" as long integer identifier', | |
| 114 'lowercase-l-suffix', | |
| 115 'Used when a lower case "l" is used to mark a long integer. You ' | 70 '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 ' | 71 'should use a upper case "L" since the letter "l" looks too much ' |
| 117 'like the digit "1"', | 72 'like the digit "1"'), |
| 118 {'maxversion': (3, 0)}), | |
| 119 'W0333': ('Use of the `` operator', | 73 'W0333': ('Use of the `` operator', |
| 120 'backtick', | |
| 121 'Used when the deprecated "``" (backtick) operator is used ' | 74 'Used when the deprecated "``" (backtick) operator is used ' |
| 122 'instead of the str() function.', | 75 'instead of the str() function.'), |
| 123 {'scope': WarningScope.NODE, 'maxversion': (3, 0)}), | 76 }) |
| 124 } | 77 |
| 78 # simple quoted string rgx |
| 79 SQSTRING_RGX = r'"([^"\\]|\\.)*?"' |
| 80 # simple apostrophed rgx |
| 81 SASTRING_RGX = r"'([^'\\]|\\.)*?'" |
| 82 # triple quoted string rgx |
| 83 TQSTRING_RGX = r'"""([^"]|("(?!"")))*?(""")' |
| 84 # triple apostrophed string rgx # FIXME english please |
| 85 TASTRING_RGX = r"'''([^']|('(?!'')))*?(''')" |
| 86 |
| 87 # finally, the string regular expression |
| 88 STRING_RGX = re.compile('(%s)|(%s)|(%s)|(%s)' % (TQSTRING_RGX, TASTRING_RGX, |
| 89 SQSTRING_RGX, SASTRING_RGX), |
| 90 re.MULTILINE|re.DOTALL) |
| 91 |
| 92 COMMENT_RGX = re.compile("#.*$", re.M) |
| 93 |
| 94 OPERATORS = r'!=|<=|==|>=|<|>|=|\+=|-=|\*=|/=|%' |
| 95 |
| 96 OP_RGX_MATCH_1 = r'[^(]*(?<!\s|\^|<|>|=|\+|-|\*|/|!|%%|&|\|)(%s).*' % OPERATORS |
| 97 OP_RGX_SEARCH_1 = r'(?<!\s|\^|<|>|=|\+|-|\*|/|!|%%|&|\|)(%s)' % OPERATORS |
| 98 |
| 99 OP_RGX_MATCH_2 = r'[^(]*(%s)(?!\s|=|>|<).*' % OPERATORS |
| 100 OP_RGX_SEARCH_2 = r'(%s)(?!\s|=|>)' % OPERATORS |
| 101 |
| 102 BAD_CONSTRUCT_RGXS = ( |
| 103 |
| 104 (re.compile(OP_RGX_MATCH_1, re.M), |
| 105 re.compile(OP_RGX_SEARCH_1, re.M), |
| 106 'C0322'), |
| 107 |
| 108 (re.compile(OP_RGX_MATCH_2, re.M), |
| 109 re.compile(OP_RGX_SEARCH_2, re.M), |
| 110 'C0323'), |
| 111 |
| 112 (re.compile(r'.*,[^(\s|\]|}|\))].*', re.M), |
| 113 re.compile(r',[^\s)]', re.M), |
| 114 'C0324'), |
| 115 ) |
| 125 | 116 |
| 126 | 117 |
| 127 def _underline_token(token): | 118 def get_string_coords(line): |
| 128 length = token[3][1] - token[2][1] | 119 """return a list of string positions (tuple (start, end)) in the line |
| 129 offset = token[2][1] | 120 """ |
| 130 return token[4] + (' ' * offset) + ('^' * length) | 121 result = [] |
| 122 for match in re.finditer(STRING_RGX, line): |
| 123 result.append( (match.start(), match.end()) ) |
| 124 return result |
| 125 |
| 126 def in_coords(match, string_coords): |
| 127 """return true if the match is in the string coord""" |
| 128 mstart = match.start() |
| 129 for start, end in string_coords: |
| 130 if mstart >= start and mstart < end: |
| 131 return True |
| 132 return False |
| 133 |
| 134 def check_line(line): |
| 135 """check a line for a bad construction |
| 136 if it founds one, return a message describing the problem |
| 137 else return None |
| 138 """ |
| 139 cleanstr = COMMENT_RGX.sub('', STRING_RGX.sub('', line)) |
| 140 for rgx_match, rgx_search, msg_id in BAD_CONSTRUCT_RGXS: |
| 141 if rgx_match.match(cleanstr): |
| 142 string_positions = get_string_coords(line) |
| 143 for match in re.finditer(rgx_search, line): |
| 144 if not in_coords(match, string_positions): |
| 145 return msg_id, pretty_match(match, line.rstrip()) |
| 131 | 146 |
| 132 | 147 |
| 133 def _column_distance(token1, token2): | 148 class FormatChecker(BaseRawChecker): |
| 134 if token1 == token2: | |
| 135 return 0 | |
| 136 if token2[3] < token1[3]: | |
| 137 token1, token2 = token2, token1 | |
| 138 if token1[3][0] != token2[2][0]: | |
| 139 return None | |
| 140 return token2[2][1] - token1[3][1] | |
| 141 | |
| 142 | |
| 143 def _last_token_on_line_is(tokens, line_end, token): | |
| 144 return (line_end > 0 and tokens.token(line_end-1) == token or | |
| 145 line_end > 1 and tokens.token(line_end-2) == token | |
| 146 and tokens.type(line_end-1) == tokenize.COMMENT) | |
| 147 | |
| 148 | |
| 149 def _token_followed_by_eol(tokens, position): | |
| 150 return (tokens.type(position+1) == tokenize.NL or | |
| 151 tokens.type(position+1) == tokenize.COMMENT and | |
| 152 tokens.type(position+2) == tokenize.NL) | |
| 153 | |
| 154 | |
| 155 def _get_indent_length(line): | |
| 156 """Return the length of the indentation on the given token's line.""" | |
| 157 result = 0 | |
| 158 for char in line: | |
| 159 if char == ' ': | |
| 160 result += 1 | |
| 161 elif char == '\t': | |
| 162 result += _TAB_LENGTH | |
| 163 else: | |
| 164 break | |
| 165 return result | |
| 166 | |
| 167 | |
| 168 def _get_indent_hint_line(bar_positions, bad_position): | |
| 169 """Return a line with |s for each of the positions in the given lists.""" | |
| 170 if not bar_positions: | |
| 171 return '' | |
| 172 markers = [(pos, '|') for pos in bar_positions] | |
| 173 markers.append((bad_position, '^')) | |
| 174 markers.sort() | |
| 175 line = [' '] * (markers[-1][0] + 1) | |
| 176 for position, marker in markers: | |
| 177 line[position] = marker | |
| 178 return ''.join(line) | |
| 179 | |
| 180 | |
| 181 class _ContinuedIndent(object): | |
| 182 __slots__ = ('valid_outdent_offsets', | |
| 183 'valid_continuation_offsets', | |
| 184 'context_type', | |
| 185 'token', | |
| 186 'position') | |
| 187 | |
| 188 def __init__(self, | |
| 189 context_type, | |
| 190 token, | |
| 191 position, | |
| 192 valid_outdent_offsets, | |
| 193 valid_continuation_offsets): | |
| 194 self.valid_outdent_offsets = valid_outdent_offsets | |
| 195 self.valid_continuation_offsets = valid_continuation_offsets | |
| 196 self.context_type = context_type | |
| 197 self.position = position | |
| 198 self.token = token | |
| 199 | |
| 200 | |
| 201 # The contexts for hanging indents. | |
| 202 # A hanging indented dictionary value after : | |
| 203 HANGING_DICT_VALUE = 'dict-value' | |
| 204 # Hanging indentation in an expression. | |
| 205 HANGING = 'hanging' | |
| 206 # Hanging indentation in a block header. | |
| 207 HANGING_BLOCK = 'hanging-block' | |
| 208 # Continued indentation inside an expression. | |
| 209 CONTINUED = 'continued' | |
| 210 # Continued indentation in a block header. | |
| 211 CONTINUED_BLOCK = 'continued-block' | |
| 212 | |
| 213 SINGLE_LINE = 'single' | |
| 214 WITH_BODY = 'multi' | |
| 215 | |
| 216 _CONTINUATION_MSG_PARTS = { | |
| 217 HANGING_DICT_VALUE: ('hanging', ' in dict value'), | |
| 218 HANGING: ('hanging', ''), | |
| 219 HANGING_BLOCK: ('hanging', ' before block'), | |
| 220 CONTINUED: ('continued', ''), | |
| 221 CONTINUED_BLOCK: ('continued', ' before block'), | |
| 222 } | |
| 223 | |
| 224 | |
| 225 def _Offsets(*args): | |
| 226 """Valid indentation offsets for a continued line.""" | |
| 227 return dict((a, None) for a in args) | |
| 228 | |
| 229 | |
| 230 def _BeforeBlockOffsets(single, with_body): | |
| 231 """Valid alternative indent offsets for continued lines before blocks. | |
| 232 | |
| 233 :param single: Valid offset for statements on a single logical line. | |
| 234 :param with_body: Valid offset for statements on several lines. | |
| 235 """ | |
| 236 return {single: SINGLE_LINE, with_body: WITH_BODY} | |
| 237 | |
| 238 | |
| 239 class TokenWrapper(object): | |
| 240 """A wrapper for readable access to token information.""" | |
| 241 | |
| 242 def __init__(self, tokens): | |
| 243 self._tokens = tokens | |
| 244 | |
| 245 def token(self, idx): | |
| 246 return self._tokens[idx][1] | |
| 247 | |
| 248 def type(self, idx): | |
| 249 return self._tokens[idx][0] | |
| 250 | |
| 251 def start_line(self, idx): | |
| 252 return self._tokens[idx][2][0] | |
| 253 | |
| 254 def start_col(self, idx): | |
| 255 return self._tokens[idx][2][1] | |
| 256 | |
| 257 def line(self, idx): | |
| 258 return self._tokens[idx][4] | |
| 259 | |
| 260 | |
| 261 class ContinuedLineState(object): | |
| 262 """Tracker for continued indentation inside a logical line.""" | |
| 263 | |
| 264 def __init__(self, tokens, config): | |
| 265 self._line_start = -1 | |
| 266 self._cont_stack = [] | |
| 267 self._is_block_opener = False | |
| 268 self.retained_warnings = [] | |
| 269 self._config = config | |
| 270 self._tokens = TokenWrapper(tokens) | |
| 271 | |
| 272 @property | |
| 273 def has_content(self): | |
| 274 return bool(self._cont_stack) | |
| 275 | |
| 276 @property | |
| 277 def _block_indent_size(self): | |
| 278 return len(self._config.indent_string.replace('\t', ' ' * _TAB_LENGTH)) | |
| 279 | |
| 280 @property | |
| 281 def _continuation_size(self): | |
| 282 return self._config.indent_after_paren | |
| 283 | |
| 284 def handle_line_start(self, pos): | |
| 285 """Record the first non-junk token at the start of a line.""" | |
| 286 if self._line_start > -1: | |
| 287 return | |
| 288 self._is_block_opener = self._tokens.token(pos) in _CONTINUATION_BLOCK_O
PENERS | |
| 289 self._line_start = pos | |
| 290 | |
| 291 def next_physical_line(self): | |
| 292 """Prepares the tracker for a new physical line (NL).""" | |
| 293 self._line_start = -1 | |
| 294 self._is_block_opener = False | |
| 295 | |
| 296 def next_logical_line(self): | |
| 297 """Prepares the tracker for a new logical line (NEWLINE). | |
| 298 | |
| 299 A new logical line only starts with block indentation. | |
| 300 """ | |
| 301 self.next_physical_line() | |
| 302 self.retained_warnings = [] | |
| 303 self._cont_stack = [] | |
| 304 | |
| 305 def add_block_warning(self, token_position, state, valid_offsets): | |
| 306 self.retained_warnings.append((token_position, state, valid_offsets)) | |
| 307 | |
| 308 def get_valid_offsets(self, idx): | |
| 309 """"Returns the valid offsets for the token at the given position.""" | |
| 310 # The closing brace on a dict or the 'for' in a dict comprehension may | |
| 311 # reset two indent levels because the dict value is ended implicitly | |
| 312 stack_top = -1 | |
| 313 if self._tokens.token(idx) in ('}', 'for') and self._cont_stack[-1].toke
n == ':': | |
| 314 stack_top = -2 | |
| 315 indent = self._cont_stack[stack_top] | |
| 316 if self._tokens.token(idx) in _CLOSING_BRACKETS: | |
| 317 valid_offsets = indent.valid_outdent_offsets | |
| 318 else: | |
| 319 valid_offsets = indent.valid_continuation_offsets | |
| 320 return indent, valid_offsets.copy() | |
| 321 | |
| 322 def _hanging_indent_after_bracket(self, bracket, position): | |
| 323 """Extracts indentation information for a hanging indent.""" | |
| 324 indentation = _get_indent_length(self._tokens.line(position)) | |
| 325 if self._is_block_opener and self._continuation_size == self._block_inde
nt_size: | |
| 326 return _ContinuedIndent( | |
| 327 HANGING_BLOCK, | |
| 328 bracket, | |
| 329 position, | |
| 330 _Offsets(indentation + self._continuation_size, indentation), | |
| 331 _BeforeBlockOffsets(indentation + self._continuation_size, | |
| 332 indentation + self._continuation_size * 2)) | |
| 333 elif bracket == ':': | |
| 334 # 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 | |
| 336 # current indent level | |
| 337 paren_align = self._cont_stack[-1].valid_outdent_offsets | |
| 338 next_align = self._cont_stack[-1].valid_continuation_offsets.copy() | |
| 339 next_align[next_align.keys()[0] + self._continuation_size] = True | |
| 340 # Note that the continuation of | |
| 341 # d = { | |
| 342 # 'a': 'b' | |
| 343 # 'c' | |
| 344 # } | |
| 345 # is handled by the special-casing for hanging continued string inde
nts. | |
| 346 return _ContinuedIndent(HANGING_DICT_VALUE, bracket, position, paren
_align, next_align) | |
| 347 else: | |
| 348 return _ContinuedIndent( | |
| 349 HANGING, | |
| 350 bracket, | |
| 351 position, | |
| 352 _Offsets(indentation, indentation + self._continuation_size), | |
| 353 _Offsets(indentation + self._continuation_size)) | |
| 354 | |
| 355 def _continuation_inside_bracket(self, bracket, pos): | |
| 356 """Extracts indentation information for a continued indent.""" | |
| 357 indentation = _get_indent_length(self._tokens.line(pos)) | |
| 358 if self._is_block_opener and self._tokens.start_col(pos+1) - indentation
== self._block_indent_size: | |
| 359 return _ContinuedIndent( | |
| 360 CONTINUED_BLOCK, | |
| 361 bracket, | |
| 362 pos, | |
| 363 _Offsets(self._tokens.start_col(pos)), | |
| 364 _BeforeBlockOffsets(self._tokens.start_col(pos+1), | |
| 365 self._tokens.start_col(pos+1) + self._contin
uation_size)) | |
| 366 else: | |
| 367 return _ContinuedIndent( | |
| 368 CONTINUED, | |
| 369 bracket, | |
| 370 pos, | |
| 371 _Offsets(self._tokens.start_col(pos)), | |
| 372 _Offsets(self._tokens.start_col(pos+1))) | |
| 373 | |
| 374 def pop_token(self): | |
| 375 self._cont_stack.pop() | |
| 376 | |
| 377 def push_token(self, token, position): | |
| 378 """Pushes a new token for continued indentation on the stack. | |
| 379 | |
| 380 Tokens that can modify continued indentation offsets are: | |
| 381 * opening brackets | |
| 382 * 'lambda' | |
| 383 * : inside dictionaries | |
| 384 | |
| 385 push_token relies on the caller to filter out those | |
| 386 interesting tokens. | |
| 387 | |
| 388 :param token: The concrete token | |
| 389 :param position: The position of the token in the stream. | |
| 390 """ | |
| 391 if _token_followed_by_eol(self._tokens, position): | |
| 392 self._cont_stack.append( | |
| 393 self._hanging_indent_after_bracket(token, position)) | |
| 394 else: | |
| 395 self._cont_stack.append( | |
| 396 self._continuation_inside_bracket(token, position)) | |
| 397 | |
| 398 | |
| 399 class FormatChecker(BaseTokenChecker): | |
| 400 """checks for : | 149 """checks for : |
| 401 * unauthorized constructions | 150 * unauthorized constructions |
| 402 * strict indentation | 151 * strict indentation |
| 403 * line length | 152 * line length |
| 404 * use of <> instead of != | 153 * use of <> instead of != |
| 405 """ | 154 """ |
| 406 | 155 |
| 407 __implements__ = (ITokenChecker, IAstroidChecker, IRawChecker) | 156 __implements__ = (IRawChecker, IASTNGChecker) |
| 408 | 157 |
| 409 # configuration section name | 158 # configuration section name |
| 410 name = 'format' | 159 name = 'format' |
| 411 # messages | 160 # messages |
| 412 msgs = MSGS | 161 msgs = MSGS |
| 413 # configuration options | 162 # configuration options |
| 414 # for available dict keys/values see the optik parser 'add_option' method | 163 # for available dict keys/values see the optik parser 'add_option' method |
| 415 options = (('max-line-length', | 164 options = (('max-line-length', |
| 416 {'default' : 80, 'type' : "int", 'metavar' : '<int>', | 165 {'default' : 80, 'type' : "int", 'metavar' : '<int>', |
| 417 'help' : 'Maximum number of characters on a single line.'}), | 166 'help' : 'Maximum number of characters on a single line.'}), |
| 418 ('ignore-long-lines', | |
| 419 {'type': 'regexp', 'metavar': '<regexp>', | |
| 420 'default': r'^\s*(# )?<?https?://\S+>?$', | |
| 421 'help': ('Regexp for a line that is allowed to be longer than ' | |
| 422 'the limit.')}), | |
| 423 ('single-line-if-stmt', | |
| 424 {'default': False, 'type' : 'yn', 'metavar' : '<y_or_n>', | |
| 425 'help' : ('Allow the body of an if to be on the same ' | |
| 426 'line as the test if there is no else.')}), | |
| 427 ('no-space-check', | |
| 428 {'default': ','.join(_NO_SPACE_CHECK_CHOICES), | |
| 429 'type': 'multiple_choice', | |
| 430 'choices': _NO_SPACE_CHECK_CHOICES, | |
| 431 'help': ('List of optional constructs for which whitespace ' | |
| 432 'checking is disabled')}), | |
| 433 ('max-module-lines', | 167 ('max-module-lines', |
| 434 {'default' : 1000, 'type' : 'int', 'metavar' : '<int>', | 168 {'default' : 1000, 'type' : 'int', 'metavar' : '<int>', |
| 435 'help': 'Maximum number of lines in a module'} | 169 'help': 'Maximum number of lines in a module'} |
| 436 ), | 170 ), |
| 437 ('indent-string', | 171 ('indent-string', |
| 438 {'default' : ' ', 'type' : "string", 'metavar' : '<string>', | 172 {'default' : ' ', 'type' : "string", 'metavar' : '<string>', |
| 439 'help' : 'String used as indentation unit. This is usually ' | 173 'help' : 'String used as indentation unit. This is usually \ |
| 440 '" " (4 spaces) or "\\t" (1 tab).'}), | 174 " " (4 spaces) or "\\t" (1 tab).'}), |
| 441 ('indent-after-paren', | 175 ) |
| 442 {'type': 'int', 'metavar': '<int>', 'default': 4, | |
| 443 'help': 'Number of spaces of indent required inside a hanging ' | |
| 444 ' or continued line.'}), | |
| 445 ) | |
| 446 | |
| 447 def __init__(self, linter=None): | 176 def __init__(self, linter=None): |
| 448 BaseTokenChecker.__init__(self, linter) | 177 BaseRawChecker.__init__(self, linter) |
| 449 self._lines = None | 178 self._lines = None |
| 450 self._visited_lines = None | 179 self._visited_lines = None |
| 451 self._bracket_stack = [None] | |
| 452 | 180 |
| 453 def _pop_token(self): | 181 def process_module(self, node): |
| 454 self._bracket_stack.pop() | 182 """extracts encoding from the stream and decodes each line, so that |
| 455 self._current_line.pop_token() | 183 international text's length is properly calculated. |
| 184 """ |
| 185 stream = node.file_stream |
| 186 stream.seek(0) # XXX may be removed with astng > 0.23 |
| 187 readline = stream.readline |
| 188 if sys.version_info < (3, 0): |
| 189 if node.file_encoding is not None: |
| 190 readline = lambda: stream.readline().decode(node.file_encoding,
'replace') |
| 191 self.process_tokens(tokenize.generate_tokens(readline)) |
| 456 | 192 |
| 457 def _push_token(self, token, idx): | 193 def new_line(self, tok_type, line, line_num, junk): |
| 458 self._bracket_stack.append(token) | |
| 459 self._current_line.push_token(token, idx) | |
| 460 | |
| 461 def new_line(self, tokens, line_end, line_start): | |
| 462 """a new line has been encountered, process it if necessary""" | 194 """a new line has been encountered, process it if necessary""" |
| 463 if _last_token_on_line_is(tokens, line_end, ';'): | 195 if not tok_type in junk: |
| 464 self.add_message('unnecessary-semicolon', line=tokens.start_line(lin
e_end)) | |
| 465 | |
| 466 line_num = tokens.start_line(line_start) | |
| 467 line = tokens.line(line_start) | |
| 468 if tokens.type(line_start) not in _JUNK_TOKENS: | |
| 469 self._lines[line_num] = line.split('\n')[0] | 196 self._lines[line_num] = line.split('\n')[0] |
| 470 self.check_lines(line, line_num) | 197 self.check_lines(line, line_num) |
| 471 | 198 |
| 472 def process_module(self, module): | |
| 473 self._keywords_with_parens = set() | |
| 474 if 'print_function' in module.future_imports: | |
| 475 self._keywords_with_parens.add('print') | |
| 476 | |
| 477 def _check_keyword_parentheses(self, tokens, start): | |
| 478 """Check that there are not unnecessary parens after a keyword. | |
| 479 | |
| 480 Parens are unnecessary if there is exactly one balanced outer pair on a | |
| 481 line, and it is followed by a colon, and contains no commas (i.e. is not
a | |
| 482 tuple). | |
| 483 | |
| 484 Args: | |
| 485 tokens: list of Tokens; the entire list of Tokens. | |
| 486 start: int; the position of the keyword in the token list. | |
| 487 """ | |
| 488 # If the next token is not a paren, we're fine. | |
| 489 if self._inside_brackets(':') and tokens[start][1] == 'for': | |
| 490 self._pop_token() | |
| 491 if tokens[start+1][1] != '(': | |
| 492 return | |
| 493 | |
| 494 found_and_or = False | |
| 495 depth = 0 | |
| 496 keyword_token = tokens[start][1] | |
| 497 line_num = tokens[start][2][0] | |
| 498 | |
| 499 for i in xrange(start, len(tokens) - 1): | |
| 500 token = tokens[i] | |
| 501 | |
| 502 # If we hit a newline, then assume any parens were for continuation. | |
| 503 if token[0] == tokenize.NL: | |
| 504 return | |
| 505 | |
| 506 if token[1] == '(': | |
| 507 depth += 1 | |
| 508 elif token[1] == ')': | |
| 509 depth -= 1 | |
| 510 if not depth: | |
| 511 # ')' can't happen after if (foo), since it would be a synta
x error. | |
| 512 if (tokens[i+1][1] in (':', ')', ']', '}', 'in') or | |
| 513 tokens[i+1][0] in (tokenize.NEWLINE, | |
| 514 tokenize.ENDMARKER, | |
| 515 tokenize.COMMENT)): | |
| 516 # The empty tuple () is always accepted. | |
| 517 if i == start + 2: | |
| 518 return | |
| 519 if keyword_token == 'not': | |
| 520 if not found_and_or: | |
| 521 self.add_message('superfluous-parens', line=line
_num, | |
| 522 args=keyword_token) | |
| 523 elif keyword_token in ('return', 'yield'): | |
| 524 self.add_message('superfluous-parens', line=line_num
, | |
| 525 args=keyword_token) | |
| 526 elif keyword_token not in self._keywords_with_parens: | |
| 527 if not (tokens[i+1][1] == 'in' and found_and_or): | |
| 528 self.add_message('superfluous-parens', line=line
_num, | |
| 529 args=keyword_token) | |
| 530 return | |
| 531 elif depth == 1: | |
| 532 # This is a tuple, which is always acceptable. | |
| 533 if token[1] == ',': | |
| 534 return | |
| 535 # 'and' and 'or' are the only boolean operators with lower prece
dence | |
| 536 # than 'not', so parens are only required when they are found. | |
| 537 elif token[1] in ('and', 'or'): | |
| 538 found_and_or = True | |
| 539 # A yield inside an expression must always be in parentheses, | |
| 540 # quit early without error. | |
| 541 elif token[1] == 'yield': | |
| 542 return | |
| 543 # A generator expression always has a 'for' token in it, and | |
| 544 # the 'for' token is only legal inside parens when it is in a | |
| 545 # generator expression. The parens are necessary here, so bail | |
| 546 # without an error. | |
| 547 elif token[1] == 'for': | |
| 548 return | |
| 549 | |
| 550 def _opening_bracket(self, tokens, i): | |
| 551 self._push_token(tokens[i][1], i) | |
| 552 # Special case: ignore slices | |
| 553 if tokens[i][1] == '[' and tokens[i+1][1] == ':': | |
| 554 return | |
| 555 | |
| 556 if (i > 0 and (tokens[i-1][0] == tokenize.NAME and | |
| 557 not (keyword.iskeyword(tokens[i-1][1])) | |
| 558 or tokens[i-1][1] in _CLOSING_BRACKETS)): | |
| 559 self._check_space(tokens, i, (_MUST_NOT, _MUST_NOT)) | |
| 560 else: | |
| 561 self._check_space(tokens, i, (_IGNORE, _MUST_NOT)) | |
| 562 | |
| 563 def _closing_bracket(self, tokens, i): | |
| 564 if self._inside_brackets(':'): | |
| 565 self._pop_token() | |
| 566 self._pop_token() | |
| 567 # Special case: ignore slices | |
| 568 if tokens[i-1][1] == ':' and tokens[i][1] == ']': | |
| 569 return | |
| 570 policy_before = _MUST_NOT | |
| 571 if tokens[i][1] in _CLOSING_BRACKETS and tokens[i-1][1] == ',': | |
| 572 if _TRAILING_COMMA in self.config.no_space_check: | |
| 573 policy_before = _IGNORE | |
| 574 | |
| 575 self._check_space(tokens, i, (policy_before, _IGNORE)) | |
| 576 | |
| 577 def _check_equals_spacing(self, tokens, i): | |
| 578 """Check the spacing of a single equals sign.""" | |
| 579 if self._inside_brackets('(') or self._inside_brackets('lambda'): | |
| 580 self._check_space(tokens, i, (_MUST_NOT, _MUST_NOT)) | |
| 581 else: | |
| 582 self._check_space(tokens, i, (_MUST, _MUST)) | |
| 583 | |
| 584 def _open_lambda(self, tokens, i): # pylint:disable=unused-argument | |
| 585 self._push_token('lambda', i) | |
| 586 | |
| 587 def _handle_colon(self, tokens, i): | |
| 588 # Special case: ignore slices | |
| 589 if self._inside_brackets('['): | |
| 590 return | |
| 591 if (self._inside_brackets('{') and | |
| 592 _DICT_SEPARATOR in self.config.no_space_check): | |
| 593 policy = (_IGNORE, _IGNORE) | |
| 594 else: | |
| 595 policy = (_MUST_NOT, _MUST) | |
| 596 self._check_space(tokens, i, policy) | |
| 597 | |
| 598 if self._inside_brackets('lambda'): | |
| 599 self._pop_token() | |
| 600 elif self._inside_brackets('{'): | |
| 601 self._push_token(':', i) | |
| 602 | |
| 603 def _handle_comma(self, tokens, i): | |
| 604 # Only require a following whitespace if this is | |
| 605 # not a hanging comma before a closing bracket. | |
| 606 if tokens[i+1][1] in _CLOSING_BRACKETS: | |
| 607 self._check_space(tokens, i, (_MUST_NOT, _IGNORE)) | |
| 608 else: | |
| 609 self._check_space(tokens, i, (_MUST_NOT, _MUST)) | |
| 610 if self._inside_brackets(':'): | |
| 611 self._pop_token() | |
| 612 | |
| 613 def _check_surrounded_by_space(self, tokens, i): | |
| 614 """Check that a binary operator is surrounded by exactly one space.""" | |
| 615 self._check_space(tokens, i, (_MUST, _MUST)) | |
| 616 | |
| 617 def _check_space(self, tokens, i, policies): | |
| 618 def _policy_string(policy): | |
| 619 if policy == _MUST: | |
| 620 return 'Exactly one', 'required' | |
| 621 else: | |
| 622 return 'No', 'allowed' | |
| 623 | |
| 624 def _name_construct(token): | |
| 625 if tokens[i][1] == ',': | |
| 626 return 'comma' | |
| 627 elif tokens[i][1] == ':': | |
| 628 return ':' | |
| 629 elif tokens[i][1] in '()[]{}': | |
| 630 return 'bracket' | |
| 631 elif tokens[i][1] in ('<', '>', '<=', '>=', '!=', '=='): | |
| 632 return 'comparison' | |
| 633 else: | |
| 634 if self._inside_brackets('('): | |
| 635 return 'keyword argument assignment' | |
| 636 else: | |
| 637 return 'assignment' | |
| 638 | |
| 639 good_space = [True, True] | |
| 640 pairs = [(tokens[i-1], tokens[i]), (tokens[i], tokens[i+1])] | |
| 641 | |
| 642 for other_idx, (policy, token_pair) in enumerate(zip(policies, pairs)): | |
| 643 if token_pair[other_idx][0] in _EOL or policy == _IGNORE: | |
| 644 continue | |
| 645 | |
| 646 distance = _column_distance(*token_pair) | |
| 647 if distance is None: | |
| 648 continue | |
| 649 good_space[other_idx] = ( | |
| 650 (policy == _MUST and distance == 1) or | |
| 651 (policy == _MUST_NOT and distance == 0)) | |
| 652 | |
| 653 warnings = [] | |
| 654 if not any(good_space) and policies[0] == policies[1]: | |
| 655 warnings.append((policies[0], 'around')) | |
| 656 else: | |
| 657 for ok, policy, position in zip(good_space, policies, ('before', 'af
ter')): | |
| 658 if not ok: | |
| 659 warnings.append((policy, position)) | |
| 660 for policy, position in warnings: | |
| 661 construct = _name_construct(tokens[i]) | |
| 662 count, state = _policy_string(policy) | |
| 663 self.add_message('bad-whitespace', line=tokens[i][2][0], | |
| 664 args=(count, state, position, construct, | |
| 665 _underline_token(tokens[i]))) | |
| 666 | |
| 667 def _inside_brackets(self, left): | |
| 668 return self._bracket_stack[-1] == left | |
| 669 | |
| 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): | |
| 675 raw = [ | |
| 676 (_KEYWORD_TOKENS, | |
| 677 self._check_keyword_parentheses), | |
| 678 | |
| 679 (_OPENING_BRACKETS, self._opening_bracket), | |
| 680 | |
| 681 (_CLOSING_BRACKETS, self._closing_bracket), | |
| 682 | |
| 683 (['='], self._check_equals_spacing), | |
| 684 | |
| 685 (_SPACED_OPERATORS, self._check_surrounded_by_space), | |
| 686 | |
| 687 ([','], self._handle_comma), | |
| 688 | |
| 689 ([':'], self._handle_colon), | |
| 690 | |
| 691 (['lambda'], self._open_lambda), | |
| 692 | |
| 693 (['<>'], self._handle_old_ne_operator), | |
| 694 ] | |
| 695 | |
| 696 dispatch = {} | |
| 697 for tokens, handler in raw: | |
| 698 for token in tokens: | |
| 699 dispatch[token] = handler | |
| 700 return dispatch | |
| 701 | |
| 702 def process_tokens(self, tokens): | 199 def process_tokens(self, tokens): |
| 703 """process tokens and search for : | 200 """process tokens and search for : |
| 704 | 201 |
| 705 _ non strict indentation (i.e. not always using the <indent> parameter
as | 202 _ non strict indentation (i.e. not always using the <indent> parameter
as |
| 706 indent unit) | 203 indent unit) |
| 707 _ too long lines (i.e. longer than <max_chars>) | 204 _ too long lines (i.e. longer than <max_chars>) |
| 708 _ optionally bad construct (if given, bad_construct must be a compiled | 205 _ optionally bad construct (if given, bad_construct must be a compiled |
| 709 regular expression). | 206 regular expression). |
| 710 """ | 207 """ |
| 711 self._bracket_stack = [None] | 208 indent = tokenize.INDENT |
| 209 dedent = tokenize.DEDENT |
| 210 newline = tokenize.NEWLINE |
| 211 junk = (tokenize.COMMENT, tokenize.NL) |
| 712 indents = [0] | 212 indents = [0] |
| 713 check_equal = False | 213 check_equal = 0 |
| 714 line_num = 0 | 214 line_num = 0 |
| 215 previous = None |
| 715 self._lines = {} | 216 self._lines = {} |
| 716 self._visited_lines = {} | 217 self._visited_lines = {} |
| 717 token_handlers = self._prepare_token_dispatcher() | 218 for (tok_type, token, start, _, line) in tokens: |
| 219 if start[0] != line_num: |
| 220 if previous is not None and previous[0] == tokenize.OP and previ
ous[1] == ';': |
| 221 self.add_message('W0301', line=previous[2]) |
| 222 previous = None |
| 223 line_num = start[0] |
| 224 self.new_line(tok_type, line, line_num, junk) |
| 225 if tok_type not in (indent, dedent, newline) + junk: |
| 226 previous = tok_type, token, start[0] |
| 718 | 227 |
| 719 self._current_line = ContinuedLineState(tokens, self.config) | 228 if tok_type == tokenize.OP: |
| 720 for idx, (tok_type, token, start, _, line) in enumerate(tokens): | 229 if token == '<>': |
| 721 if start[0] != line_num: | 230 self.add_message('W0331', line=line_num) |
| 722 line_num = start[0] | 231 elif tok_type == tokenize.NUMBER: |
| 723 # A tokenizer oddity: if an indented line contains a multi-line | 232 if token.endswith('l'): |
| 724 # docstring, the line member of the INDENT token does not contai
n | 233 self.add_message('W0332', line=line_num) |
| 725 # the full line; therefore we check the next token on the line. | |
| 726 if tok_type == tokenize.INDENT: | |
| 727 self.new_line(TokenWrapper(tokens), idx-1, idx+1) | |
| 728 else: | |
| 729 self.new_line(TokenWrapper(tokens), idx-1, idx) | |
| 730 | 234 |
| 731 if tok_type == tokenize.NEWLINE: | 235 elif tok_type == newline: |
| 732 # a program statement, or ENDMARKER, will eventually follow, | 236 # a program statement, or ENDMARKER, will eventually follow, |
| 733 # after some (possibly empty) run of tokens of the form | 237 # after some (possibly empty) run of tokens of the form |
| 734 # (NL | COMMENT)* (INDENT | DEDENT+)? | 238 # (NL | COMMENT)* (INDENT | DEDENT+)? |
| 735 # If an INDENT appears, setting check_equal is wrong, and will | 239 # If an INDENT appears, setting check_equal is wrong, and will |
| 736 # be undone when we see the INDENT. | 240 # be undone when we see the INDENT. |
| 737 check_equal = True | 241 check_equal = 1 |
| 738 self._process_retained_warnings(TokenWrapper(tokens), idx) | 242 |
| 739 self._current_line.next_logical_line() | 243 elif tok_type == indent: |
| 740 elif tok_type == tokenize.INDENT: | 244 check_equal = 0 |
| 741 check_equal = False | |
| 742 self.check_indent_level(token, indents[-1]+1, line_num) | 245 self.check_indent_level(token, indents[-1]+1, line_num) |
| 743 indents.append(indents[-1]+1) | 246 indents.append(indents[-1]+1) |
| 744 elif tok_type == tokenize.DEDENT: | 247 |
| 248 elif tok_type == dedent: |
| 745 # there's nothing we need to check here! what's important is | 249 # there's nothing we need to check here! what's important is |
| 746 # that when the run of DEDENTs ends, the indentation of the | 250 # that when the run of DEDENTs ends, the indentation of the |
| 747 # program statement (or ENDMARKER) that triggered the run is | 251 # program statement (or ENDMARKER) that triggered the run is |
| 748 # equal to what's left at the top of the indents stack | 252 # equal to what's left at the top of the indents stack |
| 749 check_equal = True | 253 check_equal = 1 |
| 750 if len(indents) > 1: | 254 if len(indents) > 1: |
| 751 del indents[-1] | 255 del indents[-1] |
| 752 elif tok_type == tokenize.NL: | 256 |
| 753 self._check_continued_indentation(TokenWrapper(tokens), idx+1) | 257 elif check_equal and tok_type not in junk: |
| 754 self._current_line.next_physical_line() | 258 # this is the first "real token" following a NEWLINE, so it |
| 755 elif tok_type != tokenize.COMMENT: | |
| 756 self._current_line.handle_line_start(idx) | |
| 757 # This is the first concrete token following a NEWLINE, so it | |
| 758 # must be the first token of the next program statement, or an | 259 # must be the first token of the next program statement, or an |
| 759 # ENDMARKER; the "line" argument exposes the leading whitespace | 260 # ENDMARKER; the "line" argument exposes the leading whitespace |
| 760 # for this statement; in the case of ENDMARKER, line is an empty | 261 # for this statement; in the case of ENDMARKER, line is an empty |
| 761 # string, so will properly match the empty string with which the | 262 # string, so will properly match the empty string with which the |
| 762 # "indents" stack was seeded | 263 # "indents" stack was seeded |
| 763 if check_equal: | 264 check_equal = 0 |
| 764 check_equal = False | 265 self.check_indent_level(line, indents[-1], line_num) |
| 765 self.check_indent_level(line, indents[-1], line_num) | |
| 766 | |
| 767 if tok_type == tokenize.NUMBER and token.endswith('l'): | |
| 768 self.add_message('lowercase-l-suffix', line=line_num) | |
| 769 | |
| 770 try: | |
| 771 handler = token_handlers[token] | |
| 772 except KeyError: | |
| 773 pass | |
| 774 else: | |
| 775 handler(tokens, idx) | |
| 776 | 266 |
| 777 line_num -= 1 # to be ok with "wc -l" | 267 line_num -= 1 # to be ok with "wc -l" |
| 778 if line_num > self.config.max_module_lines: | 268 if line_num > self.config.max_module_lines: |
| 779 self.add_message('too-many-lines', args=line_num, line=1) | 269 self.add_message('C0302', args=line_num, line=1) |
| 780 | 270 |
| 781 def _process_retained_warnings(self, tokens, current_pos): | 271 @check_messages('C0321' ,'C03232', 'C0323', 'C0324') |
| 782 single_line_block_stmt = not _last_token_on_line_is(tokens, current_pos,
':') | |
| 783 | |
| 784 for indent_pos, state, offsets in self._current_line.retained_warnings: | |
| 785 block_type = offsets[tokens.start_col(indent_pos)] | |
| 786 hints = dict((k, v) for k, v in offsets.iteritems() | |
| 787 if v != block_type) | |
| 788 if single_line_block_stmt and block_type == WITH_BODY: | |
| 789 self._add_continuation_message(state, hints, tokens, indent_pos) | |
| 790 elif not single_line_block_stmt and block_type == SINGLE_LINE: | |
| 791 self._add_continuation_message(state, hints, tokens, indent_pos) | |
| 792 | |
| 793 def _check_continued_indentation(self, tokens, next_idx): | |
| 794 def same_token_around_nl(token_type): | |
| 795 return (tokens.type(next_idx) == token_type and | |
| 796 tokens.type(next_idx-2) == token_type) | |
| 797 | |
| 798 # Do not issue any warnings if the next line is empty. | |
| 799 if not self._current_line.has_content or tokens.type(next_idx) == tokeni
ze.NL: | |
| 800 return | |
| 801 | |
| 802 state, valid_offsets = self._current_line.get_valid_offsets(next_idx) | |
| 803 # Special handling for hanging comments and strings. If the last line en
ded | |
| 804 # with a comment (string) and the new line contains only a comment, the
line | |
| 805 # may also be indented to the start of the previous token. | |
| 806 if same_token_around_nl(tokenize.COMMENT) or same_token_around_nl(tokeni
ze.STRING): | |
| 807 valid_offsets[tokens.start_col(next_idx-2)] = True | |
| 808 | |
| 809 # We can only decide if the indentation of a continued line before openi
ng | |
| 810 # a new block is valid once we know of the body of the block is on the | |
| 811 # same line as the block opener. Since the token processing is single-pa
ss, | |
| 812 # emitting those warnings is delayed until the block opener is processed
. | |
| 813 if (state.context_type in (HANGING_BLOCK, CONTINUED_BLOCK) | |
| 814 and tokens.start_col(next_idx) in valid_offsets): | |
| 815 self._current_line.add_block_warning(next_idx, state, valid_offsets) | |
| 816 elif tokens.start_col(next_idx) not in valid_offsets: | |
| 817 self._add_continuation_message(state, valid_offsets, tokens, next_id
x) | |
| 818 | |
| 819 def _add_continuation_message(self, state, offsets, tokens, position): | |
| 820 readable_type, readable_position = _CONTINUATION_MSG_PARTS[state.context
_type] | |
| 821 hint_line = _get_indent_hint_line(offsets, tokens.start_col(position)) | |
| 822 self.add_message( | |
| 823 'bad-continuation', | |
| 824 line=tokens.start_line(position), | |
| 825 args=(readable_type, readable_position, tokens.line(position), hint_
line)) | |
| 826 | |
| 827 @check_messages('multiple-statements') | |
| 828 def visit_default(self, node): | 272 def visit_default(self, node): |
| 829 """check the node line number and check it if not yet done""" | 273 """check the node line number and check it if not yet done""" |
| 830 if not node.is_statement: | 274 if not node.is_statement: |
| 831 return | 275 return |
| 832 if not node.root().pure_python: | 276 if not node.root().pure_python: |
| 833 return # XXX block visit of child nodes | 277 return # XXX block visit of child nodes |
| 834 prev_sibl = node.previous_sibling() | 278 prev_sibl = node.previous_sibling() |
| 835 if prev_sibl is not None: | 279 if prev_sibl is not None: |
| 836 prev_line = prev_sibl.fromlineno | 280 prev_line = prev_sibl.fromlineno |
| 837 else: | 281 else: |
| 838 # The line on which a finally: occurs in a try/finally | 282 prev_line = node.parent.statement().fromlineno |
| 839 # is not directly represented in the AST. We infer it | |
| 840 # by taking the last line of the body and adding 1, which | |
| 841 # should be the line of finally: | |
| 842 if (isinstance(node.parent, nodes.TryFinally) | |
| 843 and node in node.parent.finalbody): | |
| 844 prev_line = node.parent.body[0].tolineno + 1 | |
| 845 else: | |
| 846 prev_line = node.parent.statement().fromlineno | |
| 847 line = node.fromlineno | 283 line = node.fromlineno |
| 848 assert line, node | 284 assert line, node |
| 849 if prev_line == line and self._visited_lines.get(line) != 2: | 285 if prev_line == line and self._visited_lines.get(line) != 2: |
| 850 self._check_multi_statement_line(node, line) | 286 # py2.5 try: except: finally: |
| 287 if not (isinstance(node, nodes.TryExcept) |
| 288 and isinstance(node.parent, nodes.TryFinally) |
| 289 and node.fromlineno == node.parent.fromlineno): |
| 290 self.add_message('C0321', node=node) |
| 291 self._visited_lines[line] = 2 |
| 851 return | 292 return |
| 852 if line in self._visited_lines: | 293 if line in self._visited_lines: |
| 853 return | 294 return |
| 854 try: | 295 try: |
| 855 tolineno = node.blockstart_tolineno | 296 tolineno = node.blockstart_tolineno |
| 856 except AttributeError: | 297 except AttributeError: |
| 857 tolineno = node.tolineno | 298 tolineno = node.tolineno |
| 858 assert tolineno, node | 299 assert tolineno, node |
| 859 lines = [] | 300 lines = [] |
| 860 for line in xrange(line, tolineno + 1): | 301 for line in xrange(line, tolineno + 1): |
| 861 self._visited_lines[line] = 1 | 302 self._visited_lines[line] = 1 |
| 862 try: | 303 try: |
| 863 lines.append(self._lines[line].rstrip()) | 304 lines.append(self._lines[line].rstrip()) |
| 864 except KeyError: | 305 except KeyError: |
| 865 lines.append('') | 306 lines.append('') |
| 307 try: |
| 308 msg_def = check_line('\n'.join(lines)) |
| 309 if msg_def: |
| 310 self.add_message(msg_def[0], node=node, args=msg_def[1]) |
| 311 except KeyError: |
| 312 # FIXME: internal error ! |
| 313 pass |
| 866 | 314 |
| 867 def _check_multi_statement_line(self, node, line): | 315 @check_messages('W0333') |
| 868 """Check for lines containing multiple statements.""" | |
| 869 # Do not warn about multiple nested context managers | |
| 870 # in with statements. | |
| 871 if isinstance(node, nodes.With): | |
| 872 return | |
| 873 # For try... except... finally..., the two nodes | |
| 874 # appear to be on the same line due to how the AST is built. | |
| 875 if (isinstance(node, nodes.TryExcept) and | |
| 876 isinstance(node.parent, nodes.TryFinally)): | |
| 877 return | |
| 878 if (isinstance(node.parent, nodes.If) and not node.parent.orelse | |
| 879 and self.config.single_line_if_stmt): | |
| 880 return | |
| 881 self.add_message('multiple-statements', node=node) | |
| 882 self._visited_lines[line] = 2 | |
| 883 | |
| 884 @check_messages('backtick') | |
| 885 def visit_backquote(self, node): | 316 def visit_backquote(self, node): |
| 886 self.add_message('backtick', node=node) | 317 self.add_message('W0333', node=node) |
| 887 | 318 |
| 888 def check_lines(self, lines, i): | 319 def check_lines(self, lines, i): |
| 889 """check lines have less than a maximum number of characters | 320 """check lines have less than a maximum number of characters |
| 890 """ | 321 """ |
| 891 max_chars = self.config.max_line_length | 322 max_chars = self.config.max_line_length |
| 892 ignore_long_line = self.config.ignore_long_lines | 323 for line in lines.splitlines(): |
| 893 | 324 if len(line) > max_chars: |
| 894 for line in lines.splitlines(True): | 325 self.add_message('C0301', line=i, args=(len(line), max_chars)) |
| 895 if not line.endswith('\n'): | |
| 896 self.add_message('missing-final-newline', line=i) | |
| 897 else: | |
| 898 stripped_line = line.rstrip() | |
| 899 if line[len(stripped_line):] not in ('\n', '\r\n'): | |
| 900 self.add_message('trailing-whitespace', line=i) | |
| 901 # Don't count excess whitespace in the line length. | |
| 902 line = stripped_line | |
| 903 mobj = OPTION_RGX.search(line) | |
| 904 if mobj and mobj.group(1).split('=', 1)[0].strip() == 'disable': | |
| 905 line = line.split('#')[0].rstrip() | |
| 906 | |
| 907 if len(line) > max_chars and not ignore_long_line.search(line): | |
| 908 self.add_message('line-too-long', line=i, args=(len(line), max_c
hars)) | |
| 909 i += 1 | 326 i += 1 |
| 910 | 327 |
| 911 def check_indent_level(self, string, expected, line_num): | 328 def check_indent_level(self, string, expected, line_num): |
| 912 """return the indent level of the string | 329 """return the indent level of the string |
| 913 """ | 330 """ |
| 914 indent = self.config.indent_string | 331 indent = self.config.indent_string |
| 915 if indent == '\\t': # \t is not interpreted in the configuration file | 332 if indent == '\\t': # \t is not interpreted in the configuration file |
| 916 indent = '\t' | 333 indent = '\t' |
| 917 level = 0 | 334 level = 0 |
| 918 unit_size = len(indent) | 335 unit_size = len(indent) |
| 919 while string[:unit_size] == indent: | 336 while string[:unit_size] == indent: |
| 920 string = string[unit_size:] | 337 string = string[unit_size:] |
| 921 level += 1 | 338 level += 1 |
| 922 suppl = '' | 339 suppl = '' |
| 923 while string and string[0] in ' \t': | 340 while string and string[0] in ' \t': |
| 924 if string[0] != indent[0]: | 341 if string[0] != indent[0]: |
| 925 if string[0] == '\t': | 342 if string[0] == '\t': |
| 926 args = ('tab', 'space') | 343 args = ('tab', 'space') |
| 927 else: | 344 else: |
| 928 args = ('space', 'tab') | 345 args = ('space', 'tab') |
| 929 self.add_message('mixed-indentation', args=args, line=line_num) | 346 self.add_message('W0312', args=args, line=line_num) |
| 930 return level | 347 return level |
| 931 suppl += string[0] | 348 suppl += string[0] |
| 932 string = string[1:] | 349 string = string [1:] |
| 933 if level != expected or suppl: | 350 if level != expected or suppl: |
| 934 i_type = 'spaces' | 351 i_type = 'spaces' |
| 935 if indent[0] == '\t': | 352 if indent[0] == '\t': |
| 936 i_type = 'tabs' | 353 i_type = 'tabs' |
| 937 self.add_message('bad-indentation', line=line_num, | 354 self.add_message('W0311', line=line_num, |
| 938 args=(level * unit_size + len(suppl), i_type, | 355 args=(level * unit_size + len(suppl), i_type, |
| 939 expected * unit_size)) | 356 expected * unit_size)) |
| 940 | 357 |
| 941 | 358 |
| 942 def register(linter): | 359 def register(linter): |
| 943 """required method to auto register this checker """ | 360 """required method to auto register this checker """ |
| 944 linter.register_checker(FormatChecker(linter)) | 361 linter.register_checker(FormatChecker(linter)) |
| OLD | NEW |