pureshellcheck 0.1.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 adam2go
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,3 @@
1
+ prune tests
2
+ prune tools
3
+ prune .github
@@ -0,0 +1,184 @@
1
+ Metadata-Version: 2.4
2
+ Name: pureshellcheck
3
+ Version: 0.1.0
4
+ Summary: A pure Python reimplementation of ShellCheck's most common checks
5
+ Author: adam2go
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/adam2go/pureshellcheck
8
+ Keywords: shellcheck,shell,bash,lint,static-analysis,pure-python
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Programming Language :: Python :: Implementation :: CPython
20
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
21
+ Classifier: Topic :: Software Development :: Quality Assurance
22
+ Classifier: Topic :: Software Development :: Libraries
23
+ Requires-Python: >=3.9
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Dynamic: license-file
27
+
28
+ # pureshellcheck
29
+
30
+ [![CI](https://github.com/adam2go/pureshellcheck/actions/workflows/ci.yml/badge.svg)](https://github.com/adam2go/pureshellcheck/actions/workflows/ci.yml)
31
+ [![PyPI](https://img.shields.io/pypi/v/pureshellcheck)](https://pypi.org/project/pureshellcheck/)
32
+ [![Conformance](https://img.shields.io/badge/ShellCheck%20test%20suite-619%2F620%20(99.8%25)-brightgreen)](tests/data/expected_failures.txt)
33
+
34
+ A pure Python reimplementation of [ShellCheck](https://github.com/koalaman/shellcheck)'s
35
+ most common checks. No binaries, no Haskell runtime, no compilation —
36
+ `pip install pureshellcheck` and it works anywhere Python runs, including
37
+ AWS Lambda, Pyodide/WASM, and locked-down CI sandboxes where you can't
38
+ install the real ShellCheck binary.
39
+
40
+ ```console
41
+ $ pip install pureshellcheck
42
+ $ pureshellcheck deploy.sh
43
+
44
+ In deploy.sh line 8:
45
+ rm -rf $BUILD_DIR/*
46
+ ^--------^ SC2086 (info): Double quote to prevent globbing and word splitting.
47
+ ```
48
+
49
+ ## Why
50
+
51
+ - **Agent & tooling friendly.** LLM-generated shell scripts fail in
52
+ exactly the ways ShellCheck catches (unquoted expansions, word
53
+ splitting, `cd` without `|| exit`). Existing Python packages such as
54
+ `shellcheck-py` just download the 30 MB Haskell binary — useless in
55
+ WASM, Lambda layers, or hermetic build sandboxes. pureshellcheck is
56
+ ~7000 lines of stdlib-only Python.
57
+ - **In-process speed.** Calling `pureshellcheck.check()` takes ~2 ms for a
58
+ typical script vs ~40 ms to spawn the shellcheck binary — and it's
59
+ 8–13× faster than the binary even on 1200-line scripts (see
60
+ [Benchmarks](#benchmarks)).
61
+ - **Verified against the real thing.** Test cases are extracted from
62
+ ShellCheck's own test suite and the output is differentially tested
63
+ against the shellcheck binary on real-world scripts.
64
+
65
+ ## What it checks
66
+
67
+ 71 SC codes are implemented, chosen by real-world frequency — the quoting
68
+ and word-splitting family (SC2086, SC2046, SC2068, SC2206/2207...),
69
+ variable lifecycle (SC2034 unused, SC2154 unassigned, SC2155),
70
+ command pitfalls (SC2164 unchecked `cd`, SC2162 `read` without `-r`,
71
+ useless `cat`/`echo`, `ls | grep`, `find | xargs`, printf argument
72
+ counting, catastrophic `rm -rf`), structural mistakes
73
+ (`A && B || C`, constant test expressions, `$?` anti-patterns), and more.
74
+
75
+ <details>
76
+ <summary>All implemented codes</summary>
77
+
78
+ SC2002 SC2003 SC2004 SC2005 SC2006 SC2007 SC2009 SC2010 SC2011 SC2012
79
+ SC2015 SC2016 SC2026 SC2027 SC2028 SC2034 SC2035 SC2038 SC2041 SC2042
80
+ SC2043 SC2046 SC2048 SC2050 SC2059 SC2064 SC2065 SC2066 SC2068 SC2086
81
+ SC2089 SC2090 SC2093 SC2094 SC2103 SC2114 SC2115 SC2116 SC2126 SC2128
82
+ SC2140 SC2145 SC2148 SC2153 SC2154 SC2155 SC2162 SC2164 SC2174 SC2178
83
+ SC2179 SC2181 SC2182 SC2183 SC2187 SC2188 SC2189 SC2206 SC2207 SC2223
84
+ SC2239 SC2246 SC2248 SC2250 SC2258 SC2304 SC2305 SC2306 SC2307 SC2308
85
+
86
+ </details>
87
+
88
+ ## Conformance scoreboard
89
+
90
+ The repository vendors **1508 test cases extracted from ShellCheck's own
91
+ test suite** (`tests/data/corpus.json`), run by pytest on every commit:
92
+
93
+ | metric | result |
94
+ |---|---|
95
+ | official test cases for the implemented checks | **619/620 (99.8%)** |
96
+ | whole official corpus (incl. unimplemented checks) | 1025/1508 (68.0%) |
97
+ | real-world differential test vs `shellcheck` 0.11.0 | **113/113 findings agree, 0 missed, 0 false positives** (48 scripts from Homebrew/npm) |
98
+
99
+ The single implemented-check failure is documented in
100
+ `tests/data/expected_failures.txt` (a test of ShellCheck's non-default
101
+ `check-unassigned-uppercase` mode); every other non-passing corpus case is
102
+ listed there with a reason. Reproduce with:
103
+
104
+ ```console
105
+ $ python tools/conformance.py # scoreboard
106
+ $ python tools/diff_shellcheck.py *.sh # vs the real binary
107
+ ```
108
+
109
+ ## Usage
110
+
111
+ ### CLI
112
+
113
+ ```console
114
+ $ pureshellcheck [-s bash] [-f tty|gcc|json|json1] [-e SC2086] \
115
+ [-S error|warning|info|style] script.sh [more.sh ...]
116
+ ```
117
+
118
+ Exit status is 0 for a clean script, 1 if there are findings, 2 on file
119
+ errors — same convention as shellcheck. `# shellcheck disable=SC2086`
120
+ and `# shellcheck shell=dash` directives are honored.
121
+
122
+ ### Library
123
+
124
+ ```python
125
+ import pureshellcheck
126
+
127
+ for f in pureshellcheck.check('echo $foo', shell='bash'):
128
+ print(f.line, f.column, f.code, f.severity, f.message)
129
+ # 1 6 2086 info Double quote to prevent globbing and word splitting.
130
+ ```
131
+
132
+ `check()` returns a list of findings with `code`, `severity`
133
+ (`error|warning|info|style`), `message`, and 1-based
134
+ `line`/`column`/`end_line`/`end_column`. `pureshellcheck.parse()` exposes
135
+ the bash AST if you want to build your own analyses.
136
+
137
+ ## Benchmarks
138
+
139
+ Measured with `python tools/bench.py` (median of 7 runs, after verifying
140
+ both tools report identical findings on the workload; CPython 3.12,
141
+ shellcheck 0.11.0, Apple Silicon):
142
+
143
+ | workload | shellcheck | pureshellcheck | speedup |
144
+ |---|---|---|---|
145
+ | CLI, brew.sh (1216 lines) | 604 ms | 68 ms | **8.9×** |
146
+ | embedded `check()`, brew.sh | 604 ms | 45 ms | **13.3×** |
147
+ | CLI, 75-line script | 42 ms | 24 ms | 1.8× |
148
+ | embedded `check()`, 75-line script | 42 ms | 2.4 ms | **17×** |
149
+
150
+ The embedded rows are what an agent or editor integration pays per call:
151
+ no process spawn, no binary.
152
+
153
+ ## Compatibility notes
154
+
155
+ - Targets bash (default), sh/dash/ash and ksh dialects are accepted via
156
+ shebang, directive, or `-s`; sh-specific portability checks (the
157
+ SC2039/SC3xxx family) are not implemented yet.
158
+ - The parser is deliberately lenient: it keeps checking past constructs
159
+ the real shellcheck refuses to parse (e.g. `[ $tar --version ]`).
160
+ - Optional checks (`SC2002`, `SC2248`, `SC2250`) are off by default,
161
+ matching shellcheck 0.11; enable with `-o` / `include_optional=True`.
162
+ - Wiki links work the same: see <https://www.shellcheck.net/wiki/SC2086>
163
+ for any reported code.
164
+
165
+ ## Development
166
+
167
+ ```console
168
+ $ pip install -e . pytest
169
+ $ pytest # corpus + unit tests, < 1 s
170
+ $ python tools/extract_corpus.py /path/to/shellcheck # refresh corpus
171
+ $ python tools/update_expected_failures.py # refresh scoreboard
172
+ $ python tools/bench.py # benchmarks
173
+ ```
174
+
175
+ The package itself is MIT licensed and has zero runtime dependencies
176
+ (CPython 3.9–3.14 and PyPy). The vendored test corpus in `tests/data/` is
177
+ extracted from the GPLv3-licensed ShellCheck project and is used only for
178
+ development-time testing; it is not part of the distributed wheel.
179
+
180
+ ## See also
181
+
182
+ - [purejq](https://github.com/adam2go/purejq) — pure Python jq, same
183
+ philosophy: vendored official test suite, differential testing, no
184
+ binaries.
@@ -0,0 +1,157 @@
1
+ # pureshellcheck
2
+
3
+ [![CI](https://github.com/adam2go/pureshellcheck/actions/workflows/ci.yml/badge.svg)](https://github.com/adam2go/pureshellcheck/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/pureshellcheck)](https://pypi.org/project/pureshellcheck/)
5
+ [![Conformance](https://img.shields.io/badge/ShellCheck%20test%20suite-619%2F620%20(99.8%25)-brightgreen)](tests/data/expected_failures.txt)
6
+
7
+ A pure Python reimplementation of [ShellCheck](https://github.com/koalaman/shellcheck)'s
8
+ most common checks. No binaries, no Haskell runtime, no compilation —
9
+ `pip install pureshellcheck` and it works anywhere Python runs, including
10
+ AWS Lambda, Pyodide/WASM, and locked-down CI sandboxes where you can't
11
+ install the real ShellCheck binary.
12
+
13
+ ```console
14
+ $ pip install pureshellcheck
15
+ $ pureshellcheck deploy.sh
16
+
17
+ In deploy.sh line 8:
18
+ rm -rf $BUILD_DIR/*
19
+ ^--------^ SC2086 (info): Double quote to prevent globbing and word splitting.
20
+ ```
21
+
22
+ ## Why
23
+
24
+ - **Agent & tooling friendly.** LLM-generated shell scripts fail in
25
+ exactly the ways ShellCheck catches (unquoted expansions, word
26
+ splitting, `cd` without `|| exit`). Existing Python packages such as
27
+ `shellcheck-py` just download the 30 MB Haskell binary — useless in
28
+ WASM, Lambda layers, or hermetic build sandboxes. pureshellcheck is
29
+ ~7000 lines of stdlib-only Python.
30
+ - **In-process speed.** Calling `pureshellcheck.check()` takes ~2 ms for a
31
+ typical script vs ~40 ms to spawn the shellcheck binary — and it's
32
+ 8–13× faster than the binary even on 1200-line scripts (see
33
+ [Benchmarks](#benchmarks)).
34
+ - **Verified against the real thing.** Test cases are extracted from
35
+ ShellCheck's own test suite and the output is differentially tested
36
+ against the shellcheck binary on real-world scripts.
37
+
38
+ ## What it checks
39
+
40
+ 71 SC codes are implemented, chosen by real-world frequency — the quoting
41
+ and word-splitting family (SC2086, SC2046, SC2068, SC2206/2207...),
42
+ variable lifecycle (SC2034 unused, SC2154 unassigned, SC2155),
43
+ command pitfalls (SC2164 unchecked `cd`, SC2162 `read` without `-r`,
44
+ useless `cat`/`echo`, `ls | grep`, `find | xargs`, printf argument
45
+ counting, catastrophic `rm -rf`), structural mistakes
46
+ (`A && B || C`, constant test expressions, `$?` anti-patterns), and more.
47
+
48
+ <details>
49
+ <summary>All implemented codes</summary>
50
+
51
+ SC2002 SC2003 SC2004 SC2005 SC2006 SC2007 SC2009 SC2010 SC2011 SC2012
52
+ SC2015 SC2016 SC2026 SC2027 SC2028 SC2034 SC2035 SC2038 SC2041 SC2042
53
+ SC2043 SC2046 SC2048 SC2050 SC2059 SC2064 SC2065 SC2066 SC2068 SC2086
54
+ SC2089 SC2090 SC2093 SC2094 SC2103 SC2114 SC2115 SC2116 SC2126 SC2128
55
+ SC2140 SC2145 SC2148 SC2153 SC2154 SC2155 SC2162 SC2164 SC2174 SC2178
56
+ SC2179 SC2181 SC2182 SC2183 SC2187 SC2188 SC2189 SC2206 SC2207 SC2223
57
+ SC2239 SC2246 SC2248 SC2250 SC2258 SC2304 SC2305 SC2306 SC2307 SC2308
58
+
59
+ </details>
60
+
61
+ ## Conformance scoreboard
62
+
63
+ The repository vendors **1508 test cases extracted from ShellCheck's own
64
+ test suite** (`tests/data/corpus.json`), run by pytest on every commit:
65
+
66
+ | metric | result |
67
+ |---|---|
68
+ | official test cases for the implemented checks | **619/620 (99.8%)** |
69
+ | whole official corpus (incl. unimplemented checks) | 1025/1508 (68.0%) |
70
+ | real-world differential test vs `shellcheck` 0.11.0 | **113/113 findings agree, 0 missed, 0 false positives** (48 scripts from Homebrew/npm) |
71
+
72
+ The single implemented-check failure is documented in
73
+ `tests/data/expected_failures.txt` (a test of ShellCheck's non-default
74
+ `check-unassigned-uppercase` mode); every other non-passing corpus case is
75
+ listed there with a reason. Reproduce with:
76
+
77
+ ```console
78
+ $ python tools/conformance.py # scoreboard
79
+ $ python tools/diff_shellcheck.py *.sh # vs the real binary
80
+ ```
81
+
82
+ ## Usage
83
+
84
+ ### CLI
85
+
86
+ ```console
87
+ $ pureshellcheck [-s bash] [-f tty|gcc|json|json1] [-e SC2086] \
88
+ [-S error|warning|info|style] script.sh [more.sh ...]
89
+ ```
90
+
91
+ Exit status is 0 for a clean script, 1 if there are findings, 2 on file
92
+ errors — same convention as shellcheck. `# shellcheck disable=SC2086`
93
+ and `# shellcheck shell=dash` directives are honored.
94
+
95
+ ### Library
96
+
97
+ ```python
98
+ import pureshellcheck
99
+
100
+ for f in pureshellcheck.check('echo $foo', shell='bash'):
101
+ print(f.line, f.column, f.code, f.severity, f.message)
102
+ # 1 6 2086 info Double quote to prevent globbing and word splitting.
103
+ ```
104
+
105
+ `check()` returns a list of findings with `code`, `severity`
106
+ (`error|warning|info|style`), `message`, and 1-based
107
+ `line`/`column`/`end_line`/`end_column`. `pureshellcheck.parse()` exposes
108
+ the bash AST if you want to build your own analyses.
109
+
110
+ ## Benchmarks
111
+
112
+ Measured with `python tools/bench.py` (median of 7 runs, after verifying
113
+ both tools report identical findings on the workload; CPython 3.12,
114
+ shellcheck 0.11.0, Apple Silicon):
115
+
116
+ | workload | shellcheck | pureshellcheck | speedup |
117
+ |---|---|---|---|
118
+ | CLI, brew.sh (1216 lines) | 604 ms | 68 ms | **8.9×** |
119
+ | embedded `check()`, brew.sh | 604 ms | 45 ms | **13.3×** |
120
+ | CLI, 75-line script | 42 ms | 24 ms | 1.8× |
121
+ | embedded `check()`, 75-line script | 42 ms | 2.4 ms | **17×** |
122
+
123
+ The embedded rows are what an agent or editor integration pays per call:
124
+ no process spawn, no binary.
125
+
126
+ ## Compatibility notes
127
+
128
+ - Targets bash (default), sh/dash/ash and ksh dialects are accepted via
129
+ shebang, directive, or `-s`; sh-specific portability checks (the
130
+ SC2039/SC3xxx family) are not implemented yet.
131
+ - The parser is deliberately lenient: it keeps checking past constructs
132
+ the real shellcheck refuses to parse (e.g. `[ $tar --version ]`).
133
+ - Optional checks (`SC2002`, `SC2248`, `SC2250`) are off by default,
134
+ matching shellcheck 0.11; enable with `-o` / `include_optional=True`.
135
+ - Wiki links work the same: see <https://www.shellcheck.net/wiki/SC2086>
136
+ for any reported code.
137
+
138
+ ## Development
139
+
140
+ ```console
141
+ $ pip install -e . pytest
142
+ $ pytest # corpus + unit tests, < 1 s
143
+ $ python tools/extract_corpus.py /path/to/shellcheck # refresh corpus
144
+ $ python tools/update_expected_failures.py # refresh scoreboard
145
+ $ python tools/bench.py # benchmarks
146
+ ```
147
+
148
+ The package itself is MIT licensed and has zero runtime dependencies
149
+ (CPython 3.9–3.14 and PyPy). The vendored test corpus in `tests/data/` is
150
+ extracted from the GPLv3-licensed ShellCheck project and is used only for
151
+ development-time testing; it is not part of the distributed wheel.
152
+
153
+ ## See also
154
+
155
+ - [purejq](https://github.com/adam2go/purejq) — pure Python jq, same
156
+ philosophy: vendored official test suite, differential testing, no
157
+ binaries.
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pureshellcheck"
7
+ version = "0.1.0"
8
+ description = "A pure Python reimplementation of ShellCheck's most common checks"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "adam2go" }]
13
+ keywords = ["shellcheck", "shell", "bash", "lint", "static-analysis", "pure-python"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.9",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3.14",
25
+ "Programming Language :: Python :: Implementation :: CPython",
26
+ "Programming Language :: Python :: Implementation :: PyPy",
27
+ "Topic :: Software Development :: Quality Assurance",
28
+ "Topic :: Software Development :: Libraries",
29
+ ]
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/adam2go/pureshellcheck"
33
+
34
+ [project.scripts]
35
+ pureshellcheck = "pureshellcheck.cli:main"
36
+
37
+ [tool.setuptools.packages.find]
38
+ where = ["src"]
39
+
40
+ [tool.pytest.ini_options]
41
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,58 @@
1
+ """pureshellcheck: a pure Python reimplementation of ShellCheck's most
2
+ common checks.
3
+
4
+ >>> import pureshellcheck
5
+ >>> for finding in pureshellcheck.check('echo $foo'):
6
+ ... print(finding.line, finding.column, finding.code, finding.message)
7
+ """
8
+
9
+ __version__ = "0.1.0"
10
+
11
+ from .analyzer import Finding, run_checks # noqa: F401
12
+ from .parser import ParseError, parse # noqa: F401
13
+ from . import checks # noqa: F401 (registers all checks)
14
+
15
+
16
+ def check(source, shell=None, include_optional=False):
17
+ """Analyze a shell script and return a list of Finding objects.
18
+
19
+ `shell` overrides shebang detection ("bash", "sh", "dash", "ksh").
20
+ Findings have: code (int), severity, message, line, column, end_line,
21
+ end_column.
22
+ """
23
+ findings, _ = run_checks(source, shell=shell,
24
+ include_optional=include_optional)
25
+ return findings
26
+
27
+
28
+ def implemented_codes():
29
+ """The set of SC codes this version can emit."""
30
+ from .analyzer import NODE_CHECKS, TREE_CHECKS # noqa: F401
31
+ return set(_IMPLEMENTED)
32
+
33
+
34
+ # maintained by hand; verified by tests/test_implemented.py
35
+ _IMPLEMENTED = set()
36
+
37
+
38
+ def _register_codes(*codes):
39
+ _IMPLEMENTED.update(codes)
40
+
41
+
42
+ _register_codes(
43
+ 1073, # parse errors
44
+ 2006, 2016, 2026, 2027, 2041, 2042, 2043, 2046, 2048, 2066, 2068,
45
+ 2086, 2089, 2090, 2140, 2145, 2206, 2207, 2223, 2248, 2250, 2258,
46
+ )
47
+ _register_codes(
48
+ 2002, 2003, 2005, 2009, 2010, 2011, 2012, 2015, 2038, 2050, 2059,
49
+ 2064, 2065, 2114, 2115, 2116, 2126, 2148, 2162, 2164, 2174, 2181,
50
+ 2182, 2183, 2187, 2188, 2189, 2239, 2246, 2304, 2305,
51
+ 2306, 2307, 2308,
52
+ )
53
+ _register_codes(
54
+ 2004, 2034, 2128, 2153, 2154, 2155, 2178, 2179,
55
+ )
56
+ _register_codes(
57
+ 2007, 2028, 2035, 2093, 2094, 2103,
58
+ )