fix_metaclass.py 9.3 KB

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