17db96d56Sopenharmony_ci"""Fixer for except statements with named exceptions.
27db96d56Sopenharmony_ci
37db96d56Sopenharmony_ciThe following cases will be converted:
47db96d56Sopenharmony_ci
57db96d56Sopenharmony_ci- "except E, T:" where T is a name:
67db96d56Sopenharmony_ci
77db96d56Sopenharmony_ci    except E as T:
87db96d56Sopenharmony_ci
97db96d56Sopenharmony_ci- "except E, T:" where T is not a name, tuple or list:
107db96d56Sopenharmony_ci
117db96d56Sopenharmony_ci        except E as t:
127db96d56Sopenharmony_ci            T = t
137db96d56Sopenharmony_ci
147db96d56Sopenharmony_ci    This is done because the target of an "except" clause must be a
157db96d56Sopenharmony_ci    name.
167db96d56Sopenharmony_ci
177db96d56Sopenharmony_ci- "except E, T:" where T is a tuple or list literal:
187db96d56Sopenharmony_ci
197db96d56Sopenharmony_ci        except E as t:
207db96d56Sopenharmony_ci            T = t.args
217db96d56Sopenharmony_ci"""
227db96d56Sopenharmony_ci# Author: Collin Winter
237db96d56Sopenharmony_ci
247db96d56Sopenharmony_ci# Local imports
257db96d56Sopenharmony_cifrom .. import pytree
267db96d56Sopenharmony_cifrom ..pgen2 import token
277db96d56Sopenharmony_cifrom .. import fixer_base
287db96d56Sopenharmony_cifrom ..fixer_util import Assign, Attr, Name, is_tuple, is_list, syms
297db96d56Sopenharmony_ci
307db96d56Sopenharmony_cidef find_excepts(nodes):
317db96d56Sopenharmony_ci    for i, n in enumerate(nodes):
327db96d56Sopenharmony_ci        if n.type == syms.except_clause:
337db96d56Sopenharmony_ci            if n.children[0].value == 'except':
347db96d56Sopenharmony_ci                yield (n, nodes[i+2])
357db96d56Sopenharmony_ci
367db96d56Sopenharmony_ciclass FixExcept(fixer_base.BaseFix):
377db96d56Sopenharmony_ci    BM_compatible = True
387db96d56Sopenharmony_ci
397db96d56Sopenharmony_ci    PATTERN = """
407db96d56Sopenharmony_ci    try_stmt< 'try' ':' (simple_stmt | suite)
417db96d56Sopenharmony_ci                  cleanup=(except_clause ':' (simple_stmt | suite))+
427db96d56Sopenharmony_ci                  tail=(['except' ':' (simple_stmt | suite)]
437db96d56Sopenharmony_ci                        ['else' ':' (simple_stmt | suite)]
447db96d56Sopenharmony_ci                        ['finally' ':' (simple_stmt | suite)]) >
457db96d56Sopenharmony_ci    """
467db96d56Sopenharmony_ci
477db96d56Sopenharmony_ci    def transform(self, node, results):
487db96d56Sopenharmony_ci        syms = self.syms
497db96d56Sopenharmony_ci
507db96d56Sopenharmony_ci        tail = [n.clone() for n in results["tail"]]
517db96d56Sopenharmony_ci
527db96d56Sopenharmony_ci        try_cleanup = [ch.clone() for ch in results["cleanup"]]
537db96d56Sopenharmony_ci        for except_clause, e_suite in find_excepts(try_cleanup):
547db96d56Sopenharmony_ci            if len(except_clause.children) == 4:
557db96d56Sopenharmony_ci                (E, comma, N) = except_clause.children[1:4]
567db96d56Sopenharmony_ci                comma.replace(Name("as", prefix=" "))
577db96d56Sopenharmony_ci
587db96d56Sopenharmony_ci                if N.type != token.NAME:
597db96d56Sopenharmony_ci                    # Generate a new N for the except clause
607db96d56Sopenharmony_ci                    new_N = Name(self.new_name(), prefix=" ")
617db96d56Sopenharmony_ci                    target = N.clone()
627db96d56Sopenharmony_ci                    target.prefix = ""
637db96d56Sopenharmony_ci                    N.replace(new_N)
647db96d56Sopenharmony_ci                    new_N = new_N.clone()
657db96d56Sopenharmony_ci
667db96d56Sopenharmony_ci                    # Insert "old_N = new_N" as the first statement in
677db96d56Sopenharmony_ci                    #  the except body. This loop skips leading whitespace
687db96d56Sopenharmony_ci                    #  and indents
697db96d56Sopenharmony_ci                    #TODO(cwinter) suite-cleanup
707db96d56Sopenharmony_ci                    suite_stmts = e_suite.children
717db96d56Sopenharmony_ci                    for i, stmt in enumerate(suite_stmts):
727db96d56Sopenharmony_ci                        if isinstance(stmt, pytree.Node):
737db96d56Sopenharmony_ci                            break
747db96d56Sopenharmony_ci
757db96d56Sopenharmony_ci                    # The assignment is different if old_N is a tuple or list
767db96d56Sopenharmony_ci                    # In that case, the assignment is old_N = new_N.args
777db96d56Sopenharmony_ci                    if is_tuple(N) or is_list(N):
787db96d56Sopenharmony_ci                        assign = Assign(target, Attr(new_N, Name('args')))
797db96d56Sopenharmony_ci                    else:
807db96d56Sopenharmony_ci                        assign = Assign(target, new_N)
817db96d56Sopenharmony_ci
827db96d56Sopenharmony_ci                    #TODO(cwinter) stopgap until children becomes a smart list
837db96d56Sopenharmony_ci                    for child in reversed(suite_stmts[:i]):
847db96d56Sopenharmony_ci                        e_suite.insert_child(0, child)
857db96d56Sopenharmony_ci                    e_suite.insert_child(i, assign)
867db96d56Sopenharmony_ci                elif N.prefix == "":
877db96d56Sopenharmony_ci                    # No space after a comma is legal; no space after "as",
887db96d56Sopenharmony_ci                    # not so much.
897db96d56Sopenharmony_ci                    N.prefix = " "
907db96d56Sopenharmony_ci
917db96d56Sopenharmony_ci        #TODO(cwinter) fix this when children becomes a smart list
927db96d56Sopenharmony_ci        children = [c.clone() for c in node.children[:3]] + try_cleanup + tail
937db96d56Sopenharmony_ci        return pytree.Node(node.type, children)
94