pureshellcheck 0.2.0__tar.gz → 0.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.
Files changed (24) hide show
  1. {pureshellcheck-0.2.0/src/pureshellcheck.egg-info → pureshellcheck-0.2.1}/PKG-INFO +12 -11
  2. {pureshellcheck-0.2.0 → pureshellcheck-0.2.1}/README.md +11 -10
  3. {pureshellcheck-0.2.0 → pureshellcheck-0.2.1}/pyproject.toml +1 -1
  4. {pureshellcheck-0.2.0 → pureshellcheck-0.2.1}/src/pureshellcheck/__init__.py +1 -1
  5. {pureshellcheck-0.2.0 → pureshellcheck-0.2.1}/src/pureshellcheck/analyzer.py +2 -0
  6. {pureshellcheck-0.2.0 → pureshellcheck-0.2.1}/src/pureshellcheck/cli.py +38 -3
  7. {pureshellcheck-0.2.0 → pureshellcheck-0.2.1}/src/pureshellcheck/shast.py +13 -0
  8. {pureshellcheck-0.2.0 → pureshellcheck-0.2.1/src/pureshellcheck.egg-info}/PKG-INFO +12 -11
  9. {pureshellcheck-0.2.0 → pureshellcheck-0.2.1}/LICENSE +0 -0
  10. {pureshellcheck-0.2.0 → pureshellcheck-0.2.1}/MANIFEST.in +0 -0
  11. {pureshellcheck-0.2.0 → pureshellcheck-0.2.1}/setup.cfg +0 -0
  12. {pureshellcheck-0.2.0 → pureshellcheck-0.2.1}/src/pureshellcheck/astlib.py +0 -0
  13. {pureshellcheck-0.2.0 → pureshellcheck-0.2.1}/src/pureshellcheck/checks/__init__.py +0 -0
  14. {pureshellcheck-0.2.0 → pureshellcheck-0.2.1}/src/pureshellcheck/checks/commands.py +0 -0
  15. {pureshellcheck-0.2.0 → pureshellcheck-0.2.1}/src/pureshellcheck/checks/misc.py +0 -0
  16. {pureshellcheck-0.2.0 → pureshellcheck-0.2.1}/src/pureshellcheck/checks/quoting.py +0 -0
  17. {pureshellcheck-0.2.0 → pureshellcheck-0.2.1}/src/pureshellcheck/checks/variables.py +0 -0
  18. {pureshellcheck-0.2.0 → pureshellcheck-0.2.1}/src/pureshellcheck/parser.py +0 -0
  19. {pureshellcheck-0.2.0 → pureshellcheck-0.2.1}/src/pureshellcheck/varflow.py +0 -0
  20. {pureshellcheck-0.2.0 → pureshellcheck-0.2.1}/src/pureshellcheck/varscan.py +0 -0
  21. {pureshellcheck-0.2.0 → pureshellcheck-0.2.1}/src/pureshellcheck.egg-info/SOURCES.txt +0 -0
  22. {pureshellcheck-0.2.0 → pureshellcheck-0.2.1}/src/pureshellcheck.egg-info/dependency_links.txt +0 -0
  23. {pureshellcheck-0.2.0 → pureshellcheck-0.2.1}/src/pureshellcheck.egg-info/entry_points.txt +0 -0
  24. {pureshellcheck-0.2.0 → pureshellcheck-0.2.1}/src/pureshellcheck.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pureshellcheck
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: A pure Python reimplementation of ShellCheck's most common checks
5
5
  Author: adam2go
6
6
  License: MIT
@@ -157,24 +157,25 @@ no process spawn, no binary. A one-line snippet checks in **~40 µs**
157
157
  (~25,000 checks/second); throughput on large scripts is ~57k lines/s. CLI
158
158
  time is dominated by CPython interpreter startup (~20 ms).
159
159
 
160
- **v0.2.0 vs v0.1.0** (controlled before/after,
160
+ **v0.2.x vs v0.1.0** (controlled before/after,
161
161
  `python tools/bench_compare.py`: baseline wheel from PyPI vs this tree in
162
162
  the same interpreter, 25–200 in-process repeats, outputs verified
163
163
  identical on every workload):
164
164
 
165
- | workload | v0.1.0 | v0.2.0 | improvement |
165
+ | workload | v0.1.0 | v0.2.1 | improvement |
166
166
  |---|---|---|---|
167
- | tiny (1 line) | 0.061 ms | 0.037 ms | 1.6× |
168
- | small (75 lines) | 2.62 ms | 1.30 ms | 2.0× |
169
- | medium (263 lines) | 9.46 ms | 4.81 ms | 2.0× |
170
- | large (1216 lines) | 48.6 ms | 21.3 ms | **2.3×** |
167
+ | tiny (1 line) | 0.058 ms | 0.036 ms | 1.6× |
168
+ | small (75 lines) | 2.44 ms | 1.21 ms | 2.0× |
169
+ | medium (263 lines) | 8.76 ms | 4.48 ms | 2.0× |
170
+ | large (1216 lines) | 46.2 ms | 20.0 ms | **2.3×** |
171
171
 
172
- The v0.2.0 speedups came from caching the AST child/parent structure and a
172
+ The speedups came from caching the AST child/parent structure and a
173
173
  document-order node table (one traversal instead of dozens), making
174
174
  variable states immutable tuples so branch snapshots are plain dict
175
- copies, a banded Levenshtein for SC2153 (fuzz-tested against the
176
- reference implementation on 20,000 random pairs), and memoizing repeated
177
- word/command resolution.
175
+ copies, a leaf-node fast path, a banded Levenshtein for SC2153
176
+ (fuzz-tested against the reference implementation on 20,000 random
177
+ pairs), and memoizing repeated word/command resolution. Package import is
178
+ 3.6 ms; remaining CLI latency is CPython interpreter startup.
178
179
 
179
180
  ## Compatibility notes
180
181
 
@@ -130,24 +130,25 @@ no process spawn, no binary. A one-line snippet checks in **~40 µs**
130
130
  (~25,000 checks/second); throughput on large scripts is ~57k lines/s. CLI
131
131
  time is dominated by CPython interpreter startup (~20 ms).
132
132
 
133
- **v0.2.0 vs v0.1.0** (controlled before/after,
133
+ **v0.2.x vs v0.1.0** (controlled before/after,
134
134
  `python tools/bench_compare.py`: baseline wheel from PyPI vs this tree in
135
135
  the same interpreter, 25–200 in-process repeats, outputs verified
136
136
  identical on every workload):
137
137
 
138
- | workload | v0.1.0 | v0.2.0 | improvement |
138
+ | workload | v0.1.0 | v0.2.1 | improvement |
139
139
  |---|---|---|---|
140
- | tiny (1 line) | 0.061 ms | 0.037 ms | 1.6× |
141
- | small (75 lines) | 2.62 ms | 1.30 ms | 2.0× |
142
- | medium (263 lines) | 9.46 ms | 4.81 ms | 2.0× |
143
- | large (1216 lines) | 48.6 ms | 21.3 ms | **2.3×** |
140
+ | tiny (1 line) | 0.058 ms | 0.036 ms | 1.6× |
141
+ | small (75 lines) | 2.44 ms | 1.21 ms | 2.0× |
142
+ | medium (263 lines) | 8.76 ms | 4.48 ms | 2.0× |
143
+ | large (1216 lines) | 46.2 ms | 20.0 ms | **2.3×** |
144
144
 
145
- The v0.2.0 speedups came from caching the AST child/parent structure and a
145
+ The speedups came from caching the AST child/parent structure and a
146
146
  document-order node table (one traversal instead of dozens), making
147
147
  variable states immutable tuples so branch snapshots are plain dict
148
- copies, a banded Levenshtein for SC2153 (fuzz-tested against the
149
- reference implementation on 20,000 random pairs), and memoizing repeated
150
- word/command resolution.
148
+ copies, a leaf-node fast path, a banded Levenshtein for SC2153
149
+ (fuzz-tested against the reference implementation on 20,000 random
150
+ pairs), and memoizing repeated word/command resolution. Package import is
151
+ 3.6 ms; remaining CLI latency is CPython interpreter startup.
151
152
 
152
153
  ## Compatibility notes
153
154
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pureshellcheck"
7
- version = "0.2.0"
7
+ version = "0.2.1"
8
8
  description = "A pure Python reimplementation of ShellCheck's most common checks"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -6,7 +6,7 @@ common checks.
6
6
  ... print(finding.line, finding.column, finding.code, finding.message)
7
7
  """
8
8
 
9
- __version__ = "0.2.0"
9
+ __version__ = "0.2.1"
10
10
 
11
11
  from .analyzer import Finding, run_checks # noqa: F401
12
12
  from .parser import ParseError, parse # noqa: F401
@@ -382,6 +382,8 @@ def apply_directives(findings, directives, nodes, source, positions):
382
382
  def run_checks(source, shell=None, include_optional=False,
383
383
  filename="<stdin>"):
384
384
  """Parse and analyze a script. Returns (findings, parse_error|None)."""
385
+ if source and source[0] == chr(0xFEFF):
386
+ source = source[1:] # tolerate a UTF-8 BOM
385
387
  parser = Parser(source)
386
388
  try:
387
389
  root = parser.parse()
@@ -1,7 +1,6 @@
1
1
  """Command line interface, modeled on shellcheck's."""
2
2
 
3
3
  import argparse
4
- import json
5
4
  import sys
6
5
 
7
6
  from . import __version__, run_checks
@@ -19,6 +18,38 @@ COLORS = {
19
18
  }
20
19
 
21
20
 
21
+ _TAKES_ONE = {"-s", "--shell", "-f", "--format", "-e", "--exclude",
22
+ "-S", "--severity", "-o", "--enable"}
23
+ _COLOR_VALUES = {"auto", "always", "never"}
24
+
25
+
26
+ def _reorder(argv):
27
+ """Let options appear after files, like shellcheck: before Python 3.12,
28
+ argparse stops recognizing options once positionals begin, so
29
+ `pureshellcheck foo.sh -f gcc` would fail there. Deterministically sort
30
+ options to the front."""
31
+ opts, pos = [], []
32
+ i, n = 0, len(argv)
33
+ while i < n:
34
+ a = argv[i]
35
+ if a == "--":
36
+ pos.extend(argv[i + 1:])
37
+ break
38
+ if a.startswith("-") and a != "-":
39
+ take = 2 if a in _TAKES_ONE and i + 1 < n else 1
40
+ if a in ("-C", "--color") and i + 1 < n \
41
+ and argv[i + 1] in _COLOR_VALUES:
42
+ take = 2
43
+ opts.extend(argv[i:i + take])
44
+ i += take
45
+ else:
46
+ pos.append(a)
47
+ i += 1
48
+ if any(p.startswith("-") and p != "-" for p in pos):
49
+ pos.insert(0, "--")
50
+ return opts + pos
51
+
52
+
22
53
  def parse_args(argv):
23
54
  p = argparse.ArgumentParser(
24
55
  prog="pureshellcheck",
@@ -45,7 +76,7 @@ def parse_args(argv):
45
76
  help="use color (default: auto)")
46
77
  p.add_argument("--version", action="version",
47
78
  version="pureshellcheck %s" % __version__)
48
- return p.parse_args(argv)
79
+ return p.parse_args(_reorder(argv))
49
80
 
50
81
 
51
82
  def parse_excludes(items):
@@ -65,7 +96,9 @@ def check_file(path, args, excluded, min_rank):
65
96
  source = sys.stdin.read()
66
97
  name = "-"
67
98
  else:
68
- with open(path, encoding="utf-8", errors="replace") as f:
99
+ # utf-8-sig: tolerate a UTF-8 BOM, which would otherwise break
100
+ # shebang detection
101
+ with open(path, encoding="utf-8-sig", errors="replace") as f:
69
102
  source = f.read()
70
103
  name = path
71
104
  shell = args.shell if args.shell != "busybox" else "ash"
@@ -148,9 +181,11 @@ def main(argv=None):
148
181
  else:
149
182
  json_items.extend(finding_json(name, f) for f in findings)
150
183
  if args.format == "json":
184
+ import json
151
185
  json.dump(json_items, out)
152
186
  out.write("\n")
153
187
  elif args.format == "json1":
188
+ import json
154
189
  json.dump({"comments": json_items}, out)
155
190
  out.write("\n")
156
191
  if had_error:
@@ -82,15 +82,28 @@ def walk(node):
82
82
  stack.extend(reversed(children))
83
83
 
84
84
 
85
+ # Node kinds whose fields can never contain child nodes.
86
+ LEAF_KINDS = frozenset({
87
+ "T_Literal", "T_SingleQuoted", "T_DollarSingleQuoted", "T_Glob",
88
+ "TA_Literal", "TA_Empty", "T_IoDuplicate", "TC_Empty",
89
+ })
90
+
91
+ _EMPTY = ()
92
+
93
+
85
94
  def set_parents(root):
86
95
  """Link parents, cache children, and return all nodes in doc order."""
87
96
  nodes = []
88
97
  append = nodes.append
89
98
  stack = [root]
90
99
  pop = stack.pop
100
+ leaf = LEAF_KINDS
91
101
  while stack:
92
102
  n = pop()
93
103
  append(n)
104
+ if n.kind in leaf:
105
+ n.children = _EMPTY
106
+ continue
94
107
  children = []
95
108
  add = children.append
96
109
  for value in n.fields.values():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pureshellcheck
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: A pure Python reimplementation of ShellCheck's most common checks
5
5
  Author: adam2go
6
6
  License: MIT
@@ -157,24 +157,25 @@ no process spawn, no binary. A one-line snippet checks in **~40 µs**
157
157
  (~25,000 checks/second); throughput on large scripts is ~57k lines/s. CLI
158
158
  time is dominated by CPython interpreter startup (~20 ms).
159
159
 
160
- **v0.2.0 vs v0.1.0** (controlled before/after,
160
+ **v0.2.x vs v0.1.0** (controlled before/after,
161
161
  `python tools/bench_compare.py`: baseline wheel from PyPI vs this tree in
162
162
  the same interpreter, 25–200 in-process repeats, outputs verified
163
163
  identical on every workload):
164
164
 
165
- | workload | v0.1.0 | v0.2.0 | improvement |
165
+ | workload | v0.1.0 | v0.2.1 | improvement |
166
166
  |---|---|---|---|
167
- | tiny (1 line) | 0.061 ms | 0.037 ms | 1.6× |
168
- | small (75 lines) | 2.62 ms | 1.30 ms | 2.0× |
169
- | medium (263 lines) | 9.46 ms | 4.81 ms | 2.0× |
170
- | large (1216 lines) | 48.6 ms | 21.3 ms | **2.3×** |
167
+ | tiny (1 line) | 0.058 ms | 0.036 ms | 1.6× |
168
+ | small (75 lines) | 2.44 ms | 1.21 ms | 2.0× |
169
+ | medium (263 lines) | 8.76 ms | 4.48 ms | 2.0× |
170
+ | large (1216 lines) | 46.2 ms | 20.0 ms | **2.3×** |
171
171
 
172
- The v0.2.0 speedups came from caching the AST child/parent structure and a
172
+ The speedups came from caching the AST child/parent structure and a
173
173
  document-order node table (one traversal instead of dozens), making
174
174
  variable states immutable tuples so branch snapshots are plain dict
175
- copies, a banded Levenshtein for SC2153 (fuzz-tested against the
176
- reference implementation on 20,000 random pairs), and memoizing repeated
177
- word/command resolution.
175
+ copies, a leaf-node fast path, a banded Levenshtein for SC2153
176
+ (fuzz-tested against the reference implementation on 20,000 random
177
+ pairs), and memoizing repeated word/command resolution. Package import is
178
+ 3.6 ms; remaining CLI latency is CPython interpreter startup.
178
179
 
179
180
  ## Compatibility notes
180
181
 
File without changes
File without changes