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