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

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

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