OLD | NEW |
1 # Copyright (c) 2003-2010 Sylvain Thenault (thenault@gmail.com). | 1 # Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). |
2 # Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE). | 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 # 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 re, sys | 24 import keyword |
| 25 import sys |
25 import tokenize | 26 import tokenize |
| 27 |
26 if not hasattr(tokenize, 'NL'): | 28 if not hasattr(tokenize, 'NL'): |
27 raise ValueError("tokenize.NL doesn't exist -- tokenize module too old") | 29 raise ValueError("tokenize.NL doesn't exist -- tokenize module too old") |
28 | 30 |
29 from logilab.common.textutils import pretty_match | 31 from astroid import nodes |
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 | |
34 from pylint.checkers.utils import check_messages | 35 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] |
35 | 63 |
36 MSGS = { | 64 MSGS = { |
37 'C0301': ('Line too long (%s/%s)', | 65 'C0301': ('Line too long (%s/%s)', |
| 66 'line-too-long', |
38 'Used when a line is longer than a given number of characters.'), | 67 'Used when a line is longer than a given number of characters.'), |
39 'C0302': ('Too many lines in module (%s)', # was W0302 | 68 'C0302': ('Too many lines in module (%s)', # was W0302 |
| 69 'too-many-lines', |
40 'Used when a module has too much lines, reducing its readability.' | 70 'Used when a module has too much lines, reducing its readability.' |
41 ), | 71 ), |
42 | 72 'C0303': ('Trailing whitespace', |
| 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.'), |
43 'W0311': ('Bad indentation. Found %s %s, expected %s', | 79 'W0311': ('Bad indentation. Found %s %s, expected %s', |
| 80 'bad-indentation', |
44 'Used when an unexpected number of indentation\'s tabulations or ' | 81 'Used when an unexpected number of indentation\'s tabulations or ' |
45 'spaces has been found.'), | 82 'spaces has been found.'), |
| 83 'C0330': ('Wrong %s indentation%s.\n%s%s', |
| 84 'bad-continuation', |
| 85 'TODO'), |
46 'W0312': ('Found indentation with %ss instead of %ss', | 86 'W0312': ('Found indentation with %ss instead of %ss', |
| 87 'mixed-indentation', |
47 'Used when there are some mixed tabs and spaces in a module.'), | 88 'Used when there are some mixed tabs and spaces in a module.'), |
48 'W0301': ('Unnecessary semicolon', # was W0106 | 89 'W0301': ('Unnecessary semicolon', # was W0106 |
| 90 'unnecessary-semicolon', |
49 'Used when a statement is ended by a semi-colon (";"), which \ | 91 'Used when a statement is ended by a semi-colon (";"), which \ |
50 isn\'t necessary (that\'s python, not C ;).'), | 92 isn\'t necessary (that\'s python, not C ;).'), |
51 'C0321': ('More than one statement on a single line', | 93 'C0321': ('More than one statement on a single line', |
52 'Used when more than on statement are found on the same line.'), | 94 'multiple-statements', |
53 'C0322': ('Operator not preceded by a space\n%s', | 95 'Used when more than on statement are found on the same line.', |
54 'Used when one of the following operator (!= | <= | == | >= | < ' | 96 {'scope': WarningScope.NODE}), |
55 '| > | = | \+= | -= | \*= | /= | %) is not preceded by a space.'), | 97 'C0325' : ('Unnecessary parens after %r keyword', |
56 'C0323': ('Operator not followed by a space\n%s', | 98 'superfluous-parens', |
57 'Used when one of the following operator (!= | <= | == | >= | < ' | 99 'Used when a single item in parentheses follows an if, for, or ' |
58 '| > | = | \+= | -= | \*= | /= | %) is not followed by a space.'), | 100 'other keyword.'), |
59 'C0324': ('Comma not followed by a space\n%s', | 101 'C0326': ('%s space %s %s %s\n%s', |
60 'Used when a comma (",") is not followed by a space.'), | 102 'bad-whitespace', |
61 } | 103 ('Used when a wrong number of spaces is used around an operator, ' |
62 | 104 'bracket or block opener.'), |
63 if sys.version_info < (3, 0): | 105 {'old_names': [('C0323', 'no-space-after-operator'), |
64 | 106 ('C0324', 'no-space-after-comma'), |
65 MSGS.update({ | 107 ('C0322', 'no-space-before-operator')]}), |
66 'W0331': ('Use of the <> operator', | 108 'W0331': ('Use of the <> operator', |
67 'Used when the deprecated "<>" operator is used instead \ | 109 'old-ne-operator', |
68 of "!=".'), | 110 'Used when the deprecated "<>" operator is used instead ' |
69 'W0332': ('Use l as long integer identifier', | 111 'of "!=".', |
| 112 {'maxversion': (3, 0)}), |
| 113 'W0332': ('Use of "l" as long integer identifier', |
| 114 'lowercase-l-suffix', |
70 'Used when a lower case "l" is used to mark a long integer. You ' | 115 'Used when a lower case "l" is used to mark a long integer. You ' |
71 'should use a upper case "L" since the letter "l" looks too much ' | 116 'should use a upper case "L" since the letter "l" looks too much ' |
72 'like the digit "1"'), | 117 'like the digit "1"', |
| 118 {'maxversion': (3, 0)}), |
73 'W0333': ('Use of the `` operator', | 119 'W0333': ('Use of the `` operator', |
| 120 'backtick', |
74 'Used when the deprecated "``" (backtick) operator is used ' | 121 'Used when the deprecated "``" (backtick) operator is used ' |
75 'instead of the str() function.'), | 122 'instead of the str() function.', |
76 }) | 123 {'scope': WarningScope.NODE, 'maxversion': (3, 0)}), |
77 | 124 } |
78 # simple quoted string rgx | 125 |
79 SQSTRING_RGX = r'"([^"\\]|\\.)*?"' | 126 |
80 # simple apostrophed rgx | 127 def _underline_token(token): |
81 SASTRING_RGX = r"'([^'\\]|\\.)*?'" | 128 length = token[3][1] - token[2][1] |
82 # triple quoted string rgx | 129 offset = token[2][1] |
83 TQSTRING_RGX = r'"""([^"]|("(?!"")))*?(""")' | 130 return token[4] + (' ' * offset) + ('^' * length) |
84 # triple apostrophed string rgx # FIXME english please | 131 |
85 TASTRING_RGX = r"'''([^']|('(?!'')))*?(''')" | 132 |
86 | 133 def _column_distance(token1, token2): |
87 # finally, the string regular expression | 134 if token1 == token2: |
88 STRING_RGX = re.compile('(%s)|(%s)|(%s)|(%s)' % (TQSTRING_RGX, TASTRING_RGX, | 135 return 0 |
89 SQSTRING_RGX, SASTRING_RGX), | 136 if token2[3] < token1[3]: |
90 re.MULTILINE|re.DOTALL) | 137 token1, token2 = token2, token1 |
91 | 138 if token1[3][0] != token2[2][0]: |
92 COMMENT_RGX = re.compile("#.*$", re.M) | 139 return None |
93 | 140 return token2[2][1] - token1[3][1] |
94 OPERATORS = r'!=|<=|==|>=|<|>|=|\+=|-=|\*=|/=|%' | 141 |
95 | 142 |
96 OP_RGX_MATCH_1 = r'[^(]*(?<!\s|\^|<|>|=|\+|-|\*|/|!|%%|&|\|)(%s).*' % OPERATORS | 143 def _last_token_on_line_is(tokens, line_end, token): |
97 OP_RGX_SEARCH_1 = r'(?<!\s|\^|<|>|=|\+|-|\*|/|!|%%|&|\|)(%s)' % OPERATORS | 144 return (line_end > 0 and tokens.token(line_end-1) == token or |
98 | 145 line_end > 1 and tokens.token(line_end-2) == token |
99 OP_RGX_MATCH_2 = r'[^(]*(%s)(?!\s|=|>|<).*' % OPERATORS | 146 and tokens.type(line_end-1) == tokenize.COMMENT) |
100 OP_RGX_SEARCH_2 = r'(%s)(?!\s|=|>)' % OPERATORS | 147 |
101 | 148 |
102 BAD_CONSTRUCT_RGXS = ( | 149 def _token_followed_by_eol(tokens, position): |
103 | 150 return (tokens.type(position+1) == tokenize.NL or |
104 (re.compile(OP_RGX_MATCH_1, re.M), | 151 tokens.type(position+1) == tokenize.COMMENT and |
105 re.compile(OP_RGX_SEARCH_1, re.M), | 152 tokens.type(position+2) == tokenize.NL) |
106 'C0322'), | 153 |
107 | 154 |
108 (re.compile(OP_RGX_MATCH_2, re.M), | 155 def _get_indent_length(line): |
109 re.compile(OP_RGX_SEARCH_2, re.M), | 156 """Return the length of the indentation on the given token's line.""" |
110 'C0323'), | 157 result = 0 |
111 | 158 for char in line: |
112 (re.compile(r'.*,[^(\s|\]|}|\))].*', re.M), | 159 if char == ' ': |
113 re.compile(r',[^\s)]', re.M), | 160 result += 1 |
114 'C0324'), | 161 elif char == '\t': |
115 ) | 162 result += _TAB_LENGTH |
116 | 163 else: |
117 | 164 break |
118 def get_string_coords(line): | 165 return result |
119 """return a list of string positions (tuple (start, end)) in the line | 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. |
120 """ | 235 """ |
121 result = [] | 236 return {single: SINGLE_LINE, with_body: WITH_BODY} |
122 for match in re.finditer(STRING_RGX, line): | 237 |
123 result.append( (match.start(), match.end()) ) | 238 |
124 return result | 239 class TokenWrapper(object): |
125 | 240 """A wrapper for readable access to token information.""" |
126 def in_coords(match, string_coords): | 241 |
127 """return true if the match is in the string coord""" | 242 def __init__(self, tokens): |
128 mstart = match.start() | 243 self._tokens = tokens |
129 for start, end in string_coords: | 244 |
130 if mstart >= start and mstart < end: | 245 def token(self, idx): |
131 return True | 246 return self._tokens[idx][1] |
132 return False | 247 |
133 | 248 def type(self, idx): |
134 def check_line(line): | 249 return self._tokens[idx][0] |
135 """check a line for a bad construction | 250 |
136 if it founds one, return a message describing the problem | 251 def start_line(self, idx): |
137 else return None | 252 return self._tokens[idx][2][0] |
138 """ | 253 |
139 cleanstr = COMMENT_RGX.sub('', STRING_RGX.sub('', line)) | 254 def start_col(self, idx): |
140 for rgx_match, rgx_search, msg_id in BAD_CONSTRUCT_RGXS: | 255 return self._tokens[idx][2][1] |
141 if rgx_match.match(cleanstr): | 256 |
142 string_positions = get_string_coords(line) | 257 def line(self, idx): |
143 for match in re.finditer(rgx_search, line): | 258 return self._tokens[idx][4] |
144 if not in_coords(match, string_positions): | 259 |
145 return msg_id, pretty_match(match, line.rstrip()) | 260 |
146 | 261 class ContinuedLineState(object): |
147 | 262 """Tracker for continued indentation inside a logical line.""" |
148 class FormatChecker(BaseRawChecker): | 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): |
149 """checks for : | 400 """checks for : |
150 * unauthorized constructions | 401 * unauthorized constructions |
151 * strict indentation | 402 * strict indentation |
152 * line length | 403 * line length |
153 * use of <> instead of != | 404 * use of <> instead of != |
154 """ | 405 """ |
155 | 406 |
156 __implements__ = (IRawChecker, IASTNGChecker) | 407 __implements__ = (ITokenChecker, IAstroidChecker, IRawChecker) |
157 | 408 |
158 # configuration section name | 409 # configuration section name |
159 name = 'format' | 410 name = 'format' |
160 # messages | 411 # messages |
161 msgs = MSGS | 412 msgs = MSGS |
162 # configuration options | 413 # configuration options |
163 # for available dict keys/values see the optik parser 'add_option' method | 414 # for available dict keys/values see the optik parser 'add_option' method |
164 options = (('max-line-length', | 415 options = (('max-line-length', |
165 {'default' : 80, 'type' : "int", 'metavar' : '<int>', | 416 {'default' : 80, 'type' : "int", 'metavar' : '<int>', |
166 'help' : 'Maximum number of characters on a single line.'}), | 417 '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')}), |
167 ('max-module-lines', | 433 ('max-module-lines', |
168 {'default' : 1000, 'type' : 'int', 'metavar' : '<int>', | 434 {'default' : 1000, 'type' : 'int', 'metavar' : '<int>', |
169 'help': 'Maximum number of lines in a module'} | 435 'help': 'Maximum number of lines in a module'} |
170 ), | 436 ), |
171 ('indent-string', | 437 ('indent-string', |
172 {'default' : ' ', 'type' : "string", 'metavar' : '<string>', | 438 {'default' : ' ', 'type' : "string", 'metavar' : '<string>', |
173 'help' : 'String used as indentation unit. This is usually \ | 439 'help' : 'String used as indentation unit. This is usually ' |
174 " " (4 spaces) or "\\t" (1 tab).'}), | 440 '" " (4 spaces) or "\\t" (1 tab).'}), |
175 ) | 441 ('indent-after-paren', |
| 442 {'type': 'int', 'metavar': '<int>', 'default': 4, |
| 443 'help': 'Number of spaces of indent required inside a hanging ' |
| 444 ' or continued line.'}), |
| 445 ) |
| 446 |
176 def __init__(self, linter=None): | 447 def __init__(self, linter=None): |
177 BaseRawChecker.__init__(self, linter) | 448 BaseTokenChecker.__init__(self, linter) |
178 self._lines = None | 449 self._lines = None |
179 self._visited_lines = None | 450 self._visited_lines = None |
180 | 451 self._bracket_stack = [None] |
181 def process_module(self, node): | 452 |
182 """extracts encoding from the stream and decodes each line, so that | 453 def _pop_token(self): |
183 international text's length is properly calculated. | 454 self._bracket_stack.pop() |
184 """ | 455 self._current_line.pop_token() |
185 stream = node.file_stream | 456 |
186 stream.seek(0) # XXX may be removed with astng > 0.23 | 457 def _push_token(self, token, idx): |
187 readline = stream.readline | 458 self._bracket_stack.append(token) |
188 if sys.version_info < (3, 0): | 459 self._current_line.push_token(token, idx) |
189 if node.file_encoding is not None: | 460 |
190 readline = lambda: stream.readline().decode(node.file_encoding,
'replace') | 461 def new_line(self, tokens, line_end, line_start): |
191 self.process_tokens(tokenize.generate_tokens(readline)) | |
192 | |
193 def new_line(self, tok_type, line, line_num, junk): | |
194 """a new line has been encountered, process it if necessary""" | 462 """a new line has been encountered, process it if necessary""" |
195 if not tok_type in junk: | 463 if _last_token_on_line_is(tokens, line_end, ';'): |
| 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: |
196 self._lines[line_num] = line.split('\n')[0] | 469 self._lines[line_num] = line.split('\n')[0] |
197 self.check_lines(line, line_num) | 470 self.check_lines(line, line_num) |
198 | 471 |
| 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 |
199 def process_tokens(self, tokens): | 702 def process_tokens(self, tokens): |
200 """process tokens and search for : | 703 """process tokens and search for : |
201 | 704 |
202 _ non strict indentation (i.e. not always using the <indent> parameter
as | 705 _ non strict indentation (i.e. not always using the <indent> parameter
as |
203 indent unit) | 706 indent unit) |
204 _ too long lines (i.e. longer than <max_chars>) | 707 _ too long lines (i.e. longer than <max_chars>) |
205 _ optionally bad construct (if given, bad_construct must be a compiled | 708 _ optionally bad construct (if given, bad_construct must be a compiled |
206 regular expression). | 709 regular expression). |
207 """ | 710 """ |
208 indent = tokenize.INDENT | 711 self._bracket_stack = [None] |
209 dedent = tokenize.DEDENT | |
210 newline = tokenize.NEWLINE | |
211 junk = (tokenize.COMMENT, tokenize.NL) | |
212 indents = [0] | 712 indents = [0] |
213 check_equal = 0 | 713 check_equal = False |
214 line_num = 0 | 714 line_num = 0 |
215 previous = None | |
216 self._lines = {} | 715 self._lines = {} |
217 self._visited_lines = {} | 716 self._visited_lines = {} |
218 for (tok_type, token, start, _, line) in tokens: | 717 token_handlers = self._prepare_token_dispatcher() |
| 718 |
| 719 self._current_line = ContinuedLineState(tokens, self.config) |
| 720 for idx, (tok_type, token, start, _, line) in enumerate(tokens): |
219 if start[0] != line_num: | 721 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] | 722 line_num = start[0] |
224 self.new_line(tok_type, line, line_num, junk) | 723 # A tokenizer oddity: if an indented line contains a multi-line |
225 if tok_type not in (indent, dedent, newline) + junk: | 724 # docstring, the line member of the INDENT token does not contai
n |
226 previous = tok_type, token, start[0] | 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) |
227 | 730 |
228 if tok_type == tokenize.OP: | 731 if tok_type == tokenize.NEWLINE: |
229 if token == '<>': | |
230 self.add_message('W0331', line=line_num) | |
231 elif tok_type == tokenize.NUMBER: | |
232 if token.endswith('l'): | |
233 self.add_message('W0332', line=line_num) | |
234 | |
235 elif tok_type == newline: | |
236 # a program statement, or ENDMARKER, will eventually follow, | 732 # a program statement, or ENDMARKER, will eventually follow, |
237 # after some (possibly empty) run of tokens of the form | 733 # after some (possibly empty) run of tokens of the form |
238 # (NL | COMMENT)* (INDENT | DEDENT+)? | 734 # (NL | COMMENT)* (INDENT | DEDENT+)? |
239 # If an INDENT appears, setting check_equal is wrong, and will | 735 # If an INDENT appears, setting check_equal is wrong, and will |
240 # be undone when we see the INDENT. | 736 # be undone when we see the INDENT. |
241 check_equal = 1 | 737 check_equal = True |
242 | 738 self._process_retained_warnings(TokenWrapper(tokens), idx) |
243 elif tok_type == indent: | 739 self._current_line.next_logical_line() |
244 check_equal = 0 | 740 elif tok_type == tokenize.INDENT: |
| 741 check_equal = False |
245 self.check_indent_level(token, indents[-1]+1, line_num) | 742 self.check_indent_level(token, indents[-1]+1, line_num) |
246 indents.append(indents[-1]+1) | 743 indents.append(indents[-1]+1) |
247 | 744 elif tok_type == tokenize.DEDENT: |
248 elif tok_type == dedent: | |
249 # there's nothing we need to check here! what's important is | 745 # there's nothing we need to check here! what's important is |
250 # that when the run of DEDENTs ends, the indentation of the | 746 # that when the run of DEDENTs ends, the indentation of the |
251 # program statement (or ENDMARKER) that triggered the run is | 747 # program statement (or ENDMARKER) that triggered the run is |
252 # equal to what's left at the top of the indents stack | 748 # equal to what's left at the top of the indents stack |
253 check_equal = 1 | 749 check_equal = True |
254 if len(indents) > 1: | 750 if len(indents) > 1: |
255 del indents[-1] | 751 del indents[-1] |
256 | 752 elif tok_type == tokenize.NL: |
257 elif check_equal and tok_type not in junk: | 753 self._check_continued_indentation(TokenWrapper(tokens), idx+1) |
258 # this is the first "real token" following a NEWLINE, so it | 754 self._current_line.next_physical_line() |
| 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 |
259 # must be the first token of the next program statement, or an | 758 # must be the first token of the next program statement, or an |
260 # ENDMARKER; the "line" argument exposes the leading whitespace | 759 # ENDMARKER; the "line" argument exposes the leading whitespace |
261 # for this statement; in the case of ENDMARKER, line is an empty | 760 # for this statement; in the case of ENDMARKER, line is an empty |
262 # string, so will properly match the empty string with which the | 761 # string, so will properly match the empty string with which the |
263 # "indents" stack was seeded | 762 # "indents" stack was seeded |
264 check_equal = 0 | 763 if check_equal: |
265 self.check_indent_level(line, indents[-1], line_num) | 764 check_equal = False |
| 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) |
266 | 776 |
267 line_num -= 1 # to be ok with "wc -l" | 777 line_num -= 1 # to be ok with "wc -l" |
268 if line_num > self.config.max_module_lines: | 778 if line_num > self.config.max_module_lines: |
269 self.add_message('C0302', args=line_num, line=1) | 779 self.add_message('too-many-lines', args=line_num, line=1) |
270 | 780 |
271 @check_messages('C0321' ,'C03232', 'C0323', 'C0324') | 781 def _process_retained_warnings(self, tokens, current_pos): |
| 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') |
272 def visit_default(self, node): | 828 def visit_default(self, node): |
273 """check the node line number and check it if not yet done""" | 829 """check the node line number and check it if not yet done""" |
274 if not node.is_statement: | 830 if not node.is_statement: |
275 return | 831 return |
276 if not node.root().pure_python: | 832 if not node.root().pure_python: |
277 return # XXX block visit of child nodes | 833 return # XXX block visit of child nodes |
278 prev_sibl = node.previous_sibling() | 834 prev_sibl = node.previous_sibling() |
279 if prev_sibl is not None: | 835 if prev_sibl is not None: |
280 prev_line = prev_sibl.fromlineno | 836 prev_line = prev_sibl.fromlineno |
281 else: | 837 else: |
282 prev_line = node.parent.statement().fromlineno | 838 # The line on which a finally: occurs in a try/finally |
| 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 |
283 line = node.fromlineno | 847 line = node.fromlineno |
284 assert line, node | 848 assert line, node |
285 if prev_line == line and self._visited_lines.get(line) != 2: | 849 if prev_line == line and self._visited_lines.get(line) != 2: |
286 # py2.5 try: except: finally: | 850 self._check_multi_statement_line(node, line) |
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 | |
292 return | 851 return |
293 if line in self._visited_lines: | 852 if line in self._visited_lines: |
294 return | 853 return |
295 try: | 854 try: |
296 tolineno = node.blockstart_tolineno | 855 tolineno = node.blockstart_tolineno |
297 except AttributeError: | 856 except AttributeError: |
298 tolineno = node.tolineno | 857 tolineno = node.tolineno |
299 assert tolineno, node | 858 assert tolineno, node |
300 lines = [] | 859 lines = [] |
301 for line in xrange(line, tolineno + 1): | 860 for line in xrange(line, tolineno + 1): |
302 self._visited_lines[line] = 1 | 861 self._visited_lines[line] = 1 |
303 try: | 862 try: |
304 lines.append(self._lines[line].rstrip()) | 863 lines.append(self._lines[line].rstrip()) |
305 except KeyError: | 864 except KeyError: |
306 lines.append('') | 865 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 | |
314 | 866 |
315 @check_messages('W0333') | 867 def _check_multi_statement_line(self, node, line): |
| 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') |
316 def visit_backquote(self, node): | 885 def visit_backquote(self, node): |
317 self.add_message('W0333', node=node) | 886 self.add_message('backtick', node=node) |
318 | 887 |
319 def check_lines(self, lines, i): | 888 def check_lines(self, lines, i): |
320 """check lines have less than a maximum number of characters | 889 """check lines have less than a maximum number of characters |
321 """ | 890 """ |
322 max_chars = self.config.max_line_length | 891 max_chars = self.config.max_line_length |
323 for line in lines.splitlines(): | 892 ignore_long_line = self.config.ignore_long_lines |
324 if len(line) > max_chars: | 893 |
325 self.add_message('C0301', line=i, args=(len(line), max_chars)) | 894 for line in lines.splitlines(True): |
| 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)) |
326 i += 1 | 909 i += 1 |
327 | 910 |
328 def check_indent_level(self, string, expected, line_num): | 911 def check_indent_level(self, string, expected, line_num): |
329 """return the indent level of the string | 912 """return the indent level of the string |
330 """ | 913 """ |
331 indent = self.config.indent_string | 914 indent = self.config.indent_string |
332 if indent == '\\t': # \t is not interpreted in the configuration file | 915 if indent == '\\t': # \t is not interpreted in the configuration file |
333 indent = '\t' | 916 indent = '\t' |
334 level = 0 | 917 level = 0 |
335 unit_size = len(indent) | 918 unit_size = len(indent) |
336 while string[:unit_size] == indent: | 919 while string[:unit_size] == indent: |
337 string = string[unit_size:] | 920 string = string[unit_size:] |
338 level += 1 | 921 level += 1 |
339 suppl = '' | 922 suppl = '' |
340 while string and string[0] in ' \t': | 923 while string and string[0] in ' \t': |
341 if string[0] != indent[0]: | 924 if string[0] != indent[0]: |
342 if string[0] == '\t': | 925 if string[0] == '\t': |
343 args = ('tab', 'space') | 926 args = ('tab', 'space') |
344 else: | 927 else: |
345 args = ('space', 'tab') | 928 args = ('space', 'tab') |
346 self.add_message('W0312', args=args, line=line_num) | 929 self.add_message('mixed-indentation', args=args, line=line_num) |
347 return level | 930 return level |
348 suppl += string[0] | 931 suppl += string[0] |
349 string = string [1:] | 932 string = string[1:] |
350 if level != expected or suppl: | 933 if level != expected or suppl: |
351 i_type = 'spaces' | 934 i_type = 'spaces' |
352 if indent[0] == '\t': | 935 if indent[0] == '\t': |
353 i_type = 'tabs' | 936 i_type = 'tabs' |
354 self.add_message('W0311', line=line_num, | 937 self.add_message('bad-indentation', line=line_num, |
355 args=(level * unit_size + len(suppl), i_type, | 938 args=(level * unit_size + len(suppl), i_type, |
356 expected * unit_size)) | 939 expected * unit_size)) |
357 | 940 |
358 | 941 |
359 def register(linter): | 942 def register(linter): |
360 """required method to auto register this checker """ | 943 """required method to auto register this checker """ |
361 linter.register_checker(FormatChecker(linter)) | 944 linter.register_checker(FormatChecker(linter)) |
OLD | NEW |