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
21from .. import fixer_base
22from ..pygram import token
23from ..fixer_util import Name, syms, Node, Leaf
24
25
26def 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
45def 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
71def 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
95def remove_trailing_newline(node):
96    if node.children and node.children[-1].type == token.NEWLINE:
97        node.children[-1].remove()
98
99
100def 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
123def 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
145class 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