1ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh# Copyright 2006 Google, Inc. All Rights Reserved.
2ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh# Licensed to PSF under a Contributor Agreement.
3ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
4ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh"""Base class for fixers (optional, but recommended)."""
5ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
6ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh# Python imports
7ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehimport logging
8ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehimport itertools
9ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
10ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh# Local imports
11ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehfrom .patcomp import PatternCompiler
12ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehfrom . import pygram
13ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehfrom .fixer_util import does_tree_import
14ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
15ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehclass BaseFix(object):
16ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
17ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    """Optional base class for fixers.
18ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
19ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    The subclass name must be FixFooBar where FooBar is the result of
20ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    removing underscores and capitalizing the words of the fix name.
21ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    For example, the class name for a fixer named 'has_key' should be
22ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    FixHasKey.
23ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    """
24ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
25ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    PATTERN = None  # Most subclasses should override with a string literal
26ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    pattern = None  # Compiled pattern, set by compile_pattern()
27ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    pattern_tree = None # Tree representation of the pattern
28ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    options = None  # Options object passed to initializer
29ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    filename = None # The filename (set by set_filename)
30ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    logger = None   # A logger (set by set_filename)
31ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    numbers = itertools.count(1) # For new_name()
32ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    used_names = set() # A set of all used NAMEs
33ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    order = "post" # Does the fixer prefer pre- or post-order traversal
34ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    explicit = False # Is this ignored by refactor.py -f all?
35ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    run_order = 5   # Fixers will be sorted by run order before execution
36ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                    # Lower numbers will be run first.
37ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    _accept_type = None # [Advanced and not public] This tells RefactoringTool
38ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                        # which node type to accept when there's not a pattern.
39ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
40ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    keep_line_order = False # For the bottom matcher: match with the
41ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                            # original line order
42ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    BM_compatible = False # Compatibility with the bottom matching
43ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                          # module; every fixer should set this
44ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                          # manually
45ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
46ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    # Shortcut for access to Python grammar symbols
47ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    syms = pygram.python_symbols
48ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
49ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def __init__(self, options, log):
50ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        """Initializer.  Subclass may override.
51ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
52ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        Args:
53ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            options: an dict containing the options passed to RefactoringTool
54ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            that could be used to customize the fixer through the command line.
55ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            log: a list to append warnings and other messages to.
56ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        """
57ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self.options = options
58ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self.log = log
59ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self.compile_pattern()
60ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
61ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def compile_pattern(self):
62ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        """Compiles self.PATTERN into self.pattern.
63ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
64ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        Subclass may override if it doesn't want to use
65ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self.{pattern,PATTERN} in .match().
66ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        """
67ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        if self.PATTERN is not None:
68ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            PC = PatternCompiler()
69ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            self.pattern, self.pattern_tree = PC.compile_pattern(self.PATTERN,
70ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                                                                 with_tree=True)
71ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
72ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def set_filename(self, filename):
73ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        """Set the filename, and a logger derived from it.
74ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
75ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        The main refactoring tool should call this.
76ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        """
77ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self.filename = filename
78ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self.logger = logging.getLogger(filename)
79ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
80ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def match(self, node):
81ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        """Returns match for a given parse tree node.
82ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
83ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        Should return a true or false object (not necessarily a bool).
84ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        It may return a non-empty dict of matching sub-nodes as
85ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        returned by a matching pattern.
86ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
87ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        Subclass may override.
88ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        """
89ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        results = {"node": node}
90ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        return self.pattern.match(node, results) and results
91ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
92ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def transform(self, node, results):
93ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        """Returns the transformation for a given parse tree node.
94ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
95ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        Args:
96ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh          node: the root of the parse tree that matched the fixer.
97ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh          results: a dict mapping symbolic names to part of the match.
98ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
99ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        Returns:
100ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh          None, or a node that is a modified copy of the
101ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh          argument node.  The node argument may also be modified in-place to
102ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh          effect the same change.
103ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
104ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        Subclass *must* override.
105ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        """
106ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        raise NotImplementedError()
107ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
108ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def new_name(self, template=u"xxx_todo_changeme"):
109ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        """Return a string suitable for use as an identifier
110ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
111ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        The new name is guaranteed not to conflict with other identifiers.
112ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        """
113ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        name = template
114ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        while name in self.used_names:
115ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            name = template + unicode(self.numbers.next())
116ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self.used_names.add(name)
117ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        return name
118ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
119ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def log_message(self, message):
120ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        if self.first_log:
121ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            self.first_log = False
122ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            self.log.append("### In file %s ###" % self.filename)
123ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self.log.append(message)
124ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
125ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def cannot_convert(self, node, reason=None):
126ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        """Warn the user that a given chunk of code is not valid Python 3,
127ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        but that it cannot be converted automatically.
128ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
129ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        First argument is the top-level node for the code in question.
130ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        Optional second argument is why it can't be converted.
131ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        """
132ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        lineno = node.get_lineno()
133ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        for_output = node.clone()
134ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        for_output.prefix = u""
135ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        msg = "Line %d: could not convert: %s"
136ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self.log_message(msg % (lineno, for_output))
137ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        if reason:
138ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            self.log_message(reason)
139ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
140ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def warning(self, node, reason):
141ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        """Used for warning the user about possible uncertainty in the
142ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        translation.
143ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
144ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        First argument is the top-level node for the code in question.
145ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        Optional second argument is why it can't be converted.
146ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        """
147ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        lineno = node.get_lineno()
148ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self.log_message("Line %d: %s" % (lineno, reason))
149ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
150ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def start_tree(self, tree, filename):
151ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        """Some fixers need to maintain tree-wide state.
152ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        This method is called once, at the start of tree fix-up.
153ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
154ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        tree - the root node of the tree to be processed.
155ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        filename - the name of the file the tree came from.
156ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        """
157ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self.used_names = tree.used_names
158ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self.set_filename(filename)
159ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self.numbers = itertools.count(1)
160ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self.first_log = True
161ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
162ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def finish_tree(self, tree, filename):
163ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        """Some fixers need to maintain tree-wide state.
164ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        This method is called once, at the conclusion of tree fix-up.
165ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
166ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        tree - the root node of the tree to be processed.
167ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        filename - the name of the file the tree came from.
168ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        """
169ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        pass
170ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
171ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
172ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehclass ConditionalFix(BaseFix):
173ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    """ Base class for fixers which not execute if an import is found. """
174ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
175ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    # This is the name of the import which, if found, will cause the test to be skipped
176ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    skip_on = None
177ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
178ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def start_tree(self, *args):
179ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        super(ConditionalFix, self).start_tree(*args)
180ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self._should_skip = None
181ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
182ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def should_skip(self, node):
183ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        if self._should_skip is not None:
184ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            return self._should_skip
185ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        pkg = self.skip_on.split(".")
186ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        name = pkg[-1]
187ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        pkg = ".".join(pkg[:-1])
188ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self._should_skip = does_tree_import(pkg, name, node)
189ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        return self._should_skip
190