codesnake 1.2.1__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.
- codesnake-1.2.1/LICENSE +21 -0
- codesnake-1.2.1/PKG-INFO +377 -0
- codesnake-1.2.1/README.md +345 -0
- codesnake-1.2.1/pyproject.toml +55 -0
- codesnake-1.2.1/setup.cfg +4 -0
- codesnake-1.2.1/src/codesnake/__init__.py +77 -0
- codesnake-1.2.1/src/codesnake/__main__.py +5 -0
- codesnake-1.2.1/src/codesnake/_version.py +3 -0
- codesnake-1.2.1/src/codesnake/banner.py +76 -0
- codesnake-1.2.1/src/codesnake/checker.py +2755 -0
- codesnake-1.2.1/src/codesnake/cli.py +168 -0
- codesnake-1.2.1/src/codesnake.egg-info/PKG-INFO +377 -0
- codesnake-1.2.1/src/codesnake.egg-info/SOURCES.txt +16 -0
- codesnake-1.2.1/src/codesnake.egg-info/dependency_links.txt +1 -0
- codesnake-1.2.1/src/codesnake.egg-info/entry_points.txt +2 -0
- codesnake-1.2.1/src/codesnake.egg-info/requires.txt +10 -0
- codesnake-1.2.1/src/codesnake.egg-info/top_level.txt +1 -0
- codesnake-1.2.1/test/test_codesnake.py +2612 -0
codesnake-1.2.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CodeSnake Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
codesnake-1.2.1/PKG-INFO
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codesnake
|
|
3
|
+
Version: 1.2.1
|
|
4
|
+
Summary: Semantic code checker for Python 3
|
|
5
|
+
Author: CodeSnake Contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/bitWarrior/codesnake
|
|
8
|
+
Project-URL: Repository, https://github.com/bitWarrior/codesnake
|
|
9
|
+
Project-URL: Issues, https://github.com/bitWarrior/codesnake/issues
|
|
10
|
+
Keywords: linter,static-analysis,ast,security
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Provides-Extra: tools
|
|
24
|
+
Requires-Dist: bandit>=1.9.3; extra == "tools"
|
|
25
|
+
Requires-Dist: flake8>=7.3.0; extra == "tools"
|
|
26
|
+
Requires-Dist: isort>=7.0.0; extra == "tools"
|
|
27
|
+
Requires-Dist: mypy>=1.19.1; extra == "tools"
|
|
28
|
+
Requires-Dist: pylint>=4.0.4; extra == "tools"
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: codesnake[tools]; extra == "dev"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# CodeSnake
|
|
34
|
+
|
|
35
|
+
[](https://github.com/bitWarrior/codesnake/actions/workflows/ci.yml)
|
|
36
|
+
[](https://pypi.org/project/codesnake/)
|
|
37
|
+
[](https://pypi.org/project/codesnake/)
|
|
38
|
+
[](LICENSE)
|
|
39
|
+
|
|
40
|
+
Semantic code checker for Python 3. It parses files into an AST, walks them, and reports security problems, common bugs, unused names, and complexity smells.
|
|
41
|
+
|
|
42
|
+
Two properties set it apart from the fast general-purpose linters:
|
|
43
|
+
|
|
44
|
+
- **It never imports or executes the code it analyzes.** Everything runs on the AST from `ast.parse`, so pointing it at untrusted Python — a fork's pull request, a submitted plugin — does not run that Python.
|
|
45
|
+
- **It has no runtime dependencies.** Standard library only, so it vendors cleanly, works air-gapped, and adds nothing to your supply chain.
|
|
46
|
+
|
|
47
|
+
It also does light **taint tracking**: `eval()` on a literal is `info`, `eval()` on something derived from `input()` or `request.args` is an `error`. See [how it compares](#how-it-compares) to Ruff, Bandit, and pylint.
|
|
48
|
+
|
|
49
|
+
Requires **Python 3.10+**.
|
|
50
|
+
|
|
51
|
+
## Install
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
python3 -m venv codesnake-venv
|
|
55
|
+
source codesnake-venv/bin/activate
|
|
56
|
+
pip install -e .
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Optional companion tools (pylint, flake8, mypy, bandit, isort):
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install -e ".[tools]"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Or run `./setup.sh`, which creates the venv and installs from `pyproject.toml`.
|
|
66
|
+
|
|
67
|
+
After an editable install, `codesnake` is on `PATH`. You can also run:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
python -m codesnake check file.py # after install
|
|
71
|
+
PYTHONPATH=src python -m codesnake file.py # straight from a checkout
|
|
72
|
+
./codesnake.sh file.py # creates/activates codesnake-venv/
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## First run on an existing codebase
|
|
76
|
+
|
|
77
|
+
CodeSnake reports complexity, length, and unused-name findings by default, so the
|
|
78
|
+
first run on a mature codebase is loud — expect roughly ten warnings per file. That
|
|
79
|
+
is a backlog, not an emergency: only `error` severity fails the run. Start narrow and
|
|
80
|
+
widen when you are ready.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# 1. What would actually fail CI. Start here.
|
|
84
|
+
codesnake check --severity error src/
|
|
85
|
+
|
|
86
|
+
# 2. Snapshot everything else, so CI only fails on NEW findings.
|
|
87
|
+
codesnake check --update-baseline .codesnake-baseline.json src/
|
|
88
|
+
git add .codesnake-baseline.json
|
|
89
|
+
|
|
90
|
+
# 3. From now on, this is your CI command.
|
|
91
|
+
codesnake check --baseline .codesnake-baseline.json src/
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Then tune thresholds in `.codesnake.json` and shrink the baseline as you go. The full
|
|
95
|
+
adoption path is in [docs/INTEGRATIONS.md](docs/INTEGRATIONS.md#adopting-codesnake-on-an-existing-codebase).
|
|
96
|
+
|
|
97
|
+
## Usage
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# Files or directories (walks *.py, skips venvs, caches, and .gitignore)
|
|
101
|
+
codesnake check src/codesnake/checker.py test/example_bad_code.py
|
|
102
|
+
codesnake check src/
|
|
103
|
+
|
|
104
|
+
# Same thing without the subcommand
|
|
105
|
+
codesnake src/
|
|
106
|
+
|
|
107
|
+
# JSON for CI
|
|
108
|
+
codesnake check --format json --no-color src/
|
|
109
|
+
|
|
110
|
+
# Errors only
|
|
111
|
+
codesnake check --severity error src/
|
|
112
|
+
|
|
113
|
+
# Merge Bandit findings (needs pip install -e ".[tools]")
|
|
114
|
+
codesnake check --bandit src/
|
|
115
|
+
|
|
116
|
+
# Only Python files staged in git
|
|
117
|
+
codesnake check --staged
|
|
118
|
+
|
|
119
|
+
# Snapshot findings, then fail only on new ones
|
|
120
|
+
codesnake check --update-baseline .codesnake-baseline.json src/
|
|
121
|
+
codesnake check --baseline .codesnake-baseline.json src/
|
|
122
|
+
|
|
123
|
+
# Custom thresholds
|
|
124
|
+
codesnake check --config .codesnake.json src/
|
|
125
|
+
|
|
126
|
+
# Write a default config file (refuses to overwrite; pass --force to replace)
|
|
127
|
+
codesnake config -o .codesnake.json
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### CLI flags
|
|
131
|
+
|
|
132
|
+
| Flag | Meaning |
|
|
133
|
+
|---|---|
|
|
134
|
+
| `--config PATH` | `.codesnake.json` or a `pyproject.toml` with `[tool.codesnake]` (otherwise the nearest one found walking up to the repository root, else defaults) |
|
|
135
|
+
| `--format text\|json\|github\|sarif` | Report format (default `text`) |
|
|
136
|
+
| `--severity error\|warning\|info` | Minimum severity to print |
|
|
137
|
+
| `--no-color` | Disable ANSI color (`NO_COLOR` also works) |
|
|
138
|
+
| `--bandit` | Merge Bandit results when the `bandit` executable is installed |
|
|
139
|
+
| `--staged` | Check `git diff --cached` Python files only |
|
|
140
|
+
| `--baseline FILE` | Hide issues whose fingerprint is already in the baseline |
|
|
141
|
+
| `--update-baseline FILE` | Write the current finding set as a baseline |
|
|
142
|
+
| `-j`, `--jobs N` | Worker processes (default: auto — one per CPU once 8+ files are checked; `1` disables) |
|
|
143
|
+
|
|
144
|
+
`--staged` needs no file arguments and works from any directory inside the repository (paths from git are resolved against the repo root). With no staged `.py` files it exits **0**. `--baseline` fingerprints are `filename|code|message-with-numbers-normalized|occurrence`, so line-only edits and count changes (`52 lines long` → `53 lines long`) do not re-fail CI, while a *second* identical violation in the same file still does. Version-1 baselines are read transparently; `--update-baseline` writes version 2. A missing baseline file fails closed (exit 1).
|
|
145
|
+
|
|
146
|
+
### Output formats
|
|
147
|
+
|
|
148
|
+
| `--format` | Use |
|
|
149
|
+
|---|---|
|
|
150
|
+
| `text` (default) | Human-readable; color when stdout is a TTY; includes a one-line suggestion |
|
|
151
|
+
| `json` | Per-file issues plus a summary (`end_line`, `end_col`, `suggestion`, `source`) |
|
|
152
|
+
| `github` | GitHub Actions workflow commands (`::error file=...,line=...,col=...,title=...::message`), properly `%`-escaped |
|
|
153
|
+
| `sarif` | SARIF 2.1.0 with rule metadata (`helpUri`, default level, suggestion) and repo-relative URIs for code-scanning dashboards |
|
|
154
|
+
|
|
155
|
+
### Exit codes
|
|
156
|
+
|
|
157
|
+
| Code | Meaning |
|
|
158
|
+
|---|---|
|
|
159
|
+
| `0` | No error-severity issues (or `--staged` with no staged Python files) |
|
|
160
|
+
| `1` | At least one error, I/O failure, syntax error, bad config/baseline, or git failure |
|
|
161
|
+
| `2` | CLI usage error (unknown flag, or `check` with neither files nor `--staged`) |
|
|
162
|
+
|
|
163
|
+
Missing files, empty directories, and decode failures are **IO001**. Syntax errors are **SYN001**. Those always fail the run; they are never printed as “no issues found.”
|
|
164
|
+
|
|
165
|
+
## What it checks
|
|
166
|
+
|
|
167
|
+
| Code | Severity | What |
|
|
168
|
+
|---|---|---|
|
|
169
|
+
| **SEC001** | info / error | `eval()` / `exec()` — **info** on a constant, **error** on untrusted input |
|
|
170
|
+
| **SEC002** | warning | Unsafe deserialization: `pickle` / `dill` / `cloudpickle` / `jsonpickle` loads, `pickle.Unpickler(...).load()`, `marshal.load(s)`, `shelve.open`, and `yaml.load` without a safe `Loader` |
|
|
171
|
+
| **SEC003** | warning / error | `subprocess` with `shell=True`, or `os.system` / `os.popen` / `subprocess.getoutput` (**error** if the command is untrusted) |
|
|
172
|
+
| **SEC004** | warning | `subprocess` command (`run`, `call`, `Popen`, `check_call`, `check_output`) built from untrusted input |
|
|
173
|
+
| **BUG001** | error | Mutable default arguments (`[]`, `{}`, `set()`, `list()`, kw-only, `lambda`, `async def`) |
|
|
174
|
+
| **BUG002** | warning | Duplicate key in a dict literal, including tuple keys |
|
|
175
|
+
| **EXC001** | warning | Bare `except:` |
|
|
176
|
+
| **EXC002** | info | `except Exception`, including `builtins.Exception` and tuple clauses like `except (ValueError, Exception)` |
|
|
177
|
+
| **EXC003** | warning | Empty `except` body (`pass`) |
|
|
178
|
+
| **EXC004** | warning | `raise Exception()` with no message |
|
|
179
|
+
| **EXC005** | warning | `raise NewError(...)` inside `except` / `except*` without `from` |
|
|
180
|
+
| **COMP001** | warning | Too many parameters |
|
|
181
|
+
| **COMP002** | warning | Cyclomatic complexity too high (nested functions are not charged to the parent) |
|
|
182
|
+
| **COMP003** | warning | Function longer than the configured maximum |
|
|
183
|
+
| **COMP004** | warning | Class with too many methods |
|
|
184
|
+
| **COMP005** | warning | Too many `self.*` **assignments** in `__init__` (method calls are ignored) |
|
|
185
|
+
| **PERF001** | info | `for i in range(len(...))` |
|
|
186
|
+
| **STYLE001** | info | `is True` / `is False` |
|
|
187
|
+
| **IMP001** | warning | `from module import *` |
|
|
188
|
+
| **IMP002** | warning | Imported name is never used (module level or inside a function) |
|
|
189
|
+
| **IMP003** | error | Relative import of a name the sibling module does not define |
|
|
190
|
+
| **VAR001** | warning | Unused local or nested function (loop targets, tuple unpacking, bare annotations, and decorated nested functions are exempt) |
|
|
191
|
+
| **VAR002** | warning | Unused argument (`self` / `cls`, `_`-prefixed names, `*args` / `**kwargs`, lambda and dunder-method parameters, and abstract/stub bodies are skipped) |
|
|
192
|
+
| **VAR003** | info | Local name shadows an enclosing function binding |
|
|
193
|
+
| **REL002** | info | `assert` is stripped under `-O` (skipped in `test_*.py`, `*_test.py`, `conftest.py`, and `test(s)/` directories) |
|
|
194
|
+
| **RES001** | warning | `open()` used without `with` (anything inside a `with` item, `contextlib.closing(...)`, or `stack.enter_context(...)` counts as owned) |
|
|
195
|
+
| **ASY001** | warning | `async def` that never `await`s (stubs and `@abstractmethod` skipped) |
|
|
196
|
+
| **SYN001** | error | Syntax error |
|
|
197
|
+
| **IO001** | error | File missing, not a file, unreadable, or empty directory |
|
|
198
|
+
| **B###** | varies | Bandit test ids, only when `--bandit` / `use_bandit` is on (`source: bandit`) |
|
|
199
|
+
|
|
200
|
+
Call checks resolve imports (`from subprocess import call`, `import pickle as pkl`) instead of matching only the AST shape. `shell=True` is also detected via a local or module constant (`shell = True; run(..., shell=shell)`); reassigning the name invalidates the constant.
|
|
201
|
+
|
|
202
|
+
Function bodies are analyzed after the enclosing scope is fully bound, so a closure that references a variable assigned *after* the `def` does not produce a false "unused variable" warning.
|
|
203
|
+
|
|
204
|
+
Untrusted input (taint) is tracked from `input()`, `sys.argv`, `os.environ` / `os.getenv`, and `args` / `GET` / `POST` / `json` / `form`-style attributes read from a request object (`request`, `req`, `self.request`), including f-strings, `+`, `.format()`, `.get()`, and subscripts. Passing tainted data through `shlex.quote`, `int()`, `re.escape`, `html.escape`, or `urllib.parse.quote` clears the taint.
|
|
205
|
+
|
|
206
|
+
**IMP003** runs when several files are checked together. `from .foo import bar` is an error only if `foo.py` (or `foo/__init__.py`) is in the same run and does not define `bar`. Unused imports inside `if TYPE_CHECKING:` and names listed in `__all__` are not flagged as IMP002.
|
|
207
|
+
|
|
208
|
+
### Suppressing findings
|
|
209
|
+
|
|
210
|
+
On the same line as the issue:
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
eval("1+1") # noqa
|
|
214
|
+
eval("1+1") # noqa: SEC001
|
|
215
|
+
import os # codesnake: ignore
|
|
216
|
+
import os # codesnake: ignore=IMP002
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
`# noqa` with no codes suppresses every finding on that line.
|
|
220
|
+
|
|
221
|
+
## Configuration
|
|
222
|
+
|
|
223
|
+
CodeSnake looks for configuration starting in the current directory and walking up to the repository root (the first directory containing `.git`). In each directory a `.codesnake.json` wins over a `pyproject.toml` `[tool.codesnake]` table. `--config PATH` (JSON or TOML) overrides discovery.
|
|
224
|
+
|
|
225
|
+
```toml
|
|
226
|
+
# pyproject.toml — same keys as the JSON file (reading TOML needs Python 3.11+)
|
|
227
|
+
[tool.codesnake]
|
|
228
|
+
max_complexity = 8
|
|
229
|
+
check_style = false
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
{
|
|
234
|
+
"max_function_length": 50,
|
|
235
|
+
"max_function_params": 7,
|
|
236
|
+
"max_complexity": 10,
|
|
237
|
+
"max_class_methods": 20,
|
|
238
|
+
"max_instance_vars": 10,
|
|
239
|
+
"check_security": true,
|
|
240
|
+
"check_bugs": true,
|
|
241
|
+
"check_exceptions": true,
|
|
242
|
+
"check_complexity": true,
|
|
243
|
+
"check_performance": true,
|
|
244
|
+
"check_imports": true,
|
|
245
|
+
"check_style": true,
|
|
246
|
+
"check_unused": true,
|
|
247
|
+
"check_reliability": true,
|
|
248
|
+
"use_bandit": false,
|
|
249
|
+
"report_errors": true,
|
|
250
|
+
"report_warnings": true,
|
|
251
|
+
"report_info": true
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`max_*` thresholds must be 1 or greater; anything lower is a config error. `check_*` turns whole categories off (`check_reliability` covers REL002 and ASY001). `report_*` filters by severity. `use_bandit` merges Bandit when it is installed. A stricter sample lives in `examples/strict.codesnake.json`.
|
|
256
|
+
|
|
257
|
+
## Library API
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
from codesnake import CheckerConfig, SemanticChecker, check_file, run_check
|
|
261
|
+
|
|
262
|
+
issues = SemanticChecker(source, filename="app.py").analyze()
|
|
263
|
+
issues = check_file("app.py") # I/O failures become IO001
|
|
264
|
+
issues = check_file("app.py", source=text) # analyze already-read text
|
|
265
|
+
|
|
266
|
+
config = CheckerConfig(max_complexity=8, check_style=False)
|
|
267
|
+
rc = run_check(
|
|
268
|
+
["app.py", "pkg/"],
|
|
269
|
+
config=config,
|
|
270
|
+
output_format="json",
|
|
271
|
+
min_severity="warning",
|
|
272
|
+
color=False,
|
|
273
|
+
staged=False,
|
|
274
|
+
baseline_path=".codesnake-baseline.json",
|
|
275
|
+
use_bandit=False,
|
|
276
|
+
)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Each `Issue` includes `line`, `col`, `end_line`, `end_col`, `suggestion`, and `source` (`codesnake` or `bandit`). `col` / `end_col` are **0-based character offsets** (AST byte offsets are converted), and JSON output reports them as-is. The `text`, `github`, and `sarif` formats print **1-based** columns.
|
|
280
|
+
|
|
281
|
+
## How it compares
|
|
282
|
+
|
|
283
|
+
CodeSnake is not trying to replace Ruff. Use both.
|
|
284
|
+
|
|
285
|
+
| | CodeSnake | Ruff | Bandit | pylint |
|
|
286
|
+
|---|---|---|---|---|
|
|
287
|
+
| Speed, 167 stdlib files | 1.7s | **0.14s** | 9.1s | 17.4s |
|
|
288
|
+
| Rules | ~25 | 800+ | ~70 security | 400+ |
|
|
289
|
+
| Runtime dependencies | **none** | none (Rust binary) | several | several |
|
|
290
|
+
| Imports the analyzed code | **never** | never | never | in some modes |
|
|
291
|
+
| Taint tracking | **yes** | no | limited | no |
|
|
292
|
+
| Autofix | no | **yes** | no | no |
|
|
293
|
+
| SARIF output | **yes** | no | **yes** | no |
|
|
294
|
+
| Baselines | **yes** | no | via `--baseline` | no |
|
|
295
|
+
|
|
296
|
+
**Ruff is roughly 13x faster and has 30x the rules.** If you want one fast
|
|
297
|
+
general-purpose linter with autofix, use Ruff — CodeSnake is not competing for that job.
|
|
298
|
+
Among the Python-implemented checkers, though, CodeSnake is the quick one: about 5x
|
|
299
|
+
faster than Bandit and 10x faster than pylint on the same files.
|
|
300
|
+
|
|
301
|
+
<sub>Measured on Python 3.12, best of 2–3 runs over the same 167 files from the standard
|
|
302
|
+
library, each tool using its own parallelism where it has any (`codesnake` auto,
|
|
303
|
+
`pylint -j 0`). pylint ran with `--disable=all --enable=W,E`, a reduced rule set in its
|
|
304
|
+
favor. Your numbers will differ; the ranking is the point, not the digits.</sub>
|
|
305
|
+
|
|
306
|
+
CodeSnake is worth adding when you want one of these:
|
|
307
|
+
|
|
308
|
+
- **Taint tracking.** `eval(x)` where `x` came from `input()` or `request.args` is an
|
|
309
|
+
`error`; `eval("1+1")` is `info`. The fast linters flag the call site without asking
|
|
310
|
+
where the data came from.
|
|
311
|
+
- **Analysis of untrusted code.** No import, no execution, no dependencies — safe to
|
|
312
|
+
run over a fork's PR or a user-submitted plugin.
|
|
313
|
+
- **A vendorable checker.** One pure-Python package with an empty dependency list,
|
|
314
|
+
auditable in an afternoon, no toolchain.
|
|
315
|
+
- **SARIF plus stable baselines**, for GitHub code scanning on a codebase with an
|
|
316
|
+
existing backlog.
|
|
317
|
+
|
|
318
|
+
Bandit has far broader security coverage; `--bandit` merges its findings into the same
|
|
319
|
+
report if you want both.
|
|
320
|
+
|
|
321
|
+
## Performance
|
|
322
|
+
|
|
323
|
+
Files are analyzed in a process pool once there are 8 or more of them (one worker per CPU); pass `--jobs 1` for a strictly sequential run or `--jobs N` to pin the count. Output order is always the input order.
|
|
324
|
+
|
|
325
|
+
Roughly 100 files/second single-process on a modern laptop. CodeSnake is pure Python
|
|
326
|
+
doing a full AST walk per file; if analysis time dominates your CI, reach for Ruff.
|
|
327
|
+
|
|
328
|
+
## Tests
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
python -m unittest discover -s test -p 'test_*.py'
|
|
332
|
+
python test/test_codesnake.py # same suite, with a summary
|
|
333
|
+
./codesnake-launcher.sh --no-venv --test
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
CI (`.github/workflows/ci.yml`) runs the suite on Python 3.10–3.13 and then runs `codesnake check --format github src/` on the checker's own source.
|
|
337
|
+
|
|
338
|
+
`test/example_bad_code.py` is a fixture with intentional issues:
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
codesnake check test/example_bad_code.py
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## Project layout
|
|
345
|
+
|
|
346
|
+
```
|
|
347
|
+
codesnake/
|
|
348
|
+
├── pyproject.toml # packaging, extras, console script
|
|
349
|
+
├── LICENSE # MIT
|
|
350
|
+
├── .codesnake.json # default checker config
|
|
351
|
+
├── setup.sh # venv + editable install
|
|
352
|
+
├── codesnake.sh # simple launcher
|
|
353
|
+
├── codesnake-launcher.sh # flags, --test, --create-venv, --no-venv
|
|
354
|
+
├── src/codesnake/
|
|
355
|
+
│ ├── __init__.py # public API, __version__
|
|
356
|
+
│ ├── __main__.py # python -m codesnake
|
|
357
|
+
│ ├── _version.py # the one place the version lives
|
|
358
|
+
│ ├── checker.py # SemanticChecker, config, discovery, formats, run_check
|
|
359
|
+
│ ├── cli.py # check / config / version
|
|
360
|
+
│ └── banner.py
|
|
361
|
+
├── test/ # unittest suite + fixture
|
|
362
|
+
├── examples/ # strict.codesnake.json
|
|
363
|
+
├── docs/ # INTEGRATIONS, BASH_SCRIPTS_GUIDE, PROJECT_STRUCTURE
|
|
364
|
+
└── .github/workflows/ci.yml
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Releasing: bump `__version__` in `src/codesnake/_version.py`; `pyproject.toml` reads it dynamically.
|
|
368
|
+
|
|
369
|
+
## Further reading
|
|
370
|
+
|
|
371
|
+
- [`docs/INTEGRATIONS.md`](docs/INTEGRATIONS.md) — pre-commit hooks, GitHub Actions (annotations, SARIF upload, baselines), VS Code, Makefile, adopting CodeSnake on an existing codebase, and how it overlaps with flake8/Bandit/pylint.
|
|
372
|
+
- [`docs/BASH_SCRIPTS_GUIDE.md`](docs/BASH_SCRIPTS_GUIDE.md) — `setup.sh`, `codesnake.sh`, and `codesnake-launcher.sh`, and the virtual environment they manage.
|
|
373
|
+
- [`docs/PROJECT_STRUCTURE.md`](docs/PROJECT_STRUCTURE.md) — module map, how to add a rule, how to release.
|
|
374
|
+
|
|
375
|
+
## License
|
|
376
|
+
|
|
377
|
+
MIT
|