Move the first issue to the new rule engine.

This commit is contained in:
Dave Halter
2017-08-17 10:25:11 +02:00
parent 8d20f3d469
commit 06d7da3f3e
2 changed files with 63 additions and 45 deletions

View File

@@ -1,22 +1,32 @@
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):
self._grammar = grammar
self._config = config
self.issues = []
self._rule_type_instances = self._instantiate_rules(self._rule_type_classes)
self._rule_value_instances = self._instantiate_rules(self._rule_value_classes)
self._rule_type_instances = self._instantiate_rules('rule_type_classes')
self._rule_value_instances = self._instantiate_rules('rule_value_classes')
def _instantiate_rules(self, rules_map):
def _instantiate_rules(self, attr):
dct = {}
for type_, rule_classes in rules_map.items():
dct[type_] = [rule_cls(self) for rule_cls in rule_classes]
for base in type(self).mro():
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
def walk(self, node):
@@ -36,14 +46,15 @@ class Normalizer(object):
@contextmanager
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, []):
rule.feed_node(node)
yield
def visit_leaf(self, leaf):
for rule in self._rule_type_instances.get(leaf.type, []):
rule.feed_node(leaf)
self._check_type_rules(leaf)
for rule in self._rule_value_instances.get(leaf.value, []):
rule.feed_node(leaf)
@@ -81,9 +92,9 @@ class Normalizer(object):
def decorator(rule_cls):
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:
cls._rule_type_classes.setdefault(type, []).append(rule_cls)
cls.rule_type_classes.setdefault(type, []).append(rule_cls)
return rule_cls
return decorator
@@ -133,20 +144,24 @@ class Rule(object):
def get_node(self, 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):
if code is None:
code = self.code
if code is None:
raise ValueError("The error code on the class is not set.")
if message is None:
message = self.message
if message is None:
raise ValueError("The message on the class is not set.")
message = self._get_message(message)
self._normalizer.add_issue(node, code, message)
def feed_node(self, node):
if self.check(node):
if self.is_issue(node):
issue_node = self.get_node(node)
self.add_issue(issue_node)

View File

@@ -270,7 +270,7 @@ class ErrorFinder(Normalizer):
return _Context(node, self._add_syntax_error, 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
def visit(self, node):
@@ -285,6 +285,8 @@ class ErrorFinder(Normalizer):
@contextmanager
def visit_node(self, node):
self._check_type_rules(node)
if node.type == 'error_node':
leaf = node.get_next_leaf()
if node.children[-1].type == 'newline':
@@ -312,8 +314,8 @@ class ErrorFinder(Normalizer):
if expr_list.type != 'expr_list': # Already handled.
self._check_assignment(expr_list)
with self._context.add_block(node):
if len(self._context.blocks) == _MAX_BLOCK_SIZE:
with self.context.add_block(node):
if len(self.context.blocks) == _MAX_BLOCK_SIZE:
self._add_syntax_error("too many statically nested blocks", node)
yield
return
@@ -338,7 +340,7 @@ class ErrorFinder(Normalizer):
message = "future feature %s is not defined" % name
self._add_syntax_error(message, node)
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"
self._add_syntax_error(message, node)
elif node.type == 'import_as_names':
@@ -404,7 +406,7 @@ class ErrorFinder(Normalizer):
self._check_assignment(expr_list)
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"
self._add_syntax_error(message, node)
elif node.type == 'arglist':
@@ -529,12 +531,12 @@ class ErrorFinder(Normalizer):
message = "keyword can't be an expression"
self._add_syntax_error(message, first)
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"
self._add_syntax_error(message, node)
elif self._context.is_function():
elif self.context.is_function():
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:
pass
elif node.type == 'expr_stmt':
@@ -565,9 +567,9 @@ class ErrorFinder(Normalizer):
if node.type == 'suite':
self._indentation_count -= 1
elif node.type in ('classdef', 'funcdef'):
context = self._context
self._context = context.parent_context
self._context.close_child_context(context)
context = self.context
self.context = context.parent_context
self.context.close_child_context(context)
def visit_leaf(self, 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():
self._add_syntax_error('cannot assign to None', leaf)
self._context.add_name(leaf)
self.context.add_name(leaf)
elif leaf.type == 'string':
string_prefix = leaf.string_prefix.lower()
if 'b' in string_prefix \
@@ -642,7 +644,7 @@ class ErrorFinder(Normalizer):
elif leaf.value == 'continue':
in_loop = False
for block in self._context.blocks:
for block in self.context.blocks:
if block.type == 'for_stmt':
in_loop = True
if block.type == 'try_stmt':
@@ -655,27 +657,24 @@ class ErrorFinder(Normalizer):
self._add_syntax_error(message, leaf)
elif leaf.value == 'break':
in_loop = False
for block in self._context.blocks:
for block in self.context.blocks:
if block.type in ('for_stmt', 'while_stmt'):
in_loop = True
if not in_loop:
self._add_syntax_error("'break' outside loop", leaf)
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)
elif self._context.is_async_funcdef() \
and any(self._context.node.iter_yield_exprs()):
elif self.context.is_async_funcdef() \
and any(self.context.node.iter_yield_exprs()):
if leaf.value == 'return' and leaf.parent.type == 'return_stmt':
self._add_syntax_error("'return' with value in async generator", leaf.parent)
elif leaf.value == 'yield' \
and leaf.get_next_leaf() != 'from' \
and self._version == (3, 5):
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' \
and self._context.is_async_funcdef():
and self.context.is_async_funcdef():
yield_ = leaf.parent.parent
self._add_syntax_error("'yield from' inside async function", yield_)
elif leaf.value == '*':
@@ -698,10 +697,10 @@ class ErrorFinder(Normalizer):
elif leaf.value == ':':
parent = leaf.parent
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):
error = None
@@ -775,7 +774,7 @@ class ErrorFinder(Normalizer):
self._error_dict.setdefault(line, args)
def finalize(self):
self._context.finalize()
self.context.finalize()
for code, message, node in self._error_dict.values():
self.issues.append(Issue(node, code, message))
@@ -788,6 +787,10 @@ class ErrorFinderConfig(NormalizerConfig):
class SyntaxRule(Rule):
code = 901
def _get_message(self, message):
message = super(SyntaxRule, self)._get_message(message)
return "SyntaxError: " + message
class IndentationRule(Rule):
code = 903
@@ -797,8 +800,8 @@ class IndentationRule(Rule):
class AwaitOutsideAsync(SyntaxRule):
message = "'await' outside async function"
def check_leaf_value(self, leaf):
return not self._context.is_async_funcdef()
def is_issue(self, leaf):
return not self._normalizer.context.is_async_funcdef()
def get_error_node(self, node):
# Return the whole await statement.