skylos 1.2.0__tar.gz → 1.2.2__tar.gz
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-1.2.0 → skylos-1.2.2}/PKG-INFO +1 -1
- {skylos-1.2.0 → skylos-1.2.2}/README.md +2 -0
- {skylos-1.2.0 → skylos-1.2.2}/pyproject.toml +1 -1
- {skylos-1.2.0 → skylos-1.2.2}/setup.py +1 -1
- {skylos-1.2.0 → skylos-1.2.2}/skylos/analyzer.py +42 -4
- {skylos-1.2.0 → skylos-1.2.2}/skylos.egg-info/PKG-INFO +1 -1
- {skylos-1.2.0 → skylos-1.2.2}/test/test_analyzer.py +33 -3
- {skylos-1.2.0 → skylos-1.2.2}/setup.cfg +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/skylos/__init__.py +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/skylos/cli.py +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/skylos/constants.py +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/skylos/framework_aware.py +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/skylos/test_aware.py +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/skylos/visitor.py +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/skylos.egg-info/SOURCES.txt +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/skylos.egg-info/dependency_links.txt +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/skylos.egg-info/entry_points.txt +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/skylos.egg-info/requires.txt +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/skylos.egg-info/top_level.txt +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/test/__init__.py +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/test/compare_tools.py +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/test/conftest.py +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/test/diagnostics.py +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/test/sample_repo/__init__.py +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/test/sample_repo/app.py +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/test/sample_repo/sample_repo/__init__.py +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/test/sample_repo/sample_repo/commands.py +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/test/sample_repo/sample_repo/models.py +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/test/sample_repo/sample_repo/routes.py +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/test/sample_repo/sample_repo/utils.py +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/test/test_changes_analyzer.py +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/test/test_cli.py +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/test/test_constants.py +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/test/test_framework_aware.py +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/test/test_integration.py +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/test/test_skylos.py +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/test/test_test_aware.py +0 -0
- {skylos-1.2.0 → skylos-1.2.2}/test/test_visitor.py +0 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|

|
|
4
4
|

|
|
5
5
|

|
|
6
|
+

|
|
6
7
|
|
|
7
8
|
<div align="center">
|
|
8
9
|
<img src="assets/SKYLOS.png" alt="Skylos Logo" width="200">
|
|
@@ -40,6 +41,7 @@
|
|
|
40
41
|
* **Unused Classes**: Detects classes that are not instantiated or inherited
|
|
41
42
|
* **Unused Imports**: Identifies imports that are not used
|
|
42
43
|
* **Folder Management**: Inclusion/exclusion of directories
|
|
44
|
+
* **Ignore Pragmas**: Skip lines tagged with `# pragma: no skylos`, `# pragma: no cover`, or `# noqa`
|
|
43
45
|
|
|
44
46
|
## Benchmark (You can find this benchmark test in `test` folder)
|
|
45
47
|
|
|
@@ -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,7 +283,12 @@ class Skylos:
|
|
|
251
283
|
|
|
252
284
|
unused = []
|
|
253
285
|
for d in self.defs.values():
|
|
254
|
-
|
|
286
|
+
# skip anything on line that carries ignore pragma
|
|
287
|
+
if d.line in self.ignored_lines:
|
|
288
|
+
continue
|
|
289
|
+
|
|
290
|
+
if (d.references == 0 and not d.is_exported
|
|
291
|
+
and d.confidence >= thr):
|
|
255
292
|
unused.append(d.to_dict())
|
|
256
293
|
|
|
257
294
|
result = {
|
|
@@ -288,6 +325,7 @@ def proc_file(file_or_args, mod=None):
|
|
|
288
325
|
|
|
289
326
|
try:
|
|
290
327
|
source = Path(file).read_text(encoding="utf-8")
|
|
328
|
+
ignored = _collect_ignored_lines(source)
|
|
291
329
|
tree = ast.parse(source)
|
|
292
330
|
|
|
293
331
|
tv = TestAwareVisitor(filename=file)
|
|
@@ -299,7 +337,7 @@ def proc_file(file_or_args, mod=None):
|
|
|
299
337
|
v = Visitor(mod, file)
|
|
300
338
|
v.visit(tree)
|
|
301
339
|
|
|
302
|
-
return v.defs, v.refs, v.dyn, v.exports, tv, fv
|
|
340
|
+
return v.defs, v.refs, v.dyn, v.exports, tv, fv, ignored
|
|
303
341
|
except Exception as e:
|
|
304
342
|
logger.error(f"{file}: {e}")
|
|
305
343
|
if os.getenv("SKYLOS_DEBUG"):
|
|
@@ -307,7 +345,7 @@ def proc_file(file_or_args, mod=None):
|
|
|
307
345
|
dummy_visitor = TestAwareVisitor(filename=file)
|
|
308
346
|
dummy_framework_visitor = FrameworkAwareVisitor(filename=file)
|
|
309
347
|
|
|
310
|
-
return [], [], set(), set(), dummy_visitor, dummy_framework_visitor
|
|
348
|
+
return [], [], set(), set(), dummy_visitor, dummy_framework_visitor, set()
|
|
311
349
|
|
|
312
350
|
def analyze(path,conf=60, exclude_folders=None):
|
|
313
351
|
return Skylos().analyze(path,conf, exclude_folders)
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|