2 from contextlib
import contextmanager
3 from typing
import Tuple
5 from parso
.python
.errors
import ErrorFinder
, ErrorFinderConfig
6 from parso
.normalizer
import Rule
7 from parso
.python
.tree
import Flow
, Scope
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, ...] = (
21 '<', '>', '==', '>=', '<=', '<>', '!=',
22 '+=', '-=', '*=', '@=', '/=', '%=', '&=', '|=', '^=', '<<=',
24 _NEEDS_SPACE
+= _BITWISE_OPERATOR
25 _IMPLICIT_INDENTATION_TYPES
= ('dictorsetmaker', 'argument')
26 _POSSIBLE_SLICE_PARENTS
= ('subscript', 'subscriptlist', 'sliceop')
29 class IndentationTypes
:
30 VERTICAL_BRACKET
= object()
31 HANGING_BRACKET
= object()
37 class IndentationNode(object):
38 type = IndentationTypes
.SUITE
40 def __init__(self
, config
, indentation
, parent
=None):
41 self
.bracket_indentation
= self
.indentation
= indentation
45 return '<%s>' % self
.__class
__.__name
__
47 def get_latest_suite_node(self
):
50 if n
.type == IndentationTypes
.SUITE
:
56 class BracketNode(IndentationNode
):
57 def __init__(self
, config
, leaf
, parent
, in_suite_introducer
=False):
60 # Figure out here what the indentation is. For chained brackets
61 # we can basically use the previous indentation.
64 if n
.type == IndentationTypes
.IMPLICIT
:
67 if hasattr(n
, 'leaf') and previous_leaf
.line
!= n
.leaf
.line
:
70 previous_leaf
= previous_leaf
.get_previous_leaf()
71 if not isinstance(n
, BracketNode
) or previous_leaf
!= n
.leaf
:
74 parent_indentation
= n
.indentation
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:
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
93 expected_end_indent
= leaf
.end_pos
[1]
94 if '\t' in config
.indentation
:
95 self
.indentation
= None
97 self
.indentation
= ' ' * expected_end_indent
98 self
.bracket_indentation
= self
.indentation
99 self
.type = IndentationTypes
.VERTICAL_BRACKET
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
109 class ImplicitNode(BracketNode
):
111 Implicit indentation after keyword arguments, default arguments,
112 annotations and dict values.
114 def __init__(self
, config
, leaf
, parent
):
115 super().__init
__(config
, leaf
, parent
)
116 self
.type = IndentationTypes
.IMPLICIT
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
+= ' '
123 class BackslashNode(IndentationNode
):
124 type = IndentationTypes
.BACKSLASH
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]
131 if '\t' in config
.indentation
:
132 # TODO unite with the code of BracketNode
133 self
.indentation
= None
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
140 # +1 because there is a space.
141 self
.indentation
= ' ' * (equals
.end_pos
[1] + 1)
143 self
.indentation
= parent_indentation
+ config
.indentation
144 self
.bracket_indentation
= self
.indentation
148 def _is_magic_name(name
):
149 return name
.value
.startswith('__') and name
.value
.endswith('__')
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
168 if ' ' in self
._config
.indentation
:
169 self
._indentation
_type
= 'spaces'
170 self
._wrong
_indentation
_char
= '\t'
172 self
._indentation
_type
= 'tabs'
173 self
._wrong
_indentation
_char
= ' '
176 def visit_node(self
, node
):
177 with
super().visit_node(node
):
178 with self
._visit
_node
(node
):
182 def _visit_node(self
, node
):
185 if typ
in 'import_name':
186 names
= node
.get_defined_names()
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'):
207 if len(child
.children
) > 2:
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()")
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")
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
]:
230 if child
.type == 'simple_stmt':
231 # Remove the newline.
232 children
= child
.children
[:-1]
234 found_docstring
= False
236 if c
.type == 'string' and not found_docstring
:
238 found_docstring
= True
240 if c
.type == 'expr_stmt' and \
241 all(_is_magic_name(n
) for n
in c
.get_defined_names()):
244 if c
.type in _IMPORT_TYPES
or isinstance(c
, Flow
):
247 self
.add_issue(node
, 402, 'Module level import not at top of file')
253 implicit_indentation_possible
= typ
in _IMPLICIT_INDENTATION_TYPES
254 in_introducer
= typ
in _SUITE_INTRODUCERS
256 self
._in
_suite
_introducer
= True
258 if self
._indentation
_tos
.type == IndentationTypes
.BACKSLASH
:
259 self
._indentation
_tos
= self
._indentation
_tos
.parent
261 self
._indentation
_tos
= IndentationNode(
263 self
._indentation
_tos
.indentation
+ self
._config
.indentation
,
264 parent
=self
._indentation
_tos
266 elif implicit_indentation_possible
:
267 self
._implicit
_indentation
_possible
= True
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
279 self
._in
_suite
_introducer
= False
280 if typ
in ('classdef', 'funcdef'):
281 self
._wanted
_newline
_count
= self
._get
_wanted
_blank
_lines
_count
()
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
)
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
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
)
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.
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
311 self
._wanted
_newline
_count
= None
314 wanted
= self
._get
_wanted
_blank
_lines
_count
()
315 actual
= self
._max
_new
_lines
_in
_prefix
- 1
319 val
== '@' and leaf
.parent
.type == 'decorator'
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'
326 if needs_lines
and actual
< wanted
:
327 func_or_cls
= leaf
.parent
328 suite
= func_or_cls
.parent
329 if suite
.type == 'decorated':
332 # The first leaf of a file or a suite should not need blank
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" \
338 self
.add_issue(spacing
, code
, message
)
340 self
._max
_new
_lines
_in
_prefix
= 0
342 self
._newline
_count
= 0
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.
350 self
._visit
_part
(part
, part
.create_spacing_part(), leaf
)
352 self
._analyse
_non
_prefix
(leaf
)
353 self
._visit
_part
(leaf
, part
, leaf
)
356 self
._last
_indentation
_tos
= self
._indentation
_tos
358 self
._new
_statement
= leaf
.type == 'newline'
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
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
370 if not self
._new
_statement
:
371 self
._reset
_newlines
(part
, leaf
)
372 self
._max
_blank
_lines
= 0
374 self
._previous
_leaf
= leaf
378 def _visit_part(self
, part
, spacing
, leaf
):
381 if type_
== 'error_leaf':
384 if value
== ',' and part
.parent
.type == 'dictorsetmaker':
385 self
._indentation
_tos
= self
._indentation
_tos
.parent
387 node
= self
._indentation
_tos
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 '# '")
399 if not re
.match(r
'#:? [^ ]', value
):
400 self
.add_issue(part
, 262, "Inline comment should start with '# '")
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")
410 self
._newline
_count
+= 1
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')
418 indentation
= node
.indentation
419 if self
._in
_suite
_introducer
and node
.type == IndentationTypes
.SUITE
:
420 indentation
+= self
._config
.indentation
422 self
._indentation
_tos
= BackslashNode(
427 parent
=self
._indentation
_tos
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
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
441 if len(indentation
) > len(n
.indentation
):
444 should_be_indentation
= n
.indentation
446 self
._last
_indentation
_tos
= n
451 if self
._new
_statement
:
452 if type_
== 'newline':
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
)
460 should_be_indentation
= node
.bracket_indentation
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'}:
471 if node
.type == IndentationTypes
.VERTICAL_BRACKET
:
475 "Closing bracket does not match visual indentation"
481 "Losing bracket does not match "
482 "indentation of opening bracket's line"
485 if len(indentation
) < len(should_be_indentation
):
486 if node
.type == IndentationTypes
.VERTICAL_BRACKET
:
490 'Continuation line under-indented for visual indent'
492 elif node
.type == IndentationTypes
.BACKSLASH
:
496 'Continuation line missing indentation or outdented'
498 elif node
.type == IndentationTypes
.IMPLICIT
:
499 self
.add_issue(part
, 135, 'xxx')
504 'Continuation line under-indented for hanging indent'
507 if node
.type == IndentationTypes
.VERTICAL_BRACKET
:
511 'Continuation line over-indented for visual indent'
513 elif node
.type == IndentationTypes
.IMPLICIT
:
514 self
.add_issue(part
, 136, 'xxx')
519 'Continuation line over-indented for hanging indent'
522 self
._check
_spacing
(part
, spacing
)
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(
533 parent
=self
._indentation
_tos
,
534 in_suite_introducer
=self
._in
_suite
_introducer
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
546 self
._on
_newline
= type_
in ('newline', 'backslash', 'bom')
548 self
._previous
_part
= part
549 self
._previous
_spacing
= spacing
551 def _check_line_length(self
, part
, spacing
):
552 if part
.type == 'backslash':
553 last_column
= part
.start_pos
[1] + 1
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.
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:
570 'Line too long (%s > %s characters)' %
571 (last_column
, self
._config
.max_characters
),
574 def _check_spacing(self
, part
, spacing
):
575 def add_if_spaces(*args
):
577 return self
.add_issue(*args
)
579 def add_not_spaces(*args
):
581 return self
.add_issue(*args
)
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':
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')
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
:
611 elif prev
in (',', ';', ':'):
612 add_not_spaces(spacing
, 231, "missing whitespace after '%s'")
613 elif part
== ':': # Is a subscript
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
:
621 elif prev
in _FACTOR
and prev
.parent
.type == 'factor':
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'):
632 if param
.type == 'param' and param
.annotation
:
633 add_not_spaces(spacing
, 252, 'Expected spaces around annotation equals')
638 'Unexpected spaces around keyword / parameter equals'
640 elif part
in _BITWISE_OPERATOR
or prev
in _BITWISE_OPERATOR
:
644 'Missing whitespace around bitwise or shift operator'
646 elif part
== '%' or prev
== '%':
647 add_not_spaces(spacing
, 228, 'Missing whitespace around modulo operator')
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')
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
)
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
)
669 def _analyse_non_prefix(self
, leaf
):
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')
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)')
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')
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
)
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
)
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'")
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.
723 elif typ
== 'endmarker':
724 if self
._newline
_count
>= 2:
725 self
.add_issue(leaf
, 391, 'Blank line at end of file')
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:
731 if self
._previous
_leaf
.type == 'error_leaf':
733 if node
.search_ancestor('error_node') is not None:
735 if code
in (901, 903):
736 # 901 and 903 are raised by the ErrorFinder.
737 super().add_issue(node
, code
, message
)
739 # Skip ErrorFinder here, because it has custom behavior.
740 super(ErrorFinder
, self
).add_issue(node
, code
, message
)
743 class PEP8NormalizerConfig(ErrorFinderConfig
):
744 normalizer_class
= PEP8Normalizer
746 Normalizing to PEP8. Not really implemented, yet.
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
760 # TODO this is not yet ready.
761 # @PEP8Normalizer.register_rule(type='endmarker')
762 class BlankLineAtEnd(Rule
):
764 message
= 'Blank line at end of file'
766 def is_issue(self
, leaf
):
767 return self
._newline
_count
>= 2