(root)/
Python-3.12.0/
Lib/
lib2to3/
fixes/
fix_metaclass.py
       1  """Fixer for __metaclass__ = X -> (metaclass=X) methods.
       2  
       3     The various forms of classef (inherits nothing, inherits once, inherits
       4     many) don't parse the same in the CST so we look at ALL classes for
       5     a __metaclass__ and if we find one normalize the inherits to all be
       6     an arglist.
       7  
       8     For one-liner classes ('class X: pass') there is no indent/dedent so
       9     we normalize those into having a suite.
      10  
      11     Moving the __metaclass__ into the classdef can also cause the class
      12     body to be empty so there is some special casing for that as well.
      13  
      14     This fixer also tries very hard to keep original indenting and spacing
      15     in all those corner cases.
      16  
      17  """
      18  # Author: Jack Diederich
      19  
      20  # Local imports
      21  from .. import fixer_base
      22  from ..pygram import token
      23  from ..fixer_util import syms, Node, Leaf
      24  
      25  
      26  def has_metaclass(parent):
      27      """ we have to check the cls_node without changing it.
      28          There are two possibilities:
      29            1)  clsdef => suite => simple_stmt => expr_stmt => Leaf('__meta')
      30            2)  clsdef => simple_stmt => expr_stmt => Leaf('__meta')
      31      """
      32      for node in parent.children:
      33          if node.type == syms.suite:
      34              return has_metaclass(node)
      35          elif node.type == syms.simple_stmt and node.children:
      36              expr_node = node.children[0]
      37              if expr_node.type == syms.expr_stmt and expr_node.children:
      38                  left_side = expr_node.children[0]
      39                  if isinstance(left_side, Leaf) and \
      40                          left_side.value == '__metaclass__':
      41                      return True
      42      return False
      43  
      44  
      45  def fixup_parse_tree(cls_node):
      46      """ one-line classes don't get a suite in the parse tree so we add
      47          one to normalize the tree
      48      """
      49      for node in cls_node.children:
      50          if node.type == syms.suite:
      51              # already in the preferred format, do nothing
      52              return
      53  
      54      # !%@#! one-liners have no suite node, we have to fake one up
      55      for i, node in enumerate(cls_node.children):
      56          if node.type == token.COLON:
      57              break
      58      else:
      59          raise ValueError("No class suite and no ':'!")
      60  
      61      # move everything into a suite node
      62      suite = Node(syms.suite, [])
      63      while cls_node.children[i+1:]:
      64          move_node = cls_node.children[i+1]
      65          suite.append_child(move_node.clone())
      66          move_node.remove()
      67      cls_node.append_child(suite)
      68      node = suite
      69  
      70  
      71  def fixup_simple_stmt(parent, i, stmt_node):
      72      """ if there is a semi-colon all the parts count as part of the same
      73          simple_stmt.  We just want the __metaclass__ part so we move
      74          everything after the semi-colon into its own simple_stmt node
      75      """
      76      for semi_ind, node in enumerate(stmt_node.children):
      77          if node.type == token.SEMI: # *sigh*
      78              break
      79      else:
      80          return
      81  
      82      node.remove() # kill the semicolon
      83      new_expr = Node(syms.expr_stmt, [])
      84      new_stmt = Node(syms.simple_stmt, [new_expr])
      85      while stmt_node.children[semi_ind:]:
      86          move_node = stmt_node.children[semi_ind]
      87          new_expr.append_child(move_node.clone())
      88          move_node.remove()
      89      parent.insert_child(i, new_stmt)
      90      new_leaf1 = new_stmt.children[0].children[0]
      91      old_leaf1 = stmt_node.children[0].children[0]
      92      new_leaf1.prefix = old_leaf1.prefix
      93  
      94  
      95  def remove_trailing_newline(node):
      96      if node.children and node.children[-1].type == token.NEWLINE:
      97          node.children[-1].remove()
      98  
      99  
     100  def find_metas(cls_node):
     101      # find the suite node (Mmm, sweet nodes)
     102      for node in cls_node.children:
     103          if node.type == syms.suite:
     104              break
     105      else:
     106          raise ValueError("No class suite!")
     107  
     108      # look for simple_stmt[ expr_stmt[ Leaf('__metaclass__') ] ]
     109      for i, simple_node in list(enumerate(node.children)):
     110          if simple_node.type == syms.simple_stmt and simple_node.children:
     111              expr_node = simple_node.children[0]
     112              if expr_node.type == syms.expr_stmt and expr_node.children:
     113                  # Check if the expr_node is a simple assignment.
     114                  left_node = expr_node.children[0]
     115                  if isinstance(left_node, Leaf) and \
     116                          left_node.value == '__metaclass__':
     117                      # We found an assignment to __metaclass__.
     118                      fixup_simple_stmt(node, i, simple_node)
     119                      remove_trailing_newline(simple_node)
     120                      yield (node, i, simple_node)
     121  
     122  
     123  def fixup_indent(suite):
     124      """ If an INDENT is followed by a thing with a prefix then nuke the prefix
     125          Otherwise we get in trouble when removing __metaclass__ at suite start
     126      """
     127      kids = suite.children[::-1]
     128      # find the first indent
     129      while kids:
     130          node = kids.pop()
     131          if node.type == token.INDENT:
     132              break
     133  
     134      # find the first Leaf
     135      while kids:
     136          node = kids.pop()
     137          if isinstance(node, Leaf) and node.type != token.DEDENT:
     138              if node.prefix:
     139                  node.prefix = ''
     140              return
     141          else:
     142              kids.extend(node.children[::-1])
     143  
     144  
     145  class ESC[4;38;5;81mFixMetaclass(ESC[4;38;5;149mfixer_baseESC[4;38;5;149m.ESC[4;38;5;149mBaseFix):
     146      BM_compatible = True
     147  
     148      PATTERN = """
     149      classdef<any*>
     150      """
     151  
     152      def transform(self, node, results):
     153          if not has_metaclass(node):
     154              return
     155  
     156          fixup_parse_tree(node)
     157  
     158          # find metaclasses, keep the last one
     159          last_metaclass = None
     160          for suite, i, stmt in find_metas(node):
     161              last_metaclass = stmt
     162              stmt.remove()
     163  
     164          text_type = node.children[0].type # always Leaf(nnn, 'class')
     165  
     166          # figure out what kind of classdef we have
     167          if len(node.children) == 7:
     168              # Node(classdef, ['class', 'name', '(', arglist, ')', ':', suite])
     169              #                 0        1       2    3        4    5    6
     170              if node.children[3].type == syms.arglist:
     171                  arglist = node.children[3]
     172              # Node(classdef, ['class', 'name', '(', 'Parent', ')', ':', suite])
     173              else:
     174                  parent = node.children[3].clone()
     175                  arglist = Node(syms.arglist, [parent])
     176                  node.set_child(3, arglist)
     177          elif len(node.children) == 6:
     178              # Node(classdef, ['class', 'name', '(',  ')', ':', suite])
     179              #                 0        1       2     3    4    5
     180              arglist = Node(syms.arglist, [])
     181              node.insert_child(3, arglist)
     182          elif len(node.children) == 4:
     183              # Node(classdef, ['class', 'name', ':', suite])
     184              #                 0        1       2    3
     185              arglist = Node(syms.arglist, [])
     186              node.insert_child(2, Leaf(token.RPAR, ')'))
     187              node.insert_child(2, arglist)
     188              node.insert_child(2, Leaf(token.LPAR, '('))
     189          else:
     190              raise ValueError("Unexpected class definition")
     191  
     192          # now stick the metaclass in the arglist
     193          meta_txt = last_metaclass.children[0].children[0]
     194          meta_txt.value = 'metaclass'
     195          orig_meta_prefix = meta_txt.prefix
     196  
     197          if arglist.children:
     198              arglist.append_child(Leaf(token.COMMA, ','))
     199              meta_txt.prefix = ' '
     200          else:
     201              meta_txt.prefix = ''
     202  
     203          # compact the expression "metaclass = Meta" -> "metaclass=Meta"
     204          expr_stmt = last_metaclass.children[0]
     205          assert expr_stmt.type == syms.expr_stmt
     206          expr_stmt.children[1].prefix = ''
     207          expr_stmt.children[2].prefix = ''
     208  
     209          arglist.append_child(last_metaclass)
     210  
     211          fixup_indent(suite)
     212  
     213          # check for empty suite
     214          if not suite.children:
     215              # one-liner that was just __metaclass_
     216              suite.remove()
     217              pass_leaf = Leaf(text_type, 'pass')
     218              pass_leaf.prefix = orig_meta_prefix
     219              node.append_child(pass_leaf)
     220              node.append_child(Leaf(token.NEWLINE, '\n'))
     221  
     222          elif len(suite.children) > 1 and \
     223                   (suite.children[-2].type == token.INDENT and
     224                    suite.children[-1].type == token.DEDENT):
     225              # there was only one line in the class body and it was __metaclass__
     226              pass_leaf = Leaf(text_type, 'pass')
     227              suite.insert_child(-1, pass_leaf)
     228              suite.insert_child(-1, Leaf(token.NEWLINE, '\n'))