|
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 prefered 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.set_prefix(old_leaf1.get_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 leaf_node = expr_node.children[0] |
|
114 if leaf_node.value == '__metaclass__': |
|
115 fixup_simple_stmt(node, i, simple_node) |
|
116 remove_trailing_newline(simple_node) |
|
117 yield (node, i, simple_node) |
|
118 |
|
119 |
|
120 def fixup_indent(suite): |
|
121 """ If an INDENT is followed by a thing with a prefix then nuke the prefix |
|
122 Otherwise we get in trouble when removing __metaclass__ at suite start |
|
123 """ |
|
124 kids = suite.children[::-1] |
|
125 # find the first indent |
|
126 while kids: |
|
127 node = kids.pop() |
|
128 if node.type == token.INDENT: |
|
129 break |
|
130 |
|
131 # find the first Leaf |
|
132 while kids: |
|
133 node = kids.pop() |
|
134 if isinstance(node, Leaf) and node.type != token.DEDENT: |
|
135 if node.prefix: |
|
136 node.set_prefix('') |
|
137 return |
|
138 else: |
|
139 kids.extend(node.children[::-1]) |
|
140 |
|
141 |
|
142 class FixMetaclass(fixer_base.BaseFix): |
|
143 |
|
144 PATTERN = """ |
|
145 classdef<any*> |
|
146 """ |
|
147 |
|
148 def transform(self, node, results): |
|
149 if not has_metaclass(node): |
|
150 return node |
|
151 |
|
152 fixup_parse_tree(node) |
|
153 |
|
154 # find metaclasses, keep the last one |
|
155 last_metaclass = None |
|
156 for suite, i, stmt in find_metas(node): |
|
157 last_metaclass = stmt |
|
158 stmt.remove() |
|
159 |
|
160 text_type = node.children[0].type # always Leaf(nnn, 'class') |
|
161 |
|
162 # figure out what kind of classdef we have |
|
163 if len(node.children) == 7: |
|
164 # Node(classdef, ['class', 'name', '(', arglist, ')', ':', suite]) |
|
165 # 0 1 2 3 4 5 6 |
|
166 if node.children[3].type == syms.arglist: |
|
167 arglist = node.children[3] |
|
168 # Node(classdef, ['class', 'name', '(', 'Parent', ')', ':', suite]) |
|
169 else: |
|
170 parent = node.children[3].clone() |
|
171 arglist = Node(syms.arglist, [parent]) |
|
172 node.set_child(3, arglist) |
|
173 elif len(node.children) == 6: |
|
174 # Node(classdef, ['class', 'name', '(', ')', ':', suite]) |
|
175 # 0 1 2 3 4 5 |
|
176 arglist = Node(syms.arglist, []) |
|
177 node.insert_child(3, arglist) |
|
178 elif len(node.children) == 4: |
|
179 # Node(classdef, ['class', 'name', ':', suite]) |
|
180 # 0 1 2 3 |
|
181 arglist = Node(syms.arglist, []) |
|
182 node.insert_child(2, Leaf(token.RPAR, ')')) |
|
183 node.insert_child(2, arglist) |
|
184 node.insert_child(2, Leaf(token.LPAR, '(')) |
|
185 else: |
|
186 raise ValueError("Unexpected class definition") |
|
187 |
|
188 # now stick the metaclass in the arglist |
|
189 meta_txt = last_metaclass.children[0].children[0] |
|
190 meta_txt.value = 'metaclass' |
|
191 orig_meta_prefix = meta_txt.get_prefix() |
|
192 |
|
193 if arglist.children: |
|
194 arglist.append_child(Leaf(token.COMMA, ',')) |
|
195 meta_txt.set_prefix(' ') |
|
196 else: |
|
197 meta_txt.set_prefix('') |
|
198 |
|
199 # compact the expression "metaclass = Meta" -> "metaclass=Meta" |
|
200 expr_stmt = last_metaclass.children[0] |
|
201 assert expr_stmt.type == syms.expr_stmt |
|
202 expr_stmt.children[1].set_prefix('') |
|
203 expr_stmt.children[2].set_prefix('') |
|
204 |
|
205 arglist.append_child(last_metaclass) |
|
206 |
|
207 fixup_indent(suite) |
|
208 |
|
209 # check for empty suite |
|
210 if not suite.children: |
|
211 # one-liner that was just __metaclass_ |
|
212 suite.remove() |
|
213 pass_leaf = Leaf(text_type, 'pass') |
|
214 pass_leaf.set_prefix(orig_meta_prefix) |
|
215 node.append_child(pass_leaf) |
|
216 node.append_child(Leaf(token.NEWLINE, '\n')) |
|
217 |
|
218 elif len(suite.children) > 1 and \ |
|
219 (suite.children[-2].type == token.INDENT and |
|
220 suite.children[-1].type == token.DEDENT): |
|
221 # there was only one line in the class body and it was __metaclass__ |
|
222 pass_leaf = Leaf(text_type, 'pass') |
|
223 suite.insert_child(-1, pass_leaf) |
|
224 suite.insert_child(-1, Leaf(token.NEWLINE, '\n')) |