sh-ast 0.1.0 → 0.2.0

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 (39) hide show
  1. package/dist/analyze/enumerate-commands.d.ts +7 -1
  2. package/dist/analyze/enumerate-commands.d.ts.map +1 -1
  3. package/dist/analyze/enumerate-commands.js +7 -1
  4. package/dist/analyze/enumerate-commands.js.map +1 -1
  5. package/dist/analyze/index.d.ts +12 -4
  6. package/dist/analyze/index.d.ts.map +1 -1
  7. package/dist/analyze/index.js +18 -15
  8. package/dist/analyze/index.js.map +1 -1
  9. package/dist/analyze/resolve-argv0.d.ts +335 -0
  10. package/dist/analyze/resolve-argv0.d.ts.map +1 -0
  11. package/dist/analyze/resolve-argv0.js +510 -0
  12. package/dist/analyze/resolve-argv0.js.map +1 -0
  13. package/dist/analyze/resolve-word.d.ts +5 -0
  14. package/dist/analyze/resolve-word.d.ts.map +1 -1
  15. package/dist/analyze/resolve-word.js +5 -0
  16. package/dist/analyze/resolve-word.js.map +1 -1
  17. package/dist/analyze.d.ts +592 -211
  18. package/dist/errors.d.ts +81 -16
  19. package/dist/errors.d.ts.map +1 -1
  20. package/dist/errors.js +90 -17
  21. package/dist/errors.js.map +1 -1
  22. package/dist/index.d.ts +1 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +1 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/parse-depth-guard.d.ts +11 -0
  27. package/dist/parse-depth-guard.d.ts.map +1 -0
  28. package/dist/parse-depth-guard.js +633 -0
  29. package/dist/parse-depth-guard.js.map +1 -0
  30. package/dist/parse.d.ts +7 -2
  31. package/dist/parse.d.ts.map +1 -1
  32. package/dist/parse.js +15 -8
  33. package/dist/parse.js.map +1 -1
  34. package/dist/sh-ast.d.ts +121 -45
  35. package/dist/types.d.ts +5 -1
  36. package/dist/types.d.ts.map +1 -1
  37. package/dist/wasm-instance.js +3 -3
  38. package/dist/wasm-instance.js.map +1 -1
  39. package/package.json +1 -1
@@ -0,0 +1,633 @@
1
+ import { ShParseMaxDepthError } from './errors.js';
2
+ /**
3
+ * The maximum estimated structural nesting depth {@link assertParseDepthWithinLimit}
4
+ * accepts before refusing to hand `text` to the WASM parser at all. A single
5
+ * hard-coded, non-configurable safe default — see that function's doc comment
6
+ * for the empirical justification.
7
+ *
8
+ * Measured empirically (this repo's `vitest` environment, Node 24, the
9
+ * committed linux/amd64 `sh-ast.wasm` shim — which runs identically on
10
+ * darwin, since WASM bytecode is host-portable even though the *build* of
11
+ * that `.wasm` is not; see `shim/`'s rebuild-match CI job): feeding
12
+ * `parseSync` a flat top-level script consisting of `N` genuinely nested
13
+ * frames hits a raw, uncatchable-from-inside-the-shim
14
+ * `RangeError: Maximum call stack size exceeded` (thrown from `wasm-function`
15
+ * stack frames — i.e. inside mvdan/sh's own recursive-descent parser,
16
+ * running inside the WASM instance) at these depths, depending on which
17
+ * syntax construct supplies the nesting:
18
+ *
19
+ * | construct | first crash observed at N = |
20
+ * | -------------------------------------------- | ---------------------------- |
21
+ * | `$(...)` command substitution (worst case) | 252 |
22
+ * | `${a:-${a:-...}}` nested parameter expansion | ~400–600 |
23
+ * | `f(){ ... }` nested function definitions | ~300–350 |
24
+ * | `case`/`esac` nesting | ~350–400 |
25
+ * | `while`/`done` nesting | ~450–800 |
26
+ * | `$((...))` nested arithmetic expansion | ~450–800 |
27
+ * | `(...)` nested subshells | ~1,000–1,500 |
28
+ * | `\|`/`&&`/`\|\|` chained pipeline/list stages | ~1,000 |
29
+ * | `if`/`fi`, `for`/`done`, `{ }` block nesting | ~1,000 |
30
+ *
31
+ * `$(...)` command substitution is the tightest bound by a wide margin —
32
+ * each level costs the parser noticeably more native stack than a bare
33
+ * subshell paren — so the limit below is sized against *that* vector, not
34
+ * the more forgiving ones. The exact crash point is a hard native-stack
35
+ * limit, not a clean threshold, and it shrinks further the deeper the
36
+ * *caller's own* JS call stack already is when it invokes `parseSync`
37
+ * (measured: with as little as ~50–1,000 frames of unrelated pre-existing
38
+ * recursion already on the stack, the same 252-deep `$(...)` input that
39
+ * succeeds from a fresh top-level script instead crashes at a noticeably
40
+ * lower depth) — exactly the situation a real embedding (an ESLint rule, a
41
+ * CLI, a permission hook) is in, never a pristine empty stack.
42
+ *
43
+ * 150 leaves roughly 100 frames (⁓1.7x) of headroom below the *lowest*
44
+ * observed native-crash depth (252, `$(...)`) measured from a fresh stack,
45
+ * and was independently confirmed safe with as much as 3,000 frames of
46
+ * *additional*, unrelated caller-stack depth already consumed before the
47
+ * pathological input is scanned — comfortably past what any realistic
48
+ * caller stack looks like. It is not a round, "generous"-sounding number
49
+ * like 10,000 for the same reason `MAX_STRUCTURAL_DEPTH` (in
50
+ * `analyze/enumerate-commands.ts`) isn't: a limit that high would never
51
+ * actually fire before the real, uncatchable crash does. 150 also
52
+ * comfortably exceeds any realistic hand-written script's nesting — this
53
+ * is pathological-input protection, not a normal-usage ceiling (a
54
+ * deliberately deep-but-legitimate ~100-level script is still accepted;
55
+ * see `parse-depth-guard.test.ts`).
56
+ */
57
+ const MAX_PARSE_NESTING_DEPTH = 150;
58
+ const WHITESPACE = new Set([' ', '\t', '\n', '\r']);
59
+ /**
60
+ * Characters that end a bare (unquoted) word during the word-boundary scan
61
+ * `estimateMaxNestingDepth` uses to recognize `if`/`for`/`while`/`until`/
62
+ * `case`/`select` (openers) and `fi`/`done`/`esac` (closers). Deliberately
63
+ * broader than strict shell tokenization needs to be — the goal is never to
64
+ * *merge* two words that should be separate (which could hide a keyword),
65
+ * not to perfectly reject non-keyword-boundary characters.
66
+ */
67
+ const WORD_DELIMITERS = new Set([
68
+ ';',
69
+ '|',
70
+ '&',
71
+ '(',
72
+ ')',
73
+ '{',
74
+ '}',
75
+ '<',
76
+ '>',
77
+ '`',
78
+ '"',
79
+ "'",
80
+ '#',
81
+ '\\',
82
+ ' ',
83
+ '\t',
84
+ '\n',
85
+ '\r',
86
+ ]);
87
+ /**
88
+ * Keywords that open a genuinely-nested structural region, other than
89
+ * `case` (see {@link CASE_OPENER_KEYWORD} — it needs a distinct
90
+ * {@link ScanContext} kind, not this generic one, to avoid a case arm's
91
+ * pattern-terminating `)` being misread as this region's own closing
92
+ * paren). Paired with {@link STRUCTURAL_CLOSER_KEYWORDS}.
93
+ */
94
+ const STRUCTURAL_OPENER_KEYWORDS = new Set(['if', 'for', 'while', 'until', 'select']);
95
+ /** Keywords that close a genuinely-nested structural region opened by {@link STRUCTURAL_OPENER_KEYWORDS}. */
96
+ const STRUCTURAL_CLOSER_KEYWORDS = new Set(['fi', 'done']);
97
+ /**
98
+ * `case` needs its own {@link ScanContext} kind (`'case'`), distinct from
99
+ * the generic `'other'` used for `(`/`{`/`if`/`for`/`while`/`until`/
100
+ * `select`: each `case` arm is terminated by a bare `)` —
101
+ * `case WORD in PATTERN)` — that is **not** a closing paren for anything.
102
+ * If `case` pushed a generic `'other'` region, the first arm's `)` would
103
+ * blind-pop-close that region immediately (see {@link estimateMaxNestingDepth}'s
104
+ * "never matches specific opener/closer pairs" design), silently
105
+ * *under-counting* the case body's real nesting contribution for every
106
+ * arm after the first — the one direction this estimator must never be
107
+ * wrong in. A `'case'` region instead ignores a bare `)` outright (a
108
+ * pattern terminator, not a real closer) and is only closed by the
109
+ * word-boundary `esac` keyword; a *real* nested paren opened inside an
110
+ * arm's action list (e.g. `case x in a) (subshell) ;; esac`) still pushes
111
+ * its own `'other'` region on top of `'case'` and is closed by its own
112
+ * `)` exactly as usual, since that `)` pops the (different) top-of-stack
113
+ * entry, never the `'case'` entry beneath it.
114
+ */
115
+ const CASE_OPENER_KEYWORD = 'case';
116
+ const CASE_CLOSER_KEYWORD = 'esac';
117
+ function isWordChar(ch) {
118
+ return ch !== undefined && !WORD_DELIMITERS.has(ch);
119
+ }
120
+ /**
121
+ * Conservatively upper-bounds the structural nesting depth mvdan/sh's
122
+ * parser will recurse through for `text`, via a single left-to-right
123
+ * character scan — **without** actually parsing the shell grammar. This is
124
+ * a heuristic, not a real shell tokenizer: see the module doc comment for
125
+ * the false-positive risk this trades for correctness.
126
+ *
127
+ * Combines two running counters into one "effective depth" (`bracketDepth +
128
+ * chainLen`), checked against `limit` after every increment:
129
+ *
130
+ * - `bracketDepth`, incremented for each of: an unquoted `(` or `{` (covers
131
+ * subshells, brace groups, function bodies, `$(...)` command/`$((...))`
132
+ * arithmetic substitution, process substitution, and — doubly, which
133
+ * only makes the estimate *more* conservative — each paren of
134
+ * `$((...))`), an unquoted backtick command-substitution open, and a
135
+ * word-boundary `if`/`for`/`while`/`until`/`case`/`select` keyword; and
136
+ * decremented for the corresponding `)`, `}`, closing backtick, or
137
+ * `fi`/`done`/`esac` keyword. **Never** matches specific opener/closer
138
+ * *pairs* — any closer decrements whatever is currently open, and a
139
+ * closer seen with nothing open is simply ignored (never lets the
140
+ * counter go negative) — so a malformed/mismatched-bracket input can
141
+ * only ever make this estimate *more* conservative (higher), never miss
142
+ * real nesting.
143
+ * - `chainLen`, tracking how many `|`/`|&`/`&&`/`\|\|` pipeline/list-operator
144
+ * tokens are chained in the *current* statement — mvdan/sh's parser
145
+ * recurses per chain link the same way it does per bracket, so a long
146
+ * flat `a|a|a|...` chain is just as much a real stack-depth risk as
147
+ * nested subshells are (measured: ~1,000 stages — see
148
+ * `MAX_PARSE_NESTING_DEPTH`'s doc comment), even though it is *not*
149
+ * nested tree structure. Reset to `0` on a statement boundary (`;`, an
150
+ * unescaped newline, or an unpaired `&`), and saved/restored (not
151
+ * dropped) around a bracket/keyword region so a chain that continues
152
+ * after a nested subshell (`a | (b) | c`) still accumulates its full
153
+ * length rather than silently restarting. Correctly **not** counted: a
154
+ * bare `|` while the innermost `case`'s current arm is still in
155
+ * *pattern* position (`PATTERN1|PATTERN2|...)`) — that's glob-pattern
156
+ * alternation, not a pipeline (see `casePatternStack`'s doc comment) —
157
+ * while a `|` in that same arm's *action* list (after its terminating
158
+ * `)`) is a real pipeline and is counted normally.
159
+ *
160
+ * Skips (does not count structure inside): single-quoted strings, `$'...'`
161
+ * ANSI-C-quoted strings, `#`-comments (only recognized at a word boundary,
162
+ * matching shell comment syntax), and backslash-escaped characters — none
163
+ * of these can contain an expansion mvdan/sh recurses into. Double-quoted
164
+ * strings are handled more carefully: their *literal* content (including
165
+ * multibyte characters — scanning is by JS string index, i.e. UTF-16 code
166
+ * unit, which never misaligns with a surrogate pair or any ASCII structural
167
+ * character) is skipped, but a `$(...)` command substitution or backtick
168
+ * expansion **nested inside** a double-quoted string is still a real,
169
+ * depth-bearing recursion in mvdan/sh's grammar and is still counted; once
170
+ * that nested region closes, scanning correctly resumes in "inside a
171
+ * double-quoted string" mode rather than losing track of the enclosing
172
+ * quote.
173
+ *
174
+ * Known, accepted false-positive sources (all documented here rather than
175
+ * papered over — see the module doc comment's "may over-estimate, must
176
+ * never under-estimate" rule):
177
+ * - A keyword word (`if`, `done`, …) used as a plain *argument*
178
+ * (`echo if`) is indistinguishable from real command-position syntax by
179
+ * this scanner and is still counted, since this scanner never tracks
180
+ * grammatical position — only real parsing does that.
181
+ * - A bare `(`/`{` inside a double-quoted string is correctly *not*
182
+ * counted (see above), but the same characters appearing entirely
183
+ * outside of any quoting are always counted even in contexts a full
184
+ * parser might not treat as nesting.
185
+ * - A pipeline/list chain that runs many *unrelated* statements in a row,
186
+ * each separated by `;`/newline, correctly resets `chainLen` between
187
+ * them — but a script with an unusually long *single* chain of harmless
188
+ * `true | true | true | …` well past what any real script would ever
189
+ * write is rejected exactly like a genuinely pathological one, since
190
+ * this scanner cannot distinguish "long but benign" from "adversarial"
191
+ * chain length any more than it can for bracket nesting.
192
+ *
193
+ * Heredoc bodies (`<<EOF` / `<<-EOF` / `<<'EOF'` through the matching
194
+ * delimiter line) are tracked, not merely skipped as ordinary text: a
195
+ * heredoc body is scanned line-by-line for its terminating delimiter, and
196
+ * — critically — a `$(...)` or backtick expansion appearing *inside* the
197
+ * body (still real recursion in mvdan/sh's grammar for an unquoted
198
+ * heredoc) is still counted, exactly as inside a double-quoted string.
199
+ * This is deliberate, not incidental: without heredoc-aware handling, a
200
+ * heredoc body containing plausible-looking closer text (stray `)`/`}`/
201
+ * `fi`/`done`/`esac` — completely inert as far as the real parser is
202
+ * concerned, since it's just heredoc body data) would blind-pop-close
203
+ * *real* open regions from **outside** the heredoc, silently
204
+ * *under-counting* genuine nesting split across a `<<EOF ... EOF` boundary
205
+ * — exactly the failure this estimator must never produce.
206
+ *
207
+ * Stops scanning and returns as soon as the running depth exceeds `limit`
208
+ * (returning `limit + 1`, not necessarily the "true" final maximum) — this
209
+ * keeps the guard itself cheap even against a very large adversarial input,
210
+ * since only "did it exceed the limit" is ever needed by
211
+ * {@link assertParseDepthWithinLimit}.
212
+ *
213
+ * Not exported beyond this module — {@link assertParseDepthWithinLimit} is
214
+ * the only caller; exercised indirectly, through `parseSync`, by
215
+ * `test/parse-depth-guard.test.ts`.
216
+ */
217
+ function estimateMaxNestingDepth(text, limit) {
218
+ let bracketDepth = 0;
219
+ let chainLen = 0;
220
+ let maxDepth = 0;
221
+ const contextStack = [];
222
+ const chainLenStack = [];
223
+ const heredocInfoStack = [];
224
+ const pendingHeredocs = [];
225
+ const n = text.length;
226
+ let i = 0;
227
+ // Parallel to `contextStack`, but pushed/popped only in lockstep with a
228
+ // `'case'` entry specifically (not with every entry): `true` while
229
+ // scanning the *pattern* list of the innermost open `case`'s current arm
230
+ // (`PATTERN)` — where a bare `|` is alternation, not a pipeline — and
231
+ // `false` once that arm's terminating `)` has been seen (its *action*
232
+ // list, `LIST` in `PATTERN) LIST ;;`, where `|` is once again a real
233
+ // pipeline). Reset back to `true` at that arm's `;;`/`;&`/`;;&`
234
+ // terminator (see the `;` handler below) for the next arm's pattern.
235
+ const casePatternStack = [];
236
+ // Read cursor into `pendingHeredocs` (append-only — see
237
+ // `startNextHeredocIfAny`) instead of `Array.prototype.shift()`, which is
238
+ // O(k) per call (re-indexes the whole remaining array) and so made a
239
+ // script queuing many heredocs an accidental O(k²) cost overall — this
240
+ // guard must stay cheap even against a large adversarial input (see this
241
+ // function's doc comment) for *every* operation it performs, not just the
242
+ // early-exit-on-limit-exceeded one.
243
+ let pendingHeredocCursor = 0;
244
+ function noteDepth() {
245
+ const total = bracketDepth + chainLen;
246
+ if (total > maxDepth)
247
+ maxDepth = total;
248
+ }
249
+ function open() {
250
+ bracketDepth++;
251
+ noteDepth();
252
+ }
253
+ function close() {
254
+ if (bracketDepth > 0)
255
+ bracketDepth--;
256
+ }
257
+ function pushRegion(kind) {
258
+ contextStack.push(kind);
259
+ chainLenStack.push(chainLen);
260
+ chainLen = 0;
261
+ }
262
+ /**
263
+ * Pops `contextStack`'s top entry — and, in lockstep, restores `chainLen`
264
+ * from `chainLenStack` — **only if** it actually equals `kind`; otherwise
265
+ * does nothing at all and returns `false`. Every caller **must** gate its
266
+ * own `close()` (or any other closing side effect) on this return value:
267
+ * a closer token whose region doesn't actually match the top of the
268
+ * stack (a stray `}` with no open `{`, an `esac` with no open `case`, …)
269
+ * is inert text as far as the *real* parser is concerned, and must be
270
+ * exactly as inert here — closing anyway (the bug this comment guards
271
+ * against: see `ShParseMaxDepthError`'s regression tests for
272
+ * `'case x in a}) '.repeat(n) + ... `) lets a self-canceling stray-closer
273
+ * input decrement `bracketDepth` once per repetition while `contextStack`
274
+ * (the *real* nesting mvdan/sh's parser will recurse through) keeps
275
+ * growing unboundedly — silently hiding real, unbounded nesting from this
276
+ * estimator, which is the one direction it must never be wrong in.
277
+ */
278
+ function popRegion(kind) {
279
+ if (contextStack.length > 0 && contextStack[contextStack.length - 1] === kind) {
280
+ contextStack.pop();
281
+ chainLen = chainLenStack.pop() ?? 0;
282
+ return true;
283
+ }
284
+ return false;
285
+ }
286
+ function toggleBacktick() {
287
+ if (popRegion('backtick')) {
288
+ close();
289
+ }
290
+ else {
291
+ pushRegion('backtick');
292
+ open();
293
+ }
294
+ }
295
+ function resetChain() {
296
+ chainLen = 0;
297
+ }
298
+ function chainLink() {
299
+ chainLen++;
300
+ noteDepth();
301
+ }
302
+ /**
303
+ * `true` iff the innermost open region is `'case'` and its current arm is
304
+ * still in *pattern* position — see `casePatternStack`'s doc comment.
305
+ */
306
+ function inCasePatternPosition() {
307
+ if (contextStack.length === 0 ||
308
+ contextStack[contextStack.length - 1] !== 'case' ||
309
+ casePatternStack.length === 0) {
310
+ return false;
311
+ }
312
+ return casePatternStack[casePatternStack.length - 1];
313
+ }
314
+ function startNextHeredocIfAny() {
315
+ if (pendingHeredocCursor < pendingHeredocs.length) {
316
+ // `pendingHeredocCursor < pendingHeredocs.length` just checked above,
317
+ // so this is a real element.
318
+ const next = pendingHeredocs[pendingHeredocCursor];
319
+ pendingHeredocCursor++;
320
+ pushRegion('heredoc');
321
+ heredocInfoStack.push(next);
322
+ }
323
+ }
324
+ while (i < n) {
325
+ if (maxDepth > limit)
326
+ return maxDepth;
327
+ const topKind = contextStack.length > 0 ? contextStack[contextStack.length - 1] : undefined;
328
+ const ch = text[i];
329
+ if (topKind === 'heredoc') {
330
+ const atLineStart = i === 0 || text[i - 1] === '\n';
331
+ // `heredocInfoStack` is pushed/popped in lockstep with every
332
+ // `'heredoc'` entry in `contextStack` (see `startNextHeredocIfAny`
333
+ // and the delimiter-match branch below) — `topKind === 'heredoc'`
334
+ // here already guarantees a corresponding entry exists.
335
+ const info = heredocInfoStack[heredocInfoStack.length - 1];
336
+ if (atLineStart) {
337
+ let lineEnd = i;
338
+ while (lineEnd < n && text[lineEnd] !== '\n')
339
+ lineEnd++;
340
+ const lineRaw = text.slice(i, lineEnd);
341
+ const compareLine = info.stripLeadingTabs ? lineRaw.replace(/^\t+/, '') : lineRaw;
342
+ if (compareLine === info.delimiter) {
343
+ popRegion('heredoc');
344
+ heredocInfoStack.pop();
345
+ i = lineEnd < n ? lineEnd + 1 : lineEnd;
346
+ startNextHeredocIfAny();
347
+ continue;
348
+ }
349
+ }
350
+ // Heredoc body content, not the terminator line: `$(...)`/backtick
351
+ // are still real recursion (see the module doc comment); everything
352
+ // else — including a bare `(`/`{`/keyword-looking text and multibyte
353
+ // characters — is inert body data.
354
+ if (ch === '\\') {
355
+ i += 2;
356
+ continue;
357
+ }
358
+ if (ch === '$' && text[i + 1] === '(') {
359
+ pushRegion('other');
360
+ open();
361
+ i += 2;
362
+ continue;
363
+ }
364
+ if (ch === '`') {
365
+ toggleBacktick();
366
+ i++;
367
+ continue;
368
+ }
369
+ i++;
370
+ continue;
371
+ }
372
+ if (topKind === 'dquote') {
373
+ if (ch === '\\') {
374
+ i += 2;
375
+ continue;
376
+ }
377
+ if (ch === '"') {
378
+ contextStack.pop();
379
+ i++;
380
+ continue;
381
+ }
382
+ if (ch === '$' && text[i + 1] === '(') {
383
+ pushRegion('other');
384
+ open();
385
+ i += 2;
386
+ continue;
387
+ }
388
+ if (ch === '`') {
389
+ toggleBacktick();
390
+ i++;
391
+ continue;
392
+ }
393
+ // Literal double-quoted content — including multibyte characters —
394
+ // never affects depth.
395
+ i++;
396
+ continue;
397
+ }
398
+ // "code" scanning mode (top-level, or inside an already-open paren/
399
+ // brace/keyword/backtick region — all of those contain ordinary shell
400
+ // code, so they share this same scanning mode).
401
+ if (ch === '\\') {
402
+ i += 2;
403
+ continue;
404
+ }
405
+ if (ch === '#') {
406
+ const prev = i > 0 ? text[i - 1] : undefined;
407
+ if (prev === undefined || WHITESPACE.has(prev) || WORD_DELIMITERS.has(prev)) {
408
+ while (i < n && text[i] !== '\n')
409
+ i++;
410
+ continue;
411
+ }
412
+ i++;
413
+ continue;
414
+ }
415
+ if (ch === "'") {
416
+ i++;
417
+ while (i < n && text[i] !== "'")
418
+ i++;
419
+ if (i < n)
420
+ i++;
421
+ continue;
422
+ }
423
+ if (ch === '$' && text[i + 1] === "'") {
424
+ i += 2;
425
+ while (i < n && text[i] !== "'") {
426
+ if (text[i] === '\\') {
427
+ i += 2;
428
+ continue;
429
+ }
430
+ i++;
431
+ }
432
+ if (i < n)
433
+ i++;
434
+ continue;
435
+ }
436
+ if (ch === '"') {
437
+ contextStack.push('dquote');
438
+ i++;
439
+ continue;
440
+ }
441
+ if (ch === '`') {
442
+ toggleBacktick();
443
+ i++;
444
+ continue;
445
+ }
446
+ if (ch === '(' || ch === '{') {
447
+ pushRegion('other');
448
+ open();
449
+ i++;
450
+ continue;
451
+ }
452
+ if (ch === ')') {
453
+ // A bare `)` directly inside a `case` region (no intervening real
454
+ // opener) is that arm's pattern terminator, not a closing paren —
455
+ // see CASE_OPENER_KEYWORD's doc comment. Leave the region open, but
456
+ // this arm's pattern list has ended: everything until the next
457
+ // `;;`/`;&`/`;;&` is now its *action* list (see `casePatternStack`'s
458
+ // doc comment) — a bare `|` there is a real pipeline again.
459
+ if (contextStack.length > 0 && contextStack[contextStack.length - 1] === 'case') {
460
+ if (casePatternStack.length > 0) {
461
+ casePatternStack[casePatternStack.length - 1] = false;
462
+ }
463
+ i++;
464
+ continue;
465
+ }
466
+ if (popRegion('other'))
467
+ close();
468
+ i++;
469
+ continue;
470
+ }
471
+ if (ch === '}') {
472
+ if (popRegion('other'))
473
+ close();
474
+ i++;
475
+ continue;
476
+ }
477
+ if (ch === '<' && text[i + 1] === '<' && text[i + 2] !== '<') {
478
+ // A `<<`/`<<-` heredoc redirect — queue its delimiter; the body
479
+ // itself starts at the *next* newline (there may be more of this
480
+ // line — another redirect, another heredoc, the rest of the command
481
+ // — still to scan first).
482
+ let j = i + 2;
483
+ let stripLeadingTabs = false;
484
+ if (text[j] === '-') {
485
+ stripLeadingTabs = true;
486
+ j++;
487
+ }
488
+ while (j < n && (text[j] === ' ' || text[j] === '\t'))
489
+ j++;
490
+ let delimiter = '';
491
+ if (text[j] === "'" || text[j] === '"') {
492
+ const quote = text[j];
493
+ j++;
494
+ while (j < n && text[j] !== quote) {
495
+ delimiter += text[j];
496
+ j++;
497
+ }
498
+ if (j < n)
499
+ j++;
500
+ }
501
+ else {
502
+ while (j < n && isWordChar(text[j])) {
503
+ if (text[j] === '\\') {
504
+ j++;
505
+ if (j < n) {
506
+ delimiter += text[j];
507
+ j++;
508
+ }
509
+ continue;
510
+ }
511
+ delimiter += text[j];
512
+ j++;
513
+ }
514
+ }
515
+ pendingHeredocs.push({ delimiter, stripLeadingTabs });
516
+ i = j;
517
+ continue;
518
+ }
519
+ if (ch === ';') {
520
+ // `;;`/`;;&` (both start with two semicolons) or `;&` end the
521
+ // *current* case arm and return to *pattern* position for the next
522
+ // one — see `casePatternStack`'s doc comment. A single `;` mid-arm
523
+ // (an ordinary statement separator within one arm's action list)
524
+ // does not; only the first `;` of one of these two-character
525
+ // sequences flips the flag, matched by lookahead without consuming
526
+ // extra characters (the loop still advances one `;` at a time below,
527
+ // which is harmless — `resetChain()`/this flag flip are each
528
+ // idempotent).
529
+ if (contextStack.length > 0 &&
530
+ contextStack[contextStack.length - 1] === 'case' &&
531
+ casePatternStack.length > 0 &&
532
+ !casePatternStack[casePatternStack.length - 1] &&
533
+ (text[i + 1] === ';' || text[i + 1] === '&')) {
534
+ casePatternStack[casePatternStack.length - 1] = true;
535
+ }
536
+ resetChain();
537
+ i++;
538
+ continue;
539
+ }
540
+ if (ch === '\n') {
541
+ resetChain();
542
+ i++;
543
+ startNextHeredocIfAny();
544
+ continue;
545
+ }
546
+ if (ch === '&' && text[i + 1] === '&') {
547
+ if (!inCasePatternPosition())
548
+ chainLink();
549
+ i += 2;
550
+ continue;
551
+ }
552
+ if (ch === '|' && text[i + 1] === '|') {
553
+ if (!inCasePatternPosition())
554
+ chainLink();
555
+ i += 2;
556
+ continue;
557
+ }
558
+ if (ch === '|' && text[i + 1] === '&') {
559
+ if (!inCasePatternPosition())
560
+ chainLink();
561
+ i += 2;
562
+ continue;
563
+ }
564
+ if (ch === '|') {
565
+ // A bare `|` while the innermost `case`'s current arm is still in
566
+ // *pattern* position (`PATTERN1|PATTERN2|...)`) is glob-pattern
567
+ // alternation, not a pipeline — mvdan/sh's parser doesn't recurse per
568
+ // alternative the way it does per pipeline stage, so counting it as
569
+ // a chain link would falsely reject e.g. a 200-alternative case arm
570
+ // that real bash parses trivially. Once this arm's action list
571
+ // begins (after its terminating `)`), `|` is a real pipeline again —
572
+ // see `casePatternStack`'s and the `)` handler's doc comments.
573
+ if (!inCasePatternPosition())
574
+ chainLink();
575
+ i++;
576
+ continue;
577
+ }
578
+ if (ch === '&') {
579
+ // An unpaired `&` (background-job separator) ends the current
580
+ // pipeline/list the same way `;` does.
581
+ resetChain();
582
+ i++;
583
+ continue;
584
+ }
585
+ if (isWordChar(ch)) {
586
+ let j = i + 1;
587
+ while (j < n && isWordChar(text[j]))
588
+ j++;
589
+ const word = text.slice(i, j);
590
+ if (STRUCTURAL_OPENER_KEYWORDS.has(word)) {
591
+ pushRegion('other');
592
+ open();
593
+ }
594
+ else if (STRUCTURAL_CLOSER_KEYWORDS.has(word)) {
595
+ if (popRegion('other'))
596
+ close();
597
+ }
598
+ else if (word === CASE_OPENER_KEYWORD) {
599
+ pushRegion('case');
600
+ casePatternStack.push(true);
601
+ open();
602
+ }
603
+ else if (word === CASE_CLOSER_KEYWORD) {
604
+ if (popRegion('case')) {
605
+ close();
606
+ casePatternStack.pop();
607
+ }
608
+ }
609
+ i = j;
610
+ continue;
611
+ }
612
+ // Whitespace or another shell metacharacter with no structural meaning
613
+ // for this scanner's purposes (`<`, `>`).
614
+ i++;
615
+ }
616
+ return maxDepth;
617
+ }
618
+ /**
619
+ * Throws {@link ShParseMaxDepthError} if `text`'s conservatively-estimated
620
+ * structural nesting depth (see {@link estimateMaxNestingDepth}) exceeds
621
+ * {@link MAX_PARSE_NESTING_DEPTH} — called by {@link parseSync} *before* it
622
+ * ever hands `text` to the WASM shim, so pathological input never reaches
623
+ * (and never risks crashing) the shared WASM instance in the first place.
624
+ *
625
+ * @internal
626
+ */
627
+ export function assertParseDepthWithinLimit(text) {
628
+ const estimated = estimateMaxNestingDepth(text, MAX_PARSE_NESTING_DEPTH);
629
+ if (estimated > MAX_PARSE_NESTING_DEPTH) {
630
+ throw new ShParseMaxDepthError(MAX_PARSE_NESTING_DEPTH, estimated);
631
+ }
632
+ }
633
+ //# sourceMappingURL=parse-depth-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-depth-guard.js","sourceRoot":"","sources":["../src/parse-depth-guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,MAAM,uBAAuB,GAAG,GAAG,CAAC;AAEpC,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AAEpD;;;;;;;GAOG;AACH,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC;IAC9B,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,IAAI;IACJ,GAAG;IACH,IAAI;IACJ,IAAI;IACJ,IAAI;CACL,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,0BAA0B,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;AACtF,6GAA6G;AAC7G,MAAM,0BAA0B,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;AAC3D;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,mBAAmB,GAAG,MAAM,CAAC;AACnC,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAEnC,SAAS,UAAU,CAAC,EAAsB;IACxC,OAAO,EAAE,KAAK,SAAS,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AACtD,CAAC;AAYD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgGG;AACH,SAAS,uBAAuB,CAAC,IAAY,EAAE,KAAa;IAC1D,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,MAAM,YAAY,GAAkB,EAAE,CAAC;IACvC,MAAM,aAAa,GAAa,EAAE,CAAC;IACnC,MAAM,gBAAgB,GAAqB,EAAE,CAAC;IAC9C,MAAM,eAAe,GAAqB,EAAE,CAAC;IAC7C,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;IACtB,IAAI,CAAC,GAAG,CAAC,CAAC;IAEV,wEAAwE;IACxE,mEAAmE;IACnE,yEAAyE;IACzE,sEAAsE;IACtE,sEAAsE;IACtE,qEAAqE;IACrE,gEAAgE;IAChE,qEAAqE;IACrE,MAAM,gBAAgB,GAAc,EAAE,CAAC;IACvC,wDAAwD;IACxD,0EAA0E;IAC1E,qEAAqE;IACrE,uEAAuE;IACvE,yEAAyE;IACzE,0EAA0E;IAC1E,oCAAoC;IACpC,IAAI,oBAAoB,GAAG,CAAC,CAAC;IAE7B,SAAS,SAAS;QAChB,MAAM,KAAK,GAAG,YAAY,GAAG,QAAQ,CAAC;QACtC,IAAI,KAAK,GAAG,QAAQ;YAAE,QAAQ,GAAG,KAAK,CAAC;IACzC,CAAC;IACD,SAAS,IAAI;QACX,YAAY,EAAE,CAAC;QACf,SAAS,EAAE,CAAC;IACd,CAAC;IACD,SAAS,KAAK;QACZ,IAAI,YAAY,GAAG,CAAC;YAAE,YAAY,EAAE,CAAC;IACvC,CAAC;IACD,SAAS,UAAU,CAAC,IAAiB;QACnC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxB,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC7B,QAAQ,GAAG,CAAC,CAAC;IACf,CAAC;IACD;;;;;;;;;;;;;;;OAeG;IACH,SAAS,SAAS,CAAC,IAAiB;QAClC,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,IAAI,YAAY,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YAC9E,YAAY,CAAC,GAAG,EAAE,CAAC;YACnB,QAAQ,GAAG,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACpC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,SAAS,cAAc;QACrB,IAAI,SAAS,CAAC,UAAU,CAAC,EAAE,CAAC;YAC1B,KAAK,EAAE,CAAC;QACV,CAAC;aAAM,CAAC;YACN,UAAU,CAAC,UAAU,CAAC,CAAC;YACvB,IAAI,EAAE,CAAC;QACT,CAAC;IACH,CAAC;IACD,SAAS,UAAU;QACjB,QAAQ,GAAG,CAAC,CAAC;IACf,CAAC;IACD,SAAS,SAAS;QAChB,QAAQ,EAAE,CAAC;QACX,SAAS,EAAE,CAAC;IACd,CAAC;IACD;;;OAGG;IACH,SAAS,qBAAqB;QAC5B,IACE,YAAY,CAAC,MAAM,KAAK,CAAC;YACzB,YAAY,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,MAAM;YAChD,gBAAgB,CAAC,MAAM,KAAK,CAAC,EAC7B,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,gBAAgB,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACvD,CAAC;IACD,SAAS,qBAAqB;QAC5B,IAAI,oBAAoB,GAAG,eAAe,CAAC,MAAM,EAAE,CAAC;YAClD,sEAAsE;YACtE,6BAA6B;YAC7B,MAAM,IAAI,GAAG,eAAe,CAAC,oBAAoB,CAAC,CAAC;YACnD,oBAAoB,EAAE,CAAC;YACvB,UAAU,CAAC,SAAS,CAAC,CAAC;YACtB,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACb,IAAI,QAAQ,GAAG,KAAK;YAAE,OAAO,QAAQ,CAAC;QAEtC,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC5F,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAEnB,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,MAAM,WAAW,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC;YACpD,6DAA6D;YAC7D,mEAAmE;YACnE,kEAAkE;YAClE,wDAAwD;YACxD,MAAM,IAAI,GAAG,gBAAgB,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YAC3D,IAAI,WAAW,EAAE,CAAC;gBAChB,IAAI,OAAO,GAAG,CAAC,CAAC;gBAChB,OAAO,OAAO,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI;oBAAE,OAAO,EAAE,CAAC;gBACxD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;gBACvC,MAAM,WAAW,GAAG,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;gBAClF,IAAI,WAAW,KAAK,IAAI,CAAC,SAAS,EAAE,CAAC;oBACnC,SAAS,CAAC,SAAS,CAAC,CAAC;oBACrB,gBAAgB,CAAC,GAAG,EAAE,CAAC;oBACvB,CAAC,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;oBACxC,qBAAqB,EAAE,CAAC;oBACxB,SAAS;gBACX,CAAC;YACH,CAAC;YACD,mEAAmE;YACnE,oEAAoE;YACpE,qEAAqE;YACrE,mCAAmC;YACnC,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;gBAChB,CAAC,IAAI,CAAC,CAAC;gBACP,SAAS;YACX,CAAC;YACD,IAAI,EAAE,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;gBACtC,UAAU,CAAC,OAAO,CAAC,CAAC;gBACpB,IAAI,EAAE,CAAC;gBACP,CAAC,IAAI,CAAC,CAAC;gBACP,SAAS;YACX,CAAC;YACD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBACf,cAAc,EAAE,CAAC;gBACjB,CAAC,EAAE,CAAC;gBACJ,SAAS;YACX,CAAC;YACD,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QAED,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;YACzB,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;gBAChB,CAAC,IAAI,CAAC,CAAC;gBACP,SAAS;YACX,CAAC;YACD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBACf,YAAY,CAAC,GAAG,EAAE,CAAC;gBACnB,CAAC,EAAE,CAAC;gBACJ,SAAS;YACX,CAAC;YACD,IAAI,EAAE,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;gBACtC,UAAU,CAAC,OAAO,CAAC,CAAC;gBACpB,IAAI,EAAE,CAAC;gBACP,CAAC,IAAI,CAAC,CAAC;gBACP,SAAS;YACX,CAAC;YACD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBACf,cAAc,EAAE,CAAC;gBACjB,CAAC,EAAE,CAAC;gBACJ,SAAS;YACX,CAAC;YACD,mEAAmE;YACnE,uBAAuB;YACvB,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QAED,oEAAoE;QACpE,sEAAsE;QACtE,gDAAgD;QAChD,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;YAChB,CAAC,IAAI,CAAC,CAAC;YACP,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACf,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YAC7C,IAAI,IAAI,KAAK,SAAS,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC5E,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI;oBAAE,CAAC,EAAE,CAAC;gBACtC,SAAS;YACX,CAAC;YACD,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACf,CAAC,EAAE,CAAC;YACJ,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;gBAAE,CAAC,EAAE,CAAC;YACrC,IAAI,CAAC,GAAG,CAAC;gBAAE,CAAC,EAAE,CAAC;YACf,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YACtC,CAAC,IAAI,CAAC,CAAC;YACP,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;gBAChC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;oBACrB,CAAC,IAAI,CAAC,CAAC;oBACP,SAAS;gBACX,CAAC;gBACD,CAAC,EAAE,CAAC;YACN,CAAC;YACD,IAAI,CAAC,GAAG,CAAC;gBAAE,CAAC,EAAE,CAAC;YACf,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACf,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC5B,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACf,cAAc,EAAE,CAAC;YACjB,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YAC7B,UAAU,CAAC,OAAO,CAAC,CAAC;YACpB,IAAI,EAAE,CAAC;YACP,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACf,kEAAkE;YAClE,kEAAkE;YAClE,oEAAoE;YACpE,+DAA+D;YAC/D,qEAAqE;YACrE,4DAA4D;YAC5D,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,IAAI,YAAY,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,MAAM,EAAE,CAAC;gBAChF,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAChC,gBAAgB,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC;gBACxD,CAAC;gBACD,CAAC,EAAE,CAAC;gBACJ,SAAS;YACX,CAAC;YACD,IAAI,SAAS,CAAC,OAAO,CAAC;gBAAE,KAAK,EAAE,CAAC;YAChC,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACf,IAAI,SAAS,CAAC,OAAO,CAAC;gBAAE,KAAK,EAAE,CAAC;YAChC,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YAC7D,gEAAgE;YAChE,iEAAiE;YACjE,oEAAoE;YACpE,0BAA0B;YAC1B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACd,IAAI,gBAAgB,GAAG,KAAK,CAAC;YAC7B,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;gBACpB,gBAAgB,GAAG,IAAI,CAAC;gBACxB,CAAC,EAAE,CAAC;YACN,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;gBAAE,CAAC,EAAE,CAAC;YAC3D,IAAI,SAAS,GAAG,EAAE,CAAC;YACnB,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;gBACvC,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;gBACtB,CAAC,EAAE,CAAC;gBACJ,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC;oBAClC,SAAS,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;oBACrB,CAAC,EAAE,CAAC;gBACN,CAAC;gBACD,IAAI,CAAC,GAAG,CAAC;oBAAE,CAAC,EAAE,CAAC;YACjB,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,GAAG,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;oBACpC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;wBACrB,CAAC,EAAE,CAAC;wBACJ,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;4BACV,SAAS,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;4BACrB,CAAC,EAAE,CAAC;wBACN,CAAC;wBACD,SAAS;oBACX,CAAC;oBACD,SAAS,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;oBACrB,CAAC,EAAE,CAAC;gBACN,CAAC;YACH,CAAC;YACD,eAAe,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAC,CAAC;YACtD,CAAC,GAAG,CAAC,CAAC;YACN,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACf,8DAA8D;YAC9D,mEAAmE;YACnE,mEAAmE;YACnE,iEAAiE;YACjE,6DAA6D;YAC7D,mEAAmE;YACnE,qEAAqE;YACrE,6DAA6D;YAC7D,eAAe;YACf,IACE,YAAY,CAAC,MAAM,GAAG,CAAC;gBACvB,YAAY,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,MAAM;gBAChD,gBAAgB,CAAC,MAAM,GAAG,CAAC;gBAC3B,CAAC,gBAAgB,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC,CAAC;gBAC9C,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,EAC5C,CAAC;gBACD,gBAAgB,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC;YACvD,CAAC;YACD,UAAU,EAAE,CAAC;YACb,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;YAChB,UAAU,EAAE,CAAC;YACb,CAAC,EAAE,CAAC;YACJ,qBAAqB,EAAE,CAAC;YACxB,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YACtC,IAAI,CAAC,qBAAqB,EAAE;gBAAE,SAAS,EAAE,CAAC;YAC1C,CAAC,IAAI,CAAC,CAAC;YACP,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YACtC,IAAI,CAAC,qBAAqB,EAAE;gBAAE,SAAS,EAAE,CAAC;YAC1C,CAAC,IAAI,CAAC,CAAC;YACP,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YACtC,IAAI,CAAC,qBAAqB,EAAE;gBAAE,SAAS,EAAE,CAAC;YAC1C,CAAC,IAAI,CAAC,CAAC;YACP,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACf,kEAAkE;YAClE,gEAAgE;YAChE,sEAAsE;YACtE,oEAAoE;YACpE,oEAAoE;YACpE,+DAA+D;YAC/D,qEAAqE;YACrE,+DAA+D;YAC/D,IAAI,CAAC,qBAAqB,EAAE;gBAAE,SAAS,EAAE,CAAC;YAC1C,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACf,8DAA8D;YAC9D,uCAAuC;YACvC,UAAU,EAAE,CAAC;YACb,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QACD,IAAI,UAAU,CAAC,EAAE,CAAC,EAAE,CAAC;YACnB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACd,OAAO,CAAC,GAAG,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAAE,CAAC,EAAE,CAAC;YACzC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAC9B,IAAI,0BAA0B,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBACzC,UAAU,CAAC,OAAO,CAAC,CAAC;gBACpB,IAAI,EAAE,CAAC;YACT,CAAC;iBAAM,IAAI,0BAA0B,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBAChD,IAAI,SAAS,CAAC,OAAO,CAAC;oBAAE,KAAK,EAAE,CAAC;YAClC,CAAC;iBAAM,IAAI,IAAI,KAAK,mBAAmB,EAAE,CAAC;gBACxC,UAAU,CAAC,MAAM,CAAC,CAAC;gBACnB,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC5B,IAAI,EAAE,CAAC;YACT,CAAC;iBAAM,IAAI,IAAI,KAAK,mBAAmB,EAAE,CAAC;gBACxC,IAAI,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;oBACtB,KAAK,EAAE,CAAC;oBACR,gBAAgB,CAAC,GAAG,EAAE,CAAC;gBACzB,CAAC;YACH,CAAC;YACD,CAAC,GAAG,CAAC,CAAC;YACN,SAAS;QACX,CAAC;QAED,uEAAuE;QACvE,0CAA0C;QAC1C,CAAC,EAAE,CAAC;IACN,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,2BAA2B,CAAC,IAAY;IACtD,MAAM,SAAS,GAAG,uBAAuB,CAAC,IAAI,EAAE,uBAAuB,CAAC,CAAC;IACzE,IAAI,SAAS,GAAG,uBAAuB,EAAE,CAAC;QACxC,MAAM,IAAI,oBAAoB,CAAC,uBAAuB,EAAE,SAAS,CAAC,CAAC;IACrE,CAAC;AACH,CAAC"}
package/dist/parse.d.ts CHANGED
@@ -34,9 +34,14 @@ export declare function isResultEnvelope(value: unknown): value is ResultEnvelop
34
34
  * position appears only inside `.message`, which is mvdan/sh's own
35
35
  * formatted string, verbatim. Throws {@link ShInvalidDialectError} when
36
36
  * {@link ParseOptions.dialect} is not a supported dialect. Throws
37
- * {@link ShBridgeInternalError} for failures that should never happen given
37
+ * {@link ShInternalError} for failures that should never happen given
38
38
  * a correctly-behaving shim (malformed envelope, unexpected root node
39
- * type, shim contract violations).
39
+ * type, shim contract violations). Throws {@link ShParseMaxDepthError} if
40
+ * `text`'s conservatively estimated structural nesting depth exceeds the
41
+ * limit this bridge accepts — checked, and thrown, *before* `text` is ever
42
+ * handed to the WASM shim, since mvdan/sh's own parser has no recovery path
43
+ * for exhausting its stack on pathological nesting (see that error's doc
44
+ * comment and `parse-depth-guard.ts`).
40
45
  *
41
46
  * @public
42
47
  */