17db96d56Sopenharmony_ci# Copyright 2006 Google, Inc. All Rights Reserved.
27db96d56Sopenharmony_ci# Licensed to PSF under a Contributor Agreement.
37db96d56Sopenharmony_ci
47db96d56Sopenharmony_ci"""Base class for fixers (optional, but recommended)."""
57db96d56Sopenharmony_ci
67db96d56Sopenharmony_ci# Python imports
77db96d56Sopenharmony_ciimport itertools
87db96d56Sopenharmony_ci
97db96d56Sopenharmony_ci# Local imports
107db96d56Sopenharmony_cifrom .patcomp import PatternCompiler
117db96d56Sopenharmony_cifrom . import pygram
127db96d56Sopenharmony_cifrom .fixer_util import does_tree_import
137db96d56Sopenharmony_ci
147db96d56Sopenharmony_ciclass BaseFix(object):
157db96d56Sopenharmony_ci
167db96d56Sopenharmony_ci    """Optional base class for fixers.
177db96d56Sopenharmony_ci
187db96d56Sopenharmony_ci    The subclass name must be FixFooBar where FooBar is the result of
197db96d56Sopenharmony_ci    removing underscores and capitalizing the words of the fix name.
207db96d56Sopenharmony_ci    For example, the class name for a fixer named 'has_key' should be
217db96d56Sopenharmony_ci    FixHasKey.
227db96d56Sopenharmony_ci    """
237db96d56Sopenharmony_ci
247db96d56Sopenharmony_ci    PATTERN = None  # Most subclasses should override with a string literal
257db96d56Sopenharmony_ci    pattern = None  # Compiled pattern, set by compile_pattern()
267db96d56Sopenharmony_ci    pattern_tree = None # Tree representation of the pattern
277db96d56Sopenharmony_ci    options = None  # Options object passed to initializer
287db96d56Sopenharmony_ci    filename = None # The filename (set by set_filename)
297db96d56Sopenharmony_ci    numbers = itertools.count(1) # For new_name()
307db96d56Sopenharmony_ci    used_names = set() # A set of all used NAMEs
317db96d56Sopenharmony_ci    order = "post" # Does the fixer prefer pre- or post-order traversal
327db96d56Sopenharmony_ci    explicit = False # Is this ignored by refactor.py -f all?
337db96d56Sopenharmony_ci    run_order = 5   # Fixers will be sorted by run order before execution
347db96d56Sopenharmony_ci                    # Lower numbers will be run first.
357db96d56Sopenharmony_ci    _accept_type = None # [Advanced and not public] This tells RefactoringTool
367db96d56Sopenharmony_ci                        # which node type to accept when there's not a pattern.
377db96d56Sopenharmony_ci
387db96d56Sopenharmony_ci    keep_line_order = False # For the bottom matcher: match with the
397db96d56Sopenharmony_ci                            # original line order
407db96d56Sopenharmony_ci    BM_compatible = False # Compatibility with the bottom matching
417db96d56Sopenharmony_ci                          # module; every fixer should set this
427db96d56Sopenharmony_ci                          # manually
437db96d56Sopenharmony_ci
447db96d56Sopenharmony_ci    # Shortcut for access to Python grammar symbols
457db96d56Sopenharmony_ci    syms = pygram.python_symbols
467db96d56Sopenharmony_ci
477db96d56Sopenharmony_ci    def __init__(self, options, log):
487db96d56Sopenharmony_ci        """Initializer.  Subclass may override.
497db96d56Sopenharmony_ci
507db96d56Sopenharmony_ci        Args:
517db96d56Sopenharmony_ci            options: a dict containing the options passed to RefactoringTool
527db96d56Sopenharmony_ci            that could be used to customize the fixer through the command line.
537db96d56Sopenharmony_ci            log: a list to append warnings and other messages to.
547db96d56Sopenharmony_ci        """
557db96d56Sopenharmony_ci        self.options = options
567db96d56Sopenharmony_ci        self.log = log
577db96d56Sopenharmony_ci        self.compile_pattern()
587db96d56Sopenharmony_ci
597db96d56Sopenharmony_ci    def compile_pattern(self):
607db96d56Sopenharmony_ci        """Compiles self.PATTERN into self.pattern.
617db96d56Sopenharmony_ci
627db96d56Sopenharmony_ci        Subclass may override if it doesn't want to use
637db96d56Sopenharmony_ci        self.{pattern,PATTERN} in .match().
647db96d56Sopenharmony_ci        """
657db96d56Sopenharmony_ci        if self.PATTERN is not None:
667db96d56Sopenharmony_ci            PC = PatternCompiler()
677db96d56Sopenharmony_ci            self.pattern, self.pattern_tree = PC.compile_pattern(self.PATTERN,
687db96d56Sopenharmony_ci                                                                 with_tree=True)
697db96d56Sopenharmony_ci
707db96d56Sopenharmony_ci    def set_filename(self, filename):
717db96d56Sopenharmony_ci        """Set the filename.
727db96d56Sopenharmony_ci
737db96d56Sopenharmony_ci        The main refactoring tool should call this.
747db96d56Sopenharmony_ci        """
757db96d56Sopenharmony_ci        self.filename = filename
767db96d56Sopenharmony_ci
777db96d56Sopenharmony_ci    def match(self, node):
787db96d56Sopenharmony_ci        """Returns match for a given parse tree node.
797db96d56Sopenharmony_ci
807db96d56Sopenharmony_ci        Should return a true or false object (not necessarily a bool).
817db96d56Sopenharmony_ci        It may return a non-empty dict of matching sub-nodes as
827db96d56Sopenharmony_ci        returned by a matching pattern.
837db96d56Sopenharmony_ci
847db96d56Sopenharmony_ci        Subclass may override.
857db96d56Sopenharmony_ci        """
867db96d56Sopenharmony_ci        results = {"node": node}
877db96d56Sopenharmony_ci        return self.pattern.match(node, results) and results
887db96d56Sopenharmony_ci
897db96d56Sopenharmony_ci    def transform(self, node, results):
907db96d56Sopenharmony_ci        """Returns the transformation for a given parse tree node.
917db96d56Sopenharmony_ci
927db96d56Sopenharmony_ci        Args:
937db96d56Sopenharmony_ci          node: the root of the parse tree that matched the fixer.
947db96d56Sopenharmony_ci          results: a dict mapping symbolic names to part of the match.
957db96d56Sopenharmony_ci
967db96d56Sopenharmony_ci        Returns:
977db96d56Sopenharmony_ci          None, or a node that is a modified copy of the
987db96d56Sopenharmony_ci          argument node.  The node argument may also be modified in-place to
997db96d56Sopenharmony_ci          effect the same change.
1007db96d56Sopenharmony_ci
1017db96d56Sopenharmony_ci        Subclass *must* override.
1027db96d56Sopenharmony_ci        """
1037db96d56Sopenharmony_ci        raise NotImplementedError()
1047db96d56Sopenharmony_ci
1057db96d56Sopenharmony_ci    def new_name(self, template="xxx_todo_changeme"):
1067db96d56Sopenharmony_ci        """Return a string suitable for use as an identifier
1077db96d56Sopenharmony_ci
1087db96d56Sopenharmony_ci        The new name is guaranteed not to conflict with other identifiers.
1097db96d56Sopenharmony_ci        """
1107db96d56Sopenharmony_ci        name = template
1117db96d56Sopenharmony_ci        while name in self.used_names:
1127db96d56Sopenharmony_ci            name = template + str(next(self.numbers))
1137db96d56Sopenharmony_ci        self.used_names.add(name)
1147db96d56Sopenharmony_ci        return name
1157db96d56Sopenharmony_ci
1167db96d56Sopenharmony_ci    def log_message(self, message):
1177db96d56Sopenharmony_ci        if self.first_log:
1187db96d56Sopenharmony_ci            self.first_log = False
1197db96d56Sopenharmony_ci            self.log.append("### In file %s ###" % self.filename)
1207db96d56Sopenharmony_ci        self.log.append(message)
1217db96d56Sopenharmony_ci
1227db96d56Sopenharmony_ci    def cannot_convert(self, node, reason=None):
1237db96d56Sopenharmony_ci        """Warn the user that a given chunk of code is not valid Python 3,
1247db96d56Sopenharmony_ci        but that it cannot be converted automatically.
1257db96d56Sopenharmony_ci
1267db96d56Sopenharmony_ci        First argument is the top-level node for the code in question.
1277db96d56Sopenharmony_ci        Optional second argument is why it can't be converted.
1287db96d56Sopenharmony_ci        """
1297db96d56Sopenharmony_ci        lineno = node.get_lineno()
1307db96d56Sopenharmony_ci        for_output = node.clone()
1317db96d56Sopenharmony_ci        for_output.prefix = ""
1327db96d56Sopenharmony_ci        msg = "Line %d: could not convert: %s"
1337db96d56Sopenharmony_ci        self.log_message(msg % (lineno, for_output))
1347db96d56Sopenharmony_ci        if reason:
1357db96d56Sopenharmony_ci            self.log_message(reason)
1367db96d56Sopenharmony_ci
1377db96d56Sopenharmony_ci    def warning(self, node, reason):
1387db96d56Sopenharmony_ci        """Used for warning the user about possible uncertainty in the
1397db96d56Sopenharmony_ci        translation.
1407db96d56Sopenharmony_ci
1417db96d56Sopenharmony_ci        First argument is the top-level node for the code in question.
1427db96d56Sopenharmony_ci        Optional second argument is why it can't be converted.
1437db96d56Sopenharmony_ci        """
1447db96d56Sopenharmony_ci        lineno = node.get_lineno()
1457db96d56Sopenharmony_ci        self.log_message("Line %d: %s" % (lineno, reason))
1467db96d56Sopenharmony_ci
1477db96d56Sopenharmony_ci    def start_tree(self, tree, filename):
1487db96d56Sopenharmony_ci        """Some fixers need to maintain tree-wide state.
1497db96d56Sopenharmony_ci        This method is called once, at the start of tree fix-up.
1507db96d56Sopenharmony_ci
1517db96d56Sopenharmony_ci        tree - the root node of the tree to be processed.
1527db96d56Sopenharmony_ci        filename - the name of the file the tree came from.
1537db96d56Sopenharmony_ci        """
1547db96d56Sopenharmony_ci        self.used_names = tree.used_names
1557db96d56Sopenharmony_ci        self.set_filename(filename)
1567db96d56Sopenharmony_ci        self.numbers = itertools.count(1)
1577db96d56Sopenharmony_ci        self.first_log = True
1587db96d56Sopenharmony_ci
1597db96d56Sopenharmony_ci    def finish_tree(self, tree, filename):
1607db96d56Sopenharmony_ci        """Some fixers need to maintain tree-wide state.
1617db96d56Sopenharmony_ci        This method is called once, at the conclusion of tree fix-up.
1627db96d56Sopenharmony_ci
1637db96d56Sopenharmony_ci        tree - the root node of the tree to be processed.
1647db96d56Sopenharmony_ci        filename - the name of the file the tree came from.
1657db96d56Sopenharmony_ci        """
1667db96d56Sopenharmony_ci        pass
1677db96d56Sopenharmony_ci
1687db96d56Sopenharmony_ci
1697db96d56Sopenharmony_ciclass ConditionalFix(BaseFix):
1707db96d56Sopenharmony_ci    """ Base class for fixers which not execute if an import is found. """
1717db96d56Sopenharmony_ci
1727db96d56Sopenharmony_ci    # This is the name of the import which, if found, will cause the test to be skipped
1737db96d56Sopenharmony_ci    skip_on = None
1747db96d56Sopenharmony_ci
1757db96d56Sopenharmony_ci    def start_tree(self, *args):
1767db96d56Sopenharmony_ci        super(ConditionalFix, self).start_tree(*args)
1777db96d56Sopenharmony_ci        self._should_skip = None
1787db96d56Sopenharmony_ci
1797db96d56Sopenharmony_ci    def should_skip(self, node):
1807db96d56Sopenharmony_ci        if self._should_skip is not None:
1817db96d56Sopenharmony_ci            return self._should_skip
1827db96d56Sopenharmony_ci        pkg = self.skip_on.split(".")
1837db96d56Sopenharmony_ci        name = pkg[-1]
1847db96d56Sopenharmony_ci        pkg = ".".join(pkg[:-1])
1857db96d56Sopenharmony_ci        self._should_skip = does_tree_import(pkg, name, node)
1867db96d56Sopenharmony_ci        return self._should_skip
187