comparison env/lib/python3.7/site-packages/libfuturize/fixes/fix_metaclass.py @ 0:26e78fe6e8c4 draft

"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author shellac
date Sat, 02 May 2020 07:14:21 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:26e78fe6e8c4
1 # coding: utf-8
2 """Fixer for __metaclass__ = X -> (future.utils.with_metaclass(X)) methods.
3
4 The various forms of classef (inherits nothing, inherits once, inherints
5 many) don't parse the same in the CST so we look at ALL classes for
6 a __metaclass__ and if we find one normalize the inherits to all be
7 an arglist.
8
9 For one-liner classes ('class X: pass') there is no indent/dedent so
10 we normalize those into having a suite.
11
12 Moving the __metaclass__ into the classdef can also cause the class
13 body to be empty so there is some special casing for that as well.
14
15 This fixer also tries very hard to keep original indenting and spacing
16 in all those corner cases.
17 """
18 # This is a derived work of Lib/lib2to3/fixes/fix_metaclass.py under the
19 # copyright of the Python Software Foundation, licensed under the Python
20 # Software Foundation License 2.
21 #
22 # Copyright notice:
23 #
24 # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
25 # 2011, 2012, 2013 Python Software Foundation. All rights reserved.
26 #
27 # Full license text: http://docs.python.org/3.4/license.html
28
29 # Author: Jack Diederich, Daniel Neuhäuser
30
31 # Local imports
32 from lib2to3 import fixer_base
33 from lib2to3.pygram import token
34 from lib2to3.fixer_util import Name, syms, Node, Leaf, touch_import, Call, \
35 String, Comma, parenthesize
36
37
38 def has_metaclass(parent):
39 """ we have to check the cls_node without changing it.
40 There are two possiblities:
41 1) clsdef => suite => simple_stmt => expr_stmt => Leaf('__meta')
42 2) clsdef => simple_stmt => expr_stmt => Leaf('__meta')
43 """
44 for node in parent.children:
45 if node.type == syms.suite:
46 return has_metaclass(node)
47 elif node.type == syms.simple_stmt and node.children:
48 expr_node = node.children[0]
49 if expr_node.type == syms.expr_stmt and expr_node.children:
50 left_side = expr_node.children[0]
51 if isinstance(left_side, Leaf) and \
52 left_side.value == '__metaclass__':
53 return True
54 return False
55
56
57 def fixup_parse_tree(cls_node):
58 """ one-line classes don't get a suite in the parse tree so we add
59 one to normalize the tree
60 """
61 for node in cls_node.children:
62 if node.type == syms.suite:
63 # already in the preferred format, do nothing
64 return
65
66 # !%@#! oneliners have no suite node, we have to fake one up
67 for i, node in enumerate(cls_node.children):
68 if node.type == token.COLON:
69 break
70 else:
71 raise ValueError("No class suite and no ':'!")
72
73 # move everything into a suite node
74 suite = Node(syms.suite, [])
75 while cls_node.children[i+1:]:
76 move_node = cls_node.children[i+1]
77 suite.append_child(move_node.clone())
78 move_node.remove()
79 cls_node.append_child(suite)
80 node = suite
81
82
83 def fixup_simple_stmt(parent, i, stmt_node):
84 """ if there is a semi-colon all the parts count as part of the same
85 simple_stmt. We just want the __metaclass__ part so we move
86 everything efter the semi-colon into its own simple_stmt node
87 """
88 for semi_ind, node in enumerate(stmt_node.children):
89 if node.type == token.SEMI: # *sigh*
90 break
91 else:
92 return
93
94 node.remove() # kill the semicolon
95 new_expr = Node(syms.expr_stmt, [])
96 new_stmt = Node(syms.simple_stmt, [new_expr])
97 while stmt_node.children[semi_ind:]:
98 move_node = stmt_node.children[semi_ind]
99 new_expr.append_child(move_node.clone())
100 move_node.remove()
101 parent.insert_child(i, new_stmt)
102 new_leaf1 = new_stmt.children[0].children[0]
103 old_leaf1 = stmt_node.children[0].children[0]
104 new_leaf1.prefix = old_leaf1.prefix
105
106
107 def remove_trailing_newline(node):
108 if node.children and node.children[-1].type == token.NEWLINE:
109 node.children[-1].remove()
110
111
112 def find_metas(cls_node):
113 # find the suite node (Mmm, sweet nodes)
114 for node in cls_node.children:
115 if node.type == syms.suite:
116 break
117 else:
118 raise ValueError("No class suite!")
119
120 # look for simple_stmt[ expr_stmt[ Leaf('__metaclass__') ] ]
121 for i, simple_node in list(enumerate(node.children)):
122 if simple_node.type == syms.simple_stmt and simple_node.children:
123 expr_node = simple_node.children[0]
124 if expr_node.type == syms.expr_stmt and expr_node.children:
125 # Check if the expr_node is a simple assignment.
126 left_node = expr_node.children[0]
127 if isinstance(left_node, Leaf) and \
128 left_node.value == u'__metaclass__':
129 # We found a assignment to __metaclass__.
130 fixup_simple_stmt(node, i, simple_node)
131 remove_trailing_newline(simple_node)
132 yield (node, i, simple_node)
133
134
135 def fixup_indent(suite):
136 """ If an INDENT is followed by a thing with a prefix then nuke the prefix
137 Otherwise we get in trouble when removing __metaclass__ at suite start
138 """
139 kids = suite.children[::-1]
140 # find the first indent
141 while kids:
142 node = kids.pop()
143 if node.type == token.INDENT:
144 break
145
146 # find the first Leaf
147 while kids:
148 node = kids.pop()
149 if isinstance(node, Leaf) and node.type != token.DEDENT:
150 if node.prefix:
151 node.prefix = u''
152 return
153 else:
154 kids.extend(node.children[::-1])
155
156
157 class FixMetaclass(fixer_base.BaseFix):
158 BM_compatible = True
159
160 PATTERN = """
161 classdef<any*>
162 """
163
164 def transform(self, node, results):
165 if not has_metaclass(node):
166 return
167
168 fixup_parse_tree(node)
169
170 # find metaclasses, keep the last one
171 last_metaclass = None
172 for suite, i, stmt in find_metas(node):
173 last_metaclass = stmt
174 stmt.remove()
175
176 text_type = node.children[0].type # always Leaf(nnn, 'class')
177
178 # figure out what kind of classdef we have
179 if len(node.children) == 7:
180 # Node(classdef, ['class', 'name', '(', arglist, ')', ':', suite])
181 # 0 1 2 3 4 5 6
182 if node.children[3].type == syms.arglist:
183 arglist = node.children[3]
184 # Node(classdef, ['class', 'name', '(', 'Parent', ')', ':', suite])
185 else:
186 parent = node.children[3].clone()
187 arglist = Node(syms.arglist, [parent])
188 node.set_child(3, arglist)
189 elif len(node.children) == 6:
190 # Node(classdef, ['class', 'name', '(', ')', ':', suite])
191 # 0 1 2 3 4 5
192 arglist = Node(syms.arglist, [])
193 node.insert_child(3, arglist)
194 elif len(node.children) == 4:
195 # Node(classdef, ['class', 'name', ':', suite])
196 # 0 1 2 3
197 arglist = Node(syms.arglist, [])
198 node.insert_child(2, Leaf(token.RPAR, u')'))
199 node.insert_child(2, arglist)
200 node.insert_child(2, Leaf(token.LPAR, u'('))
201 else:
202 raise ValueError("Unexpected class definition")
203
204 # now stick the metaclass in the arglist
205 meta_txt = last_metaclass.children[0].children[0]
206 meta_txt.value = 'metaclass'
207 orig_meta_prefix = meta_txt.prefix
208
209 # Was: touch_import(None, u'future.utils', node)
210 touch_import(u'future.utils', u'with_metaclass', node)
211
212 metaclass = last_metaclass.children[0].children[2].clone()
213 metaclass.prefix = u''
214
215 arguments = [metaclass]
216
217 if arglist.children:
218 if len(arglist.children) == 1:
219 base = arglist.children[0].clone()
220 base.prefix = u' '
221 else:
222 # Unfortunately six.with_metaclass() only allows one base
223 # class, so we have to dynamically generate a base class if
224 # there is more than one.
225 bases = parenthesize(arglist.clone())
226 bases.prefix = u' '
227 base = Call(Name('type'), [
228 String("'NewBase'"),
229 Comma(),
230 bases,
231 Comma(),
232 Node(
233 syms.atom,
234 [Leaf(token.LBRACE, u'{'), Leaf(token.RBRACE, u'}')],
235 prefix=u' '
236 )
237 ], prefix=u' ')
238 arguments.extend([Comma(), base])
239
240 arglist.replace(Call(
241 Name(u'with_metaclass', prefix=arglist.prefix),
242 arguments
243 ))
244
245 fixup_indent(suite)
246
247 # check for empty suite
248 if not suite.children:
249 # one-liner that was just __metaclass_
250 suite.remove()
251 pass_leaf = Leaf(text_type, u'pass')
252 pass_leaf.prefix = orig_meta_prefix
253 node.append_child(pass_leaf)
254 node.append_child(Leaf(token.NEWLINE, u'\n'))
255
256 elif len(suite.children) > 1 and \
257 (suite.children[-2].type == token.INDENT and
258 suite.children[-1].type == token.DEDENT):
259 # there was only one line in the class body and it was __metaclass__
260 pass_leaf = Leaf(text_type, u'pass')
261 suite.insert_child(-1, pass_leaf)
262 suite.insert_child(-1, Leaf(token.NEWLINE, u'\n'))