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.

@@ -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) // Remove 's' from end
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
- if name not in {d.name for d in self.defs}:
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
- return f"{self.mod}.{name}" if self.mod else name
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
- self.add_def(qualified_name,"method"if self.cls else"function",node.lineno)
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
- scope_parts = [self.mod]
251
- if self.cls: scope_parts.append(self.cls)
315
+ scope_parts = [self.mod]
316
+ if self.cls:
317
+ scope_parts.append(self.cls)
252
318
 
253
- if self.current_function_scope: scope_parts.extend(self.current_function_scope)
254
- prefix = '.'.join(filter(None, scope_parts))
255
- if prefix:
256
- var_name = f"{prefix}.{nm}"
257
- else:
258
- var_name = nm
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
- prefix = '.'.join(filter(None, scope_parts))
296
- if prefix:
297
- var_name = f"{prefix}.{name_simple}"
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
- var_name = name_simple
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.add_ref(self.local_var_maps[-1][node.id])
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: skylos
3
- Version: 2.1.1
3
+ Version: 2.2.2
4
4
  Summary: A static analysis tool for Python codebases
5
5
  Author-email: oha <aaronoh2015@gmail.com>
6
6
  Requires-Python: >=3.9
@@ -1,23 +1,28 @@
1
- skylos/__init__.py,sha256=vXBgYBrUqN3o2LxNCrT2pUVGS-Dnnkcakw7jItvQjXA,151
2
- skylos/analyzer.py,sha256=JoQ_otvLmI5k64y3h11jVZEWuA6u3npY15Nwy1vrGi0,14995
3
- skylos/cli.py,sha256=bRxhvKkw_VWQVHT6WxKyX9i3qJjGuJJaEJ3QI8FIpZw,15887
4
- skylos/codemods.py,sha256=A5dNwTJiYtgP3Mv8NQ03etdfi9qNHSECv1GRpLyDCYU,3213
5
- skylos/constants.py,sha256=vAfKls0OuR5idtXzeMDfYv5-wLtYWDe1NW5TSUJT8RI,1636
6
- skylos/framework_aware.py,sha256=eX4oU0jQwDWejkkW4kjRNctre27sVLHK1CTDDiqPqRw,13054
7
- skylos/server.py,sha256=5Rlgy3LdE8I5TWRJJh0n19JqhVYaAOc9fUtRjL-PpX8,16270
8
- skylos/test_aware.py,sha256=kxYoMG2m02kbMlxtFOM-MWJO8qqxHviP9HgAMbKRfvU,2304
9
- skylos/visitor.py,sha256=i-YWVenc8mG0dWzYi945HmktIE2RkgL7rBT7VUnhkLI,18073
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=vZ-0cSF_otm7ZYdp6U3a8eWGwRZ6Tf0tVuJxYqcxdqM,22816
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.1.1.dist-info/METADATA,sha256=Sodf-ShJV4dS2Y0AC611OAO2WmQhZT6aYIz0w0Df6a8,314
32
- skylos-2.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
33
- skylos-2.1.1.dist-info/entry_points.txt,sha256=zzRpN2ByznlQoLeuLolS_TFNYSQxUGBL1EXQsAd6bIA,43
34
- skylos-2.1.1.dist-info/top_level.txt,sha256=f8GA_7KwfaEopPMP8-EXDQXaqd4IbsOQPakZy01LkdQ,12
35
- skylos-2.1.1.dist-info/RECORD,,
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,,