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