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