Home | History | Annotate | Download | only in fixes
      1 """Fixer for __metaclass__ = X -> (metaclass=X) methods.
      2 
      3    The various forms of classef (inherits nothing, inherits once, inherints
      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 Name, 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 possiblities:
     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     # !%@#! oneliners 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 efter 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 == u'__metaclass__':
    117                     # We found a 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 = u''
    140             return
    141         else:
    142             kids.extend(node.children[::-1])
    143 
    144 
    145 class FixMetaclass(fixer_base.BaseFix):
    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, u')'))
    187             node.insert_child(2, arglist)
    188             node.insert_child(2, Leaf(token.LPAR, u'('))
    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, u','))
    199             meta_txt.prefix = u' '
    200         else:
    201             meta_txt.prefix = u''
    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 = u''
    207         expr_stmt.children[2].prefix = u''
    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, u'pass')
    218             pass_leaf.prefix = orig_meta_prefix
    219             node.append_child(pass_leaf)
    220             node.append_child(Leaf(token.NEWLINE, u'\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, u'pass')
    227             suite.insert_child(-1, pass_leaf)
    228             suite.insert_child(-1, Leaf(token.NEWLINE, u'\n'))
    229