1 | """Adjust some old Python 2 idioms to their modern counterparts.
|
---|
2 |
|
---|
3 | * Change some type comparisons to isinstance() calls:
|
---|
4 | type(x) == T -> isinstance(x, T)
|
---|
5 | type(x) is T -> isinstance(x, T)
|
---|
6 | type(x) != T -> not isinstance(x, T)
|
---|
7 | type(x) is not T -> not isinstance(x, T)
|
---|
8 |
|
---|
9 | * Change "while 1:" into "while True:".
|
---|
10 |
|
---|
11 | * Change both
|
---|
12 |
|
---|
13 | v = list(EXPR)
|
---|
14 | v.sort()
|
---|
15 | foo(v)
|
---|
16 |
|
---|
17 | and the more general
|
---|
18 |
|
---|
19 | v = EXPR
|
---|
20 | v.sort()
|
---|
21 | foo(v)
|
---|
22 |
|
---|
23 | into
|
---|
24 |
|
---|
25 | v = sorted(EXPR)
|
---|
26 | foo(v)
|
---|
27 | """
|
---|
28 | # Author: Jacques Frechet, Collin Winter
|
---|
29 |
|
---|
30 | # Local imports
|
---|
31 | from .. import fixer_base
|
---|
32 | from ..fixer_util import Call, Comma, Name, Node, BlankLine, syms
|
---|
33 |
|
---|
34 | CMP = "(n='!=' | '==' | 'is' | n=comp_op< 'is' 'not' >)"
|
---|
35 | TYPE = "power< 'type' trailer< '(' x=any ')' > >"
|
---|
36 |
|
---|
37 | class FixIdioms(fixer_base.BaseFix):
|
---|
38 | explicit = True # The user must ask for this fixer
|
---|
39 |
|
---|
40 | PATTERN = r"""
|
---|
41 | isinstance=comparison< %s %s T=any >
|
---|
42 | |
|
---|
43 | isinstance=comparison< T=any %s %s >
|
---|
44 | |
|
---|
45 | while_stmt< 'while' while='1' ':' any+ >
|
---|
46 | |
|
---|
47 | sorted=any<
|
---|
48 | any*
|
---|
49 | simple_stmt<
|
---|
50 | expr_stmt< id1=any '='
|
---|
51 | power< list='list' trailer< '(' (not arglist<any+>) any ')' > >
|
---|
52 | >
|
---|
53 | '\n'
|
---|
54 | >
|
---|
55 | sort=
|
---|
56 | simple_stmt<
|
---|
57 | power< id2=any
|
---|
58 | trailer< '.' 'sort' > trailer< '(' ')' >
|
---|
59 | >
|
---|
60 | '\n'
|
---|
61 | >
|
---|
62 | next=any*
|
---|
63 | >
|
---|
64 | |
|
---|
65 | sorted=any<
|
---|
66 | any*
|
---|
67 | simple_stmt< expr_stmt< id1=any '=' expr=any > '\n' >
|
---|
68 | sort=
|
---|
69 | simple_stmt<
|
---|
70 | power< id2=any
|
---|
71 | trailer< '.' 'sort' > trailer< '(' ')' >
|
---|
72 | >
|
---|
73 | '\n'
|
---|
74 | >
|
---|
75 | next=any*
|
---|
76 | >
|
---|
77 | """ % (TYPE, CMP, CMP, TYPE)
|
---|
78 |
|
---|
79 | def match(self, node):
|
---|
80 | r = super(FixIdioms, self).match(node)
|
---|
81 | # If we've matched one of the sort/sorted subpatterns above, we
|
---|
82 | # want to reject matches where the initial assignment and the
|
---|
83 | # subsequent .sort() call involve different identifiers.
|
---|
84 | if r and "sorted" in r:
|
---|
85 | if r["id1"] == r["id2"]:
|
---|
86 | return r
|
---|
87 | return None
|
---|
88 | return r
|
---|
89 |
|
---|
90 | def transform(self, node, results):
|
---|
91 | if "isinstance" in results:
|
---|
92 | return self.transform_isinstance(node, results)
|
---|
93 | elif "while" in results:
|
---|
94 | return self.transform_while(node, results)
|
---|
95 | elif "sorted" in results:
|
---|
96 | return self.transform_sort(node, results)
|
---|
97 | else:
|
---|
98 | raise RuntimeError("Invalid match")
|
---|
99 |
|
---|
100 | def transform_isinstance(self, node, results):
|
---|
101 | x = results["x"].clone() # The thing inside of type()
|
---|
102 | T = results["T"].clone() # The type being compared against
|
---|
103 | x.prefix = u""
|
---|
104 | T.prefix = u" "
|
---|
105 | test = Call(Name(u"isinstance"), [x, Comma(), T])
|
---|
106 | if "n" in results:
|
---|
107 | test.prefix = u" "
|
---|
108 | test = Node(syms.not_test, [Name(u"not"), test])
|
---|
109 | test.prefix = node.prefix
|
---|
110 | return test
|
---|
111 |
|
---|
112 | def transform_while(self, node, results):
|
---|
113 | one = results["while"]
|
---|
114 | one.replace(Name(u"True", prefix=one.prefix))
|
---|
115 |
|
---|
116 | def transform_sort(self, node, results):
|
---|
117 | sort_stmt = results["sort"]
|
---|
118 | next_stmt = results["next"]
|
---|
119 | list_call = results.get("list")
|
---|
120 | simple_expr = results.get("expr")
|
---|
121 |
|
---|
122 | if list_call:
|
---|
123 | list_call.replace(Name(u"sorted", prefix=list_call.prefix))
|
---|
124 | elif simple_expr:
|
---|
125 | new = simple_expr.clone()
|
---|
126 | new.prefix = u""
|
---|
127 | simple_expr.replace(Call(Name(u"sorted"), [new],
|
---|
128 | prefix=simple_expr.prefix))
|
---|
129 | else:
|
---|
130 | raise RuntimeError("should not have reached here")
|
---|
131 | sort_stmt.remove()
|
---|
132 |
|
---|
133 | btwn = sort_stmt.prefix
|
---|
134 | # Keep any prefix lines between the sort_stmt and the list_call and
|
---|
135 | # shove them right after the sorted() call.
|
---|
136 | if u"\n" in btwn:
|
---|
137 | if next_stmt:
|
---|
138 | # The new prefix should be everything from the sort_stmt's
|
---|
139 | # prefix up to the last newline, then the old prefix after a new
|
---|
140 | # line.
|
---|
141 | prefix_lines = (btwn.rpartition(u"\n")[0], next_stmt[0].prefix)
|
---|
142 | next_stmt[0].prefix = u"\n".join(prefix_lines)
|
---|
143 | else:
|
---|
144 | assert list_call.parent
|
---|
145 | assert list_call.next_sibling is None
|
---|
146 | # Put a blank line after list_call and set its prefix.
|
---|
147 | end_line = BlankLine()
|
---|
148 | list_call.parent.append_child(end_line)
|
---|
149 | assert list_call.next_sibling is end_line
|
---|
150 | # The new prefix should be everything up to the first new line
|
---|
151 | # of sort_stmt's prefix.
|
---|
152 | end_line.prefix = btwn.rpartition(u"\n")[0]
|
---|