skylos 1.0.10__py3-none-any.whl → 2.5.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.
Files changed (53) hide show
  1. skylos/__init__.py +9 -3
  2. skylos/analyzer.py +674 -168
  3. skylos/cfg_visitor.py +60 -0
  4. skylos/cli.py +719 -235
  5. skylos/codemods.py +277 -0
  6. skylos/config.py +50 -0
  7. skylos/constants.py +78 -0
  8. skylos/gatekeeper.py +147 -0
  9. skylos/linter.py +18 -0
  10. skylos/rules/base.py +20 -0
  11. skylos/rules/danger/calls.py +119 -0
  12. skylos/rules/danger/danger.py +157 -0
  13. skylos/rules/danger/danger_cmd/cmd_flow.py +75 -0
  14. skylos/rules/danger/danger_fs/__init__.py +0 -0
  15. skylos/rules/danger/danger_fs/path_flow.py +79 -0
  16. skylos/rules/danger/danger_net/__init__.py +0 -0
  17. skylos/rules/danger/danger_net/ssrf_flow.py +80 -0
  18. skylos/rules/danger/danger_sql/__init__.py +0 -0
  19. skylos/rules/danger/danger_sql/sql_flow.py +245 -0
  20. skylos/rules/danger/danger_sql/sql_raw_flow.py +96 -0
  21. skylos/rules/danger/danger_web/__init__.py +0 -0
  22. skylos/rules/danger/danger_web/xss_flow.py +170 -0
  23. skylos/rules/danger/taint.py +110 -0
  24. skylos/rules/quality/__init__.py +0 -0
  25. skylos/rules/quality/complexity.py +95 -0
  26. skylos/rules/quality/logic.py +96 -0
  27. skylos/rules/quality/nesting.py +101 -0
  28. skylos/rules/quality/structure.py +99 -0
  29. skylos/rules/secrets.py +325 -0
  30. skylos/server.py +554 -0
  31. skylos/visitor.py +502 -90
  32. skylos/visitors/__init__.py +0 -0
  33. skylos/visitors/framework_aware.py +437 -0
  34. skylos/visitors/test_aware.py +74 -0
  35. skylos-2.5.2.dist-info/METADATA +21 -0
  36. skylos-2.5.2.dist-info/RECORD +42 -0
  37. {skylos-1.0.10.dist-info → skylos-2.5.2.dist-info}/WHEEL +1 -1
  38. {skylos-1.0.10.dist-info → skylos-2.5.2.dist-info}/top_level.txt +0 -1
  39. skylos-1.0.10.dist-info/METADATA +0 -8
  40. skylos-1.0.10.dist-info/RECORD +0 -21
  41. test/compare_tools.py +0 -604
  42. test/diagnostics.py +0 -364
  43. test/sample_repo/app.py +0 -13
  44. test/sample_repo/sample_repo/commands.py +0 -81
  45. test/sample_repo/sample_repo/models.py +0 -122
  46. test/sample_repo/sample_repo/routes.py +0 -89
  47. test/sample_repo/sample_repo/utils.py +0 -36
  48. test/test_skylos.py +0 -456
  49. test/test_visitor.py +0 -220
  50. {test → skylos/rules}/__init__.py +0 -0
  51. {test/sample_repo → skylos/rules/danger}/__init__.py +0 -0
  52. {test/sample_repo/sample_repo → skylos/rules/danger/danger_cmd}/__init__.py +0 -0
  53. {skylos-1.0.10.dist-info → skylos-2.5.2.dist-info}/entry_points.txt +0 -0
@@ -1,36 +0,0 @@
1
- import datetime
2
- import re
3
- import random # Unused import
4
-
5
- def get_current_time():
6
- """Get current time as ISO string - used function."""
7
- return datetime.datetime.now().isoformat()
8
-
9
- def format_currency(amount, currency='USD'):
10
- """Format amount as currency string - used function."""
11
- symbols = {'USD': '$', 'EUR': '€', 'GBP': '£'}
12
- symbol = symbols.get(currency, '')
13
- return f"{symbol}{amount:.2f}"
14
-
15
- def generate_random_token():
16
- """Generate random token - unused function."""
17
- import secrets
18
- return secrets.token_hex(16)
19
-
20
- def validate_email(email):
21
- """Validate email format - unused function."""
22
- pattern = r'^[\w\.-]+@[\w\.-]+\.\w+$'
23
- return bool(re.match(pattern, email))
24
-
25
- class DateUtils:
26
- """Date utility class - unused class."""
27
-
28
- @staticmethod
29
- def get_tomorrow():
30
- """Get tomorrow's date - unused method."""
31
- return datetime.datetime.now() + datetime.timedelta(days=1)
32
-
33
- @staticmethod
34
- def format_date(date, format_str='%Y-%m-%d'):
35
- """Format date - unused method."""
36
- return date.strftime(format_str)
test/test_skylos.py DELETED
@@ -1,456 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- import os
4
- import sys
5
- import json
6
- import tempfile
7
- import shutil
8
- from pathlib import Path
9
-
10
- sys.path.insert(0, '/Users/oha/skylos')
11
-
12
- from skylos.analyzer import analyze, Skylos
13
-
14
- class SkylosTest:
15
- def __init__(self):
16
- self.test_dir = None
17
- self.tests_passed = 0
18
- self.tests_failed = 0
19
-
20
- def setup_test_directory(self):
21
- """Create a temporary directory with test Python files"""
22
- self.test_dir = tempfile.mkdtemp(prefix="skylos_test_")
23
- print(f"Creating test directory: {self.test_dir}")
24
-
25
- self.create_test_files()
26
-
27
- def create_test_files(self):
28
- """Create various test Python files"""
29
-
30
- test1_content = '''
31
- import os
32
- import sys # unused import
33
- import json
34
- from pathlib import Path # unused import
35
- from collections import defaultdict
36
-
37
- def used_function():
38
- """This function is used"""
39
- return "used"
40
-
41
- def unused_function():
42
- """This function is not used"""
43
- return "unused"
44
-
45
- def another_used_function():
46
- """Another used function"""
47
- result = used_function()
48
- data = defaultdict(list)
49
- return json.dumps({"result": result})
50
-
51
- class UsedClass:
52
- """This class is used"""
53
- def __init__(self):
54
- self.value = "used"
55
-
56
- def method(self):
57
- return self.value
58
-
59
- class UnusedClass:
60
- """This class is not used"""
61
- def __init__(self):
62
- self.value = "unused"
63
-
64
- # Usage
65
- instance = UsedClass()
66
- result = another_used_function()
67
- '''
68
-
69
- test2_content = '''
70
- import unittest
71
-
72
- class TestClass(unittest.TestCase):
73
- """Test class - test methods should be ignored"""
74
-
75
- def test_something(self):
76
- """Test method - should be ignored"""
77
- pass
78
-
79
- def test_another_thing(self):
80
- """Another test method - should be ignored"""
81
- pass
82
-
83
- def helper_method(self):
84
- """Non-test method in test class"""
85
- pass
86
-
87
- class RegularClass:
88
- """Regular class with magic methods"""
89
-
90
- def __init__(self, value):
91
- self.value = value
92
-
93
- def __str__(self):
94
- """Magic method - should be ignored"""
95
- return str(self.value)
96
-
97
- def __eq__(self, other):
98
- """Magic method - should be ignored"""
99
- return self.value == other.value
100
-
101
- def regular_method(self):
102
- """Regular method"""
103
- return self.value * 2
104
-
105
- def unused_method(self):
106
- """Unused regular method"""
107
- return "unused"
108
-
109
- # Only create instance, don't call regular_method
110
- obj = RegularClass(42)
111
- print(obj) # This calls __str__
112
- '''
113
-
114
- pkg_dir = Path(self.test_dir) / "testpkg"
115
- pkg_dir.mkdir()
116
-
117
- init_content = '''
118
- """Test package init file"""
119
- from .module1 import exported_function
120
- from .module2 import ExportedClass
121
-
122
- def init_function():
123
- """Function in __init__.py - should be considered exported"""
124
- return "from init"
125
-
126
- def _private_init_function():
127
- """Private function in __init__.py"""
128
- return "private"
129
- '''
130
-
131
- module1_content = '''
132
- """Module 1 in test package"""
133
-
134
- def exported_function():
135
- """This function is exported via __init__.py"""
136
- return "exported"
137
-
138
- def non_exported_function():
139
- """This function is not exported"""
140
- return "not exported"
141
-
142
- def _private_function():
143
- """Private function"""
144
- return "private"
145
- '''
146
-
147
- module2_content = '''
148
- """Module 2 in test package"""
149
-
150
- class ExportedClass:
151
- """This class is exported via __init__.py"""
152
-
153
- def __init__(self):
154
- self.value = "exported"
155
-
156
- def method(self):
157
- return self.value
158
-
159
- class NonExportedClass:
160
- """This class is not exported but is used internally"""
161
-
162
- def __init__(self):
163
- self.value = "not exported"
164
-
165
- class TrulyUnusedClass:
166
- """This class is truly unused"""
167
-
168
- def __init__(self):
169
- self.value = "unused"
170
-
171
- def utility_function():
172
- """Utility function used internally"""
173
- return "utility"
174
-
175
- # Internal usage
176
- _internal = NonExportedClass()
177
- result = utility_function()
178
- '''
179
-
180
- test3_content = '''
181
- """Complex scenarios test file"""
182
- import importlib
183
-
184
- # Dynamic import - makes analysis uncertain
185
- module_name = "json"
186
- json_module = importlib.import_module(module_name)
187
-
188
- def function_with_dynamic_usage():
189
- """Function that might be called dynamically"""
190
- return "dynamic"
191
-
192
- def definitely_unused():
193
- """Definitely unused function"""
194
- return "unused"
195
-
196
- class BaseClass:
197
- """Base class"""
198
-
199
- def base_method(self):
200
- return "base"
201
-
202
- class DerivedClass(BaseClass):
203
- """Derived class"""
204
-
205
- def __init__(self):
206
- super().__init__()
207
-
208
- def derived_method(self):
209
- return "derived"
210
-
211
- def unused_derived_method(self):
212
- return "unused derived"
213
-
214
- # Create instance but only use inherited method
215
- derived = DerivedClass()
216
- result = derived.base_method()
217
-
218
- # Simulate dynamic function call without using sys
219
- func_name = "function_with_dynamic_usage"
220
- if hasattr(globals(), func_name):
221
- globals()[func_name]()
222
- '''
223
-
224
- with open(Path(self.test_dir) / "test1.py", "w") as f:
225
- f.write(test1_content)
226
-
227
- with open(Path(self.test_dir) / "test2.py", "w") as f:
228
- f.write(test2_content)
229
-
230
- with open(pkg_dir / "__init__.py", "w") as f:
231
- f.write(init_content)
232
-
233
- with open(pkg_dir / "module1.py", "w") as f:
234
- f.write(module1_content)
235
-
236
- with open(pkg_dir / "module2.py", "w") as f:
237
- f.write(module2_content)
238
-
239
- with open(Path(self.test_dir) / "test3.py", "w") as f:
240
- f.write(test3_content)
241
-
242
- def run_analyzer(self, confidence_threshold=60):
243
- """Run the Skylos analyzer on the test directory"""
244
- print(f"\nRunning Skylos analyzer with confidence threshold: {confidence_threshold}")
245
- try:
246
- result = analyze(self.test_dir, confidence_threshold)
247
- return json.loads(result)
248
- except AttributeError as e:
249
- if "_get_base_classes" in str(e):
250
- print("\n⚠️ ERROR: Missing _get_base_classes method in Skylos class")
251
- print("This is a bug in the analyzer code. Please add the following method to the Skylos class:")
252
- print("""
253
- def _get_base_classes(self, class_name):
254
- \"\"\"Get base classes for a given class name\"\"\"
255
- if class_name not in self.defs:
256
- return []
257
-
258
- class_def = self.defs[class_name]
259
-
260
- # If the class definition has base class information, return it
261
- if hasattr(class_def, 'base_classes'):
262
- return class_def.base_classes
263
-
264
- # For now, return empty list as simplified implementation
265
- return []
266
- """)
267
- print("\nAlternatively, you can comment out the test method detection in _apply_heuristics")
268
- raise
269
- else:
270
- raise
271
-
272
- def assert_contains(self, items, name_pattern, description):
273
- """Helper to check if a pattern exists in the results"""
274
- found = any(item.get('name', '') == name_pattern for item in items)
275
- if found:
276
- print(f"✓ PASS: {description}")
277
- self.tests_passed += 1
278
- else:
279
- print(f"✗ FAIL: {description}")
280
- print(f" Expected to find item with exact name '{name_pattern}' in:")
281
- for item in items:
282
- print(f" - {item.get('name', 'unnamed')}")
283
- self.tests_failed += 1
284
- return found
285
-
286
- def assert_not_contains(self, items, name_pattern, description):
287
- """Helper to check if a pattern does NOT exist in the results"""
288
- found = any(item.get('name', '') == name_pattern for item in items)
289
- if not found:
290
- print(f"✓ PASS: {description}")
291
- self.tests_passed += 1
292
- else:
293
- print(f"✗ FAIL: {description}")
294
- print(f" Expected NOT to find item with exact name '{name_pattern}' but found:")
295
- for item in items:
296
- if item.get('name', '') == name_pattern:
297
- print(f" - {item.get('name', 'unnamed')}")
298
- self.tests_failed += 1
299
- return not found
300
-
301
- def test_basic_unused_detection(self, results):
302
- """Test basic unused function/class/import detection"""
303
- print("\n=== Testing Basic Unused Detection ===")
304
-
305
- unused_functions = results.get('unused_functions', [])
306
- unused_imports = results.get('unused_imports', [])
307
- unused_classes = results.get('unused_classes', [])
308
-
309
- self.assert_contains(unused_functions, 'unused_function',
310
- "Detects unused function")
311
- self.assert_contains(unused_classes, 'UnusedClass',
312
- "Detects unused class")
313
- self.assert_contains(unused_imports, 'sys',
314
- "Detects unused import (sys)")
315
- self.assert_contains(unused_imports, 'Path',
316
- "Detects unused import (pathlib.Path)")
317
-
318
- self.assert_not_contains(unused_functions, 'used_function',
319
- "Does not flag used function")
320
- self.assert_not_contains(unused_functions, 'another_used_function',
321
- "Does not flag another used function")
322
- self.assert_not_contains(unused_classes, 'UsedClass',
323
- "Does not flag used class")
324
- self.assert_not_contains(unused_imports, 'json',
325
- "Does not flag used import (json)")
326
- self.assert_not_contains(unused_imports, 'defaultdict',
327
- "Does not flag used import (defaultdict)")
328
-
329
- def test_magic_and_test_methods(self, results):
330
- """Test that magic methods and test methods are ignored"""
331
- print("\n=== Testing Magic Methods and Test Methods ===")
332
-
333
- unused_functions = results.get('unused_functions', [])
334
-
335
- self.assert_not_contains(unused_functions, '__str__',
336
- "Does not flag magic method __str__")
337
- self.assert_not_contains(unused_functions, '__eq__',
338
- "Does not flag magic method __eq__")
339
- self.assert_not_contains(unused_functions, '__init__',
340
- "Does not flag magic method __init__")
341
-
342
- self.assert_not_contains(unused_functions, 'test_something',
343
- "Does not flag test method")
344
- self.assert_not_contains(unused_functions, 'test_another_thing',
345
- "Does not flag another test method")
346
-
347
- self.assert_contains(unused_functions, 'RegularClass.unused_method',
348
- "Flags unused regular method")
349
-
350
- def test_package_exports(self, results):
351
- """Test package export detection"""
352
- print("\n=== Testing Package Exports ===")
353
-
354
- unused_functions = results.get('unused_functions', [])
355
- unused_classes = results.get('unused_classes', [])
356
-
357
- self.assert_not_contains(unused_functions, 'exported_function',
358
- "Does not flag exported function")
359
- self.assert_not_contains(unused_classes, 'ExportedClass',
360
- "Does not flag exported class")
361
- self.assert_not_contains(unused_functions, 'init_function',
362
- "Does not flag function in __init__.py")
363
-
364
- self.assert_contains(unused_functions, 'non_exported_function',
365
- "Flags non-exported function")
366
- self.assert_contains(unused_classes, 'TrulyUnusedClass',
367
- "Flags non-exported class")
368
-
369
- def test_confidence_threshold(self):
370
- """Test different confidence thresholds"""
371
- print("\n=== Testing Confidence Thresholds ===")
372
-
373
- results_high = self.run_analyzer(90)
374
-
375
- results_low = self.run_analyzer(30)
376
-
377
- high_count = (len(results_high.get('unused_functions', [])) +
378
- len(results_high.get('unused_imports', [])) +
379
- len(results_high.get('unused_classes', [])))
380
-
381
- low_count = (len(results_low.get('unused_functions', [])) +
382
- len(results_low.get('unused_imports', [])) +
383
- len(results_low.get('unused_classes', [])))
384
-
385
- if low_count >= high_count:
386
- print(f"✓ PASS: Lower threshold finds more/equal items ({low_count} vs {high_count})")
387
- self.tests_passed += 1
388
- else:
389
- print(f"✗ FAIL: Lower threshold should find more items ({low_count} vs {high_count})")
390
- self.tests_failed += 1
391
-
392
- def print_detailed_results(self, results):
393
- """Print detailed analysis results"""
394
- print("\n=== Detailed Results ===")
395
-
396
- print(f"\nUnused Functions ({len(results.get('unused_functions', []))}):")
397
- for func in results.get('unused_functions', []):
398
- print(f" - {func.get('name')} at line {func.get('line', '?')} in {func.get('file', '?')}")
399
-
400
- print(f"\nUnused Imports ({len(results.get('unused_imports', []))}):")
401
- for imp in results.get('unused_imports', []):
402
- print(f" - {imp.get('name')} at line {imp.get('line', '?')} in {imp.get('file', '?')}")
403
-
404
- print(f"\nUnused Classes ({len(results.get('unused_classes', []))}):")
405
- for cls in results.get('unused_classes', []):
406
- print(f" - {cls.get('name')} at line {cls.get('line', '?')} in {cls.get('file', '?')}")
407
-
408
- def run_all_tests(self):
409
- """Run all tests"""
410
- try:
411
- print("Starting Skylos Analyzer Tests...")
412
- self.setup_test_directory()
413
-
414
- results = self.run_analyzer()
415
-
416
- self.print_detailed_results(results)
417
-
418
- self.test_basic_unused_detection(results)
419
- self.test_magic_and_test_methods(results)
420
- self.test_package_exports(results)
421
- self.test_confidence_threshold()
422
-
423
- print(f"\n=== Test Summary ===")
424
- print(f"Tests Passed: {self.tests_passed}")
425
- print(f"Tests Failed: {self.tests_failed}")
426
- print(f"Success Rate: {self.tests_passed/(self.tests_passed + self.tests_failed)*100:.1f}%")
427
-
428
- if self.tests_failed == 0:
429
- print("\n🎉 All tests passed!")
430
- else:
431
- print(f"\n⚠️ {self.tests_failed} test(s) failed")
432
-
433
- except AttributeError as e:
434
- if "_get_base_classes" in str(e):
435
- print("\n❌ Cannot continue testing due to missing method in analyzer")
436
- print("Please fix the analyzer code first using the provided fix above.")
437
- return False
438
- else:
439
- raise
440
- except Exception as e:
441
- print(f"\n❌ Unexpected error during testing: {e}")
442
- raise
443
- finally:
444
- if self.test_dir and os.path.exists(self.test_dir):
445
- shutil.rmtree(self.test_dir)
446
- print(f"\nCleaned up test directory: {self.test_dir}")
447
-
448
- return True
449
-
450
- def main():
451
- """Main test runner"""
452
- test_runner = SkylosTest()
453
- test_runner.run_all_tests()
454
-
455
- if __name__ == "__main__":
456
- main()