5
5
import sys
6
6
from collections .abc import Sequence
7
7
from dataclasses import dataclass
8
- from keyword import kwlist
9
8
from pathlib import Path
10
9
from textwrap import indent
11
10
from tokenize import COMMENT as COMMENT_TOKEN
22
21
23
22
@click .command ()
24
23
@click .argument ("paths" , nargs = - 1 , type = click .Path (exists = True ))
25
- def update_html_usages (paths : list [str ]) -> None :
24
+ def rewrite_key_declarations (paths : list [str ]) -> None :
26
25
"""Rewrite files under the given paths using the new html element API.
27
26
28
27
The old API required users to pass a dictionary of attributes to html element
@@ -64,9 +63,21 @@ def update_html_usages(paths: list[str]) -> None:
64
63
def generate_rewrite (file : Path , source : str ) -> str | None :
65
64
tree = ast .parse (source )
66
65
66
+ changed = find_nodes_to_change (tree )
67
+ if not changed :
68
+ log_could_not_rewrite (file , tree )
69
+ return None
70
+
71
+ new = rewrite_changed_nodes (file , source , tree , changed )
72
+ log_could_not_rewrite (file , ast .parse (new ))
73
+
74
+ return new
75
+
76
+
77
+ def find_nodes_to_change (tree : ast .AST ) -> list [Sequence [ast .AST ]]:
67
78
changed : list [Sequence [ast .AST ]] = []
68
79
for parents , node in walk_with_parent (tree ):
69
- if not isinstance (node , ast .Call ):
80
+ if not ( isinstance (node , ast .Call ) and node . keywords ):
70
81
continue
71
82
72
83
func = node .func
@@ -77,34 +88,62 @@ def generate_rewrite(file: Path, source: str) -> str | None:
77
88
else :
78
89
continue
79
90
91
+ for kw in list (node .keywords ):
92
+ if kw .arg == "key" :
93
+ break
94
+ else :
95
+ continue
96
+
97
+ maybe_attr_dict_node = None
80
98
if name == "vdom" :
81
- if len (node .args ) < 2 :
82
- continue
83
- maybe_attr_dict_node = node .args [1 ]
84
- # remove attr dict from new args
85
- new_args = node .args [:1 ] + node .args [2 :]
99
+ if len (node .args ) == 1 :
100
+ # vdom("tag") need to add attr dict
101
+ maybe_attr_dict_node = ast .Dict (keys = [], values = [])
102
+ node .args .append (maybe_attr_dict_node )
103
+ elif isinstance (node .args [1 ], (ast .Constant , ast .JoinedStr )):
104
+ maybe_attr_dict_node = ast .Dict (keys = [], values = [])
105
+ node .args .insert (1 , maybe_attr_dict_node )
106
+ elif len (node .args ) >= 2 :
107
+ maybe_attr_dict_node = node .args [1 ]
86
108
elif hasattr (html , name ):
87
109
if len (node .args ) == 0 :
88
- continue
89
- maybe_attr_dict_node = node .args [0 ]
90
- # remove attr dict from new args
91
- new_args = node .args [1 :]
110
+ # vdom("tag") need to add attr dict
111
+ maybe_attr_dict_node = ast .Dict (keys = [], values = [])
112
+ node .args .append (maybe_attr_dict_node )
113
+ elif isinstance (node .args [0 ], (ast .Constant , ast .JoinedStr )):
114
+ maybe_attr_dict_node = ast .Dict (keys = [], values = [])
115
+ node .args .insert (0 , maybe_attr_dict_node )
116
+ else :
117
+ maybe_attr_dict_node = node .args [0 ]
118
+
119
+ if not maybe_attr_dict_node :
120
+ continue
121
+
122
+ if isinstance (maybe_attr_dict_node , ast .Dict ):
123
+ maybe_attr_dict_node .keys .append (ast .Constant ("key" ))
124
+ maybe_attr_dict_node .values .append (kw .value )
125
+ elif (
126
+ isinstance (maybe_attr_dict_node , ast .Call )
127
+ and isinstance (maybe_attr_dict_node .func , ast .Name )
128
+ and maybe_attr_dict_node .func .id == "dict"
129
+ and isinstance (maybe_attr_dict_node .func .ctx , ast .Load )
130
+ ):
131
+ maybe_attr_dict_node .keywords .append (ast .keyword (arg = "key" , value = kw .value ))
92
132
else :
93
133
continue
94
134
95
- new_keyword_info = extract_keywords (maybe_attr_dict_node )
96
- if new_keyword_info is not None :
97
- if new_keyword_info .replace :
98
- node .keywords = new_keyword_info .keywords
99
- else :
100
- node .keywords .extend (new_keyword_info .keywords )
135
+ node .keywords .remove (kw )
136
+ changed .append ((node , * parents ))
101
137
102
- node .args = new_args
103
- changed .append ((node , * parents ))
138
+ return changed
104
139
105
- if not changed :
106
- return None
107
140
141
+ def rewrite_changed_nodes (
142
+ file : str ,
143
+ source : str ,
144
+ tree : ast .AST ,
145
+ changed : list [Sequence [ast .AST ]],
146
+ ) -> str :
108
147
ast .fix_missing_locations (tree )
109
148
110
149
lines = source .split ("\n " )
@@ -171,38 +210,25 @@ def generate_rewrite(file: Path, source: str) -> str | None:
171
210
return "\n " .join (lines )
172
211
173
212
174
- def extract_keywords (node : ast .AST ) -> KeywordInfo | None :
175
- if isinstance (node , ast .Dict ):
176
- keywords : list [ast .keyword ] = []
177
- for k , v in zip (node .keys , node .values ):
178
- if isinstance (k , ast .Constant ) and isinstance (k .value , str ):
179
- if k .value == "tagName" :
180
- # this is a vdom dict declaration
181
- return None
182
- keywords .append (ast .keyword (arg = conv_attr_name (k .value ), value = v ))
183
- else :
184
- return KeywordInfo (
185
- replace = True ,
186
- keywords = [ast .keyword (arg = None , value = node )],
187
- )
188
- return KeywordInfo (replace = False , keywords = keywords )
189
- elif (
190
- isinstance (node , ast .Call )
191
- and isinstance (node .func , ast .Name )
192
- and node .func .id == "dict"
193
- and isinstance (node .func .ctx , ast .Load )
194
- ):
195
- keywords = [ast .keyword (arg = None , value = a ) for a in node .args ]
196
- for kw in node .keywords :
197
- if kw .arg == "tagName" :
198
- # this is a vdom dict declaration
199
- return None
200
- if kw .arg is not None :
201
- keywords .append (ast .keyword (arg = conv_attr_name (kw .arg ), value = kw .value ))
202
- else :
203
- keywords .append (kw )
204
- return KeywordInfo (replace = False , keywords = keywords )
205
- return None
213
+ def log_could_not_rewrite (file : str , tree : ast .AST ) -> None :
214
+ for node in ast .walk (tree ):
215
+ if not (isinstance (node , ast .Call ) and node .keywords ):
216
+ continue
217
+
218
+ func = node .func
219
+ if isinstance (func , ast .Attribute ):
220
+ name = func .attr
221
+ elif isinstance (func , ast .Name ):
222
+ name = func .id
223
+ else :
224
+ continue
225
+
226
+ if (
227
+ name == "vdom"
228
+ or hasattr (html , name )
229
+ and any (kw .arg == "key" for kw in node .keywords )
230
+ ):
231
+ click .echo (f"Unable to rewrite usage at { file } :{ node .lineno } " )
206
232
207
233
208
234
def find_comments (lines : list [str ]) -> list [str ]:
@@ -223,11 +249,6 @@ def walk_with_parent(
223
249
yield from walk_with_parent (child , parents )
224
250
225
251
226
- def conv_attr_name (name : str ) -> str :
227
- new_name = CAMEL_CASE_SUB_PATTERN .sub ("_" , name ).replace ("-" , "_" ).lower ()
228
- return f"{ new_name } _" if new_name in kwlist else new_name
229
-
230
-
231
252
@dataclass
232
253
class KeywordInfo :
233
254
replace : bool
0 commit comments