]>
Commit | Line | Data |
---|---|---|
53e6db90 DC |
1 | import re |
2 | from contextlib import contextmanager | |
3 | from typing import Tuple | |
4 | ||
5 | from parso.python.errors import ErrorFinder, ErrorFinderConfig | |
6 | from parso.normalizer import Rule | |
7 | from parso.python.tree import Flow, Scope | |
8 | ||
9 | ||
10 | _IMPORT_TYPES = ('import_name', 'import_from') | |
11 | _SUITE_INTRODUCERS = ('classdef', 'funcdef', 'if_stmt', 'while_stmt', | |
12 | 'for_stmt', 'try_stmt', 'with_stmt') | |
13 | _NON_STAR_TYPES = ('term', 'import_from', 'power') | |
14 | _OPENING_BRACKETS = '(', '[', '{' | |
15 | _CLOSING_BRACKETS = ')', ']', '}' | |
16 | _FACTOR = '+', '-', '~' | |
17 | _ALLOW_SPACE = '*', '+', '-', '**', '/', '//', '@' | |
18 | _BITWISE_OPERATOR = '<<', '>>', '|', '&', '^' | |
19 | _NEEDS_SPACE: Tuple[str, ...] = ( | |
20 | '=', '%', '->', | |
21 | '<', '>', '==', '>=', '<=', '<>', '!=', | |
22 | '+=', '-=', '*=', '@=', '/=', '%=', '&=', '|=', '^=', '<<=', | |
23 | '>>=', '**=', '//=') | |
24 | _NEEDS_SPACE += _BITWISE_OPERATOR | |
25 | _IMPLICIT_INDENTATION_TYPES = ('dictorsetmaker', 'argument') | |
26 | _POSSIBLE_SLICE_PARENTS = ('subscript', 'subscriptlist', 'sliceop') | |
27 | ||
28 | ||
29 | class IndentationTypes: | |
30 | VERTICAL_BRACKET = object() | |
31 | HANGING_BRACKET = object() | |
32 | BACKSLASH = object() | |
33 | SUITE = object() | |
34 | IMPLICIT = object() | |
35 | ||
36 | ||
37 | class IndentationNode(object): | |
38 | type = IndentationTypes.SUITE | |
39 | ||
40 | def __init__(self, config, indentation, parent=None): | |
41 | self.bracket_indentation = self.indentation = indentation | |
42 | self.parent = parent | |
43 | ||
44 | def __repr__(self): | |
45 | return '<%s>' % self.__class__.__name__ | |
46 | ||
47 | def get_latest_suite_node(self): | |
48 | n = self | |
49 | while n is not None: | |
50 | if n.type == IndentationTypes.SUITE: | |
51 | return n | |
52 | ||
53 | n = n.parent | |
54 | ||
55 | ||
56 | class BracketNode(IndentationNode): | |
57 | def __init__(self, config, leaf, parent, in_suite_introducer=False): | |
58 | self.leaf = leaf | |
59 | ||
60 | # Figure out here what the indentation is. For chained brackets | |
61 | # we can basically use the previous indentation. | |
62 | previous_leaf = leaf | |
63 | n = parent | |
64 | if n.type == IndentationTypes.IMPLICIT: | |
65 | n = n.parent | |
66 | while True: | |
67 | if hasattr(n, 'leaf') and previous_leaf.line != n.leaf.line: | |
68 | break | |
69 | ||
70 | previous_leaf = previous_leaf.get_previous_leaf() | |
71 | if not isinstance(n, BracketNode) or previous_leaf != n.leaf: | |
72 | break | |
73 | n = n.parent | |
74 | parent_indentation = n.indentation | |
75 | ||
76 | next_leaf = leaf.get_next_leaf() | |
77 | if '\n' in next_leaf.prefix or '\r' in next_leaf.prefix: | |
78 | # This implies code like: | |
79 | # foobarbaz( | |
80 | # a, | |
81 | # b, | |
82 | # ) | |
83 | self.bracket_indentation = parent_indentation \ | |
84 | + config.closing_bracket_hanging_indentation | |
85 | self.indentation = parent_indentation + config.indentation | |
86 | self.type = IndentationTypes.HANGING_BRACKET | |
87 | else: | |
88 | # Implies code like: | |
89 | # foobarbaz( | |
90 | # a, | |
91 | # b, | |
92 | # ) | |
93 | expected_end_indent = leaf.end_pos[1] | |
94 | if '\t' in config.indentation: | |
95 | self.indentation = None | |
96 | else: | |
97 | self.indentation = ' ' * expected_end_indent | |
98 | self.bracket_indentation = self.indentation | |
99 | self.type = IndentationTypes.VERTICAL_BRACKET | |
100 | ||
101 | if in_suite_introducer and parent.type == IndentationTypes.SUITE \ | |
102 | and self.indentation == parent_indentation + config.indentation: | |
103 | self.indentation += config.indentation | |
104 | # The closing bracket should have the same indentation. | |
105 | self.bracket_indentation = self.indentation | |
106 | self.parent = parent | |
107 | ||
108 | ||
109 | class ImplicitNode(BracketNode): | |
110 | """ | |
111 | Implicit indentation after keyword arguments, default arguments, | |
112 | annotations and dict values. | |
113 | """ | |
114 | def __init__(self, config, leaf, parent): | |
115 | super().__init__(config, leaf, parent) | |
116 | self.type = IndentationTypes.IMPLICIT | |
117 | ||
118 | next_leaf = leaf.get_next_leaf() | |
119 | if leaf == ':' and '\n' not in next_leaf.prefix and '\r' not in next_leaf.prefix: | |
120 | self.indentation += ' ' | |
121 | ||
122 | ||
123 | class BackslashNode(IndentationNode): | |
124 | type = IndentationTypes.BACKSLASH | |
125 | ||
126 | def __init__(self, config, parent_indentation, containing_leaf, spacing, parent=None): | |
127 | expr_stmt = containing_leaf.search_ancestor('expr_stmt') | |
128 | if expr_stmt is not None: | |
129 | equals = expr_stmt.children[-2] | |
130 | ||
131 | if '\t' in config.indentation: | |
132 | # TODO unite with the code of BracketNode | |
133 | self.indentation = None | |
134 | else: | |
135 | # If the backslash follows the equals, use normal indentation | |
136 | # otherwise it should align with the equals. | |
137 | if equals.end_pos == spacing.start_pos: | |
138 | self.indentation = parent_indentation + config.indentation | |
139 | else: | |
140 | # +1 because there is a space. | |
141 | self.indentation = ' ' * (equals.end_pos[1] + 1) | |
142 | else: | |
143 | self.indentation = parent_indentation + config.indentation | |
144 | self.bracket_indentation = self.indentation | |
145 | self.parent = parent | |
146 | ||
147 | ||
148 | def _is_magic_name(name): | |
149 | return name.value.startswith('__') and name.value.endswith('__') | |
150 | ||
151 | ||
152 | class PEP8Normalizer(ErrorFinder): | |
153 | def __init__(self, *args, **kwargs): | |
154 | super().__init__(*args, **kwargs) | |
155 | self._previous_part = None | |
156 | self._previous_leaf = None | |
157 | self._on_newline = True | |
158 | self._newline_count = 0 | |
159 | self._wanted_newline_count = None | |
160 | self._max_new_lines_in_prefix = 0 | |
161 | self._new_statement = True | |
162 | self._implicit_indentation_possible = False | |
163 | # The top of stack of the indentation nodes. | |
164 | self._indentation_tos = self._last_indentation_tos = \ | |
165 | IndentationNode(self._config, indentation='') | |
166 | self._in_suite_introducer = False | |
167 | ||
168 | if ' ' in self._config.indentation: | |
169 | self._indentation_type = 'spaces' | |
170 | self._wrong_indentation_char = '\t' | |
171 | else: | |
172 | self._indentation_type = 'tabs' | |
173 | self._wrong_indentation_char = ' ' | |
174 | ||
175 | @contextmanager | |
176 | def visit_node(self, node): | |
177 | with super().visit_node(node): | |
178 | with self._visit_node(node): | |
179 | yield | |
180 | ||
181 | @contextmanager | |
182 | def _visit_node(self, node): | |
183 | typ = node.type | |
184 | ||
185 | if typ in 'import_name': | |
186 | names = node.get_defined_names() | |
187 | if len(names) > 1: | |
188 | for name in names[:1]: | |
189 | self.add_issue(name, 401, 'Multiple imports on one line') | |
190 | elif typ == 'lambdef': | |
191 | expr_stmt = node.parent | |
192 | # Check if it's simply defining a single name, not something like | |
193 | # foo.bar or x[1], where using a lambda could make more sense. | |
194 | if expr_stmt.type == 'expr_stmt' and any(n.type == 'name' | |
195 | for n in expr_stmt.children[:-2:2]): | |
196 | self.add_issue(node, 731, 'Do not assign a lambda expression, use a def') | |
197 | elif typ == 'try_stmt': | |
198 | for child in node.children: | |
199 | # Here we can simply check if it's an except, because otherwise | |
200 | # it would be an except_clause. | |
201 | if child.type == 'keyword' and child.value == 'except': | |
202 | self.add_issue(child, 722, 'Do not use bare except, specify exception instead') | |
203 | elif typ == 'comparison': | |
204 | for child in node.children: | |
205 | if child.type not in ('atom_expr', 'power'): | |
206 | continue | |
207 | if len(child.children) > 2: | |
208 | continue | |
209 | trailer = child.children[1] | |
210 | atom = child.children[0] | |
211 | if trailer.type == 'trailer' and atom.type == 'name' \ | |
212 | and atom.value == 'type': | |
213 | self.add_issue(node, 721, "Do not compare types, use 'isinstance()") | |
214 | break | |
215 | elif typ == 'file_input': | |
216 | endmarker = node.children[-1] | |
217 | prev = endmarker.get_previous_leaf() | |
218 | prefix = endmarker.prefix | |
219 | if (not prefix.endswith('\n') and not prefix.endswith('\r') and ( | |
220 | prefix or prev is None or prev.value not in {'\n', '\r\n', '\r'})): | |
221 | self.add_issue(endmarker, 292, "No newline at end of file") | |
222 | ||
223 | if typ in _IMPORT_TYPES: | |
224 | simple_stmt = node.parent | |
225 | module = simple_stmt.parent | |
226 | if module.type == 'file_input': | |
227 | index = module.children.index(simple_stmt) | |
228 | for child in module.children[:index]: | |
229 | children = [child] | |
230 | if child.type == 'simple_stmt': | |
231 | # Remove the newline. | |
232 | children = child.children[:-1] | |
233 | ||
234 | found_docstring = False | |
235 | for c in children: | |
236 | if c.type == 'string' and not found_docstring: | |
237 | continue | |
238 | found_docstring = True | |
239 | ||
240 | if c.type == 'expr_stmt' and \ | |
241 | all(_is_magic_name(n) for n in c.get_defined_names()): | |
242 | continue | |
243 | ||
244 | if c.type in _IMPORT_TYPES or isinstance(c, Flow): | |
245 | continue | |
246 | ||
247 | self.add_issue(node, 402, 'Module level import not at top of file') | |
248 | break | |
249 | else: | |
250 | continue | |
251 | break | |
252 | ||
253 | implicit_indentation_possible = typ in _IMPLICIT_INDENTATION_TYPES | |
254 | in_introducer = typ in _SUITE_INTRODUCERS | |
255 | if in_introducer: | |
256 | self._in_suite_introducer = True | |
257 | elif typ == 'suite': | |
258 | if self._indentation_tos.type == IndentationTypes.BACKSLASH: | |
259 | self._indentation_tos = self._indentation_tos.parent | |
260 | ||
261 | self._indentation_tos = IndentationNode( | |
262 | self._config, | |
263 | self._indentation_tos.indentation + self._config.indentation, | |
264 | parent=self._indentation_tos | |
265 | ) | |
266 | elif implicit_indentation_possible: | |
267 | self._implicit_indentation_possible = True | |
268 | yield | |
269 | if typ == 'suite': | |
270 | assert self._indentation_tos.type == IndentationTypes.SUITE | |
271 | self._indentation_tos = self._indentation_tos.parent | |
272 | # If we dedent, no lines are needed anymore. | |
273 | self._wanted_newline_count = None | |
274 | elif implicit_indentation_possible: | |
275 | self._implicit_indentation_possible = False | |
276 | if self._indentation_tos.type == IndentationTypes.IMPLICIT: | |
277 | self._indentation_tos = self._indentation_tos.parent | |
278 | elif in_introducer: | |
279 | self._in_suite_introducer = False | |
280 | if typ in ('classdef', 'funcdef'): | |
281 | self._wanted_newline_count = self._get_wanted_blank_lines_count() | |
282 | ||
283 | def _check_tabs_spaces(self, spacing): | |
284 | if self._wrong_indentation_char in spacing.value: | |
285 | self.add_issue(spacing, 101, 'Indentation contains ' + self._indentation_type) | |
286 | return True | |
287 | return False | |
288 | ||
289 | def _get_wanted_blank_lines_count(self): | |
290 | suite_node = self._indentation_tos.get_latest_suite_node() | |
291 | return int(suite_node.parent is None) + 1 | |
292 | ||
293 | def _reset_newlines(self, spacing, leaf, is_comment=False): | |
294 | self._max_new_lines_in_prefix = \ | |
295 | max(self._max_new_lines_in_prefix, self._newline_count) | |
296 | ||
297 | wanted = self._wanted_newline_count | |
298 | if wanted is not None: | |
299 | # Need to substract one | |
300 | blank_lines = self._newline_count - 1 | |
301 | if wanted > blank_lines and leaf.type != 'endmarker': | |
302 | # In case of a comment we don't need to add the issue, yet. | |
303 | if not is_comment: | |
304 | # TODO end_pos wrong. | |
305 | code = 302 if wanted == 2 else 301 | |
306 | message = "expected %s blank line, found %s" \ | |
307 | % (wanted, blank_lines) | |
308 | self.add_issue(spacing, code, message) | |
309 | self._wanted_newline_count = None | |
310 | else: | |
311 | self._wanted_newline_count = None | |
312 | ||
313 | if not is_comment: | |
314 | wanted = self._get_wanted_blank_lines_count() | |
315 | actual = self._max_new_lines_in_prefix - 1 | |
316 | ||
317 | val = leaf.value | |
318 | needs_lines = ( | |
319 | val == '@' and leaf.parent.type == 'decorator' | |
320 | or ( | |
321 | val == 'class' | |
322 | or val == 'async' and leaf.get_next_leaf() == 'def' | |
323 | or val == 'def' and self._previous_leaf != 'async' | |
324 | ) and leaf.parent.parent.type != 'decorated' | |
325 | ) | |
326 | if needs_lines and actual < wanted: | |
327 | func_or_cls = leaf.parent | |
328 | suite = func_or_cls.parent | |
329 | if suite.type == 'decorated': | |
330 | suite = suite.parent | |
331 | ||
332 | # The first leaf of a file or a suite should not need blank | |
333 | # lines. | |
334 | if suite.children[int(suite.type == 'suite')] != func_or_cls: | |
335 | code = 302 if wanted == 2 else 301 | |
336 | message = "expected %s blank line, found %s" \ | |
337 | % (wanted, actual) | |
338 | self.add_issue(spacing, code, message) | |
339 | ||
340 | self._max_new_lines_in_prefix = 0 | |
341 | ||
342 | self._newline_count = 0 | |
343 | ||
344 | def visit_leaf(self, leaf): | |
345 | super().visit_leaf(leaf) | |
346 | for part in leaf._split_prefix(): | |
347 | if part.type == 'spacing': | |
348 | # This part is used for the part call after for. | |
349 | break | |
350 | self._visit_part(part, part.create_spacing_part(), leaf) | |
351 | ||
352 | self._analyse_non_prefix(leaf) | |
353 | self._visit_part(leaf, part, leaf) | |
354 | ||
355 | # Cleanup | |
356 | self._last_indentation_tos = self._indentation_tos | |
357 | ||
358 | self._new_statement = leaf.type == 'newline' | |
359 | ||
360 | # TODO does this work? with brackets and stuff? | |
361 | if leaf.type == 'newline' and \ | |
362 | self._indentation_tos.type == IndentationTypes.BACKSLASH: | |
363 | self._indentation_tos = self._indentation_tos.parent | |
364 | ||
365 | if leaf.value == ':' and leaf.parent.type in _SUITE_INTRODUCERS: | |
366 | self._in_suite_introducer = False | |
367 | elif leaf.value == 'elif': | |
368 | self._in_suite_introducer = True | |
369 | ||
370 | if not self._new_statement: | |
371 | self._reset_newlines(part, leaf) | |
372 | self._max_blank_lines = 0 | |
373 | ||
374 | self._previous_leaf = leaf | |
375 | ||
376 | return leaf.value | |
377 | ||
378 | def _visit_part(self, part, spacing, leaf): | |
379 | value = part.value | |
380 | type_ = part.type | |
381 | if type_ == 'error_leaf': | |
382 | return | |
383 | ||
384 | if value == ',' and part.parent.type == 'dictorsetmaker': | |
385 | self._indentation_tos = self._indentation_tos.parent | |
386 | ||
387 | node = self._indentation_tos | |
388 | ||
389 | if type_ == 'comment': | |
390 | if value.startswith('##'): | |
391 | # Whole blocks of # should not raise an error. | |
392 | if value.lstrip('#'): | |
393 | self.add_issue(part, 266, "Too many leading '#' for block comment.") | |
394 | elif self._on_newline: | |
395 | if not re.match(r'#:? ', value) and not value == '#' \ | |
396 | and not (value.startswith('#!') and part.start_pos == (1, 0)): | |
397 | self.add_issue(part, 265, "Block comment should start with '# '") | |
398 | else: | |
399 | if not re.match(r'#:? [^ ]', value): | |
400 | self.add_issue(part, 262, "Inline comment should start with '# '") | |
401 | ||
402 | self._reset_newlines(spacing, leaf, is_comment=True) | |
403 | elif type_ == 'newline': | |
404 | if self._newline_count > self._get_wanted_blank_lines_count(): | |
405 | self.add_issue(part, 303, "Too many blank lines (%s)" % self._newline_count) | |
406 | elif leaf in ('def', 'class') \ | |
407 | and leaf.parent.parent.type == 'decorated': | |
408 | self.add_issue(part, 304, "Blank lines found after function decorator") | |
409 | ||
410 | self._newline_count += 1 | |
411 | ||
412 | if type_ == 'backslash': | |
413 | # TODO is this enough checking? What about ==? | |
414 | if node.type != IndentationTypes.BACKSLASH: | |
415 | if node.type != IndentationTypes.SUITE: | |
416 | self.add_issue(part, 502, 'The backslash is redundant between brackets') | |
417 | else: | |
418 | indentation = node.indentation | |
419 | if self._in_suite_introducer and node.type == IndentationTypes.SUITE: | |
420 | indentation += self._config.indentation | |
421 | ||
422 | self._indentation_tos = BackslashNode( | |
423 | self._config, | |
424 | indentation, | |
425 | part, | |
426 | spacing, | |
427 | parent=self._indentation_tos | |
428 | ) | |
429 | elif self._on_newline: | |
430 | indentation = spacing.value | |
431 | if node.type == IndentationTypes.BACKSLASH \ | |
432 | and self._previous_part.type == 'newline': | |
433 | self._indentation_tos = self._indentation_tos.parent | |
434 | ||
435 | if not self._check_tabs_spaces(spacing): | |
436 | should_be_indentation = node.indentation | |
437 | if type_ == 'comment': | |
438 | # Comments can be dedented. So we have to care for that. | |
439 | n = self._last_indentation_tos | |
440 | while True: | |
441 | if len(indentation) > len(n.indentation): | |
442 | break | |
443 | ||
444 | should_be_indentation = n.indentation | |
445 | ||
446 | self._last_indentation_tos = n | |
447 | if n == node: | |
448 | break | |
449 | n = n.parent | |
450 | ||
451 | if self._new_statement: | |
452 | if type_ == 'newline': | |
453 | if indentation: | |
454 | self.add_issue(spacing, 291, 'Trailing whitespace') | |
455 | elif indentation != should_be_indentation: | |
456 | s = '%s %s' % (len(self._config.indentation), self._indentation_type) | |
457 | self.add_issue(part, 111, 'Indentation is not a multiple of ' + s) | |
458 | else: | |
459 | if value in '])}': | |
460 | should_be_indentation = node.bracket_indentation | |
461 | else: | |
462 | should_be_indentation = node.indentation | |
463 | if self._in_suite_introducer and indentation == \ | |
464 | node.get_latest_suite_node().indentation \ | |
465 | + self._config.indentation: | |
466 | self.add_issue(part, 129, "Line with same indent as next logical block") | |
467 | elif indentation != should_be_indentation: | |
468 | if not self._check_tabs_spaces(spacing) and part.value not in \ | |
469 | {'\n', '\r\n', '\r'}: | |
470 | if value in '])}': | |
471 | if node.type == IndentationTypes.VERTICAL_BRACKET: | |
472 | self.add_issue( | |
473 | part, | |
474 | 124, | |
475 | "Closing bracket does not match visual indentation" | |
476 | ) | |
477 | else: | |
478 | self.add_issue( | |
479 | part, | |
480 | 123, | |
481 | "Losing bracket does not match " | |
482 | "indentation of opening bracket's line" | |
483 | ) | |
484 | else: | |
485 | if len(indentation) < len(should_be_indentation): | |
486 | if node.type == IndentationTypes.VERTICAL_BRACKET: | |
487 | self.add_issue( | |
488 | part, | |
489 | 128, | |
490 | 'Continuation line under-indented for visual indent' | |
491 | ) | |
492 | elif node.type == IndentationTypes.BACKSLASH: | |
493 | self.add_issue( | |
494 | part, | |
495 | 122, | |
496 | 'Continuation line missing indentation or outdented' | |
497 | ) | |
498 | elif node.type == IndentationTypes.IMPLICIT: | |
499 | self.add_issue(part, 135, 'xxx') | |
500 | else: | |
501 | self.add_issue( | |
502 | part, | |
503 | 121, | |
504 | 'Continuation line under-indented for hanging indent' | |
505 | ) | |
506 | else: | |
507 | if node.type == IndentationTypes.VERTICAL_BRACKET: | |
508 | self.add_issue( | |
509 | part, | |
510 | 127, | |
511 | 'Continuation line over-indented for visual indent' | |
512 | ) | |
513 | elif node.type == IndentationTypes.IMPLICIT: | |
514 | self.add_issue(part, 136, 'xxx') | |
515 | else: | |
516 | self.add_issue( | |
517 | part, | |
518 | 126, | |
519 | 'Continuation line over-indented for hanging indent' | |
520 | ) | |
521 | else: | |
522 | self._check_spacing(part, spacing) | |
523 | ||
524 | self._check_line_length(part, spacing) | |
525 | # ------------------------------- | |
526 | # Finalizing. Updating the state. | |
527 | # ------------------------------- | |
528 | if value and value in '()[]{}' and type_ != 'error_leaf' \ | |
529 | and part.parent.type != 'error_node': | |
530 | if value in _OPENING_BRACKETS: | |
531 | self._indentation_tos = BracketNode( | |
532 | self._config, part, | |
533 | parent=self._indentation_tos, | |
534 | in_suite_introducer=self._in_suite_introducer | |
535 | ) | |
536 | else: | |
537 | assert node.type != IndentationTypes.IMPLICIT | |
538 | self._indentation_tos = self._indentation_tos.parent | |
539 | elif value in ('=', ':') and self._implicit_indentation_possible \ | |
540 | and part.parent.type in _IMPLICIT_INDENTATION_TYPES: | |
541 | indentation = node.indentation | |
542 | self._indentation_tos = ImplicitNode( | |
543 | self._config, part, parent=self._indentation_tos | |
544 | ) | |
545 | ||
546 | self._on_newline = type_ in ('newline', 'backslash', 'bom') | |
547 | ||
548 | self._previous_part = part | |
549 | self._previous_spacing = spacing | |
550 | ||
551 | def _check_line_length(self, part, spacing): | |
552 | if part.type == 'backslash': | |
553 | last_column = part.start_pos[1] + 1 | |
554 | else: | |
555 | last_column = part.end_pos[1] | |
556 | if last_column > self._config.max_characters \ | |
557 | and spacing.start_pos[1] <= self._config.max_characters: | |
558 | # Special case for long URLs in multi-line docstrings or comments, | |
559 | # but still report the error when the 72 first chars are whitespaces. | |
560 | report = True | |
561 | if part.type == 'comment': | |
562 | splitted = part.value[1:].split() | |
563 | if len(splitted) == 1 \ | |
564 | and (part.end_pos[1] - len(splitted[0])) < 72: | |
565 | report = False | |
566 | if report: | |
567 | self.add_issue( | |
568 | part, | |
569 | 501, | |
570 | 'Line too long (%s > %s characters)' % | |
571 | (last_column, self._config.max_characters), | |
572 | ) | |
573 | ||
574 | def _check_spacing(self, part, spacing): | |
575 | def add_if_spaces(*args): | |
576 | if spaces: | |
577 | return self.add_issue(*args) | |
578 | ||
579 | def add_not_spaces(*args): | |
580 | if not spaces: | |
581 | return self.add_issue(*args) | |
582 | ||
583 | spaces = spacing.value | |
584 | prev = self._previous_part | |
585 | if prev is not None and prev.type == 'error_leaf' or part.type == 'error_leaf': | |
586 | return | |
587 | ||
588 | type_ = part.type | |
589 | if '\t' in spaces: | |
590 | self.add_issue(spacing, 223, 'Used tab to separate tokens') | |
591 | elif type_ == 'comment': | |
592 | if len(spaces) < self._config.spaces_before_comment: | |
593 | self.add_issue(spacing, 261, 'At least two spaces before inline comment') | |
594 | elif type_ == 'newline': | |
595 | add_if_spaces(spacing, 291, 'Trailing whitespace') | |
596 | elif len(spaces) > 1: | |
597 | self.add_issue(spacing, 221, 'Multiple spaces used') | |
598 | else: | |
599 | if prev in _OPENING_BRACKETS: | |
600 | message = "Whitespace after '%s'" % part.value | |
601 | add_if_spaces(spacing, 201, message) | |
602 | elif part in _CLOSING_BRACKETS: | |
603 | message = "Whitespace before '%s'" % part.value | |
604 | add_if_spaces(spacing, 202, message) | |
605 | elif part in (',', ';') or part == ':' \ | |
606 | and part.parent.type not in _POSSIBLE_SLICE_PARENTS: | |
607 | message = "Whitespace before '%s'" % part.value | |
608 | add_if_spaces(spacing, 203, message) | |
609 | elif prev == ':' and prev.parent.type in _POSSIBLE_SLICE_PARENTS: | |
610 | pass # TODO | |
611 | elif prev in (',', ';', ':'): | |
612 | add_not_spaces(spacing, 231, "missing whitespace after '%s'") | |
613 | elif part == ':': # Is a subscript | |
614 | # TODO | |
615 | pass | |
616 | elif part in ('*', '**') and part.parent.type not in _NON_STAR_TYPES \ | |
617 | or prev in ('*', '**') \ | |
618 | and prev.parent.type not in _NON_STAR_TYPES: | |
619 | # TODO | |
620 | pass | |
621 | elif prev in _FACTOR and prev.parent.type == 'factor': | |
622 | pass | |
623 | elif prev == '@' and prev.parent.type == 'decorator': | |
624 | pass # TODO should probably raise an error if there's a space here | |
625 | elif part in _NEEDS_SPACE or prev in _NEEDS_SPACE: | |
626 | if part == '=' and part.parent.type in ('argument', 'param') \ | |
627 | or prev == '=' and prev.parent.type in ('argument', 'param'): | |
628 | if part == '=': | |
629 | param = part.parent | |
630 | else: | |
631 | param = prev.parent | |
632 | if param.type == 'param' and param.annotation: | |
633 | add_not_spaces(spacing, 252, 'Expected spaces around annotation equals') | |
634 | else: | |
635 | add_if_spaces( | |
636 | spacing, | |
637 | 251, | |
638 | 'Unexpected spaces around keyword / parameter equals' | |
639 | ) | |
640 | elif part in _BITWISE_OPERATOR or prev in _BITWISE_OPERATOR: | |
641 | add_not_spaces( | |
642 | spacing, | |
643 | 227, | |
644 | 'Missing whitespace around bitwise or shift operator' | |
645 | ) | |
646 | elif part == '%' or prev == '%': | |
647 | add_not_spaces(spacing, 228, 'Missing whitespace around modulo operator') | |
648 | else: | |
649 | message_225 = 'Missing whitespace between tokens' | |
650 | add_not_spaces(spacing, 225, message_225) | |
651 | elif type_ == 'keyword' or prev.type == 'keyword': | |
652 | add_not_spaces(spacing, 275, 'Missing whitespace around keyword') | |
653 | else: | |
654 | prev_spacing = self._previous_spacing | |
655 | if prev in _ALLOW_SPACE and spaces != prev_spacing.value \ | |
656 | and '\n' not in self._previous_leaf.prefix \ | |
657 | and '\r' not in self._previous_leaf.prefix: | |
658 | message = "Whitespace before operator doesn't match with whitespace after" | |
659 | self.add_issue(spacing, 229, message) | |
660 | ||
661 | if spaces and part not in _ALLOW_SPACE and prev not in _ALLOW_SPACE: | |
662 | message_225 = 'Missing whitespace between tokens' | |
663 | # self.add_issue(spacing, 225, message_225) | |
664 | # TODO why only brackets? | |
665 | if part in _OPENING_BRACKETS: | |
666 | message = "Whitespace before '%s'" % part.value | |
667 | add_if_spaces(spacing, 211, message) | |
668 | ||
669 | def _analyse_non_prefix(self, leaf): | |
670 | typ = leaf.type | |
671 | if typ == 'name' and leaf.value in ('l', 'O', 'I'): | |
672 | if leaf.is_definition(): | |
673 | message = "Do not define %s named 'l', 'O', or 'I' one line" | |
674 | if leaf.parent.type == 'class' and leaf.parent.name == leaf: | |
675 | self.add_issue(leaf, 742, message % 'classes') | |
676 | elif leaf.parent.type == 'function' and leaf.parent.name == leaf: | |
677 | self.add_issue(leaf, 743, message % 'function') | |
678 | else: | |
679 | self.add_issuadd_issue(741, message % 'variables', leaf) | |
680 | elif leaf.value == ':': | |
681 | if isinstance(leaf.parent, (Flow, Scope)) and leaf.parent.type != 'lambdef': | |
682 | next_leaf = leaf.get_next_leaf() | |
683 | if next_leaf.type != 'newline': | |
684 | if leaf.parent.type == 'funcdef': | |
685 | self.add_issue(next_leaf, 704, 'Multiple statements on one line (def)') | |
686 | else: | |
687 | self.add_issue(next_leaf, 701, 'Multiple statements on one line (colon)') | |
688 | elif leaf.value == ';': | |
689 | if leaf.get_next_leaf().type in ('newline', 'endmarker'): | |
690 | self.add_issue(leaf, 703, 'Statement ends with a semicolon') | |
691 | else: | |
692 | self.add_issue(leaf, 702, 'Multiple statements on one line (semicolon)') | |
693 | elif leaf.value in ('==', '!='): | |
694 | comparison = leaf.parent | |
695 | index = comparison.children.index(leaf) | |
696 | left = comparison.children[index - 1] | |
697 | right = comparison.children[index + 1] | |
698 | for node in left, right: | |
699 | if node.type == 'keyword' or node.type == 'name': | |
700 | if node.value == 'None': | |
701 | message = "comparison to None should be 'if cond is None:'" | |
702 | self.add_issue(leaf, 711, message) | |
703 | break | |
704 | elif node.value in ('True', 'False'): | |
705 | message = "comparison to False/True should be " \ | |
706 | "'if cond is True:' or 'if cond:'" | |
707 | self.add_issue(leaf, 712, message) | |
708 | break | |
709 | elif leaf.value in ('in', 'is'): | |
710 | comparison = leaf.parent | |
711 | if comparison.type == 'comparison' and comparison.parent.type == 'not_test': | |
712 | if leaf.value == 'in': | |
713 | self.add_issue(leaf, 713, "test for membership should be 'not in'") | |
714 | else: | |
715 | self.add_issue(leaf, 714, "test for object identity should be 'is not'") | |
716 | elif typ == 'string': | |
717 | # Checking multiline strings | |
718 | for i, line in enumerate(leaf.value.splitlines()[1:]): | |
719 | indentation = re.match(r'[ \t]*', line).group(0) | |
720 | start_pos = leaf.line + i, len(indentation) | |
721 | # TODO check multiline indentation. | |
722 | start_pos | |
723 | elif typ == 'endmarker': | |
724 | if self._newline_count >= 2: | |
725 | self.add_issue(leaf, 391, 'Blank line at end of file') | |
726 | ||
727 | def add_issue(self, node, code, message): | |
728 | if self._previous_leaf is not None: | |
729 | if self._previous_leaf.search_ancestor('error_node') is not None: | |
730 | return | |
731 | if self._previous_leaf.type == 'error_leaf': | |
732 | return | |
733 | if node.search_ancestor('error_node') is not None: | |
734 | return | |
735 | if code in (901, 903): | |
736 | # 901 and 903 are raised by the ErrorFinder. | |
737 | super().add_issue(node, code, message) | |
738 | else: | |
739 | # Skip ErrorFinder here, because it has custom behavior. | |
740 | super(ErrorFinder, self).add_issue(node, code, message) | |
741 | ||
742 | ||
743 | class PEP8NormalizerConfig(ErrorFinderConfig): | |
744 | normalizer_class = PEP8Normalizer | |
745 | """ | |
746 | Normalizing to PEP8. Not really implemented, yet. | |
747 | """ | |
748 | def __init__(self, indentation=' ' * 4, hanging_indentation=None, | |
749 | max_characters=79, spaces_before_comment=2): | |
750 | self.indentation = indentation | |
751 | if hanging_indentation is None: | |
752 | hanging_indentation = indentation | |
753 | self.hanging_indentation = hanging_indentation | |
754 | self.closing_bracket_hanging_indentation = '' | |
755 | self.break_after_binary = False | |
756 | self.max_characters = max_characters | |
757 | self.spaces_before_comment = spaces_before_comment | |
758 | ||
759 | ||
760 | # TODO this is not yet ready. | |
761 | # @PEP8Normalizer.register_rule(type='endmarker') | |
762 | class BlankLineAtEnd(Rule): | |
763 | code = 392 | |
764 | message = 'Blank line at end of file' | |
765 | ||
766 | def is_issue(self, leaf): | |
767 | return self._newline_count >= 2 |