safelint 1.3.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.
Files changed (40) hide show
  1. safelint-1.3.2/LICENSE +21 -0
  2. safelint-1.3.2/PKG-INFO +287 -0
  3. safelint-1.3.2/README.md +255 -0
  4. safelint-1.3.2/pyproject.toml +297 -0
  5. safelint-1.3.2/setup.cfg +4 -0
  6. safelint-1.3.2/src/safelint/__init__.py +29 -0
  7. safelint-1.3.2/src/safelint/analysis/__init__.py +8 -0
  8. safelint-1.3.2/src/safelint/analysis/dataflow.py +174 -0
  9. safelint-1.3.2/src/safelint/cli.py +463 -0
  10. safelint-1.3.2/src/safelint/core/__init__.py +8 -0
  11. safelint-1.3.2/src/safelint/core/config.py +281 -0
  12. safelint-1.3.2/src/safelint/core/engine.py +292 -0
  13. safelint-1.3.2/src/safelint/core/runner.py +62 -0
  14. safelint-1.3.2/src/safelint/py.typed +0 -0
  15. safelint-1.3.2/src/safelint/rules/__init__.py +72 -0
  16. safelint-1.3.2/src/safelint/rules/base.py +61 -0
  17. safelint-1.3.2/src/safelint/rules/complexity.py +55 -0
  18. safelint-1.3.2/src/safelint/rules/dataflow.py +243 -0
  19. safelint-1.3.2/src/safelint/rules/documentation.py +35 -0
  20. safelint-1.3.2/src/safelint/rules/error_handling.py +90 -0
  21. safelint-1.3.2/src/safelint/rules/function_length.py +39 -0
  22. safelint-1.3.2/src/safelint/rules/loop_safety.py +43 -0
  23. safelint-1.3.2/src/safelint/rules/max_arguments.py +40 -0
  24. safelint-1.3.2/src/safelint/rules/nesting_depth.py +58 -0
  25. safelint-1.3.2/src/safelint/rules/resource_lifecycle.py +58 -0
  26. safelint-1.3.2/src/safelint/rules/side_effects.py +96 -0
  27. safelint-1.3.2/src/safelint/rules/state_purity.py +83 -0
  28. safelint-1.3.2/src/safelint/rules/test_coverage.py +78 -0
  29. safelint-1.3.2/src/safelint.egg-info/PKG-INFO +287 -0
  30. safelint-1.3.2/src/safelint.egg-info/SOURCES.txt +38 -0
  31. safelint-1.3.2/src/safelint.egg-info/dependency_links.txt +1 -0
  32. safelint-1.3.2/src/safelint.egg-info/entry_points.txt +2 -0
  33. safelint-1.3.2/src/safelint.egg-info/requires.txt +12 -0
  34. safelint-1.3.2/src/safelint.egg-info/top_level.txt +1 -0
  35. safelint-1.3.2/tests/test_cli.py +176 -0
  36. safelint-1.3.2/tests/test_config.py +155 -0
  37. safelint-1.3.2/tests/test_coverage.py +1021 -0
  38. safelint-1.3.2/tests/test_dataflow.py +494 -0
  39. safelint-1.3.2/tests/test_engine.py +147 -0
  40. safelint-1.3.2/tests/test_suppression.py +286 -0
safelint-1.3.2/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rahul Shelke
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.
@@ -0,0 +1,287 @@
1
+ Metadata-Version: 2.4
2
+ Name: safelint
3
+ Version: 1.3.2
4
+ Summary: Engineering safety lint rules and pre-commit integration for modern Python codebases
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/shelkesays/safelint
7
+ Project-URL: Repository, https://github.com/shelkesays/safelint
8
+ Project-URL: Issues, https://github.com/shelkesays/safelint/issues
9
+ Keywords: lint,pre-commit,static-analysis,safety,python
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Software Development :: Quality Assurance
17
+ Classifier: Topic :: Software Development :: Testing
18
+ Requires-Python: >=3.11
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Provides-Extra: yaml
22
+ Requires-Dist: PyYAML>=6.0; extra == "yaml"
23
+ Provides-Extra: dev
24
+ Requires-Dist: pre-commit>=4.5.1; extra == "dev"
25
+ Requires-Dist: pytest>=9.0.2; extra == "dev"
26
+ Requires-Dist: pytest-cov>=7.1.0; extra == "dev"
27
+ Requires-Dist: pytest-mock>=3.15.1; extra == "dev"
28
+ Requires-Dist: ruff>=0.15.8; extra == "dev"
29
+ Requires-Dist: ty>=0.0.26; extra == "dev"
30
+ Requires-Dist: PyYAML>=6.0; extra == "dev"
31
+ Dynamic: license-file
32
+
33
+ # SafeLint
34
+
35
+ [![CI](https://github.com/shelkesays/safelint/actions/workflows/ci.yml/badge.svg)](https://github.com/shelkesays/safelint/actions/workflows/ci.yml)
36
+ [![PyPI](https://img.shields.io/pypi/v/safelint)](https://pypi.org/project/safelint/)
37
+ [![Python](https://img.shields.io/pypi/pyversions/safelint)](https://pypi.org/project/safelint/)
38
+
39
+ SafeLint is a configurable static analysis tool that enforces safety-critical coding practices inspired by Gerard J. Holzmann's "Power of Ten" rules at NASA/JPL.
40
+
41
+ Originally designed for mission-critical systems, these principles apply to any modern Python codebase - and are especially valuable when code is written fast, reviewed quickly, or generated by AI.
42
+
43
+ SafeLint integrates with pre-commit and CI pipelines to prevent unsafe code from entering your codebase.
44
+
45
+ ## Why SafeLint?
46
+
47
+ Fast-moving codebases - whether written by humans under pressure or generated by AI tools - tend to drift toward the same failure patterns:
48
+
49
+ - Unbounded loops
50
+ - Silent error handling
51
+ - Hidden side effects
52
+ - Poor resource management
53
+
54
+ SafeLint catches these early, automatically, regardless of who wrote the code.
55
+
56
+ ## Philosophy
57
+
58
+ > "When it really counts, it may be worth going the extra mile and living within stricter limits than may be desirable."
59
+ > - Gerard J. Holzmann, NASA/JPL
60
+
61
+ ---
62
+
63
+ ## Power of Ten - adapted for Python
64
+
65
+ In 1987, Holzmann wrote ten rules for spacecraft software at NASA/JPL. Nearly four decades later, the same failure patterns appear in every Python codebase. SafeLint is those ten rules, adapted for Python and automated.
66
+
67
+ | # | Holzmann's Rule | SafeLint Rule | Code |
68
+ |---|---|---|---|
69
+ | 1 | No complex control flow - no `goto`, no deep recursion | `nesting_depth`, `complexity` | [SAFE102](CONFIGURATION.md#safe102----nesting_depth), [SAFE104](CONFIGURATION.md#safe104----complexity) |
70
+ | 2 | All loops must have a fixed upper bound | `unbounded_loops` | [SAFE501](CONFIGURATION.md#safe501----unbounded_loops) |
71
+ | 3 | No dynamic memory allocation after startup | - | *(not applicable to Python)* |
72
+ | 4 | Functions must fit on one printed page | `function_length` | [SAFE101](CONFIGURATION.md#safe101----function_length) |
73
+ | 5 | Use at least two assertions per function | `missing_assertions` | [SAFE601](CONFIGURATION.md#safe601----missing_assertions) |
74
+ | 6 | Declare variables at the smallest scope | - | *(Python handles this)* |
75
+ | 7 | Check the return value of every non-void function | `return_value_ignored`, `bare_except`, `empty_except` | [SAFE802](CONFIGURATION.md#safe802----return_value_ignored), [SAFE201](CONFIGURATION.md#safe201----bare_except), [SAFE202](CONFIGURATION.md#safe202----empty_except) |
76
+ | 8 | Limit preprocessor use | - | *(not applicable to Python)* |
77
+ | 9 | Restrict pointer use - no chained indirection | `null_dereference` | [SAFE803](CONFIGURATION.md#safe803----null_dereference) |
78
+ | 10 | Compile with all warnings; use static analysis | SafeLint itself | - |
79
+
80
+ Original paper: [spinroot.com/gerard/pdf/P10.pdf](https://spinroot.com/gerard/pdf/P10.pdf)
81
+
82
+ ---
83
+
84
+ ## Installation
85
+
86
+ ```bash
87
+ pip install safelint
88
+ ```
89
+
90
+ To also support YAML config files (`.safelint.yaml`):
91
+
92
+ ```bash
93
+ pip install "safelint[yaml]"
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Usage
99
+
100
+ **Check modified files** (default — only files changed since last commit):
101
+
102
+ ```bash
103
+ safelint check src/
104
+ ```
105
+
106
+ **Check all files** (full scan, e.g. in CI):
107
+
108
+ ```bash
109
+ safelint check src/ --all-files
110
+ ```
111
+
112
+ **Check specific files** (pre-commit style):
113
+
114
+ ```bash
115
+ safelint src/mymodule.py src/utils.py
116
+ ```
117
+
118
+ **Fail on warnings too** (useful in CI):
119
+
120
+ ```bash
121
+ safelint check src/ --all-files --fail-on=warning
122
+ ```
123
+
124
+ **Run in CI mode** (warnings become blocking):
125
+
126
+ ```bash
127
+ safelint check src/ --all-files --mode=ci
128
+ ```
129
+
130
+ **Ignore specific rules for one run:**
131
+
132
+ ```bash
133
+ safelint check src/ --ignore SAFE203 --ignore side_effects
134
+ ```
135
+
136
+ ---
137
+
138
+ ## Pre-commit integration
139
+
140
+ Add this to your `.pre-commit-config.yaml`:
141
+
142
+ ```yaml
143
+ repos:
144
+ - repo: https://github.com/shelkesays/safelint
145
+ rev: v1.0.0 # replace with the latest release tag
146
+ hooks:
147
+ - id: safelint
148
+ args: [--fail-on=error] # use --fail-on=warning for stricter CI
149
+ files: ^src/
150
+ ```
151
+
152
+ Then install the hooks:
153
+
154
+ ```bash
155
+ pre-commit install
156
+ ```
157
+
158
+ SafeLint will now run on every `git commit` and block the commit if it finds errors.
159
+
160
+ ---
161
+
162
+ ## What it checks
163
+
164
+ | Code | Rule | What it flags |
165
+ |---|---|---|
166
+ | [SAFE101](CONFIGURATION.md#safe101----function_length) | `function_length` | Functions longer than 60 lines |
167
+ | [SAFE102](CONFIGURATION.md#safe102----nesting_depth) | `nesting_depth` | Control flow nested more than 2 levels deep |
168
+ | [SAFE103](CONFIGURATION.md#safe103----max_arguments) | `max_arguments` | Functions with more than 7 parameters |
169
+ | [SAFE104](CONFIGURATION.md#safe104----complexity) | `complexity` | Functions with high cyclomatic complexity |
170
+ | [SAFE201](CONFIGURATION.md#safe201----bare_except) | `bare_except` | `except:` with no exception type |
171
+ | [SAFE202](CONFIGURATION.md#safe202----empty_except) | `empty_except` | `except` blocks that do nothing (`pass`) |
172
+ | [SAFE203](CONFIGURATION.md#safe203----logging_on_error) | `logging_on_error` | Except blocks that swallow errors silently |
173
+ | [SAFE301](CONFIGURATION.md#safe301----global_state) | `global_state` | Use of the `global` keyword inside functions |
174
+ | [SAFE302](CONFIGURATION.md#safe302----global_mutation) | `global_mutation` | Writing to global variables inside functions |
175
+ | [SAFE303](CONFIGURATION.md#safe303----side_effects_hidden) | `side_effects_hidden` | Pure-looking functions that secretly do I/O |
176
+ | [SAFE304](CONFIGURATION.md#safe304----side_effects) | `side_effects` | Functions that call `print`, `open`, etc. without signalling intent |
177
+ | [SAFE401](CONFIGURATION.md#safe401----resource_lifecycle) | `resource_lifecycle` | Files or connections opened outside a `with` block |
178
+ | [SAFE501](CONFIGURATION.md#safe501----unbounded_loops) | `unbounded_loops` | `while True` loops with no `break` |
179
+
180
+ **Dataflow rules** (opt-in, disabled by default):
181
+
182
+ | Code | Rule | What it flags |
183
+ |---|---|---|
184
+ | [SAFE801](CONFIGURATION.md#safe801----tainted_sink) | `tainted_sink` | User input flowing into `eval`, `exec`, `subprocess`, etc. without sanitization |
185
+ | [SAFE802](CONFIGURATION.md#safe802----return_value_ignored) | `return_value_ignored` | Discarding the return value of calls like `subprocess.run` or `file.write` |
186
+ | [SAFE803](CONFIGURATION.md#safe803----null_dereference) | `null_dereference` | Chaining methods directly on calls that can return `None`, e.g. `d.get("key").strip()` |
187
+
188
+ For opt-in rules (`SAFE601`, `SAFE701`, `SAFE702`) and full configuration options for every rule, see [CONFIGURATION.md](CONFIGURATION.md).
189
+
190
+ ---
191
+
192
+ ## Suppressing violations inline
193
+
194
+ Add a `# nosafe` comment to suppress a violation on a specific line without changing global config.
195
+
196
+ **Suppress all violations on a line:**
197
+ ```python
198
+ result = eval(user_input) # nosafe
199
+ ```
200
+
201
+ **Suppress a specific rule by code:**
202
+ ```python
203
+ while True: # nosafe: SAFE501
204
+ ...
205
+ ```
206
+
207
+ **Suppress by rule name:**
208
+ ```python
209
+ while True: # nosafe: unbounded_loops
210
+ ...
211
+ ```
212
+
213
+ **Suppress multiple rules at once:**
214
+ ```python
215
+ def get_data(conn, q, p1, p2, p3, p4, p5, p6): # nosafe: SAFE101, SAFE103
216
+ ...
217
+ ```
218
+
219
+ When at least one violation is suppressed, the CLI summary reports the count so suppressions remain visible and auditable. Use `# nosafe` sparingly — it's for line-level exceptions only. For broader suppression use the config-level options:
220
+
221
+ ```toml
222
+ # pyproject.toml
223
+ [tool.safelint]
224
+ ignore = ["SAFE203", "side_effects"] # suppress project-wide
225
+
226
+ [tool.safelint.per_file_ignores]
227
+ "tests/**" = ["SAFE101", "SAFE103"] # suppress only for matching files
228
+ ```
229
+
230
+ See [CONFIGURATION.md — Inline suppression](CONFIGURATION.md#inline-suppression), [CONFIGURATION.md — Global ignore list](CONFIGURATION.md#global-ignore-list), and [CONFIGURATION.md — Per-file ignore list](CONFIGURATION.md#per-file-ignore-list) for full reference.
231
+
232
+ ---
233
+
234
+ ## Configuration
235
+
236
+ SafeLint is configured via `[tool.safelint]` in your `pyproject.toml`, or a `.safelint.yaml` file. See [CONFIGURATION.md](CONFIGURATION.md) for all options, defaults, and examples.
237
+
238
+ Ready-to-copy samples:
239
+
240
+ - [examples/sample.pyproject.toml](examples/sample.pyproject.toml) — TOML format (recommended)
241
+ - [examples/sample.safelint.yaml](examples/sample.safelint.yaml) — YAML format (legacy)
242
+
243
+ ---
244
+
245
+ ## Development
246
+
247
+ ```bash
248
+ # Install with dev dependencies
249
+ pip install -e ".[dev]"
250
+
251
+ # Run tests
252
+ pytest
253
+
254
+ # Run the linter on itself
255
+ safelint check src/
256
+ ```
257
+
258
+ ## Releasing to PyPI (Trusted Publishing)
259
+
260
+ This project publishes to PyPI via GitHub Actions using PyPI Trusted Publishing (OIDC). Do not use local `uv publish` username/password auth.
261
+
262
+ One-time setup:
263
+
264
+ 1. In PyPI, open your project → **Manage** → **Publishing** → **Add a trusted publisher**.
265
+ 2. Use:
266
+ - Owner: `shelkesays`
267
+ - Repository: `safelint`
268
+ - Workflow: `publish.yml`
269
+ - Environment: `pypi`
270
+ 3. In GitHub, create an environment named `pypi` in **Settings → Environments**.
271
+
272
+ Release flow:
273
+
274
+ ```bash
275
+ # 1) bump version in pyproject.toml
276
+ # 2) commit and push
277
+ git tag vX.Y.Z
278
+ git push origin vX.Y.Z
279
+ ```
280
+
281
+ Pushing the version tag triggers `.github/workflows/publish.yml`, which builds and publishes to PyPI.
282
+
283
+ ---
284
+
285
+ ## Contributing
286
+
287
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on bug reports, adding new rules, and opening pull requests.
@@ -0,0 +1,255 @@
1
+ # SafeLint
2
+
3
+ [![CI](https://github.com/shelkesays/safelint/actions/workflows/ci.yml/badge.svg)](https://github.com/shelkesays/safelint/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/safelint)](https://pypi.org/project/safelint/)
5
+ [![Python](https://img.shields.io/pypi/pyversions/safelint)](https://pypi.org/project/safelint/)
6
+
7
+ SafeLint is a configurable static analysis tool that enforces safety-critical coding practices inspired by Gerard J. Holzmann's "Power of Ten" rules at NASA/JPL.
8
+
9
+ Originally designed for mission-critical systems, these principles apply to any modern Python codebase - and are especially valuable when code is written fast, reviewed quickly, or generated by AI.
10
+
11
+ SafeLint integrates with pre-commit and CI pipelines to prevent unsafe code from entering your codebase.
12
+
13
+ ## Why SafeLint?
14
+
15
+ Fast-moving codebases - whether written by humans under pressure or generated by AI tools - tend to drift toward the same failure patterns:
16
+
17
+ - Unbounded loops
18
+ - Silent error handling
19
+ - Hidden side effects
20
+ - Poor resource management
21
+
22
+ SafeLint catches these early, automatically, regardless of who wrote the code.
23
+
24
+ ## Philosophy
25
+
26
+ > "When it really counts, it may be worth going the extra mile and living within stricter limits than may be desirable."
27
+ > - Gerard J. Holzmann, NASA/JPL
28
+
29
+ ---
30
+
31
+ ## Power of Ten - adapted for Python
32
+
33
+ In 1987, Holzmann wrote ten rules for spacecraft software at NASA/JPL. Nearly four decades later, the same failure patterns appear in every Python codebase. SafeLint is those ten rules, adapted for Python and automated.
34
+
35
+ | # | Holzmann's Rule | SafeLint Rule | Code |
36
+ |---|---|---|---|
37
+ | 1 | No complex control flow - no `goto`, no deep recursion | `nesting_depth`, `complexity` | [SAFE102](CONFIGURATION.md#safe102----nesting_depth), [SAFE104](CONFIGURATION.md#safe104----complexity) |
38
+ | 2 | All loops must have a fixed upper bound | `unbounded_loops` | [SAFE501](CONFIGURATION.md#safe501----unbounded_loops) |
39
+ | 3 | No dynamic memory allocation after startup | - | *(not applicable to Python)* |
40
+ | 4 | Functions must fit on one printed page | `function_length` | [SAFE101](CONFIGURATION.md#safe101----function_length) |
41
+ | 5 | Use at least two assertions per function | `missing_assertions` | [SAFE601](CONFIGURATION.md#safe601----missing_assertions) |
42
+ | 6 | Declare variables at the smallest scope | - | *(Python handles this)* |
43
+ | 7 | Check the return value of every non-void function | `return_value_ignored`, `bare_except`, `empty_except` | [SAFE802](CONFIGURATION.md#safe802----return_value_ignored), [SAFE201](CONFIGURATION.md#safe201----bare_except), [SAFE202](CONFIGURATION.md#safe202----empty_except) |
44
+ | 8 | Limit preprocessor use | - | *(not applicable to Python)* |
45
+ | 9 | Restrict pointer use - no chained indirection | `null_dereference` | [SAFE803](CONFIGURATION.md#safe803----null_dereference) |
46
+ | 10 | Compile with all warnings; use static analysis | SafeLint itself | - |
47
+
48
+ Original paper: [spinroot.com/gerard/pdf/P10.pdf](https://spinroot.com/gerard/pdf/P10.pdf)
49
+
50
+ ---
51
+
52
+ ## Installation
53
+
54
+ ```bash
55
+ pip install safelint
56
+ ```
57
+
58
+ To also support YAML config files (`.safelint.yaml`):
59
+
60
+ ```bash
61
+ pip install "safelint[yaml]"
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Usage
67
+
68
+ **Check modified files** (default — only files changed since last commit):
69
+
70
+ ```bash
71
+ safelint check src/
72
+ ```
73
+
74
+ **Check all files** (full scan, e.g. in CI):
75
+
76
+ ```bash
77
+ safelint check src/ --all-files
78
+ ```
79
+
80
+ **Check specific files** (pre-commit style):
81
+
82
+ ```bash
83
+ safelint src/mymodule.py src/utils.py
84
+ ```
85
+
86
+ **Fail on warnings too** (useful in CI):
87
+
88
+ ```bash
89
+ safelint check src/ --all-files --fail-on=warning
90
+ ```
91
+
92
+ **Run in CI mode** (warnings become blocking):
93
+
94
+ ```bash
95
+ safelint check src/ --all-files --mode=ci
96
+ ```
97
+
98
+ **Ignore specific rules for one run:**
99
+
100
+ ```bash
101
+ safelint check src/ --ignore SAFE203 --ignore side_effects
102
+ ```
103
+
104
+ ---
105
+
106
+ ## Pre-commit integration
107
+
108
+ Add this to your `.pre-commit-config.yaml`:
109
+
110
+ ```yaml
111
+ repos:
112
+ - repo: https://github.com/shelkesays/safelint
113
+ rev: v1.0.0 # replace with the latest release tag
114
+ hooks:
115
+ - id: safelint
116
+ args: [--fail-on=error] # use --fail-on=warning for stricter CI
117
+ files: ^src/
118
+ ```
119
+
120
+ Then install the hooks:
121
+
122
+ ```bash
123
+ pre-commit install
124
+ ```
125
+
126
+ SafeLint will now run on every `git commit` and block the commit if it finds errors.
127
+
128
+ ---
129
+
130
+ ## What it checks
131
+
132
+ | Code | Rule | What it flags |
133
+ |---|---|---|
134
+ | [SAFE101](CONFIGURATION.md#safe101----function_length) | `function_length` | Functions longer than 60 lines |
135
+ | [SAFE102](CONFIGURATION.md#safe102----nesting_depth) | `nesting_depth` | Control flow nested more than 2 levels deep |
136
+ | [SAFE103](CONFIGURATION.md#safe103----max_arguments) | `max_arguments` | Functions with more than 7 parameters |
137
+ | [SAFE104](CONFIGURATION.md#safe104----complexity) | `complexity` | Functions with high cyclomatic complexity |
138
+ | [SAFE201](CONFIGURATION.md#safe201----bare_except) | `bare_except` | `except:` with no exception type |
139
+ | [SAFE202](CONFIGURATION.md#safe202----empty_except) | `empty_except` | `except` blocks that do nothing (`pass`) |
140
+ | [SAFE203](CONFIGURATION.md#safe203----logging_on_error) | `logging_on_error` | Except blocks that swallow errors silently |
141
+ | [SAFE301](CONFIGURATION.md#safe301----global_state) | `global_state` | Use of the `global` keyword inside functions |
142
+ | [SAFE302](CONFIGURATION.md#safe302----global_mutation) | `global_mutation` | Writing to global variables inside functions |
143
+ | [SAFE303](CONFIGURATION.md#safe303----side_effects_hidden) | `side_effects_hidden` | Pure-looking functions that secretly do I/O |
144
+ | [SAFE304](CONFIGURATION.md#safe304----side_effects) | `side_effects` | Functions that call `print`, `open`, etc. without signalling intent |
145
+ | [SAFE401](CONFIGURATION.md#safe401----resource_lifecycle) | `resource_lifecycle` | Files or connections opened outside a `with` block |
146
+ | [SAFE501](CONFIGURATION.md#safe501----unbounded_loops) | `unbounded_loops` | `while True` loops with no `break` |
147
+
148
+ **Dataflow rules** (opt-in, disabled by default):
149
+
150
+ | Code | Rule | What it flags |
151
+ |---|---|---|
152
+ | [SAFE801](CONFIGURATION.md#safe801----tainted_sink) | `tainted_sink` | User input flowing into `eval`, `exec`, `subprocess`, etc. without sanitization |
153
+ | [SAFE802](CONFIGURATION.md#safe802----return_value_ignored) | `return_value_ignored` | Discarding the return value of calls like `subprocess.run` or `file.write` |
154
+ | [SAFE803](CONFIGURATION.md#safe803----null_dereference) | `null_dereference` | Chaining methods directly on calls that can return `None`, e.g. `d.get("key").strip()` |
155
+
156
+ For opt-in rules (`SAFE601`, `SAFE701`, `SAFE702`) and full configuration options for every rule, see [CONFIGURATION.md](CONFIGURATION.md).
157
+
158
+ ---
159
+
160
+ ## Suppressing violations inline
161
+
162
+ Add a `# nosafe` comment to suppress a violation on a specific line without changing global config.
163
+
164
+ **Suppress all violations on a line:**
165
+ ```python
166
+ result = eval(user_input) # nosafe
167
+ ```
168
+
169
+ **Suppress a specific rule by code:**
170
+ ```python
171
+ while True: # nosafe: SAFE501
172
+ ...
173
+ ```
174
+
175
+ **Suppress by rule name:**
176
+ ```python
177
+ while True: # nosafe: unbounded_loops
178
+ ...
179
+ ```
180
+
181
+ **Suppress multiple rules at once:**
182
+ ```python
183
+ def get_data(conn, q, p1, p2, p3, p4, p5, p6): # nosafe: SAFE101, SAFE103
184
+ ...
185
+ ```
186
+
187
+ When at least one violation is suppressed, the CLI summary reports the count so suppressions remain visible and auditable. Use `# nosafe` sparingly — it's for line-level exceptions only. For broader suppression use the config-level options:
188
+
189
+ ```toml
190
+ # pyproject.toml
191
+ [tool.safelint]
192
+ ignore = ["SAFE203", "side_effects"] # suppress project-wide
193
+
194
+ [tool.safelint.per_file_ignores]
195
+ "tests/**" = ["SAFE101", "SAFE103"] # suppress only for matching files
196
+ ```
197
+
198
+ See [CONFIGURATION.md — Inline suppression](CONFIGURATION.md#inline-suppression), [CONFIGURATION.md — Global ignore list](CONFIGURATION.md#global-ignore-list), and [CONFIGURATION.md — Per-file ignore list](CONFIGURATION.md#per-file-ignore-list) for full reference.
199
+
200
+ ---
201
+
202
+ ## Configuration
203
+
204
+ SafeLint is configured via `[tool.safelint]` in your `pyproject.toml`, or a `.safelint.yaml` file. See [CONFIGURATION.md](CONFIGURATION.md) for all options, defaults, and examples.
205
+
206
+ Ready-to-copy samples:
207
+
208
+ - [examples/sample.pyproject.toml](examples/sample.pyproject.toml) — TOML format (recommended)
209
+ - [examples/sample.safelint.yaml](examples/sample.safelint.yaml) — YAML format (legacy)
210
+
211
+ ---
212
+
213
+ ## Development
214
+
215
+ ```bash
216
+ # Install with dev dependencies
217
+ pip install -e ".[dev]"
218
+
219
+ # Run tests
220
+ pytest
221
+
222
+ # Run the linter on itself
223
+ safelint check src/
224
+ ```
225
+
226
+ ## Releasing to PyPI (Trusted Publishing)
227
+
228
+ This project publishes to PyPI via GitHub Actions using PyPI Trusted Publishing (OIDC). Do not use local `uv publish` username/password auth.
229
+
230
+ One-time setup:
231
+
232
+ 1. In PyPI, open your project → **Manage** → **Publishing** → **Add a trusted publisher**.
233
+ 2. Use:
234
+ - Owner: `shelkesays`
235
+ - Repository: `safelint`
236
+ - Workflow: `publish.yml`
237
+ - Environment: `pypi`
238
+ 3. In GitHub, create an environment named `pypi` in **Settings → Environments**.
239
+
240
+ Release flow:
241
+
242
+ ```bash
243
+ # 1) bump version in pyproject.toml
244
+ # 2) commit and push
245
+ git tag vX.Y.Z
246
+ git push origin vX.Y.Z
247
+ ```
248
+
249
+ Pushing the version tag triggers `.github/workflows/publish.yml`, which builds and publishes to PyPI.
250
+
251
+ ---
252
+
253
+ ## Contributing
254
+
255
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on bug reports, adding new rules, and opening pull requests.