skylos 2.1.1__py3-none-any.whl → 2.2.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 +5 -3
- skylos/analyzer.py +54 -9
- skylos/cli.py +107 -22
- skylos/codemods.py +154 -5
- skylos/constants.py +2 -3
- skylos/rules/__init__.py +0 -0
- skylos/rules/secrets.py +268 -0
- skylos/server.py +1 -12
- skylos/visitor.py +97 -24
- skylos/visitors/__init__.py +0 -0
- {skylos-2.1.1.dist-info → skylos-2.2.2.dist-info}/METADATA +1 -1
- {skylos-2.1.1.dist-info → skylos-2.2.2.dist-info}/RECORD +20 -15
- test/test_analyzer.py +6 -43
- test/test_new_behaviours.py +52 -0
- test/test_secrets.py +179 -0
- /skylos/{framework_aware.py → visitors/framework_aware.py} +0 -0
- /skylos/{test_aware.py → visitors/test_aware.py} +0 -0
- {skylos-2.1.1.dist-info → skylos-2.2.2.dist-info}/WHEEL +0 -0
- {skylos-2.1.1.dist-info → skylos-2.2.2.dist-info}/entry_points.txt +0 -0
- {skylos-2.1.1.dist-info → skylos-2.2.2.dist-info}/top_level.txt +0 -0
skylos/rules/secrets.py
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import re, ast
|
|
3
|
+
from math import log2
|
|
4
|
+
from typing import Dict, Any, Iterable, List, Optional
|
|
5
|
+
|
|
6
|
+
__all__ = ["scan_ctx"]
|
|
7
|
+
|
|
8
|
+
ALLOWED_FILE_SUFFIXES = (".py", ".pyi", ".pyw")
|
|
9
|
+
|
|
10
|
+
PROVIDER_PATTERNS = [
|
|
11
|
+
("github", re.compile(r"(ghp|gho|ghu|ghs|ghr|gpat)_[A-Za-z0-9]{36,}")),
|
|
12
|
+
("gitlab", re.compile(r"glpat-[A-Za-z0-9_-]{20,}")),
|
|
13
|
+
("slack", re.compile(r"xox[abprs]-[A-Za-z0-9-]{10,48}")),
|
|
14
|
+
("stripe", re.compile(r"sk_(live|test)_[A-Za-z0-9]{16,}")),
|
|
15
|
+
("aws_access_key_id", re.compile(r"\b(AKIA|ASIA|AGPA|AIDA|AROA|AIPA)[0-9A-Z]{16}\b")),
|
|
16
|
+
("google_api_key", re.compile(r"\bAIza[0-9A-Za-z\-_]{35}\b")),
|
|
17
|
+
("sendgrid", re.compile(r"\bSG\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}\b")),
|
|
18
|
+
("twilio", re.compile(r"\bSK[0-9a-fA-F]{32}\b")),
|
|
19
|
+
("private_key_block", re.compile(r"-----BEGIN (?:RSA|DSA|EC|OPENSSH|PGP) PRIVATE KEY-----")),
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
GENERIC_VALUE = re.compile(r"""(?ix)
|
|
23
|
+
(?:
|
|
24
|
+
(token|api[_-]?key|secret|password|passwd|pwd|bearer|auth[_-]?token|access[_-]?token)
|
|
25
|
+
\s*[:=]\s*(?P<q>['"])(?P<val>[^'"]{16,})(?P=q)
|
|
26
|
+
)|(?P<bare>[A-Za-z0-9_\-]{24,})
|
|
27
|
+
""")
|
|
28
|
+
|
|
29
|
+
SAFE_TEST_HINTS = {
|
|
30
|
+
"example", "sample", "fake", "placeholder", "dummy", "test_", "_test", "test_test_",
|
|
31
|
+
"changeme", "password", "secret", "not_a_real", "do_not_use",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
IGNORE_DIRECTIVE = "skylos: ignore[SKY-S101]"
|
|
35
|
+
DEFAULT_MIN_ENTROPY = 3.6
|
|
36
|
+
|
|
37
|
+
def _entropy(s):
|
|
38
|
+
if len(s) == 0:
|
|
39
|
+
return 0.0
|
|
40
|
+
|
|
41
|
+
char_counts = {}
|
|
42
|
+
for character in s:
|
|
43
|
+
if character in char_counts:
|
|
44
|
+
char_counts[character] += 1
|
|
45
|
+
else:
|
|
46
|
+
char_counts[character] = 1
|
|
47
|
+
|
|
48
|
+
total_chars = len(s)
|
|
49
|
+
entropy = 0.0
|
|
50
|
+
|
|
51
|
+
for count in char_counts.values():
|
|
52
|
+
probability = count / total_chars
|
|
53
|
+
entropy -= probability * log2(probability)
|
|
54
|
+
|
|
55
|
+
return entropy
|
|
56
|
+
|
|
57
|
+
def _mask(tok):
|
|
58
|
+
token_length = len(tok)
|
|
59
|
+
|
|
60
|
+
if token_length <= 8:
|
|
61
|
+
return "*" * token_length
|
|
62
|
+
|
|
63
|
+
else:
|
|
64
|
+
first_part = tok[:4]
|
|
65
|
+
last_part = tok[-4:]
|
|
66
|
+
return first_part + "…" + last_part
|
|
67
|
+
|
|
68
|
+
def _docstring_lines(tree):
|
|
69
|
+
if tree is None:
|
|
70
|
+
return set()
|
|
71
|
+
|
|
72
|
+
docstring_line_numbers = set()
|
|
73
|
+
|
|
74
|
+
def find_docstring_lines(node):
|
|
75
|
+
if not hasattr(node, "body") or not node.body:
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
first_statement = node.body[0]
|
|
79
|
+
|
|
80
|
+
is_expression = isinstance(first_statement, ast.Expr)
|
|
81
|
+
if not is_expression:
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
value = getattr(first_statement, "value", None)
|
|
85
|
+
if not isinstance(value, ast.Constant):
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
if not isinstance(value.value, str):
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
start_line = getattr(first_statement, "lineno", None)
|
|
92
|
+
end_line = getattr(first_statement, "end_lineno", start_line)
|
|
93
|
+
|
|
94
|
+
if start_line is not None:
|
|
95
|
+
if end_line is None:
|
|
96
|
+
end_line = start_line
|
|
97
|
+
|
|
98
|
+
for line_num in range(start_line, end_line + 1):
|
|
99
|
+
docstring_line_numbers.add(line_num)
|
|
100
|
+
|
|
101
|
+
if isinstance(tree, ast.Module):
|
|
102
|
+
find_docstring_lines(tree)
|
|
103
|
+
|
|
104
|
+
for node in ast.walk(tree):
|
|
105
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
|
106
|
+
find_docstring_lines(node)
|
|
107
|
+
|
|
108
|
+
return docstring_line_numbers
|
|
109
|
+
|
|
110
|
+
def scan_ctx(ctx, *, min_entropy= DEFAULT_MIN_ENTROPY, scan_comments= True,
|
|
111
|
+
scan_docstrings= True, allowlist_patterns= None, ignore_path_substrings= None):
|
|
112
|
+
|
|
113
|
+
rel_path = ctx.get("relpath", "")
|
|
114
|
+
if not rel_path.endswith(ALLOWED_FILE_SUFFIXES):
|
|
115
|
+
return []
|
|
116
|
+
|
|
117
|
+
if ignore_path_substrings:
|
|
118
|
+
for substring in ignore_path_substrings:
|
|
119
|
+
if substring and substring in rel_path:
|
|
120
|
+
return []
|
|
121
|
+
|
|
122
|
+
file_lines = ctx.get("lines") or []
|
|
123
|
+
syntax_tree = ctx.get("tree")
|
|
124
|
+
|
|
125
|
+
allowlist_regexes = []
|
|
126
|
+
if allowlist_patterns:
|
|
127
|
+
for pattern in allowlist_patterns:
|
|
128
|
+
compiled_regex = re.compile(pattern)
|
|
129
|
+
allowlist_regexes.append(compiled_regex)
|
|
130
|
+
|
|
131
|
+
if scan_docstrings:
|
|
132
|
+
docstring_lines = set()
|
|
133
|
+
else:
|
|
134
|
+
docstring_lines = _docstring_lines(syntax_tree)
|
|
135
|
+
|
|
136
|
+
findings = []
|
|
137
|
+
|
|
138
|
+
for line_number, raw_line in enumerate(file_lines, start=1):
|
|
139
|
+
line_content = raw_line.rstrip("\n")
|
|
140
|
+
|
|
141
|
+
if IGNORE_DIRECTIVE in line_content:
|
|
142
|
+
continue
|
|
143
|
+
|
|
144
|
+
stripped_line = line_content.lstrip()
|
|
145
|
+
if not scan_comments and stripped_line.startswith("#"):
|
|
146
|
+
continue
|
|
147
|
+
|
|
148
|
+
if not scan_docstrings and line_number in docstring_lines:
|
|
149
|
+
continue
|
|
150
|
+
|
|
151
|
+
should_skip_line = False
|
|
152
|
+
for regex_pattern in allowlist_regexes:
|
|
153
|
+
if regex_pattern.search(line_content):
|
|
154
|
+
should_skip_line = True
|
|
155
|
+
break
|
|
156
|
+
|
|
157
|
+
if should_skip_line:
|
|
158
|
+
continue
|
|
159
|
+
|
|
160
|
+
for provider_name, pattern_regex in PROVIDER_PATTERNS:
|
|
161
|
+
pattern_matches = pattern_regex.finditer(line_content)
|
|
162
|
+
|
|
163
|
+
for regex_match in pattern_matches:
|
|
164
|
+
potential_secret = regex_match.group(0)
|
|
165
|
+
|
|
166
|
+
token_lowercase = potential_secret.lower()
|
|
167
|
+
has_safe_hint = False
|
|
168
|
+
|
|
169
|
+
for safe_hint in SAFE_TEST_HINTS:
|
|
170
|
+
if safe_hint in token_lowercase:
|
|
171
|
+
has_safe_hint = True
|
|
172
|
+
break
|
|
173
|
+
|
|
174
|
+
if has_safe_hint:
|
|
175
|
+
continue
|
|
176
|
+
|
|
177
|
+
col_pos = line_content.find(potential_secret)
|
|
178
|
+
|
|
179
|
+
finding = {
|
|
180
|
+
"rule_id": "SKY-S101",
|
|
181
|
+
"severity": "CRITICAL",
|
|
182
|
+
"provider": provider_name,
|
|
183
|
+
"message": f"Potential {provider_name} secret detected",
|
|
184
|
+
"file": rel_path,
|
|
185
|
+
"line": line_number,
|
|
186
|
+
"col": max(0, col_pos),
|
|
187
|
+
"end_col": max(1, col_pos + len(potential_secret)),
|
|
188
|
+
"preview": _mask(potential_secret),
|
|
189
|
+
}
|
|
190
|
+
findings.append(finding)
|
|
191
|
+
|
|
192
|
+
aws_key_indicators = ["AWS_SECRET_ACCESS_KEY", "aws_secret_access_key"]
|
|
193
|
+
line_has_aws_key = False
|
|
194
|
+
|
|
195
|
+
for indicator in aws_key_indicators:
|
|
196
|
+
if indicator in line_content or indicator in line_content.lower():
|
|
197
|
+
line_has_aws_key = True
|
|
198
|
+
break
|
|
199
|
+
|
|
200
|
+
if line_has_aws_key:
|
|
201
|
+
aws_secret_pattern = r"['\"]?([A-Za-z0-9/+=]{40})['\"]?"
|
|
202
|
+
aws_match = re.search(aws_secret_pattern, line_content)
|
|
203
|
+
|
|
204
|
+
if aws_match:
|
|
205
|
+
aws_token = aws_match.group(1)
|
|
206
|
+
tok_entropy = _entropy(aws_token)
|
|
207
|
+
|
|
208
|
+
if tok_entropy >= min_entropy:
|
|
209
|
+
col_pos = line_content.find(aws_token)
|
|
210
|
+
|
|
211
|
+
aws_finding = {
|
|
212
|
+
"rule_id": "SKY-S101",
|
|
213
|
+
"severity": "CRITICAL",
|
|
214
|
+
"provider": "aws_secret_access_key",
|
|
215
|
+
"message": "Potential AWS secret access key detected",
|
|
216
|
+
"file": rel_path,
|
|
217
|
+
"line": line_number,
|
|
218
|
+
"col": max(0, col_pos),
|
|
219
|
+
"end_col": max(1, col_pos + len(aws_token)),
|
|
220
|
+
"preview": _mask(aws_token),
|
|
221
|
+
"entropy": round(tok_entropy, 2),
|
|
222
|
+
}
|
|
223
|
+
findings.append(aws_finding)
|
|
224
|
+
|
|
225
|
+
generic_match = GENERIC_VALUE.search(line_content)
|
|
226
|
+
if generic_match:
|
|
227
|
+
val_group = generic_match.group("val")
|
|
228
|
+
bare_group = generic_match.group("bare")
|
|
229
|
+
|
|
230
|
+
if val_group:
|
|
231
|
+
extracted_token = val_group
|
|
232
|
+
elif bare_group:
|
|
233
|
+
extracted_token = bare_group
|
|
234
|
+
else:
|
|
235
|
+
extracted_token = ""
|
|
236
|
+
|
|
237
|
+
clean_token = extracted_token.strip()
|
|
238
|
+
|
|
239
|
+
if clean_token:
|
|
240
|
+
token_lowercase = clean_token.lower()
|
|
241
|
+
has_safe_hint = False
|
|
242
|
+
|
|
243
|
+
for safe_hint in SAFE_TEST_HINTS:
|
|
244
|
+
if safe_hint in token_lowercase:
|
|
245
|
+
has_safe_hint = True
|
|
246
|
+
break
|
|
247
|
+
|
|
248
|
+
if not has_safe_hint:
|
|
249
|
+
tok_entropy = _entropy(clean_token)
|
|
250
|
+
|
|
251
|
+
if tok_entropy >= min_entropy and len(clean_token) >= 20:
|
|
252
|
+
col_pos = line_content.find(clean_token)
|
|
253
|
+
|
|
254
|
+
generic_finding = {
|
|
255
|
+
"rule_id": "SKY-S101",
|
|
256
|
+
"severity": "CRITICAL",
|
|
257
|
+
"provider": "generic",
|
|
258
|
+
"message": f"High-entropy value detected (entropy={tok_entropy:.2f})",
|
|
259
|
+
"file": rel_path,
|
|
260
|
+
"line": line_number,
|
|
261
|
+
"col": max(0, col_pos),
|
|
262
|
+
"end_col": max(1, col_pos + len(clean_token)),
|
|
263
|
+
"preview": _mask(clean_token),
|
|
264
|
+
"entropy": round(tok_entropy, 2),
|
|
265
|
+
}
|
|
266
|
+
findings.append(generic_finding)
|
|
267
|
+
|
|
268
|
+
return findings
|
skylos/server.py
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
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
1
|
from flask import Flask, request, jsonify
|
|
8
2
|
from flask_cors import CORS
|
|
9
3
|
import skylos
|
|
@@ -17,7 +11,6 @@ CORS(app)
|
|
|
17
11
|
|
|
18
12
|
@app.route('/')
|
|
19
13
|
def serve_frontend():
|
|
20
|
-
"""Serve the frontend HTML"""
|
|
21
14
|
return """<!DOCTYPE html>
|
|
22
15
|
<html lang="en">
|
|
23
16
|
<head>
|
|
@@ -461,7 +454,6 @@ def serve_frontend():
|
|
|
461
454
|
classes: analysisData.unused_classes || []
|
|
462
455
|
};
|
|
463
456
|
|
|
464
|
-
// Filter by confidence threshold
|
|
465
457
|
Object.keys(data).forEach(key => {
|
|
466
458
|
data[key] = data[key].filter(item => item.confidence >= confidenceThreshold);
|
|
467
459
|
});
|
|
@@ -481,12 +473,11 @@ def serve_frontend():
|
|
|
481
473
|
const listElement = document.getElementById('deadCodeList');
|
|
482
474
|
const allItems = [];
|
|
483
475
|
|
|
484
|
-
// Combine all items with their categories
|
|
485
476
|
Object.keys(data).forEach(category => {
|
|
486
477
|
data[category].forEach(item => {
|
|
487
478
|
allItems.push({
|
|
488
479
|
...item,
|
|
489
|
-
category: category.slice(0, -1)
|
|
480
|
+
category: category.slice(0, -1)
|
|
490
481
|
});
|
|
491
482
|
});
|
|
492
483
|
});
|
|
@@ -500,7 +491,6 @@ def serve_frontend():
|
|
|
500
491
|
return;
|
|
501
492
|
}
|
|
502
493
|
|
|
503
|
-
// Sort by confidence (highest first)
|
|
504
494
|
allItems.sort((a, b) => b.confidence - a.confidence);
|
|
505
495
|
|
|
506
496
|
listElement.innerHTML = allItems.map(item => `
|
|
@@ -551,7 +541,6 @@ def start_server():
|
|
|
551
541
|
print(" Starting Skylos Web Interface...")
|
|
552
542
|
print("Opening browser at: http://localhost:5090")
|
|
553
543
|
|
|
554
|
-
# Open browser after a short delay
|
|
555
544
|
Timer(1.5, open_browser).start()
|
|
556
545
|
|
|
557
546
|
app.run(debug=False, host='0.0.0.0', port=5090, use_reloader=False)
|
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])
|
|
File without changes
|
|
@@ -1,23 +1,28 @@
|
|
|
1
|
-
skylos/__init__.py,sha256=
|
|
2
|
-
skylos/analyzer.py,sha256=
|
|
3
|
-
skylos/cli.py,sha256=
|
|
4
|
-
skylos/codemods.py,sha256=
|
|
5
|
-
skylos/constants.py,sha256=
|
|
6
|
-
skylos/
|
|
7
|
-
skylos/
|
|
8
|
-
skylos/
|
|
9
|
-
skylos/
|
|
1
|
+
skylos/__init__.py,sha256=C4oz_FAjQTq_0XiSSxpDnngdL9YlWLpuam7NNvB_vPs,229
|
|
2
|
+
skylos/analyzer.py,sha256=G_8pw7GmChATc5h5XXij2pcHirhh_5G9Y8dlAC1dx38,16735
|
|
3
|
+
skylos/cli.py,sha256=DOV6nwPbi5zh-OJ3wXjIaPxCfXRtiPMNqo9Zp2nvDBA,19475
|
|
4
|
+
skylos/codemods.py,sha256=QdOwtbE2PpLsgCqpeScFg-pCfcpfHw9hFu-0WKKKBQg,9084
|
|
5
|
+
skylos/constants.py,sha256=kU-2FKQAV-ju4rYw62Tw25uRvqauzjZFUqtvGWaI6Es,1571
|
|
6
|
+
skylos/server.py,sha256=oHuevjdDFvJVbvpTtCDjSLOJ6Zy1jL4BYLYV4VFNMXs,15903
|
|
7
|
+
skylos/visitor.py,sha256=o_2JxjXKXAcWLQr8nmoatGAz2EhBk225qE_piHf3hDg,20458
|
|
8
|
+
skylos/rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
skylos/rules/secrets.py,sha256=FD7cXZ4_Zfg_Si1qFXLVK-5OIX1HXtCr_yJM0OlRsbI,9425
|
|
10
|
+
skylos/visitors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
skylos/visitors/framework_aware.py,sha256=eX4oU0jQwDWejkkW4kjRNctre27sVLHK1CTDDiqPqRw,13054
|
|
12
|
+
skylos/visitors/test_aware.py,sha256=kxYoMG2m02kbMlxtFOM-MWJO8qqxHviP9HgAMbKRfvU,2304
|
|
10
13
|
test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
14
|
test/compare_tools.py,sha256=0g9PDeJlbst-7hOaQzrL4MiJFQKpqM8q8VeBGzpPczg,22738
|
|
12
15
|
test/conftest.py,sha256=57sTF6vLL5U0CVKwGQFJcRs6n7t1dEHIriQoSluNmAI,6418
|
|
13
16
|
test/diagnostics.py,sha256=ExuFOCVpc9BDwNYapU96vj9RXLqxji32Sv6wVF4nJYU,13802
|
|
14
|
-
test/test_analyzer.py,sha256=
|
|
17
|
+
test/test_analyzer.py,sha256=WhEYSI1atRee2Gqd9-Edw6F-tdRS9DBqS0D0aqK6MKc,21093
|
|
15
18
|
test/test_changes_analyzer.py,sha256=l1hspCFz-sF8gqOilJvntUDuGckwhYsVtzvSRB13ZWw,5085
|
|
16
19
|
test/test_cli.py,sha256=rtdKzanDRJT_F92jKkCQFdhvlfwVJxfXKO8Hrbn-mIg,13180
|
|
17
20
|
test/test_codemods.py,sha256=Tbp9jE95HDl77EenTmyTtB1Sc3L8fwY9xiMNVDN5lxo,4522
|
|
18
21
|
test/test_constants.py,sha256=pMuDy0UpC81zENMDCeK6Bqmm3BR_HHZQSlMG-9TgOm0,12602
|
|
19
22
|
test/test_framework_aware.py,sha256=G9va1dEQ31wsvd4X7ROf_1YhhAQG5CogB7v0hYCojQ8,8802
|
|
20
23
|
test/test_integration.py,sha256=bNKGUe-w0xEZEdnoQNHbssvKMGs9u9fmFQTOz1lX9_k,12398
|
|
24
|
+
test/test_new_behaviours.py,sha256=vAON8rR09RXawZT1knYtZHseyRcECKz5bdOspJoMjUA,1472
|
|
25
|
+
test/test_secrets.py,sha256=rjC-iQD9s8U296PyHP0vMEjC6CC9mJwKiFj7Sm-xsuo,5711
|
|
21
26
|
test/test_skylos.py,sha256=kz77STrS4k3Eez5RDYwGxOg2WH3e7zNZPUYEaTLbGTs,15608
|
|
22
27
|
test/test_test_aware.py,sha256=VmbR_MQY0m941CAxxux8OxJHIr7l8crfWRouSeBMhIo,9390
|
|
23
28
|
test/test_visitor.py,sha256=xAbGv-XaozKm_0WJJhr0hMb6mLaJcbPz57G9-SWkxFU,22764
|
|
@@ -28,8 +33,8 @@ test/sample_repo/sample_repo/commands.py,sha256=b6gQ9YDabt2yyfqGbOpLo0osF7wya8O4
|
|
|
28
33
|
test/sample_repo/sample_repo/models.py,sha256=xXIg3pToEZwKuUCmKX2vTlCF_VeFA0yZlvlBVPIy5Qw,3320
|
|
29
34
|
test/sample_repo/sample_repo/routes.py,sha256=8yITrt55BwS01G7nWdESdx8LuxmReqop1zrGUKPeLi8,2475
|
|
30
35
|
test/sample_repo/sample_repo/utils.py,sha256=S56hEYh8wkzwsD260MvQcmUFOkw2EjFU27nMLFE6G2k,1103
|
|
31
|
-
skylos-2.
|
|
32
|
-
skylos-2.
|
|
33
|
-
skylos-2.
|
|
34
|
-
skylos-2.
|
|
35
|
-
skylos-2.
|
|
36
|
+
skylos-2.2.2.dist-info/METADATA,sha256=LfA3i21DFqm9HGGJLXEkSwkK1Adv-58LPnIAmwguvwQ,314
|
|
37
|
+
skylos-2.2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
38
|
+
skylos-2.2.2.dist-info/entry_points.txt,sha256=zzRpN2ByznlQoLeuLolS_TFNYSQxUGBL1EXQsAd6bIA,43
|
|
39
|
+
skylos-2.2.2.dist-info/top_level.txt,sha256=f8GA_7KwfaEopPMP8-EXDQXaqd4IbsOQPakZy01LkdQ,12
|
|
40
|
+
skylos-2.2.2.dist-info/RECORD,,
|