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