skylos 1.2.2__py3-none-any.whl → 2.0.0__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/framework_aware.py CHANGED
@@ -2,7 +2,6 @@ import ast
2
2
  import fnmatch
3
3
  from pathlib import Path
4
4
 
5
- # decorator patterns for each of the more popular frameworks
6
5
  FRAMEWORK_DECORATORS = [
7
6
  # flask
8
7
  "@app.route", "@app.get", "@app.post", "@app.put", "@app.delete",
@@ -83,14 +82,17 @@ class FrameworkAwareVisitor:
83
82
  def visit_ImportFrom(self, node):
84
83
  if node.module:
85
84
  module_name = node.module.split('.')[0].lower()
85
+
86
86
  if module_name in FRAMEWORK_IMPORTS:
87
87
  self.is_framework_file = True
88
88
  self.detected_frameworks.add(module_name)
89
+
89
90
  self.generic_visit(node)
90
91
 
91
92
  def visit_FunctionDef(self, node):
92
93
  for deco in node.decorator_list:
93
94
  decorator_str = self._normalize_decorator(deco)
95
+
94
96
  if self._matches_framework_pattern(decorator_str, FRAMEWORK_DECORATORS):
95
97
  self.framework_decorated_lines.add(node.lineno)
96
98
  self.is_framework_file = True
@@ -116,13 +118,17 @@ class FrameworkAwareVisitor:
116
118
  def _normalize_decorator(self, decorator_node):
117
119
  if isinstance(decorator_node, ast.Name):
118
120
  return f"@{decorator_node.id}"
121
+
119
122
  elif isinstance(decorator_node, ast.Attribute):
120
123
  if isinstance(decorator_node.value, ast.Name):
121
124
  return f"@{decorator_node.value.id}.{decorator_node.attr}"
125
+
122
126
  else:
123
127
  return f"@{decorator_node.attr}"
128
+
124
129
  elif isinstance(decorator_node, ast.Call):
125
130
  return self._normalize_decorator(decorator_node.func)
131
+
126
132
  return "@unknown"
127
133
 
128
134
  def _matches_framework_pattern(self, text, patterns):
@@ -141,7 +147,7 @@ class FrameworkAwareVisitor:
141
147
  except:
142
148
  pass
143
149
 
144
- def detect_framework_usage(definition, decorator_nodes=None, visitor=None):
150
+ def detect_framework_usage(definition, visitor=None):
145
151
  if not visitor:
146
152
  return None
147
153
 
skylos/server.py ADDED
@@ -0,0 +1,560 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Skylos Web Server
4
+ Serves the frontend and provides API to analyze projects using skylos
5
+ """
6
+
7
+ from flask import Flask, request, jsonify
8
+ from flask_cors import CORS
9
+ import skylos
10
+ import json
11
+ import os
12
+ import webbrowser
13
+ from threading import Timer
14
+
15
+ app = Flask(__name__)
16
+ CORS(app)
17
+
18
+ @app.route('/')
19
+ def serve_frontend():
20
+ """Serve the frontend HTML"""
21
+ return """<!DOCTYPE html>
22
+ <html lang="en">
23
+ <head>
24
+ <meta charset="UTF-8">
25
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
26
+ <title>Skylos Dead Code Analyzer</title>
27
+ <style>
28
+ * {
29
+ margin: 0;
30
+ padding: 0;
31
+ box-sizing: border-box;
32
+ }
33
+
34
+ body {
35
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', sans-serif;
36
+ background: #000000;
37
+ color: #ffffff;
38
+ min-height: 100vh;
39
+ padding: 20px;
40
+ }
41
+
42
+ .container {
43
+ max-width: 1400px;
44
+ margin: 0 auto;
45
+ }
46
+
47
+ .header {
48
+ text-align: center;
49
+ margin-bottom: 40px;
50
+ }
51
+
52
+ .header h1 {
53
+ font-size: 3rem;
54
+ margin-bottom: 10px;
55
+ color: #ffffff;
56
+ font-weight: 300;
57
+ letter-spacing: -1px;
58
+ }
59
+
60
+ .header p {
61
+ color: #888888;
62
+ font-size: 1.1rem;
63
+ margin-top: 10px;
64
+ font-weight: 400;
65
+ }
66
+
67
+ .controls {
68
+ background: #111111;
69
+ border: 1px solid #333333;
70
+ border-radius: 12px;
71
+ padding: 32px;
72
+ margin-bottom: 32px;
73
+ }
74
+
75
+ .folder-input {
76
+ margin-bottom: 25px;
77
+ }
78
+
79
+ .folder-input label {
80
+ display: block;
81
+ font-weight: bold;
82
+ margin-bottom: 10px;
83
+ color: #ffffff;
84
+ }
85
+
86
+ .folder-input input {
87
+ width: 100%;
88
+ padding: 16px;
89
+ background: #222222;
90
+ color: #ffffff;
91
+ border: 1px solid #444444;
92
+ border-radius: 8px;
93
+ font-family: inherit;
94
+ font-size: 14px;
95
+ transition: border-color 0.2s ease;
96
+ }
97
+
98
+ .folder-input input:focus {
99
+ outline: none;
100
+ border-color: #ffffff;
101
+ }
102
+
103
+ .folder-input input::placeholder {
104
+ color: #888888;
105
+ }
106
+
107
+ .analyze-btn {
108
+ background: #ffffff;
109
+ color: #000000;
110
+ border: none;
111
+ padding: 16px 32px;
112
+ border-radius: 8px;
113
+ cursor: pointer;
114
+ font-weight: 500;
115
+ font-family: inherit;
116
+ font-size: 14px;
117
+ margin-right: 15px;
118
+ transition: all 0.2s ease;
119
+ }
120
+
121
+ .analyze-btn:hover {
122
+ background: #f0f0f0;
123
+ transform: translateY(-1px);
124
+ }
125
+
126
+ .analyze-btn:disabled {
127
+ background: #666666;
128
+ color: #ffffff;
129
+ cursor: not-allowed;
130
+ transform: none;
131
+ }
132
+
133
+ .confidence-control {
134
+ margin-top: 25px;
135
+ }
136
+
137
+ .confidence-control label {
138
+ display: block;
139
+ font-weight: bold;
140
+ margin-bottom: 10px;
141
+ color: #ffffff;
142
+ }
143
+
144
+ .confidence-slider {
145
+ width: 100%;
146
+ height: 6px;
147
+ background: #333333;
148
+ outline: none;
149
+ border-radius: 3px;
150
+ -webkit-appearance: none;
151
+ }
152
+
153
+ .confidence-slider::-webkit-slider-thumb {
154
+ -webkit-appearance: none;
155
+ appearance: none;
156
+ width: 20px;
157
+ height: 20px;
158
+ border-radius: 50%;
159
+ background: #ffffff;
160
+ cursor: pointer;
161
+ }
162
+
163
+ .confidence-slider::-moz-range-thumb {
164
+ width: 20px;
165
+ height: 20px;
166
+ border-radius: 50%;
167
+ background: #ffffff;
168
+ cursor: pointer;
169
+ border: none;
170
+ }
171
+
172
+ .summary {
173
+ background: #111111;
174
+ border: 1px solid #333333;
175
+ border-radius: 8px;
176
+ padding: 25px;
177
+ margin-bottom: 30px;
178
+ }
179
+
180
+ .summary h2 {
181
+ margin-bottom: 20px;
182
+ color: #ffffff;
183
+ }
184
+
185
+ .summary-grid {
186
+ display: grid;
187
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
188
+ gap: 15px;
189
+ }
190
+
191
+ .summary-item {
192
+ background: #222222;
193
+ border: 1px solid #444444;
194
+ border-radius: 4px;
195
+ padding: 15px;
196
+ text-align: center;
197
+ }
198
+
199
+ .summary-item .count {
200
+ font-size: 2rem;
201
+ font-weight: bold;
202
+ color: #ffffff;
203
+ margin-bottom: 5px;
204
+ }
205
+
206
+ .summary-item .label {
207
+ color: #cccccc;
208
+ text-transform: uppercase;
209
+ font-size: 0.9rem;
210
+ }
211
+
212
+ .results {
213
+ background: #111111;
214
+ border: 1px solid #333333;
215
+ border-radius: 8px;
216
+ padding: 25px;
217
+ }
218
+
219
+ .results h2 {
220
+ margin-bottom: 20px;
221
+ color: #ffffff;
222
+ }
223
+
224
+ .dead-code-list {
225
+ max-height: 600px;
226
+ overflow-y: auto;
227
+ }
228
+
229
+ .dead-code-item {
230
+ background: #222222;
231
+ border: 1px solid #444444;
232
+ border-radius: 4px;
233
+ padding: 15px;
234
+ margin-bottom: 10px;
235
+ display: flex;
236
+ justify-content: space-between;
237
+ align-items: center;
238
+ }
239
+
240
+ .item-details {
241
+ flex-grow: 1;
242
+ }
243
+
244
+ .item-name {
245
+ font-weight: bold;
246
+ color: #ffffff;
247
+ margin-bottom: 5px;
248
+ }
249
+
250
+ .item-location {
251
+ color: #999999;
252
+ font-size: 0.9rem;
253
+ }
254
+
255
+ .item-meta {
256
+ display: flex;
257
+ align-items: center;
258
+ gap: 10px;
259
+ }
260
+
261
+ .item-type {
262
+ background: #ffffff;
263
+ color: #000000;
264
+ padding: 6px 12px;
265
+ border-radius: 6px;
266
+ font-size: 0.8rem;
267
+ font-weight: 500;
268
+ text-transform: uppercase;
269
+ letter-spacing: 0.5px;
270
+ }
271
+
272
+ .item-confidence {
273
+ color: #ffffff;
274
+ font-weight: bold;
275
+ }
276
+
277
+ .no-results {
278
+ text-align: center;
279
+ padding: 40px;
280
+ color: #666666;
281
+ }
282
+
283
+ .loading {
284
+ text-align: center;
285
+ padding: 40px;
286
+ color: #ffffff;
287
+ }
288
+
289
+ .error {
290
+ background: #330000;
291
+ border: 1px solid #660000;
292
+ color: #ff6666;
293
+ padding: 15px;
294
+ border-radius: 4px;
295
+ margin-bottom: 20px;
296
+ }
297
+
298
+ ::-webkit-scrollbar {
299
+ width: 8px;
300
+ }
301
+
302
+ ::-webkit-scrollbar-track {
303
+ background: #222222;
304
+ }
305
+
306
+ ::-webkit-scrollbar-thumb {
307
+ background: #444444;
308
+ border-radius: 4px;
309
+ }
310
+
311
+ ::-webkit-scrollbar-thumb:hover {
312
+ background: #666666;
313
+ }
314
+ </style>
315
+ </head>
316
+ <body>
317
+ <div class="container">
318
+ <div class="header">
319
+ <h1>Skylos Dead Code Analyzer</h1>
320
+ <p>Find and eliminate unused code in your Python projects</p>
321
+ </div>
322
+
323
+ <div class="controls">
324
+ <div class="folder-input">
325
+ <label for="folderPath">Project Path:</label>
326
+ <input type="text" id="folderPath" placeholder="/path/to/your/python/project" value="./">
327
+ </div>
328
+
329
+ <button class="analyze-btn" id="analyzeBtn">Analyze Project</button>
330
+
331
+ <div class="confidence-control">
332
+ <label for="confidenceSlider">Confidence Threshold: <span id="confidenceValue">60</span>%</label>
333
+ <input type="range" id="confidenceSlider" class="confidence-slider"
334
+ min="0" max="100" value="60" step="1">
335
+ </div>
336
+ </div>
337
+
338
+ <div id="errorMessage"></div>
339
+
340
+ <div class="summary">
341
+ <h2>Summary</h2>
342
+ <div class="summary-grid">
343
+ <div class="summary-item">
344
+ <div class="count" id="functionsCount">0</div>
345
+ <div class="label">Unreachable Functions</div>
346
+ </div>
347
+ <div class="summary-item">
348
+ <div class="count" id="importsCount">0</div>
349
+ <div class="label">Unused Imports</div>
350
+ </div>
351
+ <div class="summary-item">
352
+ <div class="count" id="parametersCount">0</div>
353
+ <div class="label">Unused Parameters</div>
354
+ </div>
355
+ <div class="summary-item">
356
+ <div class="count" id="variablesCount">0</div>
357
+ <div class="label">Unused Variables</div>
358
+ </div>
359
+ <div class="summary-item">
360
+ <div class="count" id="classesCount">0</div>
361
+ <div class="label">Unused Classes</div>
362
+ </div>
363
+ </div>
364
+ </div>
365
+
366
+ <div class="results">
367
+ <h2>Dead Code Items</h2>
368
+ <div class="dead-code-list" id="deadCodeList">
369
+ <div class="no-results">
370
+ <p>Enter a project path and click "Analyze Project" to scan for dead code.</p>
371
+ </div>
372
+ </div>
373
+ </div>
374
+ </div>
375
+
376
+ <script>
377
+ let analysisData = null;
378
+ let confidenceThreshold = 60;
379
+
380
+ const slider = document.getElementById('confidenceSlider');
381
+ const confidenceValue = document.getElementById('confidenceValue');
382
+ const analyzeBtn = document.getElementById('analyzeBtn');
383
+ const folderPath = document.getElementById('folderPath');
384
+ const errorMessage = document.getElementById('errorMessage');
385
+
386
+ slider.addEventListener('input', (e) => {
387
+ confidenceThreshold = parseInt(e.target.value);
388
+ confidenceValue.textContent = confidenceThreshold;
389
+ if (analysisData) {
390
+ updateDisplay();
391
+ }
392
+ });
393
+
394
+ analyzeBtn.addEventListener('click', analyzeProject);
395
+
396
+ function showError(message) {
397
+ errorMessage.innerHTML = `<div class="error">${message}</div>`;
398
+ }
399
+
400
+ function clearError() {
401
+ errorMessage.innerHTML = '';
402
+ }
403
+
404
+ async function analyzeProject() {
405
+ const path = folderPath.value.trim();
406
+ if (!path) {
407
+ showError('Please enter a project path');
408
+ return;
409
+ }
410
+
411
+ clearError();
412
+ analyzeBtn.textContent = 'Analyzing...';
413
+ analyzeBtn.disabled = true;
414
+
415
+ document.getElementById('deadCodeList').innerHTML = '<div class="loading">Analyzing project...</div>';
416
+
417
+ try {
418
+ const response = await fetch('/api/analyze', {
419
+ method: 'POST',
420
+ headers: {
421
+ 'Content-Type': 'application/json',
422
+ },
423
+ body: JSON.stringify({
424
+ path: path,
425
+ confidence: confidenceThreshold
426
+ })
427
+ });
428
+
429
+ if (!response.ok) {
430
+ const errorData = await response.json();
431
+ throw new Error(errorData.error || `Analysis failed: ${response.statusText}`);
432
+ }
433
+
434
+ const result = await response.json();
435
+ analysisData = result;
436
+ updateDisplay();
437
+
438
+ } catch (error) {
439
+ showError(`Error: ${error.message}`);
440
+ document.getElementById('deadCodeList').innerHTML = '<div class="no-results"><p>Analysis failed. Check the error message above.</p></div>';
441
+ }
442
+
443
+ analyzeBtn.textContent = 'Analyze Project';
444
+ analyzeBtn.disabled = false;
445
+ }
446
+
447
+ function updateDisplay() {
448
+ if (!analysisData) return;
449
+
450
+ const filteredData = getFilteredData();
451
+ updateSummary(filteredData);
452
+ updateDeadCodeList(filteredData);
453
+ }
454
+
455
+ function getFilteredData() {
456
+ const data = {
457
+ functions: analysisData.unused_functions || [],
458
+ imports: analysisData.unused_imports || [],
459
+ parameters: analysisData.unused_parameters || [],
460
+ variables: analysisData.unused_variables || [],
461
+ classes: analysisData.unused_classes || []
462
+ };
463
+
464
+ // Filter by confidence threshold
465
+ Object.keys(data).forEach(key => {
466
+ data[key] = data[key].filter(item => item.confidence >= confidenceThreshold);
467
+ });
468
+
469
+ return data;
470
+ }
471
+
472
+ function updateSummary(data) {
473
+ document.getElementById('functionsCount').textContent = data.functions.length;
474
+ document.getElementById('importsCount').textContent = data.imports.length;
475
+ document.getElementById('parametersCount').textContent = data.parameters.length;
476
+ document.getElementById('variablesCount').textContent = data.variables.length;
477
+ document.getElementById('classesCount').textContent = data.classes.length;
478
+ }
479
+
480
+ function updateDeadCodeList(data) {
481
+ const listElement = document.getElementById('deadCodeList');
482
+ const allItems = [];
483
+
484
+ // Combine all items with their categories
485
+ Object.keys(data).forEach(category => {
486
+ data[category].forEach(item => {
487
+ allItems.push({
488
+ ...item,
489
+ category: category.slice(0, -1) // Remove 's' from end
490
+ });
491
+ });
492
+ });
493
+
494
+ if (allItems.length === 0) {
495
+ listElement.innerHTML = `
496
+ <div class="no-results">
497
+ <p>No dead code found at confidence level ${confidenceThreshold}%</p>
498
+ </div>
499
+ `;
500
+ return;
501
+ }
502
+
503
+ // Sort by confidence (highest first)
504
+ allItems.sort((a, b) => b.confidence - a.confidence);
505
+
506
+ listElement.innerHTML = allItems.map(item => `
507
+ <div class="dead-code-item">
508
+ <div class="item-details">
509
+ <div class="item-name">${item.name}</div>
510
+ <div class="item-location">${item.file}:${item.line}</div>
511
+ </div>
512
+ <div class="item-meta">
513
+ <span class="item-type">${item.category}</span>
514
+ <span class="item-confidence">${item.confidence}%</span>
515
+ </div>
516
+ </div>
517
+ `).join('');
518
+ }
519
+ </script>
520
+ </body>
521
+ </html>"""
522
+
523
+ @app.route('/api/analyze', methods=['POST'])
524
+ def analyze_project():
525
+ """Analyze a project using skylos"""
526
+ try:
527
+ data = request.json
528
+ path = data.get('path')
529
+ confidence = data.get('confidence', 60)
530
+
531
+ if not path:
532
+ return jsonify({'error': 'Path is required'}), 400
533
+
534
+ if not os.path.exists(path):
535
+ return jsonify({'error': f'Path does not exist: {path}'}), 400
536
+
537
+ # Call your actual skylos analyzer
538
+ result_json = skylos.analyze(path, conf=confidence)
539
+ result = json.loads(result_json)
540
+
541
+ return jsonify(result)
542
+
543
+ except Exception as e:
544
+ return jsonify({'error': str(e)}), 500
545
+
546
+ def start_server():
547
+ """Start the web server"""
548
+ def open_browser():
549
+ webbrowser.open('http://localhost:5090')
550
+
551
+ print(" Starting Skylos Web Interface...")
552
+ print("Opening browser at: http://localhost:5090")
553
+
554
+ # Open browser after a short delay
555
+ Timer(1.5, open_browser).start()
556
+
557
+ app.run(debug=False, host='0.0.0.0', port=5090, use_reloader=False)
558
+
559
+ if __name__ == '__main__':
560
+ start_server()
skylos/test_aware.py CHANGED
@@ -7,7 +7,6 @@ class TestAwareVisitor:
7
7
  self.is_test_file = False
8
8
  self.test_decorated_lines = set()
9
9
 
10
- # mark as test file based on the file path/name NOT imports
11
10
  if filename and TEST_FILE_RE.search(str(filename)):
12
11
  self.is_test_file = True
13
12