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.
- skylos/__init__.py +9 -3
- skylos/analyzer.py +674 -168
- skylos/cfg_visitor.py +60 -0
- skylos/cli.py +719 -235
- skylos/codemods.py +277 -0
- skylos/config.py +50 -0
- skylos/constants.py +78 -0
- skylos/gatekeeper.py +147 -0
- skylos/linter.py +18 -0
- skylos/rules/base.py +20 -0
- skylos/rules/danger/calls.py +119 -0
- skylos/rules/danger/danger.py +157 -0
- skylos/rules/danger/danger_cmd/cmd_flow.py +75 -0
- skylos/rules/danger/danger_fs/__init__.py +0 -0
- skylos/rules/danger/danger_fs/path_flow.py +79 -0
- skylos/rules/danger/danger_net/__init__.py +0 -0
- skylos/rules/danger/danger_net/ssrf_flow.py +80 -0
- skylos/rules/danger/danger_sql/__init__.py +0 -0
- skylos/rules/danger/danger_sql/sql_flow.py +245 -0
- skylos/rules/danger/danger_sql/sql_raw_flow.py +96 -0
- skylos/rules/danger/danger_web/__init__.py +0 -0
- skylos/rules/danger/danger_web/xss_flow.py +170 -0
- skylos/rules/danger/taint.py +110 -0
- skylos/rules/quality/__init__.py +0 -0
- skylos/rules/quality/complexity.py +95 -0
- skylos/rules/quality/logic.py +96 -0
- skylos/rules/quality/nesting.py +101 -0
- skylos/rules/quality/structure.py +99 -0
- skylos/rules/secrets.py +325 -0
- skylos/server.py +554 -0
- skylos/visitor.py +502 -90
- skylos/visitors/__init__.py +0 -0
- skylos/visitors/framework_aware.py +437 -0
- skylos/visitors/test_aware.py +74 -0
- skylos-2.5.2.dist-info/METADATA +21 -0
- skylos-2.5.2.dist-info/RECORD +42 -0
- {skylos-1.0.10.dist-info → skylos-2.5.2.dist-info}/WHEEL +1 -1
- {skylos-1.0.10.dist-info → skylos-2.5.2.dist-info}/top_level.txt +0 -1
- skylos-1.0.10.dist-info/METADATA +0 -8
- skylos-1.0.10.dist-info/RECORD +0 -21
- test/compare_tools.py +0 -604
- test/diagnostics.py +0 -364
- test/sample_repo/app.py +0 -13
- test/sample_repo/sample_repo/commands.py +0 -81
- test/sample_repo/sample_repo/models.py +0 -122
- test/sample_repo/sample_repo/routes.py +0 -89
- test/sample_repo/sample_repo/utils.py +0 -36
- test/test_skylos.py +0 -456
- test/test_visitor.py +0 -220
- {test → skylos/rules}/__init__.py +0 -0
- {test/sample_repo → skylos/rules/danger}/__init__.py +0 -0
- {test/sample_repo/sample_repo → skylos/rules/danger/danger_cmd}/__init__.py +0 -0
- {skylos-1.0.10.dist-info → skylos-2.5.2.dist-info}/entry_points.txt +0 -0
|
File without changes
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import fnmatch
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
FRAMEWORK_DECORATORS = [
|
|
6
|
+
"@*.route",
|
|
7
|
+
"@*.get",
|
|
8
|
+
"@*.post",
|
|
9
|
+
"@*.put",
|
|
10
|
+
"@*.delete",
|
|
11
|
+
"@*.patch",
|
|
12
|
+
"@*.before_request",
|
|
13
|
+
"@*.after_request",
|
|
14
|
+
"@*.errorhandler",
|
|
15
|
+
"@*.teardown_*",
|
|
16
|
+
"@*.head",
|
|
17
|
+
"@*.options",
|
|
18
|
+
"@*.trace",
|
|
19
|
+
"@*.websocket",
|
|
20
|
+
"@*.middleware",
|
|
21
|
+
"@*.on_event",
|
|
22
|
+
"@*.exception_handler",
|
|
23
|
+
"@*_required",
|
|
24
|
+
"@login_required",
|
|
25
|
+
"@permission_required",
|
|
26
|
+
"django.views.decorators.*",
|
|
27
|
+
"@*.simple_tag",
|
|
28
|
+
"@*.inclusion_tag",
|
|
29
|
+
"@validator",
|
|
30
|
+
"@field_validator",
|
|
31
|
+
"@model_validator",
|
|
32
|
+
"@root_validator",
|
|
33
|
+
"@field_serializer",
|
|
34
|
+
"@model_serializer",
|
|
35
|
+
"@computed_field",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
FRAMEWORK_FUNCTIONS = [
|
|
39
|
+
"get",
|
|
40
|
+
"post",
|
|
41
|
+
"put",
|
|
42
|
+
"patch",
|
|
43
|
+
"delete",
|
|
44
|
+
"head",
|
|
45
|
+
"options",
|
|
46
|
+
"trace",
|
|
47
|
+
"*_queryset",
|
|
48
|
+
"get_queryset",
|
|
49
|
+
"get_object",
|
|
50
|
+
"get_context_data",
|
|
51
|
+
"*_form",
|
|
52
|
+
"form_valid",
|
|
53
|
+
"form_invalid",
|
|
54
|
+
"get_form_*",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
FRAMEWORK_IMPORTS = {
|
|
58
|
+
"flask",
|
|
59
|
+
"fastapi",
|
|
60
|
+
"django",
|
|
61
|
+
"rest_framework",
|
|
62
|
+
"pydantic",
|
|
63
|
+
"celery",
|
|
64
|
+
"starlette",
|
|
65
|
+
"uvicorn",
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class FrameworkAwareVisitor:
|
|
70
|
+
def __init__(self, filename=None):
|
|
71
|
+
self.is_framework_file = False
|
|
72
|
+
self.detected_frameworks = set()
|
|
73
|
+
self.framework_decorated_lines = set()
|
|
74
|
+
self.func_defs = {}
|
|
75
|
+
self.class_defs = {}
|
|
76
|
+
self.class_method_lines = {}
|
|
77
|
+
self.pydantic_models = set()
|
|
78
|
+
self._mark_functions = set()
|
|
79
|
+
self._mark_classes = set()
|
|
80
|
+
self.declarative_classes = set()
|
|
81
|
+
self._mark_cbv_http_methods = set()
|
|
82
|
+
self._type_refs_in_routes = set()
|
|
83
|
+
if filename:
|
|
84
|
+
self._check_framework_imports_in_file(filename)
|
|
85
|
+
|
|
86
|
+
def visit(self, node):
|
|
87
|
+
method = "visit_" + node.__class__.__name__
|
|
88
|
+
visitor = getattr(self, method, self.generic_visit)
|
|
89
|
+
return visitor(node)
|
|
90
|
+
|
|
91
|
+
def generic_visit(self, node):
|
|
92
|
+
for field, value in ast.iter_fields(node):
|
|
93
|
+
if isinstance(value, list):
|
|
94
|
+
for item in value:
|
|
95
|
+
if isinstance(item, ast.AST):
|
|
96
|
+
self.visit(item)
|
|
97
|
+
elif isinstance(value, ast.AST):
|
|
98
|
+
self.visit(value)
|
|
99
|
+
|
|
100
|
+
def visit_Import(self, node: ast.Import):
|
|
101
|
+
for alias in node.names:
|
|
102
|
+
name = alias.name.lower()
|
|
103
|
+
|
|
104
|
+
for fw in FRAMEWORK_IMPORTS:
|
|
105
|
+
if fw in name:
|
|
106
|
+
self.is_framework_file = True
|
|
107
|
+
framework_name = name.split(".")[0]
|
|
108
|
+
self.detected_frameworks.add(framework_name)
|
|
109
|
+
break
|
|
110
|
+
|
|
111
|
+
self.generic_visit(node)
|
|
112
|
+
|
|
113
|
+
def visit_ImportFrom(self, node: ast.ImportFrom):
|
|
114
|
+
if node.module:
|
|
115
|
+
module_name = node.module.split(".")[0].lower()
|
|
116
|
+
if module_name in FRAMEWORK_IMPORTS:
|
|
117
|
+
self.is_framework_file = True
|
|
118
|
+
self.detected_frameworks.add(module_name)
|
|
119
|
+
self.generic_visit(node)
|
|
120
|
+
|
|
121
|
+
def visit_FunctionDef(self, node: ast.FunctionDef):
|
|
122
|
+
self.func_defs.setdefault(node.name, node.lineno)
|
|
123
|
+
for deco in node.decorator_list:
|
|
124
|
+
d = self._normalize_decorator(deco)
|
|
125
|
+
|
|
126
|
+
if self._matches_framework_pattern(d, FRAMEWORK_DECORATORS):
|
|
127
|
+
self.framework_decorated_lines.add(node.lineno)
|
|
128
|
+
self.is_framework_file = True
|
|
129
|
+
|
|
130
|
+
if self._decorator_base_name_is(deco, "receiver"):
|
|
131
|
+
self.framework_decorated_lines.add(node.lineno)
|
|
132
|
+
self.is_framework_file = True
|
|
133
|
+
|
|
134
|
+
defaults_to_scan = []
|
|
135
|
+
if node.args.defaults:
|
|
136
|
+
defaults_to_scan.extend(node.args.defaults)
|
|
137
|
+
if node.args.kw_defaults:
|
|
138
|
+
defaults_to_scan.extend(node.args.kw_defaults)
|
|
139
|
+
|
|
140
|
+
for default in defaults_to_scan:
|
|
141
|
+
self._scan_for_depends(default)
|
|
142
|
+
|
|
143
|
+
is_route = False
|
|
144
|
+
if node.lineno in self.framework_decorated_lines:
|
|
145
|
+
is_route = True
|
|
146
|
+
|
|
147
|
+
if is_route:
|
|
148
|
+
self._collect_annotation_type_refs(node)
|
|
149
|
+
self.generic_visit(node)
|
|
150
|
+
|
|
151
|
+
visit_AsyncFunctionDef = visit_FunctionDef
|
|
152
|
+
|
|
153
|
+
def visit_ClassDef(self, node: ast.ClassDef):
|
|
154
|
+
self.class_defs[node.name] = node
|
|
155
|
+
for item in node.body:
|
|
156
|
+
if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
157
|
+
self.class_method_lines[(node.name, item.name)] = item.lineno
|
|
158
|
+
bases = self._base_names(node)
|
|
159
|
+
|
|
160
|
+
is_view_like = False
|
|
161
|
+
for base in bases:
|
|
162
|
+
for token in ("view", "viewset", "apiview", "handler"):
|
|
163
|
+
if token in base:
|
|
164
|
+
is_view_like = True
|
|
165
|
+
break
|
|
166
|
+
if is_view_like:
|
|
167
|
+
break
|
|
168
|
+
|
|
169
|
+
is_pydantic = False
|
|
170
|
+
for base in bases:
|
|
171
|
+
if "basemodel" in base:
|
|
172
|
+
is_pydantic = True
|
|
173
|
+
break
|
|
174
|
+
|
|
175
|
+
if is_view_like:
|
|
176
|
+
self.is_framework_file = True
|
|
177
|
+
self._mark_cbv_http_methods.add(node.name)
|
|
178
|
+
|
|
179
|
+
if is_pydantic:
|
|
180
|
+
self.pydantic_models.add(node.name)
|
|
181
|
+
self.declarative_classes.add(node.name)
|
|
182
|
+
self.is_framework_file = True
|
|
183
|
+
|
|
184
|
+
else:
|
|
185
|
+
for base in bases:
|
|
186
|
+
tail = base.split(".")[-1]
|
|
187
|
+
if tail in ("schema", "model"):
|
|
188
|
+
self.declarative_classes.add(node.name)
|
|
189
|
+
break
|
|
190
|
+
|
|
191
|
+
self.generic_visit(node)
|
|
192
|
+
|
|
193
|
+
def visit_Assign(self, node: ast.Assign):
|
|
194
|
+
targets = []
|
|
195
|
+
for t in node.targets:
|
|
196
|
+
if isinstance(t, ast.Name):
|
|
197
|
+
targets.append(t.id)
|
|
198
|
+
|
|
199
|
+
if "urlpatterns" in targets:
|
|
200
|
+
self.is_framework_file = True
|
|
201
|
+
for elt in self._iter_list_elts(node.value):
|
|
202
|
+
if isinstance(elt, ast.Call) and self._call_name_endswith(
|
|
203
|
+
elt, {"path", "re_path"}
|
|
204
|
+
):
|
|
205
|
+
view_expr = self._get_posarg(elt, 1)
|
|
206
|
+
self._mark_view_from_url_pattern(view_expr)
|
|
207
|
+
self.generic_visit(node)
|
|
208
|
+
|
|
209
|
+
def visit_Call(self, node: ast.Call):
|
|
210
|
+
if isinstance(node.func, ast.Attribute) and node.func.attr == "register":
|
|
211
|
+
if len(node.args) >= 2:
|
|
212
|
+
vs = node.args[1]
|
|
213
|
+
cls_name = self._simple_name(vs)
|
|
214
|
+
if cls_name:
|
|
215
|
+
self._mark_classes.add(cls_name)
|
|
216
|
+
self._mark_cbv_http_methods.add(cls_name)
|
|
217
|
+
self.is_framework_file = True
|
|
218
|
+
if (
|
|
219
|
+
isinstance(node.func, ast.Attribute)
|
|
220
|
+
and node.func.attr == "connect"
|
|
221
|
+
and node.args
|
|
222
|
+
):
|
|
223
|
+
func_name = self._simple_name(node.args[0])
|
|
224
|
+
if func_name:
|
|
225
|
+
self._mark_functions.add(func_name)
|
|
226
|
+
self.is_framework_file = True
|
|
227
|
+
self.generic_visit(node)
|
|
228
|
+
|
|
229
|
+
def finalize(self):
|
|
230
|
+
for fname in self._mark_functions:
|
|
231
|
+
if fname in self.func_defs:
|
|
232
|
+
self.framework_decorated_lines.add(self.func_defs[fname])
|
|
233
|
+
for cname in self._mark_classes:
|
|
234
|
+
cls_node = self.class_defs.get(cname)
|
|
235
|
+
if cls_node is not None:
|
|
236
|
+
self.framework_decorated_lines.add(cls_node.lineno)
|
|
237
|
+
for cname in self._mark_cbv_http_methods:
|
|
238
|
+
for meth in (
|
|
239
|
+
"get",
|
|
240
|
+
"post",
|
|
241
|
+
"put",
|
|
242
|
+
"patch",
|
|
243
|
+
"delete",
|
|
244
|
+
"head",
|
|
245
|
+
"options",
|
|
246
|
+
"trace",
|
|
247
|
+
"list",
|
|
248
|
+
"create",
|
|
249
|
+
"retrieve",
|
|
250
|
+
"update",
|
|
251
|
+
"partial_update",
|
|
252
|
+
"destroy",
|
|
253
|
+
):
|
|
254
|
+
lino = self.class_method_lines.get((cname, meth))
|
|
255
|
+
if lino:
|
|
256
|
+
self.framework_decorated_lines.add(lino)
|
|
257
|
+
|
|
258
|
+
typed_models = set()
|
|
259
|
+
for t in self._type_refs_in_routes:
|
|
260
|
+
if t in self.pydantic_models:
|
|
261
|
+
typed_models.add(t)
|
|
262
|
+
|
|
263
|
+
self._mark_classes.update(typed_models)
|
|
264
|
+
for cname in typed_models:
|
|
265
|
+
cls_node = self.class_defs.get(cname)
|
|
266
|
+
if cls_node is not None:
|
|
267
|
+
self.framework_decorated_lines.add(cls_node.lineno)
|
|
268
|
+
|
|
269
|
+
def _check_framework_imports_in_file(self, filename):
|
|
270
|
+
try:
|
|
271
|
+
content = Path(filename).read_text(encoding="utf-8")
|
|
272
|
+
|
|
273
|
+
for framework in FRAMEWORK_IMPORTS:
|
|
274
|
+
import_statement = f"import {framework}"
|
|
275
|
+
from_statement = f"from {framework}"
|
|
276
|
+
|
|
277
|
+
has_import = import_statement in content
|
|
278
|
+
has_from_import = from_statement in content
|
|
279
|
+
|
|
280
|
+
if has_import or has_from_import:
|
|
281
|
+
self.is_framework_file = True
|
|
282
|
+
self.detected_frameworks.add(framework)
|
|
283
|
+
break
|
|
284
|
+
|
|
285
|
+
except Exception:
|
|
286
|
+
pass
|
|
287
|
+
|
|
288
|
+
def _normalize_decorator(self, dec: ast.AST):
|
|
289
|
+
if isinstance(dec, ast.Call):
|
|
290
|
+
return self._normalize_decorator(dec.func)
|
|
291
|
+
if isinstance(dec, ast.Name):
|
|
292
|
+
return f"@{dec.id}"
|
|
293
|
+
if isinstance(dec, ast.Attribute):
|
|
294
|
+
return f"@{self._attr_to_str(dec)}"
|
|
295
|
+
return "@unknown"
|
|
296
|
+
|
|
297
|
+
def _matches_framework_pattern(self, text, patterns):
|
|
298
|
+
text_clean = text.lstrip("@")
|
|
299
|
+
|
|
300
|
+
for pattern in patterns:
|
|
301
|
+
pattern_clean = pattern.lstrip("@")
|
|
302
|
+
if fnmatch.fnmatch(text_clean, pattern_clean):
|
|
303
|
+
return True
|
|
304
|
+
|
|
305
|
+
return False
|
|
306
|
+
|
|
307
|
+
def _decorator_base_name_is(self, dec: ast.AST, name):
|
|
308
|
+
if isinstance(dec, ast.Call):
|
|
309
|
+
dec = dec.func
|
|
310
|
+
if isinstance(dec, ast.Name):
|
|
311
|
+
return dec.id == name
|
|
312
|
+
if isinstance(dec, ast.Attribute):
|
|
313
|
+
return dec.attr == name or self._attr_to_str(dec).endswith("." + name)
|
|
314
|
+
return False
|
|
315
|
+
|
|
316
|
+
def _attr_to_str(self, node: ast.Attribute):
|
|
317
|
+
parts = []
|
|
318
|
+
cur = node
|
|
319
|
+
while isinstance(cur, ast.Attribute):
|
|
320
|
+
parts.append(cur.attr)
|
|
321
|
+
cur = cur.value
|
|
322
|
+
if isinstance(cur, ast.Name):
|
|
323
|
+
parts.append(cur.id)
|
|
324
|
+
|
|
325
|
+
parts.reverse()
|
|
326
|
+
return ".".join(parts)
|
|
327
|
+
|
|
328
|
+
def _base_names(self, node: ast.ClassDef):
|
|
329
|
+
out = []
|
|
330
|
+
for b in node.bases:
|
|
331
|
+
if isinstance(b, ast.Name):
|
|
332
|
+
out.append(b.id.lower())
|
|
333
|
+
elif isinstance(b, ast.Attribute):
|
|
334
|
+
out.append(self._attr_to_str(b).lower())
|
|
335
|
+
return out
|
|
336
|
+
|
|
337
|
+
def _iter_list_elts(self, node: ast.AST):
|
|
338
|
+
if isinstance(node, (ast.List, ast.Tuple, ast.Set)):
|
|
339
|
+
for elt in node.elts:
|
|
340
|
+
yield elt
|
|
341
|
+
|
|
342
|
+
def _call_name_endswith(self, call: ast.Call, names):
|
|
343
|
+
if isinstance(call.func, ast.Name):
|
|
344
|
+
return call.func.id in names
|
|
345
|
+
if isinstance(call.func, ast.Attribute):
|
|
346
|
+
return call.func.attr in names
|
|
347
|
+
return False
|
|
348
|
+
|
|
349
|
+
def _get_posarg(self, call: ast.Call, idx):
|
|
350
|
+
return call.args[idx] if len(call.args) > idx else None
|
|
351
|
+
|
|
352
|
+
def _simple_name(self, node: ast.AST):
|
|
353
|
+
if isinstance(node, ast.Name):
|
|
354
|
+
return node.id
|
|
355
|
+
if isinstance(node, ast.Attribute):
|
|
356
|
+
return node.attr
|
|
357
|
+
return None
|
|
358
|
+
|
|
359
|
+
def _mark_view_from_url_pattern(self, view_expr):
|
|
360
|
+
if view_expr is None:
|
|
361
|
+
return
|
|
362
|
+
if (
|
|
363
|
+
isinstance(view_expr, ast.Call)
|
|
364
|
+
and isinstance(view_expr.func, ast.Attribute)
|
|
365
|
+
and view_expr.func.attr == "as_view"
|
|
366
|
+
):
|
|
367
|
+
cls_name = self._simple_name(view_expr.func.value)
|
|
368
|
+
if cls_name:
|
|
369
|
+
self._mark_classes.add(cls_name)
|
|
370
|
+
self._mark_cbv_http_methods.add(cls_name)
|
|
371
|
+
else:
|
|
372
|
+
fname = self._simple_name(view_expr)
|
|
373
|
+
if fname:
|
|
374
|
+
self._mark_functions.add(fname)
|
|
375
|
+
|
|
376
|
+
def _scan_for_depends(self, node):
|
|
377
|
+
if not isinstance(node, ast.Call):
|
|
378
|
+
return
|
|
379
|
+
is_depends = False
|
|
380
|
+
if isinstance(node.func, ast.Name) and node.func.id == "Depends":
|
|
381
|
+
is_depends = True
|
|
382
|
+
elif isinstance(node.func, ast.Attribute) and node.func.attr == "Depends":
|
|
383
|
+
is_depends = True
|
|
384
|
+
if not is_depends:
|
|
385
|
+
return
|
|
386
|
+
if node.args:
|
|
387
|
+
dep = node.args[0]
|
|
388
|
+
dep_name = self._simple_name(dep)
|
|
389
|
+
if dep_name:
|
|
390
|
+
self._mark_functions.add(dep_name)
|
|
391
|
+
self.is_framework_file = True
|
|
392
|
+
|
|
393
|
+
def _collect_annotation_type_refs(self, fn: ast.FunctionDef):
|
|
394
|
+
def collect(t):
|
|
395
|
+
if t is None:
|
|
396
|
+
return
|
|
397
|
+
|
|
398
|
+
if isinstance(t, ast.Name):
|
|
399
|
+
self._type_refs_in_routes.add(t.id)
|
|
400
|
+
return
|
|
401
|
+
|
|
402
|
+
if isinstance(t, ast.Attribute):
|
|
403
|
+
self._type_refs_in_routes.add(t.attr)
|
|
404
|
+
return
|
|
405
|
+
|
|
406
|
+
if isinstance(t, ast.Subscript):
|
|
407
|
+
collect(t.value)
|
|
408
|
+
slice_node = t.slice
|
|
409
|
+
if isinstance(slice_node, ast.Tuple):
|
|
410
|
+
for element in slice_node.elts:
|
|
411
|
+
collect(element)
|
|
412
|
+
else:
|
|
413
|
+
collect(slice_node)
|
|
414
|
+
return
|
|
415
|
+
|
|
416
|
+
if isinstance(t, ast.Tuple):
|
|
417
|
+
for element in t.elts:
|
|
418
|
+
collect(element)
|
|
419
|
+
|
|
420
|
+
all_args = []
|
|
421
|
+
all_args.extend(fn.args.args)
|
|
422
|
+
all_args.extend(fn.args.posonlyargs)
|
|
423
|
+
all_args.extend(fn.args.kwonlyargs)
|
|
424
|
+
|
|
425
|
+
for arg in all_args:
|
|
426
|
+
collect(arg.annotation)
|
|
427
|
+
|
|
428
|
+
if fn.returns:
|
|
429
|
+
collect(fn.returns)
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def detect_framework_usage(definition, visitor=None):
|
|
433
|
+
if not visitor:
|
|
434
|
+
return None
|
|
435
|
+
if definition.line in visitor.framework_decorated_lines:
|
|
436
|
+
return 1
|
|
437
|
+
return None
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
from skylos.constants import TEST_IMPORT_RE, TEST_DECOR_RE, TEST_FILE_RE
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TestAwareVisitor:
|
|
6
|
+
def __init__(self, filename=None):
|
|
7
|
+
self.is_test_file = False
|
|
8
|
+
self.test_decorated_lines = set()
|
|
9
|
+
|
|
10
|
+
if filename and TEST_FILE_RE.search(str(filename)):
|
|
11
|
+
self.is_test_file = True
|
|
12
|
+
|
|
13
|
+
def visit(self, node):
|
|
14
|
+
method = "visit_" + node.__class__.__name__
|
|
15
|
+
visitor = getattr(self, method, self.generic_visit)
|
|
16
|
+
return visitor(node)
|
|
17
|
+
|
|
18
|
+
def generic_visit(self, node):
|
|
19
|
+
for field, value in ast.iter_fields(node):
|
|
20
|
+
if isinstance(value, list):
|
|
21
|
+
for item in value:
|
|
22
|
+
if isinstance(item, ast.AST):
|
|
23
|
+
self.visit(item)
|
|
24
|
+
elif isinstance(value, ast.AST):
|
|
25
|
+
self.visit(value)
|
|
26
|
+
|
|
27
|
+
def visit_Import(self, node):
|
|
28
|
+
if self.is_test_file:
|
|
29
|
+
for alias in node.names:
|
|
30
|
+
if TEST_IMPORT_RE.match(alias.name):
|
|
31
|
+
pass
|
|
32
|
+
self.generic_visit(node)
|
|
33
|
+
|
|
34
|
+
def visit_ImportFrom(self, node):
|
|
35
|
+
if self.is_test_file:
|
|
36
|
+
if node.module and TEST_IMPORT_RE.match(node.module):
|
|
37
|
+
pass
|
|
38
|
+
self.generic_visit(node)
|
|
39
|
+
|
|
40
|
+
def visit_FunctionDef(self, node):
|
|
41
|
+
if (
|
|
42
|
+
node.name.startswith("test_")
|
|
43
|
+
or node.name.endswith("_test")
|
|
44
|
+
or any(node.name.startswith(prefix) for prefix in ["setup", "teardown"])
|
|
45
|
+
or node.name
|
|
46
|
+
in [
|
|
47
|
+
"setUp",
|
|
48
|
+
"tearDown",
|
|
49
|
+
"setUpClass",
|
|
50
|
+
"tearDownClass",
|
|
51
|
+
"setUpModule",
|
|
52
|
+
"tearDownModule",
|
|
53
|
+
]
|
|
54
|
+
):
|
|
55
|
+
self.test_decorated_lines.add(node.lineno)
|
|
56
|
+
|
|
57
|
+
for deco in node.decorator_list:
|
|
58
|
+
name = self._decorator_name(deco)
|
|
59
|
+
if name and (
|
|
60
|
+
TEST_DECOR_RE.match(name) or "pytest" in name or "fixture" in name
|
|
61
|
+
):
|
|
62
|
+
self.test_decorated_lines.add(node.lineno)
|
|
63
|
+
self.generic_visit(node)
|
|
64
|
+
|
|
65
|
+
def visit_AsyncFunctionDef(self, node):
|
|
66
|
+
self.visit_FunctionDef(node)
|
|
67
|
+
|
|
68
|
+
def _decorator_name(self, deco):
|
|
69
|
+
if isinstance(deco, ast.Name):
|
|
70
|
+
return deco.id
|
|
71
|
+
if isinstance(deco, ast.Attribute):
|
|
72
|
+
parent = self._decorator_name(deco.value)
|
|
73
|
+
return f"{parent}.{deco.attr}" if parent else deco.attr
|
|
74
|
+
return ""
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: skylos
|
|
3
|
+
Version: 2.5.2
|
|
4
|
+
Summary: An analysis tool for Python codebases
|
|
5
|
+
Author-email: oha <aaronoh2015@gmail.com>
|
|
6
|
+
Classifier: Development Status :: 4 - Beta
|
|
7
|
+
Classifier: Intended Audience :: Developers
|
|
8
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
15
|
+
Classifier: Topic :: Software Development :: Testing
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Requires-Dist: inquirer>=3.0.0
|
|
18
|
+
Requires-Dist: flask>=2.1.1
|
|
19
|
+
Requires-Dist: flask-cors>=3.0.0
|
|
20
|
+
Requires-Dist: libcst>=1.8.2
|
|
21
|
+
Requires-Dist: rich>=14.0.0
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
skylos/__init__.py,sha256=LfCvFSFmNNylU9vOh4akThF09VoyWbLBQtHuu3sKToE,233
|
|
2
|
+
skylos/analyzer.py,sha256=K16xuj76d82HDzE15TRRXhqz-_3KQ3cknSQ3dupUViI,25448
|
|
3
|
+
skylos/cfg_visitor.py,sha256=ct5CGU4Is4logPGA_BCBKnapH-tYnGsCTdC9RRBHSDs,1741
|
|
4
|
+
skylos/cli.py,sha256=1IcsS0tUeTGC1KGsraNzpyCrWTUhoq2Sxzv9VzNRu_k,27615
|
|
5
|
+
skylos/codemods.py,sha256=XarxwgeVca3nQkEqR-VmYnD2T8dwlWZZv8DXipifmBM,9718
|
|
6
|
+
skylos/config.py,sha256=Y_sd8M63qR7t2Dj7K0rL0mhgKu_-4_5OOXoMT-N4PhQ,1055
|
|
7
|
+
skylos/constants.py,sha256=6Gy3NrnUAdiRd0o9OnRB0tZo1dh96bXGJrNxkN4FkQM,1623
|
|
8
|
+
skylos/gatekeeper.py,sha256=UaPNqDPAPZPYGy6ItCCWhTxoSrrsAeNQeQA-Xhx0h7U,5013
|
|
9
|
+
skylos/linter.py,sha256=jbaFON9eFjPWqjsgZXuchTO9q9VH4JRFdFtn03_QXmM,493
|
|
10
|
+
skylos/server.py,sha256=G0eBJmk4Yg1nu3mY0tL5bsDOUNg9hXh4ecZkOTiDcfc,15853
|
|
11
|
+
skylos/visitor.py,sha256=-QwNH1BbRQl-UVsRw6CUrynnQqFoyhcrWgzk6_w-lDI,21141
|
|
12
|
+
skylos/rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
skylos/rules/base.py,sha256=a99wScVY-hnLK4wgtv0YuZ4Rnp93mgsQ5YHhsStdwv0,328
|
|
14
|
+
skylos/rules/secrets.py,sha256=prqQe1oIIgTiAynGJ626WU7F4Kiqnv1PsPCWsHcP934,9727
|
|
15
|
+
skylos/rules/danger/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
skylos/rules/danger/calls.py,sha256=ImiaPsBb9VxCy6EuNBKwnb1LMk1ZeK-Z5m61fNeiVMg,3329
|
|
17
|
+
skylos/rules/danger/danger.py,sha256=x7TQZoQuUEd-kjqWJ3ORgFsjDwVARvqgqymar8RAqCQ,4360
|
|
18
|
+
skylos/rules/danger/taint.py,sha256=Hs8wvCk5Cb6htjaZ_p3DK_HtDXHzyHKwDc7WYxgX54Y,3343
|
|
19
|
+
skylos/rules/danger/danger_cmd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
+
skylos/rules/danger/danger_cmd/cmd_flow.py,sha256=KCQbIB1P9d2ym4hfmrqyf5LZpPk0WU_3qpmWIAY1N9M,2287
|
|
21
|
+
skylos/rules/danger/danger_fs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
+
skylos/rules/danger/danger_fs/path_flow.py,sha256=l47jK89qnDq0p5KPHrY_sQ8THpmDXfCUNKsjb-b0M2o,2462
|
|
23
|
+
skylos/rules/danger/danger_net/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
+
skylos/rules/danger/danger_net/ssrf_flow.py,sha256=E0YKXf-EqDs5a6FllmUYd7MG4LseSY0avsDXV0tG9MY,2639
|
|
25
|
+
skylos/rules/danger/danger_sql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
+
skylos/rules/danger/danger_sql/sql_flow.py,sha256=YQ98ry-Jmg0gjbYuW00Iwj3oopFry0XNXPlmbOs_zD4,8336
|
|
27
|
+
skylos/rules/danger/danger_sql/sql_raw_flow.py,sha256=tYxUWmJv8yF9qrHWSyq2lfU70aLIG0D9RHpTWqg2VwQ,3117
|
|
28
|
+
skylos/rules/danger/danger_web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
|
+
skylos/rules/danger/danger_web/xss_flow.py,sha256=paNd1-54Kyz6XGsLloVU_Flr4ejg6HFDn4tJmwPOrjo,5667
|
|
30
|
+
skylos/rules/quality/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
|
+
skylos/rules/quality/complexity.py,sha256=RPEXF1kq5QVcR4kBge_MsOOFwjb326PZKe1scmBbaQs,2346
|
|
32
|
+
skylos/rules/quality/logic.py,sha256=TqSakQRjkt5crpFhpMKxzBxua6SexsjDHSPycEt2-zY,3612
|
|
33
|
+
skylos/rules/quality/nesting.py,sha256=rp7n513RMzgMRABxZFrfh-e4mOZXyGSk5H9qdUNurXk,3025
|
|
34
|
+
skylos/rules/quality/structure.py,sha256=HQt1l2G6s_Gb92qQC-ijLJbuTBmXxcbK1p_zQGUlf_o,2946
|
|
35
|
+
skylos/visitors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
|
+
skylos/visitors/framework_aware.py,sha256=O3uBy_1E2vMcJRX_cyi6W9KLY0e7c32sdHX63uSM7lA,13682
|
|
37
|
+
skylos/visitors/test_aware.py,sha256=eed7RJfo-8OE2TGYNmzqqgF8S5T25IjnlvcGkT_-BfU,2426
|
|
38
|
+
skylos-2.5.2.dist-info/METADATA,sha256=wvdyBcPqdMrrhpb54r3HOOj5KlVIkrX9eiZmA7TTw5E,824
|
|
39
|
+
skylos-2.5.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
40
|
+
skylos-2.5.2.dist-info/entry_points.txt,sha256=zzRpN2ByznlQoLeuLolS_TFNYSQxUGBL1EXQsAd6bIA,43
|
|
41
|
+
skylos-2.5.2.dist-info/top_level.txt,sha256=GHAM9ZPjXcmflVoLHDN1wMtwoUBFZ3I0AYKOXvfHv70,7
|
|
42
|
+
skylos-2.5.2.dist-info/RECORD,,
|
skylos-1.0.10.dist-info/METADATA
DELETED
skylos-1.0.10.dist-info/RECORD
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
skylos/__init__.py,sha256=U8NngtBsmaUGNLstSCYzDjfSCT1KyFOKExYRX7VmZfw,152
|
|
2
|
-
skylos/analyzer.py,sha256=1joG0Ek3jNj7qv7w3gFiNeb2BD8CXgX5PF6fVc_HIG4,9710
|
|
3
|
-
skylos/cli.py,sha256=l-qfaC0RUH2L9YgjlMOvlQrCPD5hcV3McHIk1az-CI4,13525
|
|
4
|
-
skylos/visitor.py,sha256=uHNHKf7Kf8Qg1sIa-PsH2NHCQD6R9Bd_NELs-41deE8,9339
|
|
5
|
-
test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
test/compare_tools.py,sha256=0g9PDeJlbst-7hOaQzrL4MiJFQKpqM8q8VeBGzpPczg,22738
|
|
7
|
-
test/diagnostics.py,sha256=ExuFOCVpc9BDwNYapU96vj9RXLqxji32Sv6wVF4nJYU,13802
|
|
8
|
-
test/test_skylos.py,sha256=kz77STrS4k3Eez5RDYwGxOg2WH3e7zNZPUYEaTLbGTs,15608
|
|
9
|
-
test/test_visitor.py,sha256=bxUY_Zn_gLadZlz_n3Mu6rhVcExqElISwwVBo4eqVAY,7337
|
|
10
|
-
test/sample_repo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
-
test/sample_repo/app.py,sha256=M5XgoAn-LPz50mKAj_ZacRKf-Pg7I4HbjWP7Z9jE4a0,226
|
|
12
|
-
test/sample_repo/sample_repo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
-
test/sample_repo/sample_repo/commands.py,sha256=b6gQ9YDabt2yyfqGbOpLo0osF7wya8O4Lm7m8gtCr3g,2575
|
|
14
|
-
test/sample_repo/sample_repo/models.py,sha256=xXIg3pToEZwKuUCmKX2vTlCF_VeFA0yZlvlBVPIy5Qw,3320
|
|
15
|
-
test/sample_repo/sample_repo/routes.py,sha256=8yITrt55BwS01G7nWdESdx8LuxmReqop1zrGUKPeLi8,2475
|
|
16
|
-
test/sample_repo/sample_repo/utils.py,sha256=S56hEYh8wkzwsD260MvQcmUFOkw2EjFU27nMLFE6G2k,1103
|
|
17
|
-
skylos-1.0.10.dist-info/METADATA,sha256=iVIhmRsXWo0WPlMjYVrZpsi2mUVdEbKYrXDvOW4hbIk,225
|
|
18
|
-
skylos-1.0.10.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
|
19
|
-
skylos-1.0.10.dist-info/entry_points.txt,sha256=zzRpN2ByznlQoLeuLolS_TFNYSQxUGBL1EXQsAd6bIA,43
|
|
20
|
-
skylos-1.0.10.dist-info/top_level.txt,sha256=f8GA_7KwfaEopPMP8-EXDQXaqd4IbsOQPakZy01LkdQ,12
|
|
21
|
-
skylos-1.0.10.dist-info/RECORD,,
|