fix_kwargs.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. u"""
  2. Fixer for Python 3 function parameter syntax
  3. This fixer is rather sensitive to incorrect py3k syntax.
  4. """
  5. # Note: "relevant" parameters are parameters following the first STAR in the list.
  6. from lib2to3 import fixer_base
  7. from lib2to3.fixer_util import token, String, Newline, Comma, Name
  8. from libfuturize.fixer_util import indentation, suitify, DoubleStar
  9. _assign_template = u"%(name)s = %(kwargs)s['%(name)s']; del %(kwargs)s['%(name)s']"
  10. _if_template = u"if '%(name)s' in %(kwargs)s: %(assign)s"
  11. _else_template = u"else: %(name)s = %(default)s"
  12. _kwargs_default_name = u"_3to2kwargs"
  13. def gen_params(raw_params):
  14. u"""
  15. Generator that yields tuples of (name, default_value) for each parameter in the list
  16. If no default is given, then it is default_value is None (not Leaf(token.NAME, 'None'))
  17. """
  18. assert raw_params[0].type == token.STAR and len(raw_params) > 2
  19. curr_idx = 2 # the first place a keyword-only parameter name can be is index 2
  20. max_idx = len(raw_params)
  21. while curr_idx < max_idx:
  22. curr_item = raw_params[curr_idx]
  23. prev_item = curr_item.prev_sibling
  24. if curr_item.type != token.NAME:
  25. curr_idx += 1
  26. continue
  27. if prev_item is not None and prev_item.type == token.DOUBLESTAR:
  28. break
  29. name = curr_item.value
  30. nxt = curr_item.next_sibling
  31. if nxt is not None and nxt.type == token.EQUAL:
  32. default_value = nxt.next_sibling
  33. curr_idx += 2
  34. else:
  35. default_value = None
  36. yield (name, default_value)
  37. curr_idx += 1
  38. def remove_params(raw_params, kwargs_default=_kwargs_default_name):
  39. u"""
  40. Removes all keyword-only args from the params list and a bare star, if any.
  41. Does not add the kwargs dict if needed.
  42. Returns True if more action is needed, False if not
  43. (more action is needed if no kwargs dict exists)
  44. """
  45. assert raw_params[0].type == token.STAR
  46. if raw_params[1].type == token.COMMA:
  47. raw_params[0].remove()
  48. raw_params[1].remove()
  49. kw_params = raw_params[2:]
  50. else:
  51. kw_params = raw_params[3:]
  52. for param in kw_params:
  53. if param.type != token.DOUBLESTAR:
  54. param.remove()
  55. else:
  56. return False
  57. else:
  58. return True
  59. def needs_fixing(raw_params, kwargs_default=_kwargs_default_name):
  60. u"""
  61. Returns string with the name of the kwargs dict if the params after the first star need fixing
  62. Otherwise returns empty string
  63. """
  64. found_kwargs = False
  65. needs_fix = False
  66. for t in raw_params[2:]:
  67. if t.type == token.COMMA:
  68. # Commas are irrelevant at this stage.
  69. continue
  70. elif t.type == token.NAME and not found_kwargs:
  71. # Keyword-only argument: definitely need to fix.
  72. needs_fix = True
  73. elif t.type == token.NAME and found_kwargs:
  74. # Return 'foobar' of **foobar, if needed.
  75. return t.value if needs_fix else u''
  76. elif t.type == token.DOUBLESTAR:
  77. # Found either '*' from **foobar.
  78. found_kwargs = True
  79. else:
  80. # Never found **foobar. Return a synthetic name, if needed.
  81. return kwargs_default if needs_fix else u''
  82. class FixKwargs(fixer_base.BaseFix):
  83. run_order = 7 # Run after function annotations are removed
  84. PATTERN = u"funcdef< 'def' NAME parameters< '(' arglist=typedargslist< params=any* > ')' > ':' suite=any >"
  85. def transform(self, node, results):
  86. params_rawlist = results[u"params"]
  87. for i, item in enumerate(params_rawlist):
  88. if item.type == token.STAR:
  89. params_rawlist = params_rawlist[i:]
  90. break
  91. else:
  92. return
  93. # params is guaranteed to be a list starting with *.
  94. # if fixing is needed, there will be at least 3 items in this list:
  95. # [STAR, COMMA, NAME] is the minimum that we need to worry about.
  96. new_kwargs = needs_fixing(params_rawlist)
  97. # new_kwargs is the name of the kwargs dictionary.
  98. if not new_kwargs:
  99. return
  100. suitify(node)
  101. # At this point, params_rawlist is guaranteed to be a list
  102. # beginning with a star that includes at least one keyword-only param
  103. # e.g., [STAR, NAME, COMMA, NAME, COMMA, DOUBLESTAR, NAME] or
  104. # [STAR, COMMA, NAME], or [STAR, COMMA, NAME, COMMA, DOUBLESTAR, NAME]
  105. # Anatomy of a funcdef: ['def', 'name', parameters, ':', suite]
  106. # Anatomy of that suite: [NEWLINE, INDENT, first_stmt, all_other_stmts]
  107. # We need to insert our new stuff before the first_stmt and change the
  108. # first_stmt's prefix.
  109. suite = node.children[4]
  110. first_stmt = suite.children[2]
  111. ident = indentation(first_stmt)
  112. for name, default_value in gen_params(params_rawlist):
  113. if default_value is None:
  114. suite.insert_child(2, Newline())
  115. suite.insert_child(2, String(_assign_template %{u'name':name, u'kwargs':new_kwargs}, prefix=ident))
  116. else:
  117. suite.insert_child(2, Newline())
  118. suite.insert_child(2, String(_else_template %{u'name':name, u'default':default_value}, prefix=ident))
  119. suite.insert_child(2, Newline())
  120. suite.insert_child(2, String(_if_template %{u'assign':_assign_template %{u'name':name, u'kwargs':new_kwargs}, u'name':name, u'kwargs':new_kwargs}, prefix=ident))
  121. first_stmt.prefix = ident
  122. suite.children[2].prefix = u""
  123. # Now, we need to fix up the list of params.
  124. must_add_kwargs = remove_params(params_rawlist)
  125. if must_add_kwargs:
  126. arglist = results[u'arglist']
  127. if len(arglist.children) > 0 and arglist.children[-1].type != token.COMMA:
  128. arglist.append_child(Comma())
  129. arglist.append_child(DoubleStar(prefix=u" "))
  130. arglist.append_child(Name(new_kwargs))