skylos 2.0.0__py3-none-any.whl → 2.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of skylos might be problematic. Click here for more details.
- skylos/__init__.py +1 -1
- skylos/analyzer.py +48 -29
- skylos/cli.py +26 -79
- skylos/codemods.py +89 -0
- skylos/framework_aware.py +288 -94
- skylos/visitor.py +187 -42
- {skylos-2.0.0.dist-info → skylos-2.1.1.dist-info}/METADATA +4 -1
- {skylos-2.0.0.dist-info → skylos-2.1.1.dist-info}/RECORD +13 -11
- test/test_codemods.py +153 -0
- test/test_framework_aware.py +176 -242
- {skylos-2.0.0.dist-info → skylos-2.1.1.dist-info}/WHEEL +0 -0
- {skylos-2.0.0.dist-info → skylos-2.1.1.dist-info}/entry_points.txt +0 -0
- {skylos-2.0.0.dist-info → skylos-2.1.1.dist-info}/top_level.txt +0 -0
skylos/visitor.py
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
import ast
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
import
|
|
5
|
-
import re, logging, traceback
|
|
6
|
-
DBG = bool(int(os.getenv("SKYLOS_DEBUG", "0")))
|
|
7
|
-
log = logging.getLogger("Skylos")
|
|
8
|
-
|
|
4
|
+
import re
|
|
9
5
|
|
|
10
6
|
PYTHON_BUILTINS={"print", "len", "str", "int", "float", "list", "dict", "set", "tuple", "range", "open",
|
|
11
7
|
"super", "object", "type", "enumerate", "zip", "map", "filter", "sorted", "sum", "min",
|
|
@@ -61,6 +57,9 @@ class Visitor(ast.NodeVisitor):
|
|
|
61
57
|
self.exports= set()
|
|
62
58
|
self.current_function_scope= []
|
|
63
59
|
self.current_function_params= []
|
|
60
|
+
self.local_var_maps = []
|
|
61
|
+
self.in_cst_class = 0
|
|
62
|
+
self.local_type_maps = []
|
|
64
63
|
|
|
65
64
|
def add_def(self, name, t, line):
|
|
66
65
|
if name not in {d.name for d in self.defs}:
|
|
@@ -89,17 +88,10 @@ class Visitor(ast.NodeVisitor):
|
|
|
89
88
|
if not isinstance(annotation_str, str):
|
|
90
89
|
return
|
|
91
90
|
|
|
92
|
-
if DBG:
|
|
93
|
-
log.debug(f"parsing annotation: {annotation_str}")
|
|
94
|
-
|
|
95
|
-
|
|
96
91
|
try:
|
|
97
92
|
parsed = ast.parse(annotation_str, mode="eval")
|
|
98
93
|
self.visit(parsed.body)
|
|
99
94
|
except:
|
|
100
|
-
if DBG:
|
|
101
|
-
if DBG: log.debug("annotation parse failed")
|
|
102
|
-
|
|
103
95
|
for tok in re.findall(r"[A-Za-z_][A-Za-z0-9_]*", annotation_str):
|
|
104
96
|
self.add_ref(tok)
|
|
105
97
|
|
|
@@ -159,7 +151,9 @@ class Visitor(ast.NodeVisitor):
|
|
|
159
151
|
self.add_def(qualified_name,"method"if self.cls else"function",node.lineno)
|
|
160
152
|
|
|
161
153
|
self.current_function_scope.append(node.name)
|
|
162
|
-
|
|
154
|
+
self.local_var_maps.append({})
|
|
155
|
+
self.local_type_maps.append({})
|
|
156
|
+
|
|
163
157
|
old_params = self.current_function_params
|
|
164
158
|
self.current_function_params = []
|
|
165
159
|
|
|
@@ -179,6 +173,8 @@ class Visitor(ast.NodeVisitor):
|
|
|
179
173
|
|
|
180
174
|
self.current_function_scope.pop()
|
|
181
175
|
self.current_function_params = old_params
|
|
176
|
+
self.local_var_maps.pop()
|
|
177
|
+
self.local_type_maps.pop()
|
|
182
178
|
|
|
183
179
|
visit_AsyncFunctionDef= visit_FunctionDef
|
|
184
180
|
|
|
@@ -186,29 +182,86 @@ class Visitor(ast.NodeVisitor):
|
|
|
186
182
|
cname =f"{self.mod}.{node.name}"
|
|
187
183
|
self.add_def(cname, "class",node.lineno)
|
|
188
184
|
|
|
185
|
+
is_cst = False
|
|
186
|
+
|
|
189
187
|
for base in node.bases:
|
|
188
|
+
base_name = ""
|
|
189
|
+
if isinstance(base, ast.Attribute):
|
|
190
|
+
base_name = base.attr
|
|
191
|
+
elif isinstance(base, ast.Name):
|
|
192
|
+
base_name = base.id
|
|
190
193
|
self.visit(base)
|
|
194
|
+
if base_name in {"CSTTransformer", "CSTVisitor"}:
|
|
195
|
+
is_cst = True
|
|
191
196
|
for keyword in node.keywords:
|
|
192
197
|
self.visit(keyword.value)
|
|
193
198
|
for decorator in node.decorator_list:
|
|
194
199
|
self.visit(decorator)
|
|
195
200
|
|
|
196
201
|
prev= self.cls
|
|
202
|
+
if is_cst:
|
|
203
|
+
self.in_cst_class += 1
|
|
197
204
|
|
|
198
205
|
self.cls= node.name
|
|
199
206
|
for b in node.body:
|
|
200
207
|
self.visit(b)
|
|
201
208
|
|
|
202
209
|
self.cls= prev
|
|
210
|
+
if is_cst:
|
|
211
|
+
self.in_cst_class -= 1
|
|
203
212
|
|
|
204
213
|
def visit_AnnAssign(self, node):
|
|
205
214
|
self.visit_annotation(node.annotation)
|
|
206
215
|
if node.value:
|
|
207
216
|
self.visit(node.value)
|
|
208
|
-
|
|
217
|
+
|
|
218
|
+
def _define(t):
|
|
219
|
+
if isinstance(t, ast.Name):
|
|
220
|
+
name_simple = t.id
|
|
221
|
+
scope_parts = [self.mod]
|
|
222
|
+
if self.cls: scope_parts.append(self.cls)
|
|
223
|
+
if self.current_function_scope: scope_parts.extend(self.current_function_scope)
|
|
224
|
+
prefix = '.'.join(filter(None, scope_parts))
|
|
225
|
+
if prefix:
|
|
226
|
+
var_name = f"{prefix}.{name_simple}"
|
|
227
|
+
else:
|
|
228
|
+
var_name = name_simple
|
|
229
|
+
|
|
230
|
+
self.add_def(var_name, "variable", t.lineno)
|
|
231
|
+
if self.current_function_scope and self.local_var_maps:
|
|
232
|
+
self.local_var_maps[-1][name_simple] = var_name
|
|
233
|
+
elif isinstance(t, (ast.Tuple, ast.List)):
|
|
234
|
+
for elt in t.elts:
|
|
235
|
+
_define(elt)
|
|
236
|
+
_define(node.target)
|
|
209
237
|
|
|
210
238
|
def visit_AugAssign(self, node):
|
|
211
|
-
|
|
239
|
+
if isinstance(node.target, ast.Name):
|
|
240
|
+
nm = node.target.id
|
|
241
|
+
if (self.current_function_scope and
|
|
242
|
+
self.local_var_maps and
|
|
243
|
+
self.local_var_maps and
|
|
244
|
+
nm in self.local_var_maps[-1]):
|
|
245
|
+
|
|
246
|
+
self.add_ref(self.local_var_maps[-1][nm])
|
|
247
|
+
|
|
248
|
+
else:
|
|
249
|
+
self.add_ref(self.qual(nm))
|
|
250
|
+
scope_parts = [self.mod]
|
|
251
|
+
if self.cls: scope_parts.append(self.cls)
|
|
252
|
+
|
|
253
|
+
if self.current_function_scope: scope_parts.extend(self.current_function_scope)
|
|
254
|
+
prefix = '.'.join(filter(None, scope_parts))
|
|
255
|
+
if prefix:
|
|
256
|
+
var_name = f"{prefix}.{nm}"
|
|
257
|
+
else:
|
|
258
|
+
var_name = nm
|
|
259
|
+
|
|
260
|
+
self.add_def(var_name, "variable", node.lineno)
|
|
261
|
+
if self.current_function_scope and self.local_var_maps:
|
|
262
|
+
self.local_var_maps[-1][nm] = var_name
|
|
263
|
+
else:
|
|
264
|
+
self.visit(node.target)
|
|
212
265
|
self.visit(node.value)
|
|
213
266
|
|
|
214
267
|
def visit_Subscript(self, node):
|
|
@@ -227,21 +280,27 @@ class Visitor(ast.NodeVisitor):
|
|
|
227
280
|
def process_target_for_def(target_node):
|
|
228
281
|
if isinstance(target_node, ast.Name):
|
|
229
282
|
name_simple = target_node.id
|
|
283
|
+
if (name_simple == "METADATA_DEPENDENCIES"
|
|
284
|
+
and self.cls and self.in_cst_class > 0):
|
|
285
|
+
return
|
|
230
286
|
if name_simple == "__all__" and not self.current_function_scope and not self.cls:
|
|
231
287
|
return
|
|
232
288
|
|
|
233
289
|
scope_parts = [self.mod]
|
|
234
|
-
|
|
235
290
|
if self.cls:
|
|
236
291
|
scope_parts.append(self.cls)
|
|
237
|
-
|
|
238
292
|
if self.current_function_scope:
|
|
239
293
|
scope_parts.extend(self.current_function_scope)
|
|
240
|
-
|
|
294
|
+
|
|
241
295
|
prefix = '.'.join(filter(None, scope_parts))
|
|
242
|
-
|
|
296
|
+
if prefix:
|
|
297
|
+
var_name = f"{prefix}.{name_simple}"
|
|
298
|
+
else:
|
|
299
|
+
var_name = name_simple
|
|
243
300
|
|
|
244
301
|
self.add_def(var_name, "variable", target_node.lineno)
|
|
302
|
+
if self.current_function_scope and self.local_var_maps:
|
|
303
|
+
self.local_var_maps[-1][name_simple] = var_name
|
|
245
304
|
|
|
246
305
|
elif isinstance(target_node, (ast.Tuple, ast.List)):
|
|
247
306
|
for elt in target_node.elts:
|
|
@@ -249,7 +308,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
249
308
|
|
|
250
309
|
for t in node.targets:
|
|
251
310
|
process_target_for_def(t)
|
|
252
|
-
|
|
311
|
+
|
|
253
312
|
for target in node.targets:
|
|
254
313
|
if isinstance(target, ast.Name) and target.id == "__all__":
|
|
255
314
|
if isinstance(node.value, (ast.List, ast.Tuple)):
|
|
@@ -259,12 +318,48 @@ class Visitor(ast.NodeVisitor):
|
|
|
259
318
|
value = elt.value
|
|
260
319
|
elif hasattr(elt, 's') and isinstance(elt.s, str):
|
|
261
320
|
value = elt.s
|
|
262
|
-
|
|
321
|
+
|
|
263
322
|
if value is not None:
|
|
264
|
-
|
|
323
|
+
if self.mod:
|
|
324
|
+
export_name = f"{self.mod}.{value}"
|
|
325
|
+
else:
|
|
326
|
+
export_name = value
|
|
327
|
+
|
|
265
328
|
self.add_ref(export_name)
|
|
266
|
-
self.add_ref(value)
|
|
267
|
-
|
|
329
|
+
self.add_ref(value)
|
|
330
|
+
|
|
331
|
+
try:
|
|
332
|
+
if isinstance(node.value, ast.Call):
|
|
333
|
+
callee = node.value.func
|
|
334
|
+
fqname = None
|
|
335
|
+
|
|
336
|
+
if isinstance(callee, ast.Name):
|
|
337
|
+
fqname = self.alias.get(callee.id, self.qual(callee.id))
|
|
338
|
+
|
|
339
|
+
elif isinstance(callee, ast.Attribute):
|
|
340
|
+
parts = []
|
|
341
|
+
cur = callee
|
|
342
|
+
while isinstance(cur, ast.Attribute):
|
|
343
|
+
parts.append(cur.attr)
|
|
344
|
+
cur = cur.value
|
|
345
|
+
head = None
|
|
346
|
+
if isinstance(cur, ast.Name):
|
|
347
|
+
head = self.alias.get(cur.id, self.qual(cur.id))
|
|
348
|
+
if head:
|
|
349
|
+
fqname = ".".join([head] + list(reversed(parts)))
|
|
350
|
+
|
|
351
|
+
if fqname and self.current_function_scope and self.local_type_maps:
|
|
352
|
+
def _mark_target(t):
|
|
353
|
+
if isinstance(t, ast.Name):
|
|
354
|
+
self.local_type_maps[-1][t.id] = fqname
|
|
355
|
+
elif isinstance(t, (ast.Tuple, ast.List)):
|
|
356
|
+
for elt in t.elts:
|
|
357
|
+
_mark_target(elt)
|
|
358
|
+
for t in node.targets:
|
|
359
|
+
_mark_target(t)
|
|
360
|
+
except Exception:
|
|
361
|
+
pass
|
|
362
|
+
|
|
268
363
|
self.generic_visit(node)
|
|
269
364
|
|
|
270
365
|
def visit_Call(self, node):
|
|
@@ -284,7 +379,10 @@ class Visitor(ast.NodeVisitor):
|
|
|
284
379
|
elif isinstance(node.args[0], ast.Name):
|
|
285
380
|
target_name = node.args[0].id
|
|
286
381
|
if target_name != "self":
|
|
287
|
-
|
|
382
|
+
if self.mod:
|
|
383
|
+
self.dyn.add(self.mod.split(".")[0])
|
|
384
|
+
else:
|
|
385
|
+
self.dyn.add("")
|
|
288
386
|
|
|
289
387
|
elif isinstance(node.func, ast.Name) and node.func.id == "globals":
|
|
290
388
|
parent = getattr(node, 'parent', None)
|
|
@@ -296,33 +394,80 @@ class Visitor(ast.NodeVisitor):
|
|
|
296
394
|
self.add_ref(f"{self.mod}.{func_name}")
|
|
297
395
|
|
|
298
396
|
elif isinstance(node.func, ast.Name) and node.func.id in ("eval", "exec"):
|
|
299
|
-
|
|
397
|
+
root_mod = ""
|
|
398
|
+
if self.mod:
|
|
399
|
+
root_mod = self.mod.split(".")[0]
|
|
400
|
+
self.dyn.add(root_mod)
|
|
300
401
|
|
|
301
402
|
def visit_Name(self, node):
|
|
302
403
|
if isinstance(node.ctx, ast.Load):
|
|
303
404
|
for param_name, param_full_name in self.current_function_params:
|
|
304
405
|
if node.id == param_name:
|
|
305
406
|
self.add_ref(param_full_name)
|
|
306
|
-
|
|
407
|
+
return
|
|
408
|
+
|
|
409
|
+
if (self.current_function_scope and
|
|
410
|
+
self.local_var_maps
|
|
411
|
+
and self.local_var_maps
|
|
412
|
+
and node.id in self.local_var_maps[-1]):
|
|
307
413
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
414
|
+
self.add_ref(self.local_var_maps[-1][node.id])
|
|
415
|
+
return
|
|
416
|
+
|
|
417
|
+
qualified = self.qual(node.id)
|
|
418
|
+
self.add_ref(qualified)
|
|
419
|
+
if node.id in DYNAMIC_PATTERNS:
|
|
420
|
+
self.dyn.add(self.mod.split(".")[0])
|
|
314
421
|
|
|
315
422
|
def visit_Attribute(self, node):
|
|
316
423
|
self.generic_visit(node)
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
self.
|
|
424
|
+
|
|
425
|
+
if not isinstance(node.ctx, ast.Load):
|
|
426
|
+
return
|
|
427
|
+
|
|
428
|
+
if isinstance(node.value, ast.Name):
|
|
429
|
+
base = node.value.id
|
|
430
|
+
|
|
431
|
+
param_hit = None
|
|
432
|
+
for param_name, param_full in self.current_function_params:
|
|
433
|
+
if base == param_name:
|
|
434
|
+
param_hit = (param_name, param_full)
|
|
435
|
+
break
|
|
436
|
+
|
|
437
|
+
if param_hit:
|
|
438
|
+
self.add_ref(param_hit[1])
|
|
439
|
+
|
|
440
|
+
if self.cls and base in {"self", "cls"}:
|
|
441
|
+
if self.mod:
|
|
442
|
+
owner = f"{self.mod}.{self.cls}"
|
|
443
|
+
else:
|
|
444
|
+
owner = self.cls
|
|
445
|
+
|
|
446
|
+
self.add_ref(f"{owner}.{node.attr}")
|
|
447
|
+
return
|
|
448
|
+
|
|
449
|
+
if (self.current_function_scope and
|
|
450
|
+
self.local_type_maps and
|
|
451
|
+
self.local_type_maps[-1].get(base)):
|
|
452
|
+
|
|
453
|
+
self.add_ref(f"{self.local_type_maps[-1][base]}.{node.attr}")
|
|
454
|
+
return
|
|
455
|
+
|
|
456
|
+
self.add_ref(f"{self.qual(base)}.{node.attr}")
|
|
457
|
+
|
|
458
|
+
def visit_NamedExpr(self, node):
|
|
459
|
+
self.visit(node.value)
|
|
460
|
+
if isinstance(node.target, ast.Name):
|
|
461
|
+
nm = node.target.id
|
|
462
|
+
scope_parts = [self.mod]
|
|
463
|
+
if self.cls: scope_parts.append(self.cls)
|
|
464
|
+
if self.current_function_scope: scope_parts.extend(self.current_function_scope)
|
|
465
|
+
prefix = '.'.join(filter(None, scope_parts))
|
|
466
|
+
var_name = f"{prefix}.{nm}" if prefix else nm
|
|
467
|
+
self.add_def(var_name, "variable", node.lineno)
|
|
468
|
+
if self.current_function_scope and self.local_var_maps:
|
|
469
|
+
self.local_var_maps[-1][nm] = var_name
|
|
470
|
+
self.add_ref(var_name)
|
|
326
471
|
|
|
327
472
|
def visit_keyword(self, node):
|
|
328
473
|
self.visit(node.value)
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: skylos
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.1
|
|
4
4
|
Summary: A static analysis tool for Python codebases
|
|
5
5
|
Author-email: oha <aaronoh2015@gmail.com>
|
|
6
6
|
Requires-Python: >=3.9
|
|
7
7
|
Requires-Dist: inquirer>=3.0.0
|
|
8
|
+
Requires-Dist: flask>=2.1.1
|
|
9
|
+
Requires-Dist: flask-cors>=3.0.0
|
|
10
|
+
Requires-Dist: libcst>=1.8.2
|
|
8
11
|
Dynamic: requires-python
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
skylos/__init__.py,sha256=
|
|
2
|
-
skylos/analyzer.py,sha256=
|
|
3
|
-
skylos/cli.py,sha256=
|
|
1
|
+
skylos/__init__.py,sha256=vXBgYBrUqN3o2LxNCrT2pUVGS-Dnnkcakw7jItvQjXA,151
|
|
2
|
+
skylos/analyzer.py,sha256=JoQ_otvLmI5k64y3h11jVZEWuA6u3npY15Nwy1vrGi0,14995
|
|
3
|
+
skylos/cli.py,sha256=bRxhvKkw_VWQVHT6WxKyX9i3qJjGuJJaEJ3QI8FIpZw,15887
|
|
4
|
+
skylos/codemods.py,sha256=A5dNwTJiYtgP3Mv8NQ03etdfi9qNHSECv1GRpLyDCYU,3213
|
|
4
5
|
skylos/constants.py,sha256=vAfKls0OuR5idtXzeMDfYv5-wLtYWDe1NW5TSUJT8RI,1636
|
|
5
|
-
skylos/framework_aware.py,sha256=
|
|
6
|
+
skylos/framework_aware.py,sha256=eX4oU0jQwDWejkkW4kjRNctre27sVLHK1CTDDiqPqRw,13054
|
|
6
7
|
skylos/server.py,sha256=5Rlgy3LdE8I5TWRJJh0n19JqhVYaAOc9fUtRjL-PpX8,16270
|
|
7
8
|
skylos/test_aware.py,sha256=kxYoMG2m02kbMlxtFOM-MWJO8qqxHviP9HgAMbKRfvU,2304
|
|
8
|
-
skylos/visitor.py,sha256=
|
|
9
|
+
skylos/visitor.py,sha256=i-YWVenc8mG0dWzYi945HmktIE2RkgL7rBT7VUnhkLI,18073
|
|
9
10
|
test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
11
|
test/compare_tools.py,sha256=0g9PDeJlbst-7hOaQzrL4MiJFQKpqM8q8VeBGzpPczg,22738
|
|
11
12
|
test/conftest.py,sha256=57sTF6vLL5U0CVKwGQFJcRs6n7t1dEHIriQoSluNmAI,6418
|
|
@@ -13,8 +14,9 @@ test/diagnostics.py,sha256=ExuFOCVpc9BDwNYapU96vj9RXLqxji32Sv6wVF4nJYU,13802
|
|
|
13
14
|
test/test_analyzer.py,sha256=vZ-0cSF_otm7ZYdp6U3a8eWGwRZ6Tf0tVuJxYqcxdqM,22816
|
|
14
15
|
test/test_changes_analyzer.py,sha256=l1hspCFz-sF8gqOilJvntUDuGckwhYsVtzvSRB13ZWw,5085
|
|
15
16
|
test/test_cli.py,sha256=rtdKzanDRJT_F92jKkCQFdhvlfwVJxfXKO8Hrbn-mIg,13180
|
|
17
|
+
test/test_codemods.py,sha256=Tbp9jE95HDl77EenTmyTtB1Sc3L8fwY9xiMNVDN5lxo,4522
|
|
16
18
|
test/test_constants.py,sha256=pMuDy0UpC81zENMDCeK6Bqmm3BR_HHZQSlMG-9TgOm0,12602
|
|
17
|
-
test/test_framework_aware.py,sha256=
|
|
19
|
+
test/test_framework_aware.py,sha256=G9va1dEQ31wsvd4X7ROf_1YhhAQG5CogB7v0hYCojQ8,8802
|
|
18
20
|
test/test_integration.py,sha256=bNKGUe-w0xEZEdnoQNHbssvKMGs9u9fmFQTOz1lX9_k,12398
|
|
19
21
|
test/test_skylos.py,sha256=kz77STrS4k3Eez5RDYwGxOg2WH3e7zNZPUYEaTLbGTs,15608
|
|
20
22
|
test/test_test_aware.py,sha256=VmbR_MQY0m941CAxxux8OxJHIr7l8crfWRouSeBMhIo,9390
|
|
@@ -26,8 +28,8 @@ test/sample_repo/sample_repo/commands.py,sha256=b6gQ9YDabt2yyfqGbOpLo0osF7wya8O4
|
|
|
26
28
|
test/sample_repo/sample_repo/models.py,sha256=xXIg3pToEZwKuUCmKX2vTlCF_VeFA0yZlvlBVPIy5Qw,3320
|
|
27
29
|
test/sample_repo/sample_repo/routes.py,sha256=8yITrt55BwS01G7nWdESdx8LuxmReqop1zrGUKPeLi8,2475
|
|
28
30
|
test/sample_repo/sample_repo/utils.py,sha256=S56hEYh8wkzwsD260MvQcmUFOkw2EjFU27nMLFE6G2k,1103
|
|
29
|
-
skylos-2.
|
|
30
|
-
skylos-2.
|
|
31
|
-
skylos-2.
|
|
32
|
-
skylos-2.
|
|
33
|
-
skylos-2.
|
|
31
|
+
skylos-2.1.1.dist-info/METADATA,sha256=Sodf-ShJV4dS2Y0AC611OAO2WmQhZT6aYIz0w0Df6a8,314
|
|
32
|
+
skylos-2.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
33
|
+
skylos-2.1.1.dist-info/entry_points.txt,sha256=zzRpN2ByznlQoLeuLolS_TFNYSQxUGBL1EXQsAd6bIA,43
|
|
34
|
+
skylos-2.1.1.dist-info/top_level.txt,sha256=f8GA_7KwfaEopPMP8-EXDQXaqd4IbsOQPakZy01LkdQ,12
|
|
35
|
+
skylos-2.1.1.dist-info/RECORD,,
|
test/test_codemods.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import textwrap
|
|
2
|
+
from skylos.codemods import (
|
|
3
|
+
remove_unused_import_cst,
|
|
4
|
+
remove_unused_function_cst,
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
def _line_no(code: str, startswith: str) -> int:
|
|
8
|
+
for i, line in enumerate(code.splitlines(), start=1):
|
|
9
|
+
if line.lstrip().startswith(startswith):
|
|
10
|
+
return i
|
|
11
|
+
raise AssertionError(f"Line starting with {startswith!r} not found")
|
|
12
|
+
|
|
13
|
+
def test_remove_simple_import_entire_line():
|
|
14
|
+
code = "import os\nprint(1)\n"
|
|
15
|
+
ln = _line_no(code, "import os")
|
|
16
|
+
new, changed = remove_unused_import_cst(code, "os", ln)
|
|
17
|
+
assert changed is True
|
|
18
|
+
assert "import os" not in new
|
|
19
|
+
assert "print(1)" in new
|
|
20
|
+
|
|
21
|
+
def test_remove_one_name_from_multi_import():
|
|
22
|
+
code = "import os, sys\n"
|
|
23
|
+
ln = _line_no(code, "import os, sys")
|
|
24
|
+
new, changed = remove_unused_import_cst(code, "os", ln)
|
|
25
|
+
assert changed is True
|
|
26
|
+
assert new.strip() == "import sys"
|
|
27
|
+
|
|
28
|
+
def test_remove_from_import_keeps_other_names():
|
|
29
|
+
code = "from a import b, c\n"
|
|
30
|
+
ln = _line_no(code, "from a import")
|
|
31
|
+
new, changed = remove_unused_import_cst(code, "b", ln)
|
|
32
|
+
assert changed is True
|
|
33
|
+
assert new.strip() == "from a import c"
|
|
34
|
+
|
|
35
|
+
def test_remove_from_import_with_alias_uses_bound_name():
|
|
36
|
+
code = "from a import b as c, d\n"
|
|
37
|
+
ln = _line_no(code, "from a import")
|
|
38
|
+
new, changed = remove_unused_import_cst(code, "c", ln)
|
|
39
|
+
assert changed is True
|
|
40
|
+
assert new.strip() == "from a import d"
|
|
41
|
+
|
|
42
|
+
def test_parenthesized_multiline_from_import_preserves_formatting():
|
|
43
|
+
code = textwrap.dedent(
|
|
44
|
+
"""\
|
|
45
|
+
from x.y import (
|
|
46
|
+
a,
|
|
47
|
+
b, # keep me
|
|
48
|
+
c,
|
|
49
|
+
)
|
|
50
|
+
use = b
|
|
51
|
+
"""
|
|
52
|
+
)
|
|
53
|
+
ln = _line_no(code, "from x.y import")
|
|
54
|
+
new, changed = remove_unused_import_cst(code, "a", ln)
|
|
55
|
+
assert changed is True
|
|
56
|
+
assert "a," not in new
|
|
57
|
+
assert "b, # keep me" in new
|
|
58
|
+
assert "c," in new
|
|
59
|
+
|
|
60
|
+
def test_import_star_is_noop():
|
|
61
|
+
code = "from x import *\n"
|
|
62
|
+
ln = _line_no(code, "from x import *")
|
|
63
|
+
new, changed = remove_unused_import_cst(code, "*", ln)
|
|
64
|
+
assert changed is False
|
|
65
|
+
assert new == code
|
|
66
|
+
|
|
67
|
+
def test_dotted_import_requires_bound_leftmost_segment():
|
|
68
|
+
code = "import pkg.sub\n"
|
|
69
|
+
ln = _line_no(code, "import pkg.sub")
|
|
70
|
+
new, changed = remove_unused_import_cst(code, "pkg", ln)
|
|
71
|
+
assert changed is True
|
|
72
|
+
assert "import pkg.sub" not in new
|
|
73
|
+
|
|
74
|
+
new2, changed2 = remove_unused_import_cst(code, "sub", ln)
|
|
75
|
+
assert changed2 is False
|
|
76
|
+
assert new2 == code
|
|
77
|
+
|
|
78
|
+
def test_import_idempotency():
|
|
79
|
+
code = "import os, sys\n"
|
|
80
|
+
ln = _line_no(code, "import os, sys")
|
|
81
|
+
new, changed = remove_unused_import_cst(code, "os", ln)
|
|
82
|
+
assert changed is True
|
|
83
|
+
new2, changed2 = remove_unused_import_cst(new, "os", ln)
|
|
84
|
+
assert changed2 is False
|
|
85
|
+
assert new2 == new
|
|
86
|
+
|
|
87
|
+
def test_remove_simple_function_block():
|
|
88
|
+
code = textwrap.dedent(
|
|
89
|
+
"""\
|
|
90
|
+
def unused():
|
|
91
|
+
x = 1
|
|
92
|
+
return x
|
|
93
|
+
|
|
94
|
+
def used():
|
|
95
|
+
return 42
|
|
96
|
+
"""
|
|
97
|
+
)
|
|
98
|
+
ln = _line_no(code, "def unused")
|
|
99
|
+
new, changed = remove_unused_function_cst(code, "unused", ln)
|
|
100
|
+
assert changed is True
|
|
101
|
+
assert "def unused" not in new
|
|
102
|
+
assert "def used" in new
|
|
103
|
+
|
|
104
|
+
def test_remove_decorated_function_removes_decorators_too():
|
|
105
|
+
code = textwrap.dedent(
|
|
106
|
+
"""\
|
|
107
|
+
@dec1
|
|
108
|
+
@dec2(arg=1)
|
|
109
|
+
def target():
|
|
110
|
+
return 1
|
|
111
|
+
|
|
112
|
+
def other():
|
|
113
|
+
return 2
|
|
114
|
+
"""
|
|
115
|
+
)
|
|
116
|
+
ln = _line_no(code, "def target")
|
|
117
|
+
new, changed = remove_unused_function_cst(code, "target", ln)
|
|
118
|
+
assert changed is True
|
|
119
|
+
assert "@dec1" not in new and "@dec2" not in new
|
|
120
|
+
assert "def target" not in new
|
|
121
|
+
assert "def other" in new
|
|
122
|
+
|
|
123
|
+
def test_remove_async_function():
|
|
124
|
+
code = textwrap.dedent(
|
|
125
|
+
"""\
|
|
126
|
+
async def coro():
|
|
127
|
+
return 1
|
|
128
|
+
|
|
129
|
+
def ok():
|
|
130
|
+
return 2
|
|
131
|
+
"""
|
|
132
|
+
)
|
|
133
|
+
ln = _line_no(code, "async def coro")
|
|
134
|
+
new, changed = remove_unused_function_cst(code, "coro", ln)
|
|
135
|
+
assert changed is True
|
|
136
|
+
assert "async def coro" not in new
|
|
137
|
+
assert "def ok" in new
|
|
138
|
+
|
|
139
|
+
def test_function_wrong_line_noop():
|
|
140
|
+
code = "def f():\n return 1\n"
|
|
141
|
+
ln = 999
|
|
142
|
+
new, changed = remove_unused_function_cst(code, "f", ln)
|
|
143
|
+
assert changed is False
|
|
144
|
+
assert new == code
|
|
145
|
+
|
|
146
|
+
def test_function_idempotency():
|
|
147
|
+
code = "def g():\n return 1\n"
|
|
148
|
+
ln = _line_no(code, "def g")
|
|
149
|
+
new, changed = remove_unused_function_cst(code, "g", ln)
|
|
150
|
+
assert changed is True
|
|
151
|
+
new2, changed2 = remove_unused_function_cst(new, "g", ln)
|
|
152
|
+
assert changed2 is False
|
|
153
|
+
assert new2 == new
|