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