| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- from contextlib import contextmanager
- from typing import Dict, List, Any
- class _NormalizerMeta(type):
- rule_value_classes: Any
- rule_type_classes: Any
- 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(metaclass=_NormalizerMeta):
- _rule_type_instances: Dict[str, List[type]] = {}
- _rule_value_instances: Dict[str, List[type]] = {}
- def __init__(self, grammar, config):
- self.grammar = grammar
- self._config = config
- self.issues = []
- self._rule_type_instances = self._instantiate_rules('rule_type_classes')
- self._rule_value_instances = self._instantiate_rules('rule_value_classes')
- def _instantiate_rules(self, attr):
- dct = {}
- 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):
- self.initialize(node)
- value = self.visit(node)
- self.finalize()
- return value
- def visit(self, node):
- try:
- children = node.children
- except AttributeError:
- return self.visit_leaf(node)
- else:
- with self.visit_node(node):
- return ''.join(self.visit(child) for child in children)
- @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)
- def visit_leaf(self, leaf):
- self._check_type_rules(leaf)
- for rule in self._rule_value_instances.get(leaf.value, []):
- rule.feed_node(leaf)
- return leaf.prefix + leaf.value
- def initialize(self, node):
- pass
- def finalize(self):
- pass
- def add_issue(self, node, code, message):
- issue = Issue(node, code, message)
- if issue not in self.issues:
- self.issues.append(issue)
- return True
- @classmethod
- def register_rule(cls, *, value=None, values=(), type=None, types=()):
- """
- Use it as a class decorator::
- normalizer = Normalizer('grammar', 'config')
- @normalizer.register_rule(value='foo')
- class MyRule(Rule):
- error_code = 42
- """
- values = list(values)
- types = list(types)
- if value is not None:
- values.append(value)
- if type is not None:
- types.append(type)
- if not values and not types:
- raise ValueError("You must register at least something.")
- def decorator(rule_cls):
- for v in values:
- cls.rule_value_classes.setdefault(v, []).append(rule_cls)
- for t in types:
- cls.rule_type_classes.setdefault(t, []).append(rule_cls)
- return rule_cls
- return decorator
- class NormalizerConfig:
- normalizer_class = Normalizer
- def create_normalizer(self, grammar):
- return self.normalizer_class(grammar, self)
- class Issue:
- def __init__(self, node, code, message):
- self.code = code
- """
- An integer code that stands for the type of error.
- """
- self.message = message
- """
- A message (string) for the issue.
- """
- self.start_pos = node.start_pos
- """
- The start position position of the error as a tuple (line, column). As
- always in |parso| the first line is 1 and the first column 0.
- """
- self.end_pos = node.end_pos
- def __eq__(self, other):
- return self.start_pos == other.start_pos and self.code == other.code
- def __ne__(self, other):
- return not self.__eq__(other)
- def __hash__(self):
- return hash((self.code, self.start_pos))
- def __repr__(self):
- return '<%s: %s>' % (self.__class__.__name__, self.code)
- class Rule:
- code: int
- message: str
- def __init__(self, normalizer):
- self._normalizer = normalizer
- def is_issue(self, node):
- raise NotImplementedError()
- def get_node(self, node):
- return node
- def _get_message(self, message, node):
- 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.")
- message = self._get_message(message, node)
- self._normalizer.add_issue(node, code, message)
- def feed_node(self, node):
- if self.is_issue(node):
- issue_node = self.get_node(node)
- self.add_issue(issue_node)
- class RefactoringNormalizer(Normalizer):
- def __init__(self, node_to_str_map):
- self._node_to_str_map = node_to_str_map
- def visit(self, node):
- try:
- return self._node_to_str_map[node]
- except KeyError:
- return super().visit(node)
- def visit_leaf(self, leaf):
- try:
- return self._node_to_str_map[leaf]
- except KeyError:
- return super().visit_leaf(leaf)
|