Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(298)

Side by Side Diff: third_party/pylint/checkers/format.py

Issue 739393004: Revert "Revert "pylint: upgrade to 1.3.1"" (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools/
Patch Set: Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « third_party/pylint/checkers/exceptions.py ('k') | third_party/pylint/checkers/imports.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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))
OLDNEW
« no previous file with comments | « third_party/pylint/checkers/exceptions.py ('k') | third_party/pylint/checkers/imports.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698