skylos 2.1.1__tar.gz → 2.2.2__tar.gz
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-2.1.1 → skylos-2.2.2}/PKG-INFO +1 -1
- {skylos-2.1.1 → skylos-2.2.2}/README.md +222 -3
- {skylos-2.1.1 → skylos-2.2.2}/pyproject.toml +1 -1
- {skylos-2.1.1 → skylos-2.2.2}/setup.py +1 -1
- skylos-2.2.2/skylos/__init__.py +10 -0
- {skylos-2.1.1 → skylos-2.2.2}/skylos/analyzer.py +54 -9
- {skylos-2.1.1 → skylos-2.2.2}/skylos/cli.py +107 -22
- skylos-2.2.2/skylos/codemods.py +238 -0
- {skylos-2.1.1 → skylos-2.2.2}/skylos/constants.py +2 -3
- skylos-2.2.2/skylos/rules/secrets.py +268 -0
- {skylos-2.1.1 → skylos-2.2.2}/skylos/server.py +1 -12
- {skylos-2.1.1 → skylos-2.2.2}/skylos/visitor.py +97 -24
- {skylos-2.1.1 → skylos-2.2.2}/skylos.egg-info/PKG-INFO +1 -1
- {skylos-2.1.1 → skylos-2.2.2}/skylos.egg-info/SOURCES.txt +7 -2
- skylos-2.2.2/test/sample_repo/__init__.py +0 -0
- skylos-2.2.2/test/sample_repo/sample_repo/__init__.py +0 -0
- {skylos-2.1.1 → skylos-2.2.2}/test/test_analyzer.py +6 -43
- skylos-2.2.2/test/test_new_behaviours.py +52 -0
- skylos-2.2.2/test/test_secrets.py +179 -0
- skylos-2.1.1/skylos/__init__.py +0 -8
- skylos-2.1.1/skylos/codemods.py +0 -89
- {skylos-2.1.1 → skylos-2.2.2}/setup.cfg +0 -0
- {skylos-2.1.1/test → skylos-2.2.2/skylos/rules}/__init__.py +0 -0
- {skylos-2.1.1/test/sample_repo → skylos-2.2.2/skylos/visitors}/__init__.py +0 -0
- {skylos-2.1.1/skylos → skylos-2.2.2/skylos/visitors}/framework_aware.py +0 -0
- {skylos-2.1.1/skylos → skylos-2.2.2/skylos/visitors}/test_aware.py +0 -0
- {skylos-2.1.1 → skylos-2.2.2}/skylos.egg-info/dependency_links.txt +0 -0
- {skylos-2.1.1 → skylos-2.2.2}/skylos.egg-info/entry_points.txt +0 -0
- {skylos-2.1.1 → skylos-2.2.2}/skylos.egg-info/requires.txt +0 -0
- {skylos-2.1.1 → skylos-2.2.2}/skylos.egg-info/top_level.txt +0 -0
- {skylos-2.1.1/test/sample_repo/sample_repo → skylos-2.2.2/test}/__init__.py +0 -0
- {skylos-2.1.1 → skylos-2.2.2}/test/compare_tools.py +0 -0
- {skylos-2.1.1 → skylos-2.2.2}/test/conftest.py +0 -0
- {skylos-2.1.1 → skylos-2.2.2}/test/diagnostics.py +0 -0
- {skylos-2.1.1 → skylos-2.2.2}/test/sample_repo/app.py +0 -0
- {skylos-2.1.1 → skylos-2.2.2}/test/sample_repo/sample_repo/commands.py +0 -0
- {skylos-2.1.1 → skylos-2.2.2}/test/sample_repo/sample_repo/models.py +0 -0
- {skylos-2.1.1 → skylos-2.2.2}/test/sample_repo/sample_repo/routes.py +0 -0
- {skylos-2.1.1 → skylos-2.2.2}/test/sample_repo/sample_repo/utils.py +0 -0
- {skylos-2.1.1 → skylos-2.2.2}/test/test_changes_analyzer.py +0 -0
- {skylos-2.1.1 → skylos-2.2.2}/test/test_cli.py +0 -0
- {skylos-2.1.1 → skylos-2.2.2}/test/test_codemods.py +0 -0
- {skylos-2.1.1 → skylos-2.2.2}/test/test_constants.py +0 -0
- {skylos-2.1.1 → skylos-2.2.2}/test/test_framework_aware.py +0 -0
- {skylos-2.1.1 → skylos-2.2.2}/test/test_integration.py +0 -0
- {skylos-2.1.1 → skylos-2.2.2}/test/test_skylos.py +0 -0
- {skylos-2.1.1 → skylos-2.2.2}/test/test_test_aware.py +0 -0
- {skylos-2.1.1 → skylos-2.2.2}/test/test_visitor.py +0 -0
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
- [Example Output](#example-output)
|
|
33
33
|
- [Interactive Mode](#interactive-mode)
|
|
34
34
|
- [Development](#development)
|
|
35
|
+
- [CI/CD (Pre-commit & GitHub Actions)](#cicd-pre-commit--github-actions)
|
|
35
36
|
- [FAQ](#faq)
|
|
36
37
|
- [Limitations](#limitations)
|
|
37
38
|
- [Troubleshooting](#troubleshooting)
|
|
@@ -51,6 +52,8 @@
|
|
|
51
52
|
* **Unused Imports**: Identifies imports that are not used
|
|
52
53
|
* **Folder Management**: Inclusion/exclusion of directories
|
|
53
54
|
* **Ignore Pragmas**: Skip lines tagged with `# pragma: no skylos`, `# pragma: no cover`, or `# noqa`
|
|
55
|
+
**NEW** **Secrets Scanning (PoC, opt-in)**: Detects API keys & secrets (GitHub, GitLab, Slack, Stripe, AWS, Google, SendGrid, Twilio, private key blocks)
|
|
56
|
+
|
|
54
57
|
|
|
55
58
|
## Benchmark (You can find this benchmark test in `test` folder)
|
|
56
59
|
|
|
@@ -94,12 +97,17 @@ pip install .
|
|
|
94
97
|
```bash
|
|
95
98
|
skylos /path/to/your/project
|
|
96
99
|
|
|
100
|
+
skylos /path/to/your/project --secrets ## include api key scan
|
|
101
|
+
|
|
97
102
|
# To launch the front end
|
|
98
103
|
skylos run
|
|
99
104
|
|
|
100
105
|
# Interactive mode - select items to remove
|
|
101
106
|
skylos --interactive /path/to/your/project
|
|
102
107
|
|
|
108
|
+
# Comment out items
|
|
109
|
+
skylos . --interactive --comment-out
|
|
110
|
+
|
|
103
111
|
# Dry run - see what would be removed
|
|
104
112
|
skylos --interactive --dry-run /path/to/your/project
|
|
105
113
|
|
|
@@ -193,7 +201,7 @@ When Skylos detects a test file, it by default, will apply a confidence penalty
|
|
|
193
201
|
|
|
194
202
|
## Ignoring Pragmas
|
|
195
203
|
|
|
196
|
-
To ignore any warning, indicate `# pragma: no skylos` **ON THE SAME LINE** as the function/class you want to ignore
|
|
204
|
+
1. To ignore any warning, indicate `# pragma: no skylos` **ON THE SAME LINE** as the function/class you want to ignore
|
|
197
205
|
|
|
198
206
|
Example
|
|
199
207
|
|
|
@@ -203,6 +211,14 @@ Example
|
|
|
203
211
|
return new_path
|
|
204
212
|
```
|
|
205
213
|
|
|
214
|
+
2. To suppress a **secret** on a line, add: `# skylos: ignore[SKY-S101]`
|
|
215
|
+
|
|
216
|
+
Example
|
|
217
|
+
```python
|
|
218
|
+
|
|
219
|
+
API_KEY = "ghp_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" # skylos: ignore[SKY-S101]
|
|
220
|
+
```
|
|
221
|
+
|
|
206
222
|
## Including & Excluding Files
|
|
207
223
|
|
|
208
224
|
### Default Exclusions
|
|
@@ -244,6 +260,7 @@ Options:
|
|
|
244
260
|
--no-default-excludes Don't exclude default folders (__pycache__, .git, venv, etc.)
|
|
245
261
|
--list-default-excludes List the default excluded folders and
|
|
246
262
|
-c, --confidence LEVEL Confidence threshold (0-100). Lower values will show more items.
|
|
263
|
+
-- secrets Scan for api keys/secrets
|
|
247
264
|
```
|
|
248
265
|
|
|
249
266
|
## Interactive Mode
|
|
@@ -254,6 +271,206 @@ The interactive mode lets you select specific functions and imports to remove:
|
|
|
254
271
|
2. **Confirm changes**: Review selected items before applying
|
|
255
272
|
3. **Auto-cleanup**: Files are automatically updated
|
|
256
273
|
|
|
274
|
+
## CI/CD (Pre-commit & GitHub Actions)
|
|
275
|
+
|
|
276
|
+
Pick **one** (or use **both**)
|
|
277
|
+
|
|
278
|
+
1. Pre-commit (local + CI): runs Skylos before commits/PRs.
|
|
279
|
+
- You must install pre-commit locally once. Skylos gets installed automatically by the hook.
|
|
280
|
+
|
|
281
|
+
2. GitHub Actions: runs Skylos on pushes/PRs in CI.
|
|
282
|
+
- No local install needed
|
|
283
|
+
|
|
284
|
+
### Option A — Pre-commit (local + CI)
|
|
285
|
+
|
|
286
|
+
1. Create or edit `.pre-commit-config.yaml` at the repo root:
|
|
287
|
+
|
|
288
|
+
**A: Skylos hook repo**
|
|
289
|
+
```yaml
|
|
290
|
+
## .pre-commit-config.yaml
|
|
291
|
+
repos:
|
|
292
|
+
- repo: https://github.com/duriantaco/skylos
|
|
293
|
+
rev: v2.2.2
|
|
294
|
+
hooks:
|
|
295
|
+
- id: skylos-scan
|
|
296
|
+
name: skylos report
|
|
297
|
+
entry: python -m skylos.cli
|
|
298
|
+
language: python
|
|
299
|
+
types_or: [python]
|
|
300
|
+
pass_filenames: false
|
|
301
|
+
require_serial: true
|
|
302
|
+
args: [".", "--output", "report.json", "--confidence", "70"]
|
|
303
|
+
|
|
304
|
+
- repo: local
|
|
305
|
+
hooks:
|
|
306
|
+
- id: skylos-fail-on-findings
|
|
307
|
+
name: skylos
|
|
308
|
+
env:
|
|
309
|
+
SKYLOS_SOFT: "1"
|
|
310
|
+
language: python
|
|
311
|
+
language_version: python3
|
|
312
|
+
pass_filenames: false
|
|
313
|
+
require_serial: true
|
|
314
|
+
entry: >
|
|
315
|
+
python -c "import os, json, sys, pathlib;
|
|
316
|
+
p=pathlib.Path('report.json');
|
|
317
|
+
|
|
318
|
+
if not p.exists():
|
|
319
|
+
sys.exit(0);
|
|
320
|
+
|
|
321
|
+
data=json.loads(p.read_text(encoding='utf-8'));
|
|
322
|
+
|
|
323
|
+
count = 0
|
|
324
|
+
for v in data.values():
|
|
325
|
+
if isinstance(v, list):
|
|
326
|
+
count += len(v)
|
|
327
|
+
|
|
328
|
+
print(f'[skylos] findings: {count}');
|
|
329
|
+
sys.exit(0 if os.getenv('SKYLOS_SOFT') or count==0 else 1)"
|
|
330
|
+
```
|
|
331
|
+
**B: self-contained local hook**
|
|
332
|
+
|
|
333
|
+
```yaml
|
|
334
|
+
repos:
|
|
335
|
+
- repo: local
|
|
336
|
+
hooks:
|
|
337
|
+
- id: skylos-scan
|
|
338
|
+
name: skylos report
|
|
339
|
+
language: python
|
|
340
|
+
entry: python -m skylos.cli
|
|
341
|
+
pass_filenames: false
|
|
342
|
+
require_serial: true
|
|
343
|
+
additional_dependencies: [skylos==2.2.2]
|
|
344
|
+
args: [".", "--output", "report.json", "--confidence", "70"]
|
|
345
|
+
|
|
346
|
+
- id: skylos-fail-on-findings
|
|
347
|
+
name: skylos (soft)
|
|
348
|
+
language: python
|
|
349
|
+
language_version: python3
|
|
350
|
+
pass_filenames: false
|
|
351
|
+
require_serial: true
|
|
352
|
+
entry: >
|
|
353
|
+
python -c "import os, json, sys, pathlib;
|
|
354
|
+
p=pathlib.Path('report.json');
|
|
355
|
+
|
|
356
|
+
if not p.exists():
|
|
357
|
+
sys.exit(0);
|
|
358
|
+
|
|
359
|
+
data=json.loads(p.read_text(encoding='utf-8'));
|
|
360
|
+
|
|
361
|
+
count = 0
|
|
362
|
+
for v in data.values():
|
|
363
|
+
if isinstance(v, list):
|
|
364
|
+
count += len(v)
|
|
365
|
+
|
|
366
|
+
print(f'[skylos] findings: {count}');
|
|
367
|
+
sys.exit(0 if os.getenv('SKYLOS_SOFT') or count==0 else 1)"
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
**Install requirements:**
|
|
371
|
+
|
|
372
|
+
You must install pre-commit locally once:
|
|
373
|
+
```bash
|
|
374
|
+
pip install pre-commit
|
|
375
|
+
pre-commit install
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
2. pre-commit run --all-files
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
3. Run the same hooks in CI (GitHub Actions): create .github/workflows/pre-commit.yml:
|
|
382
|
+
|
|
383
|
+
```yaml
|
|
384
|
+
name: pre-commit
|
|
385
|
+
on: [push, pull_request]
|
|
386
|
+
jobs:
|
|
387
|
+
run:
|
|
388
|
+
runs-on: ubuntu-latest
|
|
389
|
+
steps:
|
|
390
|
+
- uses: actions/checkout@v4
|
|
391
|
+
- uses: actions/setup-python@v5
|
|
392
|
+
with: { python-version: "3.11", cache: "pip" }
|
|
393
|
+
- uses: pre-commit/action@v3.0.1
|
|
394
|
+
with: { extra_args: --all-files }
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
**Pre commit behavior:** the second hook is soft by default (SKYLOS_SOFT=1). This means that it prints findings and passes. You can remove the env/logic if you want pre-commit to block commits on finding
|
|
398
|
+
|
|
399
|
+
### Option B — Github Actions
|
|
400
|
+
|
|
401
|
+
1. Create .github/workflows/skylos.yml:
|
|
402
|
+
|
|
403
|
+
```yaml
|
|
404
|
+
name: Skylos Deadcode Scan
|
|
405
|
+
|
|
406
|
+
on:
|
|
407
|
+
pull_request:
|
|
408
|
+
push:
|
|
409
|
+
branches: [ main, master ]
|
|
410
|
+
workflow_dispatch:
|
|
411
|
+
|
|
412
|
+
jobs:
|
|
413
|
+
scan:
|
|
414
|
+
runs-on: ubuntu-latest
|
|
415
|
+
env:
|
|
416
|
+
SKYLOS_STRICT: ${{ vars.SKYLOS_STRICT || 'false' }}
|
|
417
|
+
steps:
|
|
418
|
+
- uses: actions/checkout@v4
|
|
419
|
+
|
|
420
|
+
- uses: actions/setup-python@v5
|
|
421
|
+
with:
|
|
422
|
+
python-version: '3.11'
|
|
423
|
+
cache: 'pip'
|
|
424
|
+
|
|
425
|
+
- name: Install Skylos
|
|
426
|
+
run: pip install skylos
|
|
427
|
+
|
|
428
|
+
- name: Run Skylos
|
|
429
|
+
env:
|
|
430
|
+
REPORT: skylos_${{ github.run_number }}_${{ github.sha }}.json
|
|
431
|
+
run: |
|
|
432
|
+
echo "REPORT=$REPORT" >> "$GITHUB_OUTPUT"
|
|
433
|
+
skylos . --json > "$REPORT"
|
|
434
|
+
id: scan
|
|
435
|
+
|
|
436
|
+
- name: Fail if there are findings
|
|
437
|
+
continue-on-error: ${{ env.SKYLOS_STRICT != 'true' }}
|
|
438
|
+
env:
|
|
439
|
+
REPORT: ${{ steps.scan.outputs.REPORT }}
|
|
440
|
+
run: |
|
|
441
|
+
python - << 'PY'
|
|
442
|
+
import json, sys, os
|
|
443
|
+
report = os.environ["REPORT"]
|
|
444
|
+
data = json.load(open(report, "r", encoding="utf-8"))
|
|
445
|
+
count = 0
|
|
446
|
+
for value in data.values():
|
|
447
|
+
if isinstance(value, list):
|
|
448
|
+
count += len(value)
|
|
449
|
+
print(f"Findings: {count}")
|
|
450
|
+
if count > 0:
|
|
451
|
+
print(f"::warning title=Skylos findings::{count} potential issues found. See {report}")
|
|
452
|
+
sys.exit(1 if count > 0 else 0)
|
|
453
|
+
PY
|
|
454
|
+
|
|
455
|
+
- name: Upload report artifact
|
|
456
|
+
if: always()
|
|
457
|
+
uses: actions/upload-artifact@v4
|
|
458
|
+
with:
|
|
459
|
+
name: ${{ steps.scan.outputs.REPORT }}
|
|
460
|
+
path: ${{ steps.scan.outputs.REPORT }}
|
|
461
|
+
|
|
462
|
+
- name: Summarize in job log
|
|
463
|
+
if: always()
|
|
464
|
+
run: |
|
|
465
|
+
echo "Skylos report: ${{ steps.scan.outputs.REPORT }}" >> $GITHUB_STEP_SUMMARY
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
**To make the job fail on findings (strict mode)**:
|
|
469
|
+
|
|
470
|
+
1. Go to GitHub -> Settings -> Secrets and variables -> Actions -> Variables
|
|
471
|
+
|
|
472
|
+
2. Add variable SKYLOS_STRICT with value true
|
|
473
|
+
|
|
257
474
|
## Development
|
|
258
475
|
|
|
259
476
|
### Prerequisites
|
|
@@ -307,6 +524,7 @@ A: Start with 60 (default) for safe cleanup. Use 30 for framework applications.
|
|
|
307
524
|
- **Frameworks**: Django models, Flask, FastAPI routes may appear unused but aren't
|
|
308
525
|
- **Test data**: Limited scenarios, your mileage may vary
|
|
309
526
|
- **False positives**: Always manually review before deleting code
|
|
527
|
+
- **Secrets PoC**: May emit both a provider hit and a generic high-entropy hit for the same token. All tokens are detected only in py files (`.py`, `.pyi`, `.pyw`).
|
|
310
528
|
|
|
311
529
|
## Troubleshooting
|
|
312
530
|
|
|
@@ -339,9 +557,10 @@ We welcome contributions! Please read our [Contributing Guidelines](CONTRIBUTING
|
|
|
339
557
|
## Roadmap
|
|
340
558
|
- [x] Expand our test cases
|
|
341
559
|
- [ ] Configuration file support
|
|
342
|
-
- [
|
|
343
|
-
- [
|
|
560
|
+
- [x] Git hooks integration
|
|
561
|
+
- [x] CI/CD integration examples
|
|
344
562
|
- [ ] Further optimization
|
|
563
|
+
- [ ] Add new rules
|
|
345
564
|
|
|
346
565
|
## License
|
|
347
566
|
|
|
@@ -7,10 +7,11 @@ from pathlib import Path
|
|
|
7
7
|
from collections import defaultdict
|
|
8
8
|
from skylos.visitor import Visitor
|
|
9
9
|
from skylos.constants import ( PENALTIES, AUTO_CALLED )
|
|
10
|
-
from skylos.test_aware import TestAwareVisitor
|
|
10
|
+
from skylos.visitors.test_aware import TestAwareVisitor
|
|
11
|
+
from skylos.rules.secrets import scan_ctx as _secrets_scan_ctx
|
|
11
12
|
import os
|
|
12
13
|
import traceback
|
|
13
|
-
from skylos.framework_aware import FrameworkAwareVisitor, detect_framework_usage
|
|
14
|
+
from skylos.visitors.framework_aware import FrameworkAwareVisitor, detect_framework_usage
|
|
14
15
|
|
|
15
16
|
logging.basicConfig(level=logging.INFO,format='%(asctime)s - %(levelname)s - %(message)s')
|
|
16
17
|
logger=logging.getLogger('Skylos')
|
|
@@ -155,18 +156,40 @@ class Skylos:
|
|
|
155
156
|
|
|
156
157
|
def _apply_penalties(self, def_obj, visitor, framework):
|
|
157
158
|
confidence=100
|
|
159
|
+
|
|
160
|
+
if getattr(visitor, "ignore_lines", None) and def_obj.line in visitor.ignore_lines:
|
|
161
|
+
def_obj.confidence = 0
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
if def_obj.type == "variable" and def_obj.simple_name == "_":
|
|
165
|
+
def_obj.confidence = 0
|
|
166
|
+
return
|
|
167
|
+
|
|
158
168
|
if def_obj.simple_name.startswith("_") and not def_obj.simple_name.startswith("__"):
|
|
159
169
|
confidence -= PENALTIES["private_name"]
|
|
170
|
+
|
|
160
171
|
if def_obj.simple_name.startswith("__") and def_obj.simple_name.endswith("__"):
|
|
161
172
|
confidence -= PENALTIES["dunder_or_magic"]
|
|
162
|
-
|
|
163
|
-
confidence = 0
|
|
173
|
+
|
|
164
174
|
if def_obj.in_init and def_obj.type in ("function", "class"):
|
|
165
175
|
confidence -= PENALTIES["in_init_file"]
|
|
176
|
+
|
|
166
177
|
if def_obj.name.split(".")[0] in self.dynamic:
|
|
167
178
|
confidence -= PENALTIES["dynamic_module"]
|
|
179
|
+
|
|
168
180
|
if visitor.is_test_file or def_obj.line in visitor.test_decorated_lines:
|
|
169
181
|
confidence -= PENALTIES["test_related"]
|
|
182
|
+
|
|
183
|
+
if def_obj.type == "variable" and getattr(framework, "dataclass_fields", None):
|
|
184
|
+
if def_obj.name in framework.dataclass_fields:
|
|
185
|
+
def_obj.confidence = 0
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
if def_obj.type == "variable":
|
|
189
|
+
fr = getattr(framework, "first_read_lineno", {}).get(def_obj.name)
|
|
190
|
+
if fr is not None and fr >= def_obj.line:
|
|
191
|
+
def_obj.confidence = 0
|
|
192
|
+
return
|
|
170
193
|
|
|
171
194
|
framework_confidence = detect_framework_usage(def_obj, visitor=framework)
|
|
172
195
|
if framework_confidence is not None:
|
|
@@ -215,7 +238,7 @@ class Skylos:
|
|
|
215
238
|
if method.simple_name == "format" and cls.endswith("Formatter"):
|
|
216
239
|
method.references += 1
|
|
217
240
|
|
|
218
|
-
def analyze(self, path, thr=60, exclude_folders=None):
|
|
241
|
+
def analyze(self, path, thr=60, exclude_folders= None, enable_secrets = False):
|
|
219
242
|
files, root = self._get_python_files(path, exclude_folders)
|
|
220
243
|
|
|
221
244
|
if not files:
|
|
@@ -238,6 +261,7 @@ class Skylos:
|
|
|
238
261
|
for f in files:
|
|
239
262
|
modmap[f] = self._module(root, f)
|
|
240
263
|
|
|
264
|
+
all_secrets = []
|
|
241
265
|
for file in files:
|
|
242
266
|
mod = modmap[file]
|
|
243
267
|
defs, refs, dyn, exports, test_flags, framework_flags = proc_file(file, mod)
|
|
@@ -250,6 +274,16 @@ class Skylos:
|
|
|
250
274
|
self.dynamic.update(dyn)
|
|
251
275
|
self.exports[mod].update(exports)
|
|
252
276
|
|
|
277
|
+
if enable_secrets and _secrets_scan_ctx is not None:
|
|
278
|
+
try:
|
|
279
|
+
src_lines = Path(file).read_text(encoding="utf-8", errors="ignore").splitlines(True)
|
|
280
|
+
ctx = {"relpath": str(file), "lines": src_lines, "tree": None}
|
|
281
|
+
findings = list(_secrets_scan_ctx(ctx))
|
|
282
|
+
if findings:
|
|
283
|
+
all_secrets.extend(findings)
|
|
284
|
+
except Exception:
|
|
285
|
+
pass
|
|
286
|
+
|
|
253
287
|
self._mark_refs()
|
|
254
288
|
self._apply_heuristics()
|
|
255
289
|
self._mark_exports()
|
|
@@ -262,7 +296,7 @@ class Skylos:
|
|
|
262
296
|
for d in sorted(self.defs.values(), key=def_sort_key):
|
|
263
297
|
if shown >= 50:
|
|
264
298
|
break
|
|
265
|
-
print(f" {d.type
|
|
299
|
+
print(f" type={d.type} refs={d.references} conf={d.confidence} exported={d.is_exported} line={d.line} name={d.name}")
|
|
266
300
|
shown += 1
|
|
267
301
|
|
|
268
302
|
unused = []
|
|
@@ -281,6 +315,9 @@ class Skylos:
|
|
|
281
315
|
"excluded_folders": exclude_folders or [],
|
|
282
316
|
}
|
|
283
317
|
}
|
|
318
|
+
|
|
319
|
+
if enable_secrets and all_secrets:
|
|
320
|
+
result["secrets"] = all_secrets
|
|
284
321
|
|
|
285
322
|
for u in unused:
|
|
286
323
|
if u["type"] in ("function", "method"):
|
|
@@ -304,10 +341,13 @@ def proc_file(file_or_args, mod=None):
|
|
|
304
341
|
|
|
305
342
|
try:
|
|
306
343
|
source = Path(file).read_text(encoding="utf-8")
|
|
307
|
-
|
|
344
|
+
ignore_lines = {i for i, line in enumerate(source.splitlines(), start=1)
|
|
345
|
+
if "pragma: no skylos" in line}
|
|
346
|
+
tree = ast.parse(source)
|
|
308
347
|
|
|
309
348
|
tv = TestAwareVisitor(filename=file)
|
|
310
349
|
tv.visit(tree)
|
|
350
|
+
tv.ignore_lines = ignore_lines
|
|
311
351
|
|
|
312
352
|
fv = FrameworkAwareVisitor(filename=file)
|
|
313
353
|
fv.visit(tree)
|
|
@@ -315,18 +355,23 @@ def proc_file(file_or_args, mod=None):
|
|
|
315
355
|
v = Visitor(mod, file)
|
|
316
356
|
v.visit(tree)
|
|
317
357
|
|
|
358
|
+
fv.dataclass_fields = getattr(v, "dataclass_fields", set())
|
|
359
|
+
fv.first_read_lineno = getattr(v, "first_read_lineno", {})
|
|
360
|
+
|
|
318
361
|
return v.defs, v.refs, v.dyn, v.exports, tv, fv
|
|
362
|
+
|
|
319
363
|
except Exception as e:
|
|
320
364
|
logger.error(f"{file}: {e}")
|
|
321
365
|
if os.getenv("SKYLOS_DEBUG"):
|
|
322
366
|
logger.error(traceback.format_exc())
|
|
323
367
|
dummy_visitor = TestAwareVisitor(filename=file)
|
|
368
|
+
dummy_visitor.ignore_lines = set()
|
|
324
369
|
dummy_framework_visitor = FrameworkAwareVisitor(filename=file)
|
|
325
370
|
|
|
326
371
|
return [], [], set(), set(), dummy_visitor, dummy_framework_visitor
|
|
327
372
|
|
|
328
|
-
def analyze(path,conf=60, exclude_folders=None):
|
|
329
|
-
return Skylos().analyze(path,conf, exclude_folders)
|
|
373
|
+
def analyze(path, conf=60, exclude_folders=None, enable_secrets=False):
|
|
374
|
+
return Skylos().analyze(path,conf, exclude_folders, enable_secrets)
|
|
330
375
|
|
|
331
376
|
if __name__ == "__main__":
|
|
332
377
|
if len(sys.argv)>1:
|