skylos 2.1.0__py3-none-any.whl → 2.1.2__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 +34 -4
- skylos/cli.py +13 -3
- skylos/constants.py +2 -3
- skylos/visitor.py +97 -24
- {skylos-2.1.0.dist-info → skylos-2.1.2.dist-info}/METADATA +2 -2
- {skylos-2.1.0.dist-info → skylos-2.1.2.dist-info}/RECORD +12 -11
- test/test_analyzer.py +6 -43
- test/test_new_behaviours.py +52 -0
- {skylos-2.1.0.dist-info → skylos-2.1.2.dist-info}/WHEEL +0 -0
- {skylos-2.1.0.dist-info → skylos-2.1.2.dist-info}/entry_points.txt +0 -0
- {skylos-2.1.0.dist-info → skylos-2.1.2.dist-info}/top_level.txt +0 -0
skylos/__init__.py
CHANGED
skylos/analyzer.py
CHANGED
|
@@ -155,18 +155,40 @@ class Skylos:
|
|
|
155
155
|
|
|
156
156
|
def _apply_penalties(self, def_obj, visitor, framework):
|
|
157
157
|
confidence=100
|
|
158
|
+
|
|
159
|
+
if getattr(visitor, "ignore_lines", None) and def_obj.line in visitor.ignore_lines:
|
|
160
|
+
def_obj.confidence = 0
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
if def_obj.type == "variable" and def_obj.simple_name == "_":
|
|
164
|
+
def_obj.confidence = 0
|
|
165
|
+
return
|
|
166
|
+
|
|
158
167
|
if def_obj.simple_name.startswith("_") and not def_obj.simple_name.startswith("__"):
|
|
159
168
|
confidence -= PENALTIES["private_name"]
|
|
169
|
+
|
|
160
170
|
if def_obj.simple_name.startswith("__") and def_obj.simple_name.endswith("__"):
|
|
161
171
|
confidence -= PENALTIES["dunder_or_magic"]
|
|
162
|
-
|
|
163
|
-
confidence = 0
|
|
172
|
+
|
|
164
173
|
if def_obj.in_init and def_obj.type in ("function", "class"):
|
|
165
174
|
confidence -= PENALTIES["in_init_file"]
|
|
175
|
+
|
|
166
176
|
if def_obj.name.split(".")[0] in self.dynamic:
|
|
167
177
|
confidence -= PENALTIES["dynamic_module"]
|
|
178
|
+
|
|
168
179
|
if visitor.is_test_file or def_obj.line in visitor.test_decorated_lines:
|
|
169
180
|
confidence -= PENALTIES["test_related"]
|
|
181
|
+
|
|
182
|
+
if def_obj.type == "variable" and getattr(framework, "dataclass_fields", None):
|
|
183
|
+
if def_obj.name in framework.dataclass_fields:
|
|
184
|
+
def_obj.confidence = 0
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
if def_obj.type == "variable":
|
|
188
|
+
fr = getattr(framework, "first_read_lineno", {}).get(def_obj.name)
|
|
189
|
+
if fr is not None and fr >= def_obj.line:
|
|
190
|
+
def_obj.confidence = 0
|
|
191
|
+
return
|
|
170
192
|
|
|
171
193
|
framework_confidence = detect_framework_usage(def_obj, visitor=framework)
|
|
172
194
|
if framework_confidence is not None:
|
|
@@ -262,7 +284,7 @@ class Skylos:
|
|
|
262
284
|
for d in sorted(self.defs.values(), key=def_sort_key):
|
|
263
285
|
if shown >= 50:
|
|
264
286
|
break
|
|
265
|
-
print(f" {d.type
|
|
287
|
+
print(f" type={d.type} refs={d.references} conf={d.confidence} exported={d.is_exported} line={d.line} name={d.name}")
|
|
266
288
|
shown += 1
|
|
267
289
|
|
|
268
290
|
unused = []
|
|
@@ -304,10 +326,13 @@ def proc_file(file_or_args, mod=None):
|
|
|
304
326
|
|
|
305
327
|
try:
|
|
306
328
|
source = Path(file).read_text(encoding="utf-8")
|
|
307
|
-
|
|
329
|
+
ignore_lines = {i for i, line in enumerate(source.splitlines(), start=1)
|
|
330
|
+
if "pragma: no skylos" in line}
|
|
331
|
+
tree = ast.parse(source)
|
|
308
332
|
|
|
309
333
|
tv = TestAwareVisitor(filename=file)
|
|
310
334
|
tv.visit(tree)
|
|
335
|
+
tv.ignore_lines = ignore_lines
|
|
311
336
|
|
|
312
337
|
fv = FrameworkAwareVisitor(filename=file)
|
|
313
338
|
fv.visit(tree)
|
|
@@ -315,12 +340,17 @@ def proc_file(file_or_args, mod=None):
|
|
|
315
340
|
v = Visitor(mod, file)
|
|
316
341
|
v.visit(tree)
|
|
317
342
|
|
|
343
|
+
fv.dataclass_fields = getattr(v, "dataclass_fields", set())
|
|
344
|
+
fv.first_read_lineno = getattr(v, "first_read_lineno", {})
|
|
345
|
+
|
|
318
346
|
return v.defs, v.refs, v.dyn, v.exports, tv, fv
|
|
347
|
+
|
|
319
348
|
except Exception as e:
|
|
320
349
|
logger.error(f"{file}: {e}")
|
|
321
350
|
if os.getenv("SKYLOS_DEBUG"):
|
|
322
351
|
logger.error(traceback.format_exc())
|
|
323
352
|
dummy_visitor = TestAwareVisitor(filename=file)
|
|
353
|
+
dummy_visitor.ignore_lines = set()
|
|
324
354
|
dummy_framework_visitor = FrameworkAwareVisitor(filename=file)
|
|
325
355
|
|
|
326
356
|
return [], [], set(), set(), dummy_visitor, dummy_framework_visitor
|
skylos/cli.py
CHANGED
|
@@ -10,6 +10,7 @@ from skylos.codemods import (
|
|
|
10
10
|
remove_unused_function_cst,
|
|
11
11
|
)
|
|
12
12
|
import pathlib
|
|
13
|
+
import skylos
|
|
13
14
|
|
|
14
15
|
try:
|
|
15
16
|
import inquirer
|
|
@@ -144,7 +145,7 @@ def print_badge(dead_code_count: int, logger):
|
|
|
144
145
|
logger.info(f"")
|
|
145
146
|
logger.info("```")
|
|
146
147
|
|
|
147
|
-
def main()
|
|
148
|
+
def main():
|
|
148
149
|
if len(sys.argv) > 1 and sys.argv[1] == 'run':
|
|
149
150
|
try:
|
|
150
151
|
start_server()
|
|
@@ -158,6 +159,14 @@ def main() -> None:
|
|
|
158
159
|
description="Detect unreachable functions and unused imports in a Python project"
|
|
159
160
|
)
|
|
160
161
|
parser.add_argument("path", help="Path to the Python project")
|
|
162
|
+
|
|
163
|
+
parser.add_argument(
|
|
164
|
+
"--version",
|
|
165
|
+
action="version",
|
|
166
|
+
version=f"skylos {skylos.__version__}",
|
|
167
|
+
help="Show version and exit"
|
|
168
|
+
)
|
|
169
|
+
|
|
161
170
|
parser.add_argument(
|
|
162
171
|
"--json",
|
|
163
172
|
action="store_true",
|
|
@@ -368,6 +377,8 @@ def main() -> None:
|
|
|
368
377
|
for i, item in enumerate(unused_variables, 1):
|
|
369
378
|
logger.info(f"{Colors.GRAY}{i:2d}. {Colors.RESET}{Colors.YELLOW}{item['name']}{Colors.RESET}")
|
|
370
379
|
logger.info(f" {Colors.GRAY}└─ {item['file']}:{item['line']}{Colors.RESET}")
|
|
380
|
+
else:
|
|
381
|
+
logger.info(f"\n{Colors.GREEN}✓ All variables are being used!{Colors.RESET}")
|
|
371
382
|
|
|
372
383
|
if unused_classes:
|
|
373
384
|
logger.info(f"\n{Colors.YELLOW}{Colors.BOLD} - Unused Classes{Colors.RESET}")
|
|
@@ -375,9 +386,8 @@ def main() -> None:
|
|
|
375
386
|
for i, item in enumerate(unused_classes, 1):
|
|
376
387
|
logger.info(f"{Colors.GRAY}{i:2d}. {Colors.RESET}{Colors.YELLOW}{item['name']}{Colors.RESET}")
|
|
377
388
|
logger.info(f" {Colors.GRAY}└─ {item['file']}:{item['line']}{Colors.RESET}")
|
|
378
|
-
|
|
379
389
|
else:
|
|
380
|
-
logger.info(f"\n{Colors.GREEN}✓ All
|
|
390
|
+
logger.info(f"\n{Colors.GREEN}✓ All classes are being used!{Colors.RESET}")
|
|
381
391
|
|
|
382
392
|
dead_code_count = len(unused_functions) + len(unused_imports) + len(unused_variables) + len(unused_classes) + len(unused_parameters)
|
|
383
393
|
|
skylos/constants.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import re
|
|
2
|
-
from pathlib import Path
|
|
3
2
|
|
|
4
3
|
PENALTIES = {
|
|
5
4
|
"private_name": 80,
|
|
@@ -35,10 +34,10 @@ DEFAULT_EXCLUDE_FOLDERS = {
|
|
|
35
34
|
"htmlcov", ".coverage", "build", "dist", "*.egg-info", "venv", ".venv"
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
def is_test_path(p
|
|
37
|
+
def is_test_path(p):
|
|
39
38
|
return bool(TEST_FILE_RE.search(str(p)))
|
|
40
39
|
|
|
41
|
-
def is_framework_path(p
|
|
40
|
+
def is_framework_path(p):
|
|
42
41
|
return bool(FRAMEWORK_FILE_RE.search(str(p)))
|
|
43
42
|
|
|
44
43
|
def parse_exclude_folders(user_exclude_folders= None, use_defaults= True, include_folders= None):
|
skylos/visitor.py
CHANGED
|
@@ -3,13 +3,15 @@ import ast
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
import re
|
|
5
5
|
|
|
6
|
-
PYTHON_BUILTINS={"print", "len", "str", "int", "float", "list", "dict", "set", "tuple", "range", "open",
|
|
6
|
+
PYTHON_BUILTINS={"print", "len", "str", "int", "float", "list", "dict", "set", "tuple", "range", "open", "reversed",
|
|
7
7
|
"super", "object", "type", "enumerate", "zip", "map", "filter", "sorted", "sum", "min",
|
|
8
8
|
"next", "iter", "bytes", "bytearray", "format", "round", "abs", "complex", "hash", "id", "bool", "callable",
|
|
9
9
|
"getattr", "max", "all", "any", "setattr", "hasattr", "isinstance", "globals", "locals",
|
|
10
10
|
"vars", "dir" ,"property", "classmethod", "staticmethod"}
|
|
11
11
|
DYNAMIC_PATTERNS={"getattr", "globals", "eval", "exec"}
|
|
12
12
|
|
|
13
|
+
## "🥚" hi :)
|
|
14
|
+
|
|
13
15
|
class Definition:
|
|
14
16
|
|
|
15
17
|
def __init__(self, name, t, filename, line):
|
|
@@ -60,9 +62,17 @@ class Visitor(ast.NodeVisitor):
|
|
|
60
62
|
self.local_var_maps = []
|
|
61
63
|
self.in_cst_class = 0
|
|
62
64
|
self.local_type_maps = []
|
|
65
|
+
self._dataclass_stack = []
|
|
66
|
+
self.dataclass_fields = set()
|
|
67
|
+
self.first_read_lineno = {}
|
|
63
68
|
|
|
64
69
|
def add_def(self, name, t, line):
|
|
65
|
-
|
|
70
|
+
found = False
|
|
71
|
+
for d in self.defs:
|
|
72
|
+
if d.name == name:
|
|
73
|
+
found = True
|
|
74
|
+
break
|
|
75
|
+
if not found:
|
|
66
76
|
self.defs.append(Definition(name, t, self.file, line))
|
|
67
77
|
|
|
68
78
|
def add_ref(self, name):
|
|
@@ -72,8 +82,23 @@ class Visitor(ast.NodeVisitor):
|
|
|
72
82
|
if name in self.alias:
|
|
73
83
|
return self.alias[name]
|
|
74
84
|
if name in PYTHON_BUILTINS:
|
|
85
|
+
if self.mod:
|
|
86
|
+
mod_candidate = f"{self.mod}.{name}"
|
|
87
|
+
else:
|
|
88
|
+
mod_candidate = name
|
|
89
|
+
if any(d.name == mod_candidate for d in self.defs):
|
|
90
|
+
return mod_candidate
|
|
91
|
+
|
|
92
|
+
if self.mod:
|
|
93
|
+
return f"{self.mod}.{name}"
|
|
94
|
+
else:
|
|
75
95
|
return name
|
|
76
|
-
|
|
96
|
+
|
|
97
|
+
def visit_Global(self, node):
|
|
98
|
+
if self.current_function_scope and self.local_var_maps:
|
|
99
|
+
for name in node.names:
|
|
100
|
+
self.local_var_maps[-1][name] = f"{self.mod}.{name}"
|
|
101
|
+
return
|
|
77
102
|
|
|
78
103
|
def visit_annotation(self, node):
|
|
79
104
|
if node is not None:
|
|
@@ -148,7 +173,11 @@ class Visitor(ast.NodeVisitor):
|
|
|
148
173
|
|
|
149
174
|
qualified_name= ".".join(filter(None, name_parts))
|
|
150
175
|
|
|
151
|
-
|
|
176
|
+
if self.cls:
|
|
177
|
+
def_type = "method"
|
|
178
|
+
else:
|
|
179
|
+
def_type = "function"
|
|
180
|
+
self.add_def(qualified_name, def_type, node.lineno)
|
|
152
181
|
|
|
153
182
|
self.current_function_scope.append(node.name)
|
|
154
183
|
self.local_var_maps.append({})
|
|
@@ -183,19 +212,41 @@ class Visitor(ast.NodeVisitor):
|
|
|
183
212
|
self.add_def(cname, "class",node.lineno)
|
|
184
213
|
|
|
185
214
|
is_cst = False
|
|
186
|
-
|
|
215
|
+
is_dc = False
|
|
216
|
+
|
|
187
217
|
for base in node.bases:
|
|
188
218
|
base_name = ""
|
|
219
|
+
|
|
189
220
|
if isinstance(base, ast.Attribute):
|
|
190
221
|
base_name = base.attr
|
|
222
|
+
|
|
191
223
|
elif isinstance(base, ast.Name):
|
|
192
224
|
base_name = base.id
|
|
193
225
|
self.visit(base)
|
|
226
|
+
|
|
194
227
|
if base_name in {"CSTTransformer", "CSTVisitor"}:
|
|
195
228
|
is_cst = True
|
|
229
|
+
|
|
196
230
|
for keyword in node.keywords:
|
|
197
231
|
self.visit(keyword.value)
|
|
232
|
+
|
|
198
233
|
for decorator in node.decorator_list:
|
|
234
|
+
def _is_dc(dec):
|
|
235
|
+
if isinstance(dec, ast.Call):
|
|
236
|
+
target = dec.func
|
|
237
|
+
else:
|
|
238
|
+
target = dec
|
|
239
|
+
|
|
240
|
+
if isinstance(target, ast.Name):
|
|
241
|
+
return target.id == "dataclass"
|
|
242
|
+
|
|
243
|
+
if isinstance(target, ast.Attribute):
|
|
244
|
+
return target.attr == "dataclass"
|
|
245
|
+
|
|
246
|
+
return False
|
|
247
|
+
|
|
248
|
+
if _is_dc(decorator):
|
|
249
|
+
is_dc = True
|
|
199
250
|
self.visit(decorator)
|
|
200
251
|
|
|
201
252
|
prev= self.cls
|
|
@@ -203,10 +254,13 @@ class Visitor(ast.NodeVisitor):
|
|
|
203
254
|
self.in_cst_class += 1
|
|
204
255
|
|
|
205
256
|
self.cls= node.name
|
|
257
|
+
self._dataclass_stack.append(is_dc)
|
|
206
258
|
for b in node.body:
|
|
207
259
|
self.visit(b)
|
|
208
260
|
|
|
209
261
|
self.cls= prev
|
|
262
|
+
self._dataclass_stack.pop()
|
|
263
|
+
|
|
210
264
|
if is_cst:
|
|
211
265
|
self.in_cst_class -= 1
|
|
212
266
|
|
|
@@ -220,6 +274,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
220
274
|
name_simple = t.id
|
|
221
275
|
scope_parts = [self.mod]
|
|
222
276
|
if self.cls: scope_parts.append(self.cls)
|
|
277
|
+
|
|
223
278
|
if self.current_function_scope: scope_parts.extend(self.current_function_scope)
|
|
224
279
|
prefix = '.'.join(filter(None, scope_parts))
|
|
225
280
|
if prefix:
|
|
@@ -228,9 +283,16 @@ class Visitor(ast.NodeVisitor):
|
|
|
228
283
|
var_name = name_simple
|
|
229
284
|
|
|
230
285
|
self.add_def(var_name, "variable", t.lineno)
|
|
286
|
+
if (self._dataclass_stack and self._dataclass_stack[-1]
|
|
287
|
+
and self.cls
|
|
288
|
+
and not self.current_function_scope):
|
|
289
|
+
self.dataclass_fields.add(var_name)
|
|
290
|
+
|
|
231
291
|
if self.current_function_scope and self.local_var_maps:
|
|
232
292
|
self.local_var_maps[-1][name_simple] = var_name
|
|
293
|
+
|
|
233
294
|
elif isinstance(t, (ast.Tuple, ast.List)):
|
|
295
|
+
|
|
234
296
|
for elt in t.elts:
|
|
235
297
|
_define(elt)
|
|
236
298
|
_define(node.target)
|
|
@@ -243,19 +305,24 @@ class Visitor(ast.NodeVisitor):
|
|
|
243
305
|
self.local_var_maps and
|
|
244
306
|
nm in self.local_var_maps[-1]):
|
|
245
307
|
|
|
246
|
-
self.add_ref(self.local_var_maps[-1][nm])
|
|
308
|
+
# self.add_ref(self.local_var_maps[-1][nm])
|
|
309
|
+
fq = self.local_var_maps[-1][nm]
|
|
310
|
+
self.add_ref(fq)
|
|
311
|
+
var_name = fq
|
|
247
312
|
|
|
248
313
|
else:
|
|
249
314
|
self.add_ref(self.qual(nm))
|
|
250
|
-
|
|
251
|
-
|
|
315
|
+
scope_parts = [self.mod]
|
|
316
|
+
if self.cls:
|
|
317
|
+
scope_parts.append(self.cls)
|
|
252
318
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
319
|
+
if self.current_function_scope:
|
|
320
|
+
scope_parts.extend(self.current_function_scope)
|
|
321
|
+
prefix = '.'.join(filter(None, scope_parts))
|
|
322
|
+
if prefix:
|
|
323
|
+
var_name = f"{prefix}.{nm}"
|
|
324
|
+
else:
|
|
325
|
+
var_name = nm
|
|
259
326
|
|
|
260
327
|
self.add_def(var_name, "variable", node.lineno)
|
|
261
328
|
if self.current_function_scope and self.local_var_maps:
|
|
@@ -292,14 +359,19 @@ class Visitor(ast.NodeVisitor):
|
|
|
292
359
|
if self.current_function_scope:
|
|
293
360
|
scope_parts.extend(self.current_function_scope)
|
|
294
361
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
var_name =
|
|
362
|
+
if (self.current_function_scope and self.local_var_maps
|
|
363
|
+
and name_simple in self.local_var_maps[-1]):
|
|
364
|
+
var_name = self.local_var_maps[-1][name_simple]
|
|
298
365
|
else:
|
|
299
|
-
|
|
366
|
+
prefix = '.'.join(filter(None, scope_parts))
|
|
367
|
+
if prefix:
|
|
368
|
+
var_name = f"{prefix}.{name_simple}"
|
|
369
|
+
else:
|
|
370
|
+
var_name = name_simple
|
|
300
371
|
|
|
301
372
|
self.add_def(var_name, "variable", target_node.lineno)
|
|
302
|
-
if self.current_function_scope and self.local_var_maps
|
|
373
|
+
if (self.current_function_scope and self.local_var_maps
|
|
374
|
+
and name_simple not in self.local_var_maps[-1]):
|
|
303
375
|
self.local_var_maps[-1][name_simple] = var_name
|
|
304
376
|
|
|
305
377
|
elif isinstance(target_node, (ast.Tuple, ast.List)):
|
|
@@ -403,18 +475,19 @@ class Visitor(ast.NodeVisitor):
|
|
|
403
475
|
if isinstance(node.ctx, ast.Load):
|
|
404
476
|
for param_name, param_full_name in self.current_function_params:
|
|
405
477
|
if node.id == param_name:
|
|
478
|
+
self.first_read_lineno.setdefault(param_full_name, node.lineno)
|
|
406
479
|
self.add_ref(param_full_name)
|
|
407
480
|
return
|
|
408
481
|
|
|
409
|
-
if (self.current_function_scope and
|
|
410
|
-
self.local_var_maps
|
|
411
|
-
and self.local_var_maps
|
|
482
|
+
if (self.current_function_scope and self.local_var_maps
|
|
412
483
|
and node.id in self.local_var_maps[-1]):
|
|
413
|
-
|
|
414
|
-
self.
|
|
484
|
+
fq = self.local_var_maps[-1][node.id]
|
|
485
|
+
self.first_read_lineno.setdefault(fq, node.lineno)
|
|
486
|
+
self.add_ref(fq)
|
|
415
487
|
return
|
|
416
488
|
|
|
417
489
|
qualified = self.qual(node.id)
|
|
490
|
+
self.first_read_lineno.setdefault(qualified, node.lineno)
|
|
418
491
|
self.add_ref(qualified)
|
|
419
492
|
if node.id in DYNAMIC_PATTERNS:
|
|
420
493
|
self.dyn.add(self.mod.split(".")[0])
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: skylos
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.2
|
|
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.
|
|
8
|
+
Requires-Dist: flask>=2.1.1
|
|
9
9
|
Requires-Dist: flask-cors>=3.0.0
|
|
10
10
|
Requires-Dist: libcst>=1.8.2
|
|
11
11
|
Dynamic: requires-python
|
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
skylos/__init__.py,sha256=
|
|
2
|
-
skylos/analyzer.py,sha256=
|
|
3
|
-
skylos/cli.py,sha256=
|
|
1
|
+
skylos/__init__.py,sha256=GEEzpCo5orfkiOXwdwQpFZjRldxDjmjHIDt54ROCdKk,151
|
|
2
|
+
skylos/analyzer.py,sha256=xeKF2_Jj9ivmUZwc9hTlI_P62rQR22mMA0WLIjKNayE,15997
|
|
3
|
+
skylos/cli.py,sha256=hT1lWhV6Bzx7M_HOSyrpBaCrDQOSsnA4IHGQgUoAjIk,16166
|
|
4
4
|
skylos/codemods.py,sha256=A5dNwTJiYtgP3Mv8NQ03etdfi9qNHSECv1GRpLyDCYU,3213
|
|
5
|
-
skylos/constants.py,sha256=
|
|
5
|
+
skylos/constants.py,sha256=kU-2FKQAV-ju4rYw62Tw25uRvqauzjZFUqtvGWaI6Es,1571
|
|
6
6
|
skylos/framework_aware.py,sha256=eX4oU0jQwDWejkkW4kjRNctre27sVLHK1CTDDiqPqRw,13054
|
|
7
7
|
skylos/server.py,sha256=5Rlgy3LdE8I5TWRJJh0n19JqhVYaAOc9fUtRjL-PpX8,16270
|
|
8
8
|
skylos/test_aware.py,sha256=kxYoMG2m02kbMlxtFOM-MWJO8qqxHviP9HgAMbKRfvU,2304
|
|
9
|
-
skylos/visitor.py,sha256=
|
|
9
|
+
skylos/visitor.py,sha256=o_2JxjXKXAcWLQr8nmoatGAz2EhBk225qE_piHf3hDg,20458
|
|
10
10
|
test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
11
|
test/compare_tools.py,sha256=0g9PDeJlbst-7hOaQzrL4MiJFQKpqM8q8VeBGzpPczg,22738
|
|
12
12
|
test/conftest.py,sha256=57sTF6vLL5U0CVKwGQFJcRs6n7t1dEHIriQoSluNmAI,6418
|
|
13
13
|
test/diagnostics.py,sha256=ExuFOCVpc9BDwNYapU96vj9RXLqxji32Sv6wVF4nJYU,13802
|
|
14
|
-
test/test_analyzer.py,sha256=
|
|
14
|
+
test/test_analyzer.py,sha256=WhEYSI1atRee2Gqd9-Edw6F-tdRS9DBqS0D0aqK6MKc,21093
|
|
15
15
|
test/test_changes_analyzer.py,sha256=l1hspCFz-sF8gqOilJvntUDuGckwhYsVtzvSRB13ZWw,5085
|
|
16
16
|
test/test_cli.py,sha256=rtdKzanDRJT_F92jKkCQFdhvlfwVJxfXKO8Hrbn-mIg,13180
|
|
17
17
|
test/test_codemods.py,sha256=Tbp9jE95HDl77EenTmyTtB1Sc3L8fwY9xiMNVDN5lxo,4522
|
|
18
18
|
test/test_constants.py,sha256=pMuDy0UpC81zENMDCeK6Bqmm3BR_HHZQSlMG-9TgOm0,12602
|
|
19
19
|
test/test_framework_aware.py,sha256=G9va1dEQ31wsvd4X7ROf_1YhhAQG5CogB7v0hYCojQ8,8802
|
|
20
20
|
test/test_integration.py,sha256=bNKGUe-w0xEZEdnoQNHbssvKMGs9u9fmFQTOz1lX9_k,12398
|
|
21
|
+
test/test_new_behaviours.py,sha256=vAON8rR09RXawZT1knYtZHseyRcECKz5bdOspJoMjUA,1472
|
|
21
22
|
test/test_skylos.py,sha256=kz77STrS4k3Eez5RDYwGxOg2WH3e7zNZPUYEaTLbGTs,15608
|
|
22
23
|
test/test_test_aware.py,sha256=VmbR_MQY0m941CAxxux8OxJHIr7l8crfWRouSeBMhIo,9390
|
|
23
24
|
test/test_visitor.py,sha256=xAbGv-XaozKm_0WJJhr0hMb6mLaJcbPz57G9-SWkxFU,22764
|
|
@@ -28,8 +29,8 @@ test/sample_repo/sample_repo/commands.py,sha256=b6gQ9YDabt2yyfqGbOpLo0osF7wya8O4
|
|
|
28
29
|
test/sample_repo/sample_repo/models.py,sha256=xXIg3pToEZwKuUCmKX2vTlCF_VeFA0yZlvlBVPIy5Qw,3320
|
|
29
30
|
test/sample_repo/sample_repo/routes.py,sha256=8yITrt55BwS01G7nWdESdx8LuxmReqop1zrGUKPeLi8,2475
|
|
30
31
|
test/sample_repo/sample_repo/utils.py,sha256=S56hEYh8wkzwsD260MvQcmUFOkw2EjFU27nMLFE6G2k,1103
|
|
31
|
-
skylos-2.1.
|
|
32
|
-
skylos-2.1.
|
|
33
|
-
skylos-2.1.
|
|
34
|
-
skylos-2.1.
|
|
35
|
-
skylos-2.1.
|
|
32
|
+
skylos-2.1.2.dist-info/METADATA,sha256=8oDAO7pv-fV0lB3JFRiX_SdqOpUJSa-P_ecR71MyLC8,314
|
|
33
|
+
skylos-2.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
34
|
+
skylos-2.1.2.dist-info/entry_points.txt,sha256=zzRpN2ByznlQoLeuLolS_TFNYSQxUGBL1EXQsAd6bIA,43
|
|
35
|
+
skylos-2.1.2.dist-info/top_level.txt,sha256=f8GA_7KwfaEopPMP8-EXDQXaqd4IbsOQPakZy01LkdQ,12
|
|
36
|
+
skylos-2.1.2.dist-info/RECORD,,
|
test/test_analyzer.py
CHANGED
|
@@ -8,11 +8,9 @@ from skylos.test_aware import TestAwareVisitor
|
|
|
8
8
|
from skylos.framework_aware import FrameworkAwareVisitor
|
|
9
9
|
|
|
10
10
|
from skylos.analyzer import (
|
|
11
|
-
Skylos,
|
|
12
|
-
parse_exclude_folders,
|
|
11
|
+
Skylos,
|
|
13
12
|
proc_file,
|
|
14
|
-
analyze
|
|
15
|
-
DEFAULT_EXCLUDE_FOLDERS,
|
|
13
|
+
analyze
|
|
16
14
|
)
|
|
17
15
|
|
|
18
16
|
@pytest.fixture
|
|
@@ -97,40 +95,6 @@ def internal_function():
|
|
|
97
95
|
|
|
98
96
|
yield temp_path
|
|
99
97
|
|
|
100
|
-
class TestParseExcludeFolders:
|
|
101
|
-
|
|
102
|
-
def test_default_exclude_folders_included(self):
|
|
103
|
-
"""default folders are included by default."""
|
|
104
|
-
result = parse_exclude_folders(None, use_defaults=True)
|
|
105
|
-
assert DEFAULT_EXCLUDE_FOLDERS.issubset(result)
|
|
106
|
-
|
|
107
|
-
def test_default_exclude_folders_disabled(self):
|
|
108
|
-
"""default folders can be disabled."""
|
|
109
|
-
result = parse_exclude_folders(None, use_defaults=False)
|
|
110
|
-
assert not DEFAULT_EXCLUDE_FOLDERS.intersection(result)
|
|
111
|
-
|
|
112
|
-
def test_user_exclude_folders_added(self):
|
|
113
|
-
"""user-specified folders are added."""
|
|
114
|
-
user_folders = {"custom_folder", "another_folder"}
|
|
115
|
-
result = parse_exclude_folders(user_folders, use_defaults=True)
|
|
116
|
-
assert user_folders.issubset(result)
|
|
117
|
-
assert DEFAULT_EXCLUDE_FOLDERS.issubset(result)
|
|
118
|
-
|
|
119
|
-
def test_include_folders_override_defaults(self):
|
|
120
|
-
"""include_folders can override defaults."""
|
|
121
|
-
include_folders = {"__pycache__", ".git"}
|
|
122
|
-
result = parse_exclude_folders(None, use_defaults=True, include_folders=include_folders)
|
|
123
|
-
for folder in include_folders:
|
|
124
|
-
assert folder not in result
|
|
125
|
-
|
|
126
|
-
def test_include_folders_override_user_excludes(self):
|
|
127
|
-
"""include_folders can override user excludes."""
|
|
128
|
-
user_excludes = {"custom_folder", "another_folder"}
|
|
129
|
-
include_folders = {"custom_folder"}
|
|
130
|
-
result = parse_exclude_folders(user_excludes, use_defaults=False, include_folders=include_folders)
|
|
131
|
-
assert "custom_folder" not in result
|
|
132
|
-
assert "another_folder" in result
|
|
133
|
-
|
|
134
98
|
class TestSkylos:
|
|
135
99
|
|
|
136
100
|
@pytest.fixture
|
|
@@ -144,7 +108,6 @@ class TestSkylos:
|
|
|
144
108
|
assert isinstance(skylos.exports, defaultdict)
|
|
145
109
|
|
|
146
110
|
def test_module_name_generation(self, skylos):
|
|
147
|
-
"""Test module name generation from file paths."""
|
|
148
111
|
root = Path("/project")
|
|
149
112
|
|
|
150
113
|
# test a regular Python file
|
|
@@ -315,7 +278,7 @@ class TestHeuristics:
|
|
|
315
278
|
|
|
316
279
|
def test_auto_called_methods_get_references(self, skylos_with_class_methods):
|
|
317
280
|
"""auto-called methods get reference counts when class is used."""
|
|
318
|
-
skylos,
|
|
281
|
+
skylos, _, mock_init, mock_enter = skylos_with_class_methods
|
|
319
282
|
|
|
320
283
|
skylos._apply_heuristics()
|
|
321
284
|
|
|
@@ -451,7 +414,7 @@ class TestClass:
|
|
|
451
414
|
mock_framework_visitor = Mock(spec=FrameworkAwareVisitor)
|
|
452
415
|
mock_framework_visitor_class.return_value = mock_framework_visitor
|
|
453
416
|
|
|
454
|
-
defs, refs, dyn, exports, test_flags, framework_flags
|
|
417
|
+
defs, refs, dyn, exports, test_flags, framework_flags = proc_file(f.name, "test_module")
|
|
455
418
|
|
|
456
419
|
mock_visitor_class.assert_called_once_with("test_module", f.name)
|
|
457
420
|
mock_visitor.visit.assert_called_once()
|
|
@@ -472,7 +435,7 @@ class TestClass:
|
|
|
472
435
|
f.flush()
|
|
473
436
|
|
|
474
437
|
try:
|
|
475
|
-
defs, refs, dyn, exports, test_flags, framework_flags
|
|
438
|
+
defs, refs, dyn, exports, test_flags, framework_flags = proc_file(f.name, "test_module")
|
|
476
439
|
|
|
477
440
|
assert defs == []
|
|
478
441
|
assert refs == []
|
|
@@ -506,7 +469,7 @@ class TestClass:
|
|
|
506
469
|
mock_framework_visitor = Mock(spec=FrameworkAwareVisitor)
|
|
507
470
|
mock_framework_visitor_class.return_value = mock_framework_visitor
|
|
508
471
|
|
|
509
|
-
defs, refs, dyn, exports, test_flags, framework_flags
|
|
472
|
+
defs, refs, dyn, exports, test_flags, framework_flags = proc_file((f.name, "test_module"))
|
|
510
473
|
|
|
511
474
|
mock_visitor_class.assert_called_once_with("test_module", f.name)
|
|
512
475
|
finally:
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from skylos.analyzer import analyze
|
|
3
|
+
|
|
4
|
+
def test_dataclass_fields(tmp_path):
|
|
5
|
+
p = tmp_path / "dc.py"
|
|
6
|
+
p.write_text(
|
|
7
|
+
"from dataclasses import dataclass\n"
|
|
8
|
+
"@dataclass\n"
|
|
9
|
+
"class C:\n"
|
|
10
|
+
" x: int = 1\n"
|
|
11
|
+
" y: int = 2\n"
|
|
12
|
+
"def f(c: C):\n"
|
|
13
|
+
" return c.x + c.y\n"
|
|
14
|
+
"f(C())\n"
|
|
15
|
+
)
|
|
16
|
+
result = json.loads(analyze(str(tmp_path), conf=0))
|
|
17
|
+
assert result['unused_variables'] == []
|
|
18
|
+
|
|
19
|
+
def test_dead_store_guard(tmp_path):
|
|
20
|
+
p = tmp_path / "dstore.py"
|
|
21
|
+
p.write_text(
|
|
22
|
+
"lst=[1,2,3]\n"
|
|
23
|
+
"dir=0\n"
|
|
24
|
+
"if 'a' in lst:\n"
|
|
25
|
+
" pass\n"
|
|
26
|
+
"else:\n"
|
|
27
|
+
" dir=1\n"
|
|
28
|
+
"print(dir)\n"
|
|
29
|
+
)
|
|
30
|
+
result = json.loads(analyze(str(tmp_path), conf=0))
|
|
31
|
+
assert result['unused_variables'] == []
|
|
32
|
+
|
|
33
|
+
def test_unused_constant_reported(tmp_path):
|
|
34
|
+
p = tmp_path / "pool.py"
|
|
35
|
+
p.write_text(
|
|
36
|
+
"from concurrent.futures import ProcessPoolExecutor\n"
|
|
37
|
+
"PROCESS_POOL=None\n"
|
|
38
|
+
"NEVER_USED=123\n"
|
|
39
|
+
"def get_pool():\n"
|
|
40
|
+
" global PROCESS_POOL\n"
|
|
41
|
+
" if PROCESS_POOL is None:\n"
|
|
42
|
+
" PROCESS_POOL=ProcessPoolExecutor(max_workers=2)\n"
|
|
43
|
+
" return PROCESS_POOL\n"
|
|
44
|
+
"get_pool()\n"
|
|
45
|
+
)
|
|
46
|
+
result = json.loads(analyze(str(tmp_path), conf=0))
|
|
47
|
+
names = set()
|
|
48
|
+
for v in result['unused_variables']:
|
|
49
|
+
names.add(v['name'])
|
|
50
|
+
|
|
51
|
+
assert 'NEVER_USED' in names
|
|
52
|
+
assert 'PROCESS_POOL' not in names
|
|
File without changes
|
|
File without changes
|
|
File without changes
|