1 # -*- coding: utf-8 -*-
5 from contextlib
import contextmanager
7 from parso
.normalizer
import Normalizer
, NormalizerConfig
, Issue
, Rule
8 from parso
.python
.tokenize
import _get_token_collection
10 _BLOCK_STMTS
= ('if_stmt', 'while_stmt', 'for_stmt', 'try_stmt', 'with_stmt')
11 _STAR_EXPR_PARENTS
= ('testlist_star_expr', 'testlist_comp', 'exprlist')
12 # This is the maximal block size given by python.
14 _MAX_INDENT_COUNT
= 100
16 'nested_scopes', 'generators', 'division', 'absolute_import',
17 'with_statement', 'print_function', 'unicode_literals', 'generator_stop',
19 _COMP_FOR_TYPES
= ('comp_for', 'sync_comp_for')
22 def _get_rhs_name(node
, version
):
24 if type_
== "lambdef":
27 comprehension
= _get_comprehension_type(node
)
28 first
, second
= node
.children
[:2]
29 if comprehension
is not None:
31 elif second
.type == "dictorsetmaker":
35 if second
.children
[1] == ":" or second
.children
[0] == "**":
42 or (len(node
.children
) == 3 and node
.children
[1].type == "testlist_comp"))
46 return _get_rhs_name(_remove_parens(node
), version
=version
)
49 elif first
== "{" and second
== "}":
51 elif first
== "{" and len(node
.children
) > 2:
53 elif type_
== "keyword":
54 if "yield" in node
.value
:
55 return "yield expression"
59 return str(node
.value
)
60 elif type_
== "operator" and node
.value
== "...":
62 elif type_
== "comparison":
64 elif type_
in ("string", "number", "strings"):
66 elif type_
== "yield_expr":
67 return "yield expression"
69 return "conditional expression"
70 elif type_
in ("atom_expr", "power"):
71 if node
.children
[0] == "await":
72 return "await expression"
73 elif node
.children
[-1].type == "trailer":
74 trailer
= node
.children
[-1]
75 if trailer
.children
[0] == "(":
76 return "function call"
77 elif trailer
.children
[0] == "[":
79 elif trailer
.children
[0] == ".":
82 ("expr" in type_
and "star_expr" not in type_
) # is a substring
84 or type_
in ("term", "factor")
87 elif type_
== "star_expr":
89 elif type_
== "testlist_star_expr":
91 elif type_
== "fstring":
92 return "f-string expression"
93 return type_
# shouldn't reach here
96 def _iter_stmts(scope
):
98 Iterates over all statements and splits up simple_stmt.
100 for child
in scope
.children
:
101 if child
.type == 'simple_stmt':
102 for child2
in child
.children
:
103 if child2
.type == 'newline' or child2
== ';':
110 def _get_comprehension_type(atom
):
111 first
, second
= atom
.children
[:2]
112 if second
.type == 'testlist_comp' and second
.children
[1].type in _COMP_FOR_TYPES
:
114 return 'list comprehension'
116 return 'generator expression'
117 elif second
.type == 'dictorsetmaker' and second
.children
[-1].type in _COMP_FOR_TYPES
:
118 if second
.children
[1] == ':':
119 return 'dict comprehension'
121 return 'set comprehension'
125 def _is_future_import(import_from
):
126 # It looks like a __future__ import that is relative is still a future
127 # import. That feels kind of odd, but whatever.
128 # if import_from.level != 0:
130 from_names
= import_from
.get_from_names()
131 return [n
.value
for n
in from_names
] == ['__future__']
134 def _remove_parens(atom
):
136 Returns the inner part of an expression like `(foo)`. Also removes nested
140 children
= atom
.children
141 except AttributeError:
144 if len(children
) == 3 and children
[0] == '(':
145 return _remove_parens(atom
.children
[1])
149 def _skip_parens_bottom_up(node
):
151 Returns an ancestor node of an expression, skipping all levels of parens
154 while node
.parent
is not None:
156 if node
.type != 'atom' or node
.children
[0] != '(':
161 def _iter_params(parent_node
):
162 return (n
for n
in parent_node
.children
if n
.type == 'param' or n
.type == 'operator')
165 def _is_future_import_first(import_from
):
167 Checks if the import is the first statement of a file.
169 found_docstring
= False
170 for stmt
in _iter_stmts(import_from
.get_root_node()):
171 if stmt
.type == 'string' and not found_docstring
:
173 found_docstring
= True
175 if stmt
== import_from
:
177 if stmt
.type == 'import_from' and _is_future_import(stmt
):
182 def _iter_definition_exprs_from_lists(exprlist
):
183 def check_expr(child
):
184 if child
.type == 'atom':
185 if child
.children
[0] == '(':
186 testlist_comp
= child
.children
[1]
187 if testlist_comp
.type == 'testlist_comp':
188 yield from _iter_definition_exprs_from_lists(testlist_comp
)
191 # It's a paren that doesn't do anything, like 1 + (1)
192 yield from check_expr(testlist_comp
)
194 elif child
.children
[0] == '[':
199 if exprlist
.type in _STAR_EXPR_PARENTS
:
200 for child
in exprlist
.children
[::2]:
201 yield from check_expr(child
)
203 yield from check_expr(exprlist
)
206 def _get_expr_stmt_definition_exprs(expr_stmt
):
208 for list_
in expr_stmt
.children
[:-2:2]:
209 if list_
.type in ('testlist_star_expr', 'testlist'):
210 exprs
+= _iter_definition_exprs_from_lists(list_
)
216 def _get_for_stmt_definition_exprs(for_stmt
):
217 exprlist
= for_stmt
.children
[1]
218 return list(_iter_definition_exprs_from_lists(exprlist
))
221 def _is_argument_comprehension(argument
):
222 return argument
.children
[1].type in _COMP_FOR_TYPES
225 def _any_fstring_error(version
, node
):
226 if version
< (3, 9) or node
is None:
228 if node
.type == "error_node":
229 return any(child
.type == "fstring_start" for child
in node
.children
)
230 elif node
.type == "fstring":
233 return node
.search_ancestor("fstring")
237 def __init__(self
, node
, add_syntax_error
, parent_context
=None):
240 self
.parent_context
= parent_context
241 self
._used
_name
_dict
= {}
242 self
._global
_names
= []
243 self
._local
_params
_names
= []
244 self
._nonlocal
_names
= []
245 self
._nonlocal
_names
_in
_subscopes
= []
246 self
._add
_syntax
_error
= add_syntax_error
248 def is_async_funcdef(self
):
249 # Stupidly enough async funcdefs can have two different forms,
250 # depending if a decorator is used or not.
251 return self
.is_function() \
252 and self
.node
.parent
.type in ('async_funcdef', 'async_stmt')
254 def is_function(self
):
255 return self
.node
.type == 'funcdef'
257 def add_name(self
, name
):
258 parent_type
= name
.parent
.type
259 if parent_type
== 'trailer':
260 # We are only interested in first level names.
263 if parent_type
== 'global_stmt':
264 self
._global
_names
.append(name
)
265 elif parent_type
== 'nonlocal_stmt':
266 self
._nonlocal
_names
.append(name
)
267 elif parent_type
== 'funcdef':
268 self
._local
_params
_names
.extend(
269 [param
.name
.value
for param
in name
.parent
.get_params()]
272 self
._used
_name
_dict
.setdefault(name
.value
, []).append(name
)
276 Returns a list of nonlocal names that need to be part of that scope.
278 self
._analyze
_names
(self
._global
_names
, 'global')
279 self
._analyze
_names
(self
._nonlocal
_names
, 'nonlocal')
281 global_name_strs
= {n
.value
: n
for n
in self
._global
_names
}
282 for nonlocal_name
in self
._nonlocal
_names
:
284 global_name
= global_name_strs
[nonlocal_name
.value
]
288 message
= "name '%s' is nonlocal and global" % global_name
.value
289 if global_name
.start_pos
< nonlocal_name
.start_pos
:
290 error_name
= global_name
292 error_name
= nonlocal_name
293 self
._add
_syntax
_error
(error_name
, message
)
295 nonlocals_not_handled
= []
296 for nonlocal_name
in self
._nonlocal
_names
_in
_subscopes
:
297 search
= nonlocal_name
.value
298 if search
in self
._local
_params
_names
:
300 if search
in global_name_strs
or self
.parent_context
is None:
301 message
= "no binding for nonlocal '%s' found" % nonlocal_name
.value
302 self
._add
_syntax
_error
(nonlocal_name
, message
)
303 elif not self
.is_function() or \
304 nonlocal_name
.value
not in self
._used
_name
_dict
:
305 nonlocals_not_handled
.append(nonlocal_name
)
306 return self
._nonlocal
_names
+ nonlocals_not_handled
308 def _analyze_names(self
, globals_or_nonlocals
, type_
):
310 self
._add
_syntax
_error
(base_name
, message
% (base_name
.value
, type_
))
313 if self
.node
.type == 'funcdef':
314 params
= self
.node
.get_params()
316 for base_name
in globals_or_nonlocals
:
317 found_global_or_nonlocal
= False
318 # Somehow Python does it the reversed way.
319 for name
in reversed(self
._used
_name
_dict
.get(base_name
.value
, [])):
320 if name
.start_pos
> base_name
.start_pos
:
321 # All following names don't have to be checked.
322 found_global_or_nonlocal
= True
325 if parent
.type == 'param' and parent
.name
== name
:
326 # Skip those here, these definitions belong to the next
330 if name
.is_definition():
331 if parent
.type == 'expr_stmt' \
332 and parent
.children
[1].type == 'annassign':
333 if found_global_or_nonlocal
:
334 # If it's after the global the error seems to be
337 raise_("annotated name '%s' can't be %s")
340 message
= "name '%s' is assigned to before %s declaration"
342 message
= "name '%s' is used prior to %s declaration"
344 if not found_global_or_nonlocal
:
346 # Only add an error for the first occurence.
350 if param
.name
.value
== base_name
.value
:
351 raise_("name '%s' is parameter and %s"),
354 def add_block(self
, node
):
355 self
.blocks
.append(node
)
359 def add_context(self
, node
):
360 return _Context(node
, self
._add
_syntax
_error
, parent_context
=self
)
362 def close_child_context(self
, child_context
):
363 self
._nonlocal
_names
_in
_subscopes
+= child_context
.finalize()
366 class ErrorFinder(Normalizer
):
368 Searches for errors in the syntax tree.
370 def __init__(self
, *args
, **kwargs
):
371 super().__init
__(*args
, **kwargs
)
372 self
._error
_dict
= {}
373 self
.version
= self
.grammar
.version_info
375 def initialize(self
, node
):
376 def create_context(node
):
380 parent_context
= create_context(node
.parent
)
381 if node
.type in ('classdef', 'funcdef', 'file_input'):
382 return _Context(node
, self
._add
_syntax
_error
, parent_context
)
383 return parent_context
385 self
.context
= create_context(node
) or _Context(node
, self
._add
_syntax
_error
)
386 self
._indentation
_count
= 0
388 def visit(self
, node
):
389 if node
.type == 'error_node':
390 with self
.visit_node(node
):
391 # Don't need to investigate the inners of an error node. We
392 # might find errors in there that should be ignored, because
393 # the error node itself already shows that there's an issue.
395 return super().visit(node
)
398 def visit_node(self
, node
):
399 self
._check
_type
_rules
(node
)
401 if node
.type in _BLOCK_STMTS
:
402 with self
.context
.add_block(node
):
403 if len(self
.context
.blocks
) == _MAX_BLOCK_SIZE
:
404 self
._add
_syntax
_error
(node
, "too many statically nested blocks")
407 elif node
.type == 'suite':
408 self
._indentation
_count
+= 1
409 if self
._indentation
_count
== _MAX_INDENT_COUNT
:
410 self
._add
_indentation
_error
(node
.children
[1], "too many levels of indentation")
414 if node
.type == 'suite':
415 self
._indentation
_count
-= 1
416 elif node
.type in ('classdef', 'funcdef'):
417 context
= self
.context
418 self
.context
= context
.parent_context
419 self
.context
.close_child_context(context
)
421 def visit_leaf(self
, leaf
):
422 if leaf
.type == 'error_leaf':
423 if leaf
.token_type
in ('INDENT', 'ERROR_DEDENT'):
424 # Indents/Dedents itself never have a prefix. They are just
425 # "pseudo" tokens that get removed by the syntax tree later.
426 # Therefore in case of an error we also have to check for this.
427 spacing
= list(leaf
.get_next_leaf()._split
_prefix
())[-1]
428 if leaf
.token_type
== 'INDENT':
429 message
= 'unexpected indent'
431 message
= 'unindent does not match any outer indentation level'
432 self
._add
_indentation
_error
(spacing
, message
)
434 if leaf
.value
.startswith('\\'):
435 message
= 'unexpected character after line continuation character'
437 match
= re
.match('\\w{,2}("{1,3}|\'{1,3})', leaf
.value
)
439 message
= 'invalid syntax'
441 self
.version
>= (3, 9)
442 and leaf
.value
in _get_token_collection(
444 ).always_break_tokens
446 message
= "f-string: " + message
448 if len(match
.group(1)) == 1:
449 message
= 'EOL while scanning string literal'
451 message
= 'EOF while scanning triple-quoted string literal'
452 self
._add
_syntax
_error
(leaf
, message
)
454 elif leaf
.value
== ':':
456 if parent
.type in ('classdef', 'funcdef'):
457 self
.context
= self
.context
.add_context(parent
)
459 # The rest is rule based.
460 return super().visit_leaf(leaf
)
462 def _add_indentation_error(self
, spacing
, message
):
463 self
.add_issue(spacing
, 903, "IndentationError: " + message
)
465 def _add_syntax_error(self
, node
, message
):
466 self
.add_issue(node
, 901, "SyntaxError: " + message
)
468 def add_issue(self
, node
, code
, message
):
469 # Overwrite the default behavior.
470 # Check if the issues are on the same line.
471 line
= node
.start_pos
[0]
472 args
= (code
, message
, node
)
473 self
._error
_dict
.setdefault(line
, args
)
476 self
.context
.finalize()
478 for code
, message
, node
in self
._error
_dict
.values():
479 self
.issues
.append(Issue(node
, code
, message
))
482 class IndentationRule(Rule
):
485 def _get_message(self
, message
, node
):
486 message
= super()._get
_message
(message
, node
)
487 return "IndentationError: " + message
490 @ErrorFinder.register_rule(type='error_node')
491 class _ExpectIndentedBlock(IndentationRule
):
492 message
= 'expected an indented block'
494 def get_node(self
, node
):
495 leaf
= node
.get_next_leaf()
496 return list(leaf
._split
_prefix
())[-1]
498 def is_issue(self
, node
):
499 # This is the beginning of a suite that is not indented.
500 return node
.children
[-1].type == 'newline'
503 class ErrorFinderConfig(NormalizerConfig
):
504 normalizer_class
= ErrorFinder
507 class SyntaxRule(Rule
):
510 def _get_message(self
, message
, node
):
511 message
= super()._get
_message
(message
, node
)
513 "f-string" not in message
514 and _any_fstring_error(self
._normalizer
.version
, node
)
516 message
= "f-string: " + message
517 return "SyntaxError: " + message
520 @ErrorFinder.register_rule(type='error_node')
521 class _InvalidSyntaxRule(SyntaxRule
):
522 message
= "invalid syntax"
523 fstring_message
= "f-string: invalid syntax"
525 def get_node(self
, node
):
526 return node
.get_next_leaf()
528 def is_issue(self
, node
):
529 error
= node
.get_next_leaf().type != 'error_leaf'
532 and _any_fstring_error(self
._normalizer
.version
, node
)
534 self
.add_issue(node
, message
=self
.fstring_message
)
536 # Error leafs will be added later as an error.
540 @ErrorFinder.register_rule(value
='await')
541 class _AwaitOutsideAsync(SyntaxRule
):
542 message
= "'await' outside async function"
544 def is_issue(self
, leaf
):
545 return not self
._normalizer
.context
.is_async_funcdef()
547 def get_error_node(self
, node
):
548 # Return the whole await statement.
552 @ErrorFinder.register_rule(value
='break')
553 class _BreakOutsideLoop(SyntaxRule
):
554 message
= "'break' outside loop"
556 def is_issue(self
, leaf
):
558 for block
in self
._normalizer
.context
.blocks
:
559 if block
.type in ('for_stmt', 'while_stmt'):
564 @ErrorFinder.register_rule(value
='continue')
565 class _ContinueChecks(SyntaxRule
):
566 message
= "'continue' not properly in loop"
567 message_in_finally
= "'continue' not supported inside 'finally' clause"
569 def is_issue(self
, leaf
):
571 for block
in self
._normalizer
.context
.blocks
:
572 if block
.type in ('for_stmt', 'while_stmt'):
574 if block
.type == 'try_stmt':
575 last_block
= block
.children
[-3]
577 last_block
== "finally"
578 and leaf
.start_pos
> last_block
.start_pos
579 and self
._normalizer
.version
< (3, 8)
581 self
.add_issue(leaf
, message
=self
.message_in_finally
)
582 return False # Error already added
587 @ErrorFinder.register_rule(value
='from')
588 class _YieldFromCheck(SyntaxRule
):
589 message
= "'yield from' inside async function"
591 def get_node(self
, leaf
):
592 return leaf
.parent
.parent
# This is the actual yield statement.
594 def is_issue(self
, leaf
):
595 return leaf
.parent
.type == 'yield_arg' \
596 and self
._normalizer
.context
.is_async_funcdef()
599 @ErrorFinder.register_rule(type='name')
600 class _NameChecks(SyntaxRule
):
601 message
= 'cannot assign to __debug__'
602 message_none
= 'cannot assign to None'
604 def is_issue(self
, leaf
):
605 self
._normalizer
.context
.add_name(leaf
)
607 if leaf
.value
== '__debug__' and leaf
.is_definition():
611 @ErrorFinder.register_rule(type='string')
612 class _StringChecks(SyntaxRule
):
613 message
= "bytes can only contain ASCII literal characters."
615 def is_issue(self
, leaf
):
616 string_prefix
= leaf
.string_prefix
.lower()
617 if 'b' in string_prefix \
618 and any(c
for c
in leaf
.value
if ord(c
) > 127):
622 if 'r' not in string_prefix
:
623 # Raw strings don't need to be checked if they have proper
626 payload
= leaf
._get
_payload
()
627 if 'b' in string_prefix
:
628 payload
= payload
.encode('utf-8')
629 func
= codecs
.escape_decode
631 func
= codecs
.unicode_escape_decode
634 with warnings
.catch_warnings():
635 # The warnings from parsing strings are not relevant.
636 warnings
.filterwarnings('ignore')
638 except UnicodeDecodeError as e
:
639 self
.add_issue(leaf
, message
='(unicode error) ' + str(e
))
640 except ValueError as e
:
641 self
.add_issue(leaf
, message
='(value error) ' + str(e
))
644 @ErrorFinder.register_rule(value
='*')
645 class _StarCheck(SyntaxRule
):
646 message
= "named arguments must follow bare *"
648 def is_issue(self
, leaf
):
650 if params
.type == 'parameters' and params
:
651 after
= params
.children
[params
.children
.index(leaf
) + 1:]
652 after
= [child
for child
in after
653 if child
not in (',', ')') and not child
.star_count
]
654 return len(after
) == 0
657 @ErrorFinder.register_rule(value
='**')
658 class _StarStarCheck(SyntaxRule
):
659 # e.g. {**{} for a in [1]}
660 # TODO this should probably get a better end_pos including
661 # the next sibling of leaf.
662 message
= "dict unpacking cannot be used in dict comprehension"
664 def is_issue(self
, leaf
):
665 if leaf
.parent
.type == 'dictorsetmaker':
666 comp_for
= leaf
.get_next_sibling().get_next_sibling()
667 return comp_for
is not None and comp_for
.type in _COMP_FOR_TYPES
670 @ErrorFinder.register_rule(value
='yield')
671 @ErrorFinder.register_rule(value
='return')
672 class _ReturnAndYieldChecks(SyntaxRule
):
673 message
= "'return' with value in async generator"
674 message_async_yield
= "'yield' inside async function"
676 def get_node(self
, leaf
):
679 def is_issue(self
, leaf
):
680 if self
._normalizer
.context
.node
.type != 'funcdef':
681 self
.add_issue(self
.get_node(leaf
), message
="'%s' outside function" % leaf
.value
)
682 elif self
._normalizer
.context
.is_async_funcdef() \
683 and any(self
._normalizer
.context
.node
.iter_yield_exprs()):
684 if leaf
.value
== 'return' and leaf
.parent
.type == 'return_stmt':
688 @ErrorFinder.register_rule(type='strings')
689 class _BytesAndStringMix(SyntaxRule
):
691 message
= "cannot mix bytes and nonbytes literals"
693 def _is_bytes_literal(self
, string
):
694 if string
.type == 'fstring':
696 return 'b' in string
.string_prefix
.lower()
698 def is_issue(self
, node
):
699 first
= node
.children
[0]
700 first_is_bytes
= self
._is
_bytes
_literal
(first
)
701 for string
in node
.children
[1:]:
702 if first_is_bytes
!= self
._is
_bytes
_literal
(string
):
706 @ErrorFinder.register_rule(type='import_as_names')
707 class _TrailingImportComma(SyntaxRule
):
708 # e.g. from foo import a,
709 message
= "trailing comma not allowed without surrounding parentheses"
711 def is_issue(self
, node
):
712 if node
.children
[-1] == ',' and node
.parent
.children
[-1] != ')':
716 @ErrorFinder.register_rule(type='import_from')
717 class _ImportStarInFunction(SyntaxRule
):
718 message
= "import * only allowed at module level"
720 def is_issue(self
, node
):
721 return node
.is_star_import() and self
._normalizer
.context
.parent_context
is not None
724 @ErrorFinder.register_rule(type='import_from')
725 class _FutureImportRule(SyntaxRule
):
726 message
= "from __future__ imports must occur at the beginning of the file"
728 def is_issue(self
, node
):
729 if _is_future_import(node
):
730 if not _is_future_import_first(node
):
733 for from_name
, future_name
in node
.get_paths():
734 name
= future_name
.value
735 allowed_futures
= list(ALLOWED_FUTURES
)
736 if self
._normalizer
.version
>= (3, 7):
737 allowed_futures
.append('annotations')
739 self
.add_issue(node
, message
="not a chance")
740 elif name
== 'barry_as_FLUFL':
741 m
= "Seriously I'm not implementing this :) ~ Dave"
742 self
.add_issue(node
, message
=m
)
743 elif name
not in allowed_futures
:
744 message
= "future feature %s is not defined" % name
745 self
.add_issue(node
, message
=message
)
748 @ErrorFinder.register_rule(type='star_expr')
749 class _StarExprRule(SyntaxRule
):
750 message_iterable_unpacking
= "iterable unpacking cannot be used in comprehension"
752 def is_issue(self
, node
):
753 def check_delete_starred(node
):
754 while node
.parent
is not None:
756 if node
.type == 'del_stmt':
758 if node
.type not in (*_STAR_EXPR_PARENTS
, 'atom'):
762 if self
._normalizer
.version
>= (3, 9):
763 ancestor
= node
.parent
765 ancestor
= _skip_parens_bottom_up(node
)
766 # starred expression not in tuple/list/set
767 if ancestor
.type not in (*_STAR_EXPR_PARENTS
, 'dictorsetmaker') \
768 and not (ancestor
.type == 'atom' and ancestor
.children
[0] != '('):
769 self
.add_issue(node
, message
="can't use starred expression here")
772 if check_delete_starred(node
):
773 if self
._normalizer
.version
>= (3, 9):
774 self
.add_issue(node
, message
="cannot delete starred")
776 self
.add_issue(node
, message
="can't use starred expression here")
779 if node
.parent
.type == 'testlist_comp':
781 if node
.parent
.children
[1].type in _COMP_FOR_TYPES
:
782 self
.add_issue(node
, message
=self
.message_iterable_unpacking
)
785 @ErrorFinder.register_rule(types
=_STAR_EXPR_PARENTS
)
786 class _StarExprParentRule(SyntaxRule
):
787 def is_issue(self
, node
):
788 def is_definition(node
, ancestor
):
792 type_
= ancestor
.type
793 if type_
== 'trailer':
796 if type_
== 'expr_stmt':
797 return node
.start_pos
< ancestor
.children
[-1].start_pos
799 return is_definition(node
, ancestor
.parent
)
801 if is_definition(node
, node
.parent
):
802 args
= [c
for c
in node
.children
if c
!= ',']
803 starred
= [c
for c
in args
if c
.type == 'star_expr']
805 if self
._normalizer
.version
< (3, 9):
806 message
= "two starred expressions in assignment"
808 message
= "multiple starred expressions in assignment"
809 self
.add_issue(starred
[1], message
=message
)
811 count
= args
.index(starred
[0])
813 message
= "too many expressions in star-unpacking assignment"
814 self
.add_issue(starred
[0], message
=message
)
817 @ErrorFinder.register_rule(type='annassign')
818 class _AnnotatorRule(SyntaxRule
):
821 message
= "illegal target for annotation"
823 def get_node(self
, node
):
826 def is_issue(self
, node
):
828 lhs
= node
.parent
.children
[0]
829 lhs
= _remove_parens(lhs
)
831 children
= lhs
.children
832 except AttributeError:
835 if ',' in children
or lhs
.type == 'atom' and children
[0] == '(':
837 elif lhs
.type == 'atom' and children
[0] == '[':
839 trailer
= children
[-1]
842 if not (lhs
.type == 'name'
843 # subscript/attributes are allowed
844 or lhs
.type in ('atom_expr', 'power')
845 and trailer
.type == 'trailer'
846 and trailer
.children
[0] != '('):
850 message
= "only single target (not %s) can be annotated"
851 self
.add_issue(lhs
.parent
, message
=message
% type_
)
854 @ErrorFinder.register_rule(type='argument')
855 class _ArgumentRule(SyntaxRule
):
856 def is_issue(self
, node
):
857 first
= node
.children
[0]
858 if self
._normalizer
.version
< (3, 8):
859 # a((b)=c) is valid in <3.8
860 first
= _remove_parens(first
)
861 if node
.children
[1] == '=' and first
.type != 'name':
862 if first
.type == 'lambdef':
864 if self
._normalizer
.version
< (3, 8):
865 message
= "lambda cannot contain assignment"
867 message
= 'expression cannot contain assignment, perhaps you meant "=="?'
870 if self
._normalizer
.version
< (3, 8):
871 message
= "keyword can't be an expression"
873 message
= 'expression cannot contain assignment, perhaps you meant "=="?'
874 self
.add_issue(first
, message
=message
)
876 if _is_argument_comprehension(node
) and node
.parent
.type == 'classdef':
877 self
.add_issue(node
, message
='invalid syntax')
880 @ErrorFinder.register_rule(type='nonlocal_stmt')
881 class _NonlocalModuleLevelRule(SyntaxRule
):
882 message
= "nonlocal declaration not allowed at module level"
884 def is_issue(self
, node
):
885 return self
._normalizer
.context
.parent_context
is None
888 @ErrorFinder.register_rule(type='arglist')
889 class _ArglistRule(SyntaxRule
):
892 if self
._normalizer
.version
< (3, 7):
893 return "Generator expression must be parenthesized if not sole argument"
895 return "Generator expression must be parenthesized"
897 def is_issue(self
, node
):
900 kw_unpacking_only
= False
901 for argument
in node
.children
:
905 if argument
.type == 'argument':
906 first
= argument
.children
[0]
907 if _is_argument_comprehension(argument
) and len(node
.children
) >= 2:
911 if first
in ('*', '**'):
913 if kw_unpacking_only
:
914 # foo(**kwargs, *args)
915 message
= "iterable argument unpacking " \
916 "follows keyword argument unpacking"
917 self
.add_issue(argument
, message
=message
)
919 kw_unpacking_only
= True
920 else: # Is a keyword argument.
922 if first
.type == 'name':
923 if first
.value
in arg_set
:
925 message
= "keyword argument repeated"
926 if self
._normalizer
.version
>= (3, 9):
927 message
+= ": {}".format(first
.value
)
928 self
.add_issue(first
, message
=message
)
930 arg_set
.add(first
.value
)
932 if kw_unpacking_only
:
934 message
= "positional argument follows keyword argument unpacking"
935 self
.add_issue(argument
, message
=message
)
938 message
= "positional argument follows keyword argument"
939 self
.add_issue(argument
, message
=message
)
942 @ErrorFinder.register_rule(type='parameters')
943 @ErrorFinder.register_rule(type='lambdef')
944 class _ParameterRule(SyntaxRule
):
945 # def f(x=3, y): pass
946 message
= "non-default argument follows default argument"
948 def is_issue(self
, node
):
952 for p
in _iter_params(node
):
953 if p
.type == 'operator':
959 if p
.name
.value
in param_names
:
960 message
= "duplicate argument '%s' in function definition"
961 self
.add_issue(p
.name
, message
=message
% p
.name
.value
)
962 param_names
.add(p
.name
.value
)
965 if p
.default
is None and not p
.star_count
:
975 @ErrorFinder.register_rule(type='try_stmt')
976 class _TryStmtRule(SyntaxRule
):
977 message
= "default 'except:' must be last"
979 def is_issue(self
, try_stmt
):
980 default_except
= None
981 for except_clause
in try_stmt
.children
[3::3]:
982 if except_clause
in ('else', 'finally'):
984 if except_clause
== 'except':
985 default_except
= except_clause
986 elif default_except
is not None:
987 self
.add_issue(default_except
, message
=self
.message
)
990 @ErrorFinder.register_rule(type='fstring')
991 class _FStringRule(SyntaxRule
):
992 _fstring_grammar
= None
993 message_expr
= "f-string expression part cannot include a backslash"
994 message_nested
= "f-string: expressions nested too deeply"
995 message_conversion
= "f-string: invalid conversion character: expected 's', 'r', or 'a'"
997 def _check_format_spec(self
, format_spec
, depth
):
998 self
._check
_fstring
_contents
(format_spec
.children
[1:], depth
)
1000 def _check_fstring_expr(self
, fstring_expr
, depth
):
1002 self
.add_issue(fstring_expr
, message
=self
.message_nested
)
1004 expr
= fstring_expr
.children
[1]
1005 if '\\' in expr
.get_code():
1006 self
.add_issue(expr
, message
=self
.message_expr
)
1008 children_2
= fstring_expr
.children
[2]
1009 if children_2
.type == 'operator' and children_2
.value
== '=':
1010 conversion
= fstring_expr
.children
[3]
1012 conversion
= children_2
1013 if conversion
.type == 'fstring_conversion':
1014 name
= conversion
.children
[1]
1015 if name
.value
not in ('s', 'r', 'a'):
1016 self
.add_issue(name
, message
=self
.message_conversion
)
1018 format_spec
= fstring_expr
.children
[-2]
1019 if format_spec
.type == 'fstring_format_spec':
1020 self
._check
_format
_spec
(format_spec
, depth
+ 1)
1022 def is_issue(self
, fstring
):
1023 self
._check
_fstring
_contents
(fstring
.children
[1:-1])
1025 def _check_fstring_contents(self
, children
, depth
=0):
1026 for fstring_content
in children
:
1027 if fstring_content
.type == 'fstring_expr':
1028 self
._check
_fstring
_expr
(fstring_content
, depth
)
1031 class _CheckAssignmentRule(SyntaxRule
):
1032 def _check_assignment(self
, node
, is_deletion
=False, is_namedexpr
=False, is_aug_assign
=False):
1035 if type_
== 'lambdef':
1037 elif type_
== 'atom':
1038 first
, second
= node
.children
[:2]
1039 error
= _get_comprehension_type(node
)
1041 if second
.type == 'dictorsetmaker':
1042 if self
._normalizer
.version
< (3, 8):
1045 if second
.children
[1] == ':':
1046 error
= 'dict display'
1048 error
= 'set display'
1049 elif first
== "{" and second
== "}":
1050 if self
._normalizer
.version
< (3, 8):
1053 error
= "dict display"
1054 elif first
== "{" and len(node
.children
) > 2:
1055 if self
._normalizer
.version
< (3, 8):
1058 error
= "set display"
1059 elif first
in ('(', '['):
1060 if second
.type == 'yield_expr':
1061 error
= 'yield expression'
1062 elif second
.type == 'testlist_comp':
1063 # ([a, b] := [1, 2])
1064 # ((a, b) := [1, 2])
1071 # This is not a comprehension, they were handled
1073 for child
in second
.children
[::2]:
1074 self
._check
_assignment
(child
, is_deletion
, is_namedexpr
, is_aug_assign
)
1075 else: # Everything handled, must be useless brackets.
1076 self
._check
_assignment
(second
, is_deletion
, is_namedexpr
, is_aug_assign
)
1077 elif type_
== 'keyword':
1078 if node
.value
== "yield":
1079 error
= "yield expression"
1080 elif self
._normalizer
.version
< (3, 8):
1083 error
= str(node
.value
)
1084 elif type_
== 'operator':
1085 if node
.value
== '...':
1087 elif type_
== 'comparison':
1088 error
= 'comparison'
1089 elif type_
in ('string', 'number', 'strings'):
1091 elif type_
== 'yield_expr':
1092 # This one seems to be a slightly different warning in Python.
1093 message
= 'assignment to yield expression not possible'
1094 self
.add_issue(node
, message
=message
)
1095 elif type_
== 'test':
1096 error
= 'conditional expression'
1097 elif type_
in ('atom_expr', 'power'):
1098 if node
.children
[0] == 'await':
1099 error
= 'await expression'
1100 elif node
.children
[-2] == '**':
1104 trailer
= node
.children
[-1]
1105 assert trailer
.type == 'trailer'
1106 if trailer
.children
[0] == '(':
1107 error
= 'function call'
1108 elif is_namedexpr
and trailer
.children
[0] == '[':
1110 elif is_namedexpr
and trailer
.children
[0] == '.':
1112 elif type_
== "fstring":
1113 if self
._normalizer
.version
< (3, 8):
1116 error
= "f-string expression"
1117 elif type_
in ('testlist_star_expr', 'exprlist', 'testlist'):
1118 for child
in node
.children
[::2]:
1119 self
._check
_assignment
(child
, is_deletion
, is_namedexpr
, is_aug_assign
)
1120 elif ('expr' in type_
and type_
!= 'star_expr' # is a substring
1122 or type_
in ('term', 'factor')):
1124 elif type_
== "star_expr":
1126 if self
._normalizer
.version
>= (3, 9):
1129 self
.add_issue(node
, message
="can't use starred expression here")
1131 if self
._normalizer
.version
>= (3, 9):
1132 ancestor
= node
.parent
1134 ancestor
= _skip_parens_bottom_up(node
)
1135 if ancestor
.type not in _STAR_EXPR_PARENTS
and not is_aug_assign \
1136 and not (ancestor
.type == 'atom' and ancestor
.children
[0] == '['):
1137 message
= "starred assignment target must be in a list or tuple"
1138 self
.add_issue(node
, message
=message
)
1140 self
._check
_assignment
(node
.children
[1])
1142 if error
is not None:
1144 message
= 'cannot use assignment expressions with %s' % error
1146 cannot
= "can't" if self
._normalizer
.version
< (3, 8) else "cannot"
1147 message
= ' '.join([cannot
, "delete" if is_deletion
else "assign to", error
])
1148 self
.add_issue(node
, message
=message
)
1151 @ErrorFinder.register_rule(type='sync_comp_for')
1152 class _CompForRule(_CheckAssignmentRule
):
1153 message
= "asynchronous comprehension outside of an asynchronous function"
1155 def is_issue(self
, node
):
1156 expr_list
= node
.children
[1]
1157 if expr_list
.type != 'expr_list': # Already handled.
1158 self
._check
_assignment
(expr_list
)
1160 return node
.parent
.children
[0] == 'async' \
1161 and not self
._normalizer
.context
.is_async_funcdef()
1164 @ErrorFinder.register_rule(type='expr_stmt')
1165 class _ExprStmtRule(_CheckAssignmentRule
):
1166 message
= "illegal expression for augmented assignment"
1167 extended_message
= "'{target}' is an " + message
1169 def is_issue(self
, node
):
1170 augassign
= node
.children
[1]
1171 is_aug_assign
= augassign
!= '=' and augassign
.type != 'annassign'
1173 if self
._normalizer
.version
<= (3, 8) or not is_aug_assign
:
1174 for before_equal
in node
.children
[:-2:2]:
1175 self
._check
_assignment
(before_equal
, is_aug_assign
=is_aug_assign
)
1178 target
= _remove_parens(node
.children
[0])
1181 if target
.type == "name" or (
1182 target
.type in ("atom_expr", "power")
1183 and target
.children
[1].type == "trailer"
1184 and target
.children
[-1].children
[0] != "("
1188 if self
._normalizer
.version
<= (3, 8):
1193 message
=self
.extended_message
.format(
1194 target
=_get_rhs_name(node
.children
[0], self
._normalizer
.version
)
1199 @ErrorFinder.register_rule(type='with_item')
1200 class _WithItemRule(_CheckAssignmentRule
):
1201 def is_issue(self
, with_item
):
1202 self
._check
_assignment
(with_item
.children
[2])
1205 @ErrorFinder.register_rule(type='del_stmt')
1206 class _DelStmtRule(_CheckAssignmentRule
):
1207 def is_issue(self
, del_stmt
):
1208 child
= del_stmt
.children
[1]
1210 if child
.type != 'expr_list': # Already handled.
1211 self
._check
_assignment
(child
, is_deletion
=True)
1214 @ErrorFinder.register_rule(type='expr_list')
1215 class _ExprListRule(_CheckAssignmentRule
):
1216 def is_issue(self
, expr_list
):
1217 for expr
in expr_list
.children
[::2]:
1218 self
._check
_assignment
(expr
)
1221 @ErrorFinder.register_rule(type='for_stmt')
1222 class _ForStmtRule(_CheckAssignmentRule
):
1223 def is_issue(self
, for_stmt
):
1224 # Some of the nodes here are already used, so no else if
1225 expr_list
= for_stmt
.children
[1]
1226 if expr_list
.type != 'expr_list': # Already handled.
1227 self
._check
_assignment
(expr_list
)
1230 @ErrorFinder.register_rule(type='namedexpr_test')
1231 class _NamedExprRule(_CheckAssignmentRule
):
1232 # namedexpr_test: test [':=' test]
1234 def is_issue(self
, namedexpr_test
):
1236 first
= namedexpr_test
.children
[0]
1238 def search_namedexpr_in_comp_for(node
):
1240 parent
= node
.parent
1243 if parent
.type == 'sync_comp_for' and parent
.children
[3] == node
:
1247 if search_namedexpr_in_comp_for(namedexpr_test
):
1248 # [i+1 for i in (i := range(5))]
1249 # [i+1 for i in (j := range(5))]
1250 # [i+1 for i in (lambda: (j := range(5)))()]
1251 message
= 'assignment expression cannot be used in a comprehension iterable expression'
1252 self
.add_issue(namedexpr_test
, message
=message
)
1257 def process_comp_for(comp_for
):
1258 if comp_for
.type == 'sync_comp_for':
1260 elif comp_for
.type == 'comp_for':
1261 comp
= comp_for
.children
[1]
1262 exprlist
.extend(_get_for_stmt_definition_exprs(comp
))
1264 def search_all_comp_ancestors(node
):
1265 has_ancestors
= False
1267 node
= node
.search_ancestor('testlist_comp', 'dictorsetmaker')
1270 for child
in node
.children
:
1271 if child
.type in _COMP_FOR_TYPES
:
1272 process_comp_for(child
)
1273 has_ancestors
= True
1275 return has_ancestors
1277 # check assignment expressions in comprehensions
1278 search_all
= search_all_comp_ancestors(namedexpr_test
)
1280 if self
._normalizer
.context
.node
.type == 'classdef':
1281 message
= 'assignment expression within a comprehension ' \
1282 'cannot be used in a class body'
1283 self
.add_issue(namedexpr_test
, message
=message
)
1285 namelist
= [expr
.value
for expr
in exprlist
if expr
.type == 'name']
1286 if first
.type == 'name' and first
.value
in namelist
:
1287 # [i := 0 for i, j in range(5)]
1288 # [[(i := i) for j in range(5)] for i in range(5)]
1289 # [i for i, j in range(5) if True or (i := 1)]
1290 # [False and (i := 0) for i, j in range(5)]
1291 message
= 'assignment expression cannot rebind ' \
1292 'comprehension iteration variable %r' % first
.value
1293 self
.add_issue(namedexpr_test
, message
=message
)
1295 self
._check
_assignment
(first
, is_namedexpr
=True)