mirror of
https://github.com/davidhalter/parso.git
synced 2025-12-11 15:11:59 +08:00
Move the first issue to the new rule engine.
This commit is contained in:
@@ -1,22 +1,32 @@
|
|||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
from parso._compatibility import use_metaclass
|
||||||
|
|
||||||
class Normalizer(object):
|
|
||||||
_rule_value_classes = {}
|
|
||||||
_rule_type_classes = {}
|
|
||||||
|
|
||||||
|
class _NormalizerMeta(type):
|
||||||
|
def __new__(cls, name, bases, dct):
|
||||||
|
new_cls = type.__new__(cls, name, bases, dct)
|
||||||
|
new_cls.rule_value_classes = {}
|
||||||
|
new_cls.rule_type_classes = {}
|
||||||
|
return new_cls
|
||||||
|
|
||||||
|
|
||||||
|
class Normalizer(use_metaclass(_NormalizerMeta)):
|
||||||
def __init__(self, grammar, config):
|
def __init__(self, grammar, config):
|
||||||
self._grammar = grammar
|
self._grammar = grammar
|
||||||
self._config = config
|
self._config = config
|
||||||
self.issues = []
|
self.issues = []
|
||||||
|
|
||||||
self._rule_type_instances = self._instantiate_rules(self._rule_type_classes)
|
self._rule_type_instances = self._instantiate_rules('rule_type_classes')
|
||||||
self._rule_value_instances = self._instantiate_rules(self._rule_value_classes)
|
self._rule_value_instances = self._instantiate_rules('rule_value_classes')
|
||||||
|
|
||||||
def _instantiate_rules(self, rules_map):
|
def _instantiate_rules(self, attr):
|
||||||
dct = {}
|
dct = {}
|
||||||
for type_, rule_classes in rules_map.items():
|
for base in type(self).mro():
|
||||||
dct[type_] = [rule_cls(self) for rule_cls in rule_classes]
|
rules_map = getattr(base, attr, {})
|
||||||
|
for type_, rule_classes in rules_map.items():
|
||||||
|
new = [rule_cls(self) for rule_cls in rule_classes]
|
||||||
|
dct.setdefault(type_, []).extend(new)
|
||||||
return dct
|
return dct
|
||||||
|
|
||||||
def walk(self, node):
|
def walk(self, node):
|
||||||
@@ -36,14 +46,15 @@ class Normalizer(object):
|
|||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def visit_node(self, node):
|
def visit_node(self, node):
|
||||||
|
self._check_type_rules(node)
|
||||||
|
yield
|
||||||
|
|
||||||
|
def _check_type_rules(self, node):
|
||||||
for rule in self._rule_type_instances.get(node.type, []):
|
for rule in self._rule_type_instances.get(node.type, []):
|
||||||
rule.feed_node(node)
|
rule.feed_node(node)
|
||||||
|
|
||||||
yield
|
|
||||||
|
|
||||||
def visit_leaf(self, leaf):
|
def visit_leaf(self, leaf):
|
||||||
for rule in self._rule_type_instances.get(leaf.type, []):
|
self._check_type_rules(leaf)
|
||||||
rule.feed_node(leaf)
|
|
||||||
|
|
||||||
for rule in self._rule_value_instances.get(leaf.value, []):
|
for rule in self._rule_value_instances.get(leaf.value, []):
|
||||||
rule.feed_node(leaf)
|
rule.feed_node(leaf)
|
||||||
@@ -81,9 +92,9 @@ class Normalizer(object):
|
|||||||
|
|
||||||
def decorator(rule_cls):
|
def decorator(rule_cls):
|
||||||
if value is not None:
|
if value is not None:
|
||||||
cls._rule_value_classes.setdefault(value, []).append(rule_cls)
|
cls.rule_value_classes.setdefault(value, []).append(rule_cls)
|
||||||
if type is not None:
|
if type is not None:
|
||||||
cls._rule_type_classes.setdefault(type, []).append(rule_cls)
|
cls.rule_type_classes.setdefault(type, []).append(rule_cls)
|
||||||
return rule_cls
|
return rule_cls
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
@@ -133,20 +144,24 @@ class Rule(object):
|
|||||||
def get_node(self, node):
|
def get_node(self, node):
|
||||||
return node
|
return node
|
||||||
|
|
||||||
|
def _get_message(self, message):
|
||||||
|
if message is None:
|
||||||
|
message = self.message
|
||||||
|
if message is None:
|
||||||
|
raise ValueError("The message on the class is not set.")
|
||||||
|
return message
|
||||||
|
|
||||||
def add_issue(self, node, code=None, message=None):
|
def add_issue(self, node, code=None, message=None):
|
||||||
if code is None:
|
if code is None:
|
||||||
code = self.code
|
code = self.code
|
||||||
if code is None:
|
if code is None:
|
||||||
raise ValueError("The error code on the class is not set.")
|
raise ValueError("The error code on the class is not set.")
|
||||||
|
|
||||||
if message is None:
|
message = self._get_message(message)
|
||||||
message = self.message
|
|
||||||
if message is None:
|
|
||||||
raise ValueError("The message on the class is not set.")
|
|
||||||
|
|
||||||
self._normalizer.add_issue(node, code, message)
|
self._normalizer.add_issue(node, code, message)
|
||||||
|
|
||||||
def feed_node(self, node):
|
def feed_node(self, node):
|
||||||
if self.check(node):
|
if self.is_issue(node):
|
||||||
issue_node = self.get_node(node)
|
issue_node = self.get_node(node)
|
||||||
self.add_issue(issue_node)
|
self.add_issue(issue_node)
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ class ErrorFinder(Normalizer):
|
|||||||
return _Context(node, self._add_syntax_error, parent_context)
|
return _Context(node, self._add_syntax_error, parent_context)
|
||||||
return parent_context
|
return parent_context
|
||||||
|
|
||||||
self._context = create_context(node) or _Context(node, self._add_syntax_error)
|
self.context = create_context(node) or _Context(node, self._add_syntax_error)
|
||||||
self._indentation_count = 0
|
self._indentation_count = 0
|
||||||
|
|
||||||
def visit(self, node):
|
def visit(self, node):
|
||||||
@@ -285,6 +285,8 @@ class ErrorFinder(Normalizer):
|
|||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def visit_node(self, node):
|
def visit_node(self, node):
|
||||||
|
self._check_type_rules(node)
|
||||||
|
|
||||||
if node.type == 'error_node':
|
if node.type == 'error_node':
|
||||||
leaf = node.get_next_leaf()
|
leaf = node.get_next_leaf()
|
||||||
if node.children[-1].type == 'newline':
|
if node.children[-1].type == 'newline':
|
||||||
@@ -312,8 +314,8 @@ class ErrorFinder(Normalizer):
|
|||||||
if expr_list.type != 'expr_list': # Already handled.
|
if expr_list.type != 'expr_list': # Already handled.
|
||||||
self._check_assignment(expr_list)
|
self._check_assignment(expr_list)
|
||||||
|
|
||||||
with self._context.add_block(node):
|
with self.context.add_block(node):
|
||||||
if len(self._context.blocks) == _MAX_BLOCK_SIZE:
|
if len(self.context.blocks) == _MAX_BLOCK_SIZE:
|
||||||
self._add_syntax_error("too many statically nested blocks", node)
|
self._add_syntax_error("too many statically nested blocks", node)
|
||||||
yield
|
yield
|
||||||
return
|
return
|
||||||
@@ -338,7 +340,7 @@ class ErrorFinder(Normalizer):
|
|||||||
message = "future feature %s is not defined" % name
|
message = "future feature %s is not defined" % name
|
||||||
self._add_syntax_error(message, node)
|
self._add_syntax_error(message, node)
|
||||||
elif node.type == 'import_from':
|
elif node.type == 'import_from':
|
||||||
if node.is_star_import() and self._context.parent_context is not None:
|
if node.is_star_import() and self.context.parent_context is not None:
|
||||||
message = "import * only allowed at module level"
|
message = "import * only allowed at module level"
|
||||||
self._add_syntax_error(message, node)
|
self._add_syntax_error(message, node)
|
||||||
elif node.type == 'import_as_names':
|
elif node.type == 'import_as_names':
|
||||||
@@ -404,7 +406,7 @@ class ErrorFinder(Normalizer):
|
|||||||
self._check_assignment(expr_list)
|
self._check_assignment(expr_list)
|
||||||
|
|
||||||
if node.children[0] == 'async' \
|
if node.children[0] == 'async' \
|
||||||
and not self._context.is_async_funcdef():
|
and not self.context.is_async_funcdef():
|
||||||
message = "asynchronous comprehension outside of an asynchronous function"
|
message = "asynchronous comprehension outside of an asynchronous function"
|
||||||
self._add_syntax_error(message, node)
|
self._add_syntax_error(message, node)
|
||||||
elif node.type == 'arglist':
|
elif node.type == 'arglist':
|
||||||
@@ -529,12 +531,12 @@ class ErrorFinder(Normalizer):
|
|||||||
message = "keyword can't be an expression"
|
message = "keyword can't be an expression"
|
||||||
self._add_syntax_error(message, first)
|
self._add_syntax_error(message, first)
|
||||||
elif node.type == 'nonlocal_stmt':
|
elif node.type == 'nonlocal_stmt':
|
||||||
if self._context.parent_context is None:
|
if self.context.parent_context is None:
|
||||||
message = "nonlocal declaration not allowed at module level"
|
message = "nonlocal declaration not allowed at module level"
|
||||||
self._add_syntax_error(message, node)
|
self._add_syntax_error(message, node)
|
||||||
elif self._context.is_function():
|
elif self.context.is_function():
|
||||||
for nonlocal_name in node.children[1::2]:
|
for nonlocal_name in node.children[1::2]:
|
||||||
param_names = [p.name.value for p in self._context.node.get_params()]
|
param_names = [p.name.value for p in self.context.node.get_params()]
|
||||||
if nonlocal_name.value == node:
|
if nonlocal_name.value == node:
|
||||||
pass
|
pass
|
||||||
elif node.type == 'expr_stmt':
|
elif node.type == 'expr_stmt':
|
||||||
@@ -565,9 +567,9 @@ class ErrorFinder(Normalizer):
|
|||||||
if node.type == 'suite':
|
if node.type == 'suite':
|
||||||
self._indentation_count -= 1
|
self._indentation_count -= 1
|
||||||
elif node.type in ('classdef', 'funcdef'):
|
elif node.type in ('classdef', 'funcdef'):
|
||||||
context = self._context
|
context = self.context
|
||||||
self._context = context.parent_context
|
self.context = context.parent_context
|
||||||
self._context.close_child_context(context)
|
self.context.close_child_context(context)
|
||||||
|
|
||||||
def visit_leaf(self, leaf):
|
def visit_leaf(self, leaf):
|
||||||
if leaf.type == 'error_leaf':
|
if leaf.type == 'error_leaf':
|
||||||
@@ -604,7 +606,7 @@ class ErrorFinder(Normalizer):
|
|||||||
if leaf.value == 'None' and self._version < (3, 0) and leaf.is_definition():
|
if leaf.value == 'None' and self._version < (3, 0) and leaf.is_definition():
|
||||||
self._add_syntax_error('cannot assign to None', leaf)
|
self._add_syntax_error('cannot assign to None', leaf)
|
||||||
|
|
||||||
self._context.add_name(leaf)
|
self.context.add_name(leaf)
|
||||||
elif leaf.type == 'string':
|
elif leaf.type == 'string':
|
||||||
string_prefix = leaf.string_prefix.lower()
|
string_prefix = leaf.string_prefix.lower()
|
||||||
if 'b' in string_prefix \
|
if 'b' in string_prefix \
|
||||||
@@ -642,7 +644,7 @@ class ErrorFinder(Normalizer):
|
|||||||
|
|
||||||
elif leaf.value == 'continue':
|
elif leaf.value == 'continue':
|
||||||
in_loop = False
|
in_loop = False
|
||||||
for block in self._context.blocks:
|
for block in self.context.blocks:
|
||||||
if block.type == 'for_stmt':
|
if block.type == 'for_stmt':
|
||||||
in_loop = True
|
in_loop = True
|
||||||
if block.type == 'try_stmt':
|
if block.type == 'try_stmt':
|
||||||
@@ -655,27 +657,24 @@ class ErrorFinder(Normalizer):
|
|||||||
self._add_syntax_error(message, leaf)
|
self._add_syntax_error(message, leaf)
|
||||||
elif leaf.value == 'break':
|
elif leaf.value == 'break':
|
||||||
in_loop = False
|
in_loop = False
|
||||||
for block in self._context.blocks:
|
for block in self.context.blocks:
|
||||||
if block.type in ('for_stmt', 'while_stmt'):
|
if block.type in ('for_stmt', 'while_stmt'):
|
||||||
in_loop = True
|
in_loop = True
|
||||||
if not in_loop:
|
if not in_loop:
|
||||||
self._add_syntax_error("'break' outside loop", leaf)
|
self._add_syntax_error("'break' outside loop", leaf)
|
||||||
elif leaf.value in ('yield', 'return'):
|
elif leaf.value in ('yield', 'return'):
|
||||||
if self._context.node.type != 'funcdef':
|
if self.context.node.type != 'funcdef':
|
||||||
self._add_syntax_error("'%s' outside function" % leaf.value, leaf.parent)
|
self._add_syntax_error("'%s' outside function" % leaf.value, leaf.parent)
|
||||||
elif self._context.is_async_funcdef() \
|
elif self.context.is_async_funcdef() \
|
||||||
and any(self._context.node.iter_yield_exprs()):
|
and any(self.context.node.iter_yield_exprs()):
|
||||||
if leaf.value == 'return' and leaf.parent.type == 'return_stmt':
|
if leaf.value == 'return' and leaf.parent.type == 'return_stmt':
|
||||||
self._add_syntax_error("'return' with value in async generator", leaf.parent)
|
self._add_syntax_error("'return' with value in async generator", leaf.parent)
|
||||||
elif leaf.value == 'yield' \
|
elif leaf.value == 'yield' \
|
||||||
and leaf.get_next_leaf() != 'from' \
|
and leaf.get_next_leaf() != 'from' \
|
||||||
and self._version == (3, 5):
|
and self._version == (3, 5):
|
||||||
self._add_syntax_error("'yield' inside async function", leaf.parent)
|
self._add_syntax_error("'yield' inside async function", leaf.parent)
|
||||||
elif leaf.value == 'await':
|
|
||||||
if not self._context.is_async_funcdef():
|
|
||||||
self._add_syntax_error("'await' outside async function", leaf.parent)
|
|
||||||
elif leaf.value == 'from' and leaf.parent.type == 'yield_arg' \
|
elif leaf.value == 'from' and leaf.parent.type == 'yield_arg' \
|
||||||
and self._context.is_async_funcdef():
|
and self.context.is_async_funcdef():
|
||||||
yield_ = leaf.parent.parent
|
yield_ = leaf.parent.parent
|
||||||
self._add_syntax_error("'yield from' inside async function", yield_)
|
self._add_syntax_error("'yield from' inside async function", yield_)
|
||||||
elif leaf.value == '*':
|
elif leaf.value == '*':
|
||||||
@@ -698,10 +697,10 @@ class ErrorFinder(Normalizer):
|
|||||||
elif leaf.value == ':':
|
elif leaf.value == ':':
|
||||||
parent = leaf.parent
|
parent = leaf.parent
|
||||||
if parent.type in ('classdef', 'funcdef'):
|
if parent.type in ('classdef', 'funcdef'):
|
||||||
self._context = self._context.add_context(parent)
|
self.context = self.context.add_context(parent)
|
||||||
|
|
||||||
|
|
||||||
return ''
|
return super(ErrorFinder, self).visit_leaf(leaf)
|
||||||
|
|
||||||
def _check_assignment(self, node, is_deletion=False):
|
def _check_assignment(self, node, is_deletion=False):
|
||||||
error = None
|
error = None
|
||||||
@@ -775,7 +774,7 @@ class ErrorFinder(Normalizer):
|
|||||||
self._error_dict.setdefault(line, args)
|
self._error_dict.setdefault(line, args)
|
||||||
|
|
||||||
def finalize(self):
|
def finalize(self):
|
||||||
self._context.finalize()
|
self.context.finalize()
|
||||||
|
|
||||||
for code, message, node in self._error_dict.values():
|
for code, message, node in self._error_dict.values():
|
||||||
self.issues.append(Issue(node, code, message))
|
self.issues.append(Issue(node, code, message))
|
||||||
@@ -788,6 +787,10 @@ class ErrorFinderConfig(NormalizerConfig):
|
|||||||
class SyntaxRule(Rule):
|
class SyntaxRule(Rule):
|
||||||
code = 901
|
code = 901
|
||||||
|
|
||||||
|
def _get_message(self, message):
|
||||||
|
message = super(SyntaxRule, self)._get_message(message)
|
||||||
|
return "SyntaxError: " + message
|
||||||
|
|
||||||
|
|
||||||
class IndentationRule(Rule):
|
class IndentationRule(Rule):
|
||||||
code = 903
|
code = 903
|
||||||
@@ -797,8 +800,8 @@ class IndentationRule(Rule):
|
|||||||
class AwaitOutsideAsync(SyntaxRule):
|
class AwaitOutsideAsync(SyntaxRule):
|
||||||
message = "'await' outside async function"
|
message = "'await' outside async function"
|
||||||
|
|
||||||
def check_leaf_value(self, leaf):
|
def is_issue(self, leaf):
|
||||||
return not self._context.is_async_funcdef()
|
return not self._normalizer.context.is_async_funcdef()
|
||||||
|
|
||||||
def get_error_node(self, node):
|
def get_error_node(self, node):
|
||||||
# Return the whole await statement.
|
# Return the whole await statement.
|
||||||
|
|||||||
Reference in New Issue
Block a user