skylos 1.2.0__py3-none-any.whl → 1.2.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/analyzer.py +40 -5
- {skylos-1.2.0.dist-info → skylos-1.2.1.dist-info}/METADATA +1 -1
- {skylos-1.2.0.dist-info → skylos-1.2.1.dist-info}/RECORD +7 -7
- test/test_analyzer.py +33 -3
- {skylos-1.2.0.dist-info → skylos-1.2.1.dist-info}/WHEEL +0 -0
- {skylos-1.2.0.dist-info → skylos-1.2.1.dist-info}/entry_points.txt +0 -0
- {skylos-1.2.0.dist-info → skylos-1.2.1.dist-info}/top_level.txt +0 -0
skylos/analyzer.py
CHANGED
|
@@ -11,6 +11,10 @@ from skylos.test_aware import TestAwareVisitor
|
|
|
11
11
|
import os
|
|
12
12
|
import traceback
|
|
13
13
|
from skylos.framework_aware import FrameworkAwareVisitor, detect_framework_usage
|
|
14
|
+
import io
|
|
15
|
+
import tokenize
|
|
16
|
+
import re
|
|
17
|
+
import warnings
|
|
14
18
|
|
|
15
19
|
logging.basicConfig(level=logging.INFO,format='%(asctime)s - %(levelname)s - %(message)s')
|
|
16
20
|
logger=logging.getLogger('Skylos')
|
|
@@ -30,12 +34,27 @@ def parse_exclude_folders(user_exclude_folders, use_defaults=True, include_folde
|
|
|
30
34
|
|
|
31
35
|
return exclude_set
|
|
32
36
|
|
|
37
|
+
IGNORE_PATTERNS = (
|
|
38
|
+
r"#\s*pragma:\s*no\s+skylos", ## our own pragma
|
|
39
|
+
r"#\s*pragma:\s*no\s+cover",
|
|
40
|
+
r"#\s*noqa(?:\b|:)", # flake8 style
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
def _collect_ignored_lines(source: str) -> set[int]:
|
|
44
|
+
ignores = set()
|
|
45
|
+
for tok in tokenize.generate_tokens(io.StringIO(source).readline):
|
|
46
|
+
if tok.type == tokenize.COMMENT:
|
|
47
|
+
if any(re.search(pat, tok.string, flags=re.I) for pat in IGNORE_PATTERNS):
|
|
48
|
+
ignores.add(tok.start[0])
|
|
49
|
+
return ignores
|
|
50
|
+
|
|
33
51
|
class Skylos:
|
|
34
52
|
def __init__(self):
|
|
35
53
|
self.defs={}
|
|
36
54
|
self.refs=[]
|
|
37
55
|
self.dynamic=set()
|
|
38
56
|
self.exports=defaultdict(set)
|
|
57
|
+
self.ignored_lines:set[int]=set()
|
|
39
58
|
|
|
40
59
|
def _module(self,root,f):
|
|
41
60
|
p=list(f.relative_to(root).parts)
|
|
@@ -232,7 +251,20 @@ class Skylos:
|
|
|
232
251
|
|
|
233
252
|
for file in files:
|
|
234
253
|
mod = modmap[file]
|
|
235
|
-
|
|
254
|
+
|
|
255
|
+
result = proc_file(file, mod)
|
|
256
|
+
|
|
257
|
+
if len(result) == 7: ##new
|
|
258
|
+
defs, refs, dyn, exports, test_flags, framework_flags, ignored = result
|
|
259
|
+
self.ignored_lines.update(ignored)
|
|
260
|
+
else: ##legacy
|
|
261
|
+
warnings.warn(
|
|
262
|
+
"proc_file() now returns 7 values (added ignored_lines). "
|
|
263
|
+
"The 6-value form is deprecated and will disappear.",
|
|
264
|
+
DeprecationWarning,
|
|
265
|
+
stacklevel=2,
|
|
266
|
+
)
|
|
267
|
+
defs, refs, dyn, exports, test_flags, framework_flags = result
|
|
236
268
|
|
|
237
269
|
# apply penalties while we still have the file-specific flags
|
|
238
270
|
for d in defs:
|
|
@@ -251,8 +283,10 @@ class Skylos:
|
|
|
251
283
|
|
|
252
284
|
unused = []
|
|
253
285
|
for d in self.defs.values():
|
|
254
|
-
if d.references == 0 and not d.is_exported
|
|
255
|
-
|
|
286
|
+
if (d.references == 0 and not d.is_exported
|
|
287
|
+
and d.confidence >= thr
|
|
288
|
+
and d.line not in self.ignored_lines):
|
|
289
|
+
unused.append(d.to_dict())
|
|
256
290
|
|
|
257
291
|
result = {
|
|
258
292
|
"unused_functions": [],
|
|
@@ -288,6 +322,7 @@ def proc_file(file_or_args, mod=None):
|
|
|
288
322
|
|
|
289
323
|
try:
|
|
290
324
|
source = Path(file).read_text(encoding="utf-8")
|
|
325
|
+
ignored = _collect_ignored_lines(source)
|
|
291
326
|
tree = ast.parse(source)
|
|
292
327
|
|
|
293
328
|
tv = TestAwareVisitor(filename=file)
|
|
@@ -299,7 +334,7 @@ def proc_file(file_or_args, mod=None):
|
|
|
299
334
|
v = Visitor(mod, file)
|
|
300
335
|
v.visit(tree)
|
|
301
336
|
|
|
302
|
-
return v.defs, v.refs, v.dyn, v.exports, tv, fv
|
|
337
|
+
return v.defs, v.refs, v.dyn, v.exports, tv, fv, ignored
|
|
303
338
|
except Exception as e:
|
|
304
339
|
logger.error(f"{file}: {e}")
|
|
305
340
|
if os.getenv("SKYLOS_DEBUG"):
|
|
@@ -307,7 +342,7 @@ def proc_file(file_or_args, mod=None):
|
|
|
307
342
|
dummy_visitor = TestAwareVisitor(filename=file)
|
|
308
343
|
dummy_framework_visitor = FrameworkAwareVisitor(filename=file)
|
|
309
344
|
|
|
310
|
-
return [], [], set(), set(), dummy_visitor, dummy_framework_visitor
|
|
345
|
+
return [], [], set(), set(), dummy_visitor, dummy_framework_visitor, set()
|
|
311
346
|
|
|
312
347
|
def analyze(path,conf=60, exclude_folders=None):
|
|
313
348
|
return Skylos().analyze(path,conf, exclude_folders)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
skylos/__init__.py,sha256=ZZWhq0TZ3G-yDi9inbYvMn8OBes-pqo1aYB5EivuxFI,152
|
|
2
|
-
skylos/analyzer.py,sha256=
|
|
2
|
+
skylos/analyzer.py,sha256=zbWheTzHGbgfwbW0Z2euQyca1dDZL4BPscIHvAMVkME,15079
|
|
3
3
|
skylos/cli.py,sha256=6udZY4vLU6PFzVMkaiCLCRcLXgquyHdmf4rIOAPosWc,18266
|
|
4
4
|
skylos/constants.py,sha256=F1kMjuTxfw2hJjd0SeOQcgex5WhHMUhXCzOlVmwuACs,1230
|
|
5
5
|
skylos/framework_aware.py,sha256=p7BGoFnzkpaLJoE3M5qgyIeZvXx17tOkdyXgeqGKmqU,5804
|
|
@@ -9,7 +9,7 @@ test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
9
9
|
test/compare_tools.py,sha256=0g9PDeJlbst-7hOaQzrL4MiJFQKpqM8q8VeBGzpPczg,22738
|
|
10
10
|
test/conftest.py,sha256=57sTF6vLL5U0CVKwGQFJcRs6n7t1dEHIriQoSluNmAI,6418
|
|
11
11
|
test/diagnostics.py,sha256=ExuFOCVpc9BDwNYapU96vj9RXLqxji32Sv6wVF4nJYU,13802
|
|
12
|
-
test/test_analyzer.py,sha256=
|
|
12
|
+
test/test_analyzer.py,sha256=vZ-0cSF_otm7ZYdp6U3a8eWGwRZ6Tf0tVuJxYqcxdqM,22816
|
|
13
13
|
test/test_changes_analyzer.py,sha256=l1hspCFz-sF8gqOilJvntUDuGckwhYsVtzvSRB13ZWw,5085
|
|
14
14
|
test/test_cli.py,sha256=rtdKzanDRJT_F92jKkCQFdhvlfwVJxfXKO8Hrbn-mIg,13180
|
|
15
15
|
test/test_constants.py,sha256=pMuDy0UpC81zENMDCeK6Bqmm3BR_HHZQSlMG-9TgOm0,12602
|
|
@@ -25,8 +25,8 @@ test/sample_repo/sample_repo/commands.py,sha256=b6gQ9YDabt2yyfqGbOpLo0osF7wya8O4
|
|
|
25
25
|
test/sample_repo/sample_repo/models.py,sha256=xXIg3pToEZwKuUCmKX2vTlCF_VeFA0yZlvlBVPIy5Qw,3320
|
|
26
26
|
test/sample_repo/sample_repo/routes.py,sha256=8yITrt55BwS01G7nWdESdx8LuxmReqop1zrGUKPeLi8,2475
|
|
27
27
|
test/sample_repo/sample_repo/utils.py,sha256=S56hEYh8wkzwsD260MvQcmUFOkw2EjFU27nMLFE6G2k,1103
|
|
28
|
-
skylos-1.2.
|
|
29
|
-
skylos-1.2.
|
|
30
|
-
skylos-1.2.
|
|
31
|
-
skylos-1.2.
|
|
32
|
-
skylos-1.2.
|
|
28
|
+
skylos-1.2.1.dist-info/METADATA,sha256=DY1_WEnwZbz1xpWVbV7WwRRmxPqg8pVdS8DlTWs62Sc,224
|
|
29
|
+
skylos-1.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
30
|
+
skylos-1.2.1.dist-info/entry_points.txt,sha256=zzRpN2ByznlQoLeuLolS_TFNYSQxUGBL1EXQsAd6bIA,43
|
|
31
|
+
skylos-1.2.1.dist-info/top_level.txt,sha256=f8GA_7KwfaEopPMP8-EXDQXaqd4IbsOQPakZy01LkdQ,12
|
|
32
|
+
skylos-1.2.1.dist-info/RECORD,,
|
test/test_analyzer.py
CHANGED
|
@@ -451,7 +451,7 @@ class TestClass:
|
|
|
451
451
|
mock_framework_visitor = Mock(spec=FrameworkAwareVisitor)
|
|
452
452
|
mock_framework_visitor_class.return_value = mock_framework_visitor
|
|
453
453
|
|
|
454
|
-
defs, refs, dyn, exports, test_flags, framework_flags = proc_file(f.name, "test_module")
|
|
454
|
+
defs, refs, dyn, exports, test_flags, framework_flags, _ = proc_file(f.name, "test_module")
|
|
455
455
|
|
|
456
456
|
mock_visitor_class.assert_called_once_with("test_module", f.name)
|
|
457
457
|
mock_visitor.visit.assert_called_once()
|
|
@@ -472,7 +472,7 @@ class TestClass:
|
|
|
472
472
|
f.flush()
|
|
473
473
|
|
|
474
474
|
try:
|
|
475
|
-
defs, refs, dyn, exports, test_flags, framework_flags = proc_file(f.name, "test_module")
|
|
475
|
+
defs, refs, dyn, exports, test_flags, framework_flags, _ = proc_file(f.name, "test_module")
|
|
476
476
|
|
|
477
477
|
assert defs == []
|
|
478
478
|
assert refs == []
|
|
@@ -506,7 +506,7 @@ class TestClass:
|
|
|
506
506
|
mock_framework_visitor = Mock(spec=FrameworkAwareVisitor)
|
|
507
507
|
mock_framework_visitor_class.return_value = mock_framework_visitor
|
|
508
508
|
|
|
509
|
-
|
|
509
|
+
defs, refs, dyn, exports, test_flags, framework_flags, _ = proc_file((f.name, "test_module"))
|
|
510
510
|
|
|
511
511
|
mock_visitor_class.assert_called_once_with("test_module", f.name)
|
|
512
512
|
finally:
|
|
@@ -608,5 +608,35 @@ class TestApplyPenalties:
|
|
|
608
608
|
skylos._apply_penalties(mock_def, mock_test_aware_visitor, mock_framework_aware_visitor)
|
|
609
609
|
assert mock_def.confidence == 0
|
|
610
610
|
|
|
611
|
+
class TestIgnorePragmas:
|
|
612
|
+
def test_analyze_respects_ignore_pragmas(self, tmp_path):
|
|
613
|
+
src = tmp_path / "demo.py"
|
|
614
|
+
src.write_text(
|
|
615
|
+
"""
|
|
616
|
+
def used():
|
|
617
|
+
pass
|
|
618
|
+
|
|
619
|
+
def unused_no_ignore():
|
|
620
|
+
pass
|
|
621
|
+
|
|
622
|
+
def unused_ignore(): # pragma: no skylos
|
|
623
|
+
pass
|
|
624
|
+
|
|
625
|
+
used()
|
|
626
|
+
"""
|
|
627
|
+
)
|
|
628
|
+
|
|
629
|
+
result_json = analyze(str(tmp_path), conf=0)
|
|
630
|
+
result = json.loads(result_json)
|
|
631
|
+
|
|
632
|
+
# collect names of functions flagged as unreachable
|
|
633
|
+
unreachable = {item["name"].split(".")[-1] for item in result["unused_functions"]}
|
|
634
|
+
|
|
635
|
+
# expectations
|
|
636
|
+
assert "unused_no_ignore" in unreachable
|
|
637
|
+
assert "unused_ignore" not in unreachable
|
|
638
|
+
assert "used" not in unreachable
|
|
639
|
+
|
|
640
|
+
|
|
611
641
|
if __name__ == "__main__":
|
|
612
642
|
pytest.main([__file__, "-v"])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|