Source code for parso.normalizer

from contextlib import contextmanager
from typing import Dict, List


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(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):
        if self.normalizer_class is None:
            return None

        return self.normalizer_class(grammar, self)


[docs]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)