sh-ast 0.0.0 → 0.1.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.
- package/README.md +147 -0
- package/dist/analyze/ansi-c-escapes.d.ts +53 -0
- package/dist/analyze/ansi-c-escapes.d.ts.map +1 -0
- package/dist/analyze/ansi-c-escapes.js +255 -0
- package/dist/analyze/ansi-c-escapes.js.map +1 -0
- package/dist/analyze/decode-lit.d.ts +74 -0
- package/dist/analyze/decode-lit.d.ts.map +1 -0
- package/dist/analyze/decode-lit.js +114 -0
- package/dist/analyze/decode-lit.js.map +1 -0
- package/dist/analyze/enumerate-commands.d.ts +159 -0
- package/dist/analyze/enumerate-commands.d.ts.map +1 -0
- package/dist/analyze/enumerate-commands.js +390 -0
- package/dist/analyze/enumerate-commands.js.map +1 -0
- package/dist/analyze/index.d.ts +18 -0
- package/dist/analyze/index.d.ts.map +1 -0
- package/dist/analyze/index.js +27 -0
- package/dist/analyze/index.js.map +1 -0
- package/dist/analyze/node-helpers.d.ts +28 -0
- package/dist/analyze/node-helpers.d.ts.map +1 -0
- package/dist/analyze/node-helpers.js +37 -0
- package/dist/analyze/node-helpers.js.map +1 -0
- package/dist/analyze/resolve-word.d.ts +146 -0
- package/dist/analyze/resolve-word.d.ts.map +1 -0
- package/dist/analyze/resolve-word.js +202 -0
- package/dist/analyze/resolve-word.js.map +1 -0
- package/dist/analyze.d.ts +390 -0
- package/dist/deep-freeze.d.ts +14 -0
- package/dist/deep-freeze.d.ts.map +1 -0
- package/dist/deep-freeze.js +25 -0
- package/dist/deep-freeze.js.map +1 -0
- package/dist/errors.d.ts +115 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +106 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/normalize.d.ts +52 -0
- package/dist/normalize.d.ts.map +1 -0
- package/dist/normalize.js +260 -0
- package/dist/normalize.js.map +1 -0
- package/dist/parse.d.ts +45 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +118 -0
- package/dist/parse.js.map +1 -0
- package/dist/sh-ast.d.ts +1270 -0
- package/dist/types.d.ts +70 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/visitor-keys.d.ts +21 -0
- package/dist/visitor-keys.d.ts.map +1 -0
- package/dist/visitor-keys.js +23 -0
- package/dist/visitor-keys.js.map +1 -0
- package/dist/walk.d.ts +18 -0
- package/dist/walk.d.ts.map +1 -0
- package/dist/walk.js +40 -0
- package/dist/walk.js.map +1 -0
- package/dist/wasm-instance.d.ts +13 -0
- package/dist/wasm-instance.d.ts.map +1 -0
- package/dist/wasm-instance.js +88 -0
- package/dist/wasm-instance.js.map +1 -0
- package/generated/child-type-schema.d.ts +14 -0
- package/generated/child-type-schema.js +188 -0
- package/generated/node-types.d.ts +958 -0
- package/generated/position-fields.d.ts +21 -0
- package/generated/position-fields.js +50 -0
- package/generated/visitor-keys.d.ts +11 -0
- package/generated/visitor-keys.js +50 -0
- package/package.json +93 -10
- package/shim/sh-ast.wasm +0 -0
- package/shim/wasm_exec.js +654 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single frame of the path from the root of the tree down to a
|
|
3
|
+
* {@link CommandSite}, describing *how* the command is reached — never
|
|
4
|
+
* whether it is safe to run. `CommandSite.context` is an ordered stack of
|
|
5
|
+
* these, outermost frame first:
|
|
6
|
+
*
|
|
7
|
+
* - `'and'`/`'or'` (`side: 'right'`) — the right-hand operand of a
|
|
8
|
+
* `BinaryCmd` (mvdan/sh's node for both `&&` and `||`); only the right
|
|
9
|
+
* side is tagged; the left side inherits the surrounding context
|
|
10
|
+
* unchanged, since it runs unconditionally relative to this operator.
|
|
11
|
+
* - `'pipeline'` (`stage: n`) — one stage of a `|`/`|&` chain (also a
|
|
12
|
+
* `BinaryCmd`, left-associatively nested by mvdan/sh); every stage is
|
|
13
|
+
* tagged, 0-indexed left to right, regardless of whether the chain mixes
|
|
14
|
+
* `|` and `|&`.
|
|
15
|
+
* - `'subshell'` — inside a `Subshell` (`( ... )`).
|
|
16
|
+
* - `'cmdSubst'`/`'procSubst'` — inside a `CmdSubst` (`$(...)`/backticks) or
|
|
17
|
+
* `ProcSubst` (`<(...)`/`>(...)`) reached from *any* word-bearing
|
|
18
|
+
* position (an argument, a redirection target, a case subject, a loop's
|
|
19
|
+
* word list, an assignment value, a test/arithmetic operand, …) — not
|
|
20
|
+
* only from `CallExpr.args`.
|
|
21
|
+
* - `'if'` (`branch: 'cond' | 'then' | 'else'`) — inside an `IfClause`'s
|
|
22
|
+
* condition, then-branch, or else-branch (mvdan/sh nests `elif` chains as
|
|
23
|
+
* `IfClause.else` pointing to another `IfClause`, so an `elif`'s own
|
|
24
|
+
* condition/then are reached through an `{kind:'if',branch:'else'}` frame
|
|
25
|
+
* first, then their own `'cond'`/`'then'` frame — reflecting the real
|
|
26
|
+
* nesting rather than collapsing it).
|
|
27
|
+
* - `'case'` — inside one `CaseClause` branch's statement list (not the
|
|
28
|
+
* case subject word or the patterns).
|
|
29
|
+
* - `'loop'` (`role` is `'body'` or `'cond'`) — inside a `ForClause`'s
|
|
30
|
+
* statement list (`role: 'body'` only — a `for` loop has no
|
|
31
|
+
* statement-list condition) or a `WhileClause`'s (`role: 'cond'` for the
|
|
32
|
+
* condition, `role: 'body'` for the loop body).
|
|
33
|
+
* - `'function'` (`name`) — inside a `FuncDecl`'s body; `name` is the
|
|
34
|
+
* function's literal name text.
|
|
35
|
+
* - `'background'`/`'negated'` — the enclosing `Stmt` has mvdan/sh's
|
|
36
|
+
* Background/Negated flag set (`cmd &`, `! cmd`).
|
|
37
|
+
* - `'coproc'` — inside a `CoprocClause`'s statement (a `coproc` block,
|
|
38
|
+
* optionally named).
|
|
39
|
+
*
|
|
40
|
+
* A `Block` grouping (`{ ...; }`) and a `TimeClause` (`time cmd`) are
|
|
41
|
+
* deliberately transparent — grouping and timing a command doesn't change
|
|
42
|
+
* how it's reached, so no frame is added for either.
|
|
43
|
+
*
|
|
44
|
+
* This union may grow in a **minor** release — a future mvdan/sh grammar
|
|
45
|
+
* construct this module starts modeling can add a new `kind` variant
|
|
46
|
+
* without that being a breaking change (mirroring
|
|
47
|
+
* {@link WordResolutionReason}'s semver policy in `resolve-word.ts`). It is
|
|
48
|
+
* deliberately not sealed against extension elsewhere in the codebase.
|
|
49
|
+
* Every existing variant's shape (its extra fields, if any) is stable —
|
|
50
|
+
* only new variants are ever added — so an exhaustive compile-time
|
|
51
|
+
* `switch` over `.kind` should still include a `default` case to stay
|
|
52
|
+
* forward-compatible.
|
|
53
|
+
*
|
|
54
|
+
* @public
|
|
55
|
+
*/
|
|
56
|
+
export declare type CommandContext = {
|
|
57
|
+
readonly kind: 'and';
|
|
58
|
+
readonly side: 'right';
|
|
59
|
+
} | {
|
|
60
|
+
readonly kind: 'or';
|
|
61
|
+
readonly side: 'right';
|
|
62
|
+
} | {
|
|
63
|
+
readonly kind: 'pipeline';
|
|
64
|
+
readonly stage: number;
|
|
65
|
+
} | {
|
|
66
|
+
readonly kind: 'subshell';
|
|
67
|
+
} | {
|
|
68
|
+
readonly kind: 'cmdSubst';
|
|
69
|
+
} | {
|
|
70
|
+
readonly kind: 'procSubst';
|
|
71
|
+
} | {
|
|
72
|
+
readonly kind: 'if';
|
|
73
|
+
readonly branch: 'then' | 'else' | 'cond';
|
|
74
|
+
} | {
|
|
75
|
+
readonly kind: 'case';
|
|
76
|
+
} | {
|
|
77
|
+
readonly kind: 'loop';
|
|
78
|
+
readonly role: 'body' | 'cond';
|
|
79
|
+
} | {
|
|
80
|
+
readonly kind: 'function';
|
|
81
|
+
readonly name: string;
|
|
82
|
+
} | {
|
|
83
|
+
readonly kind: 'background';
|
|
84
|
+
} | {
|
|
85
|
+
readonly kind: 'negated';
|
|
86
|
+
} | {
|
|
87
|
+
readonly kind: 'coproc';
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* One place in the tree where a command is actually invoked — a `CallExpr`
|
|
92
|
+
* node, together with its resolved words and the path used to reach it.
|
|
93
|
+
* Facts only, matching {@link resolveWord}'s posture: no safety verdict, no
|
|
94
|
+
* hardcoded command/wrapper list. A dynamic (`static: false`) `argv0` is a
|
|
95
|
+
* normal, expected result — not an error and not itself reported as
|
|
96
|
+
* "unknown"/"unsafe".
|
|
97
|
+
*
|
|
98
|
+
* @public
|
|
99
|
+
*/
|
|
100
|
+
export declare interface CommandSite {
|
|
101
|
+
/** The `CallExpr` node this site was found at. */
|
|
102
|
+
readonly node: ShNode;
|
|
103
|
+
/**
|
|
104
|
+
* `resolveWord` applied to the first word (`argv[0]`), with
|
|
105
|
+
* `{ context: 'command-argument' }` — every `CallExpr` word is an
|
|
106
|
+
* ordinary command-argument position, never an assignment value, so
|
|
107
|
+
* only a word-initial unquoted `~` triggers tilde expansion (an
|
|
108
|
+
* unquoted `~` after a `:`, e.g. `a:~/b`, is literal text here — see
|
|
109
|
+
* `ResolveWordOptions.context`'s doc comment).
|
|
110
|
+
*/
|
|
111
|
+
readonly argv0: WordResolution;
|
|
112
|
+
/** `resolveWord` applied to every word, in argument order (same `{ context: 'command-argument' }` as {@link CommandSite.argv0}). */
|
|
113
|
+
readonly argv: readonly WordResolution[];
|
|
114
|
+
/** The path from the tree root to this site, outermost frame first. */
|
|
115
|
+
readonly context: readonly CommandContext[];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Enumerates every command invocation (`CallExpr`) reachable from `root`,
|
|
120
|
+
* with its resolved words ({@link resolveWord}) and the path used to reach
|
|
121
|
+
* it ({@link CommandContext}). Descends everywhere a command can occur:
|
|
122
|
+
* statement lists, both sides of `&&`/`||`/pipelines, subshells/blocks,
|
|
123
|
+
* if/case branches *and* conditions, loop bodies *and* conditions, function
|
|
124
|
+
* bodies, background/negated/coproc statements, and — critically —
|
|
125
|
+
* `CmdSubst`/`ProcSubst` nested inside words, wherever those words occur
|
|
126
|
+
* (arguments, redirection targets, case subjects/patterns, loop word
|
|
127
|
+
* lists, assignment values, test/arithmetic operands, …), not only inside
|
|
128
|
+
* `CallExpr.args`.
|
|
129
|
+
*
|
|
130
|
+
* An assignment-only `CallExpr` (`FOO=bar`, `args` empty) has no first word
|
|
131
|
+
* to resolve and is not itself a command invocation — no program runs — so
|
|
132
|
+
* it produces no {@link CommandSite}; any command substitution nested in
|
|
133
|
+
* its assigned value (`FOO=$(sub)`) is still found and reported.
|
|
134
|
+
*
|
|
135
|
+
* Results are sorted by source position (`node.range[0]`), so nested
|
|
136
|
+
* command substitutions and out-of-structural-order redirection targets
|
|
137
|
+
* still come back in source order regardless of traversal order.
|
|
138
|
+
*
|
|
139
|
+
* Facts only, matching {@link resolveWord}'s posture: no safety verdict, no
|
|
140
|
+
* command/wrapper allowlist or denylist, no dataflow. A statically unknown
|
|
141
|
+
* `argv0` (`static: false`) is a normal, expected result.
|
|
142
|
+
*
|
|
143
|
+
* A long *linear* chain — `|`/`|&`/`&&`/`||` of any realistic length — is
|
|
144
|
+
* traversed iteratively and never risks a stack overflow or trips the
|
|
145
|
+
* nesting-depth guard below, regardless of how many stages/links it has.
|
|
146
|
+
*
|
|
147
|
+
* @param root - Any `ShNode` — typically a `File` from `parseSync`, but any
|
|
148
|
+
* subtree (a `Stmt`, a `Command`, or even a bare `Word`) is handled.
|
|
149
|
+
* @throws {@link ShAnalyzeMaxDepthError} if `root`'s genuinely nested
|
|
150
|
+
* structure (subshells within subshells, chained command/process
|
|
151
|
+
* substitutions, deeply nested `if`/`case`/loop/function/`time`/`{ }`
|
|
152
|
+
* bodies, chained `elif`) exceeds this module's defensive recursion-depth
|
|
153
|
+
* guard — see that error's doc comment for why this fails closed instead
|
|
154
|
+
* of returning a partial result, and why a gate-style consumer should treat
|
|
155
|
+
* it as `deny`.
|
|
156
|
+
* @public
|
|
157
|
+
*/
|
|
158
|
+
export declare function enumerateCommands(root: ShNode): CommandSite[];
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* A 1-based line/column position. Columns are UTF-16 code units, matching
|
|
162
|
+
* JavaScript string indexing (see {@link ShNode.range}).
|
|
163
|
+
*
|
|
164
|
+
* @public
|
|
165
|
+
*/
|
|
166
|
+
declare interface Position {
|
|
167
|
+
readonly line: number;
|
|
168
|
+
readonly column: number;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Determines whether a `Word` node is statically a known string, and if
|
|
173
|
+
* so, what that string is — the atom of shell-AST static analysis (e.g.
|
|
174
|
+
* "is this word literally `rm`"). A word is static iff every part is a
|
|
175
|
+
* literal after shell unquoting: a bare `Lit` (backslash escapes
|
|
176
|
+
* processed), a `SglQuoted` (including `$'...'` ANSI-C escapes decoded),
|
|
177
|
+
* or a `DblQuoted` containing only literal parts (and not itself a
|
|
178
|
+
* `$"..."` locale translation) — including concatenations of these (`'r'm`
|
|
179
|
+
* resolves to `"rm"`). A word containing any expansion (`ParamExp`,
|
|
180
|
+
* `CmdSubst`, `ArithmExp`, `ProcSubst`), a triggering unquoted `~` (see
|
|
181
|
+
* `options.context`), an unquoted glob metacharacter/bracket
|
|
182
|
+
* expression/`ExtGlob`, a `$"..."` locale translation, or an unrepresentable
|
|
183
|
+
* `$'\U...'` escape is `static: false` with a reason — never an error, and
|
|
184
|
+
* never a safety judgment (see {@link WordResolution}).
|
|
185
|
+
*
|
|
186
|
+
* @param word - A `Word` node, e.g. `parseSync(...).stmts[0].cmd.args[0]`.
|
|
187
|
+
* @param options - See {@link ResolveWordOptions}.
|
|
188
|
+
* @throws TypeError if `word.type` is not `"Word"` — a programmer-error
|
|
189
|
+
* misuse of the API, not a "malformed shell source" case (well-formed
|
|
190
|
+
* shell source, however dynamic, never throws; see {@link WordResolution}).
|
|
191
|
+
*
|
|
192
|
+
* @public
|
|
193
|
+
*/
|
|
194
|
+
export declare function resolveWord(word: ShNode, options?: ResolveWordOptions): WordResolution;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Options controlling {@link resolveWord}'s tilde-expansion detection.
|
|
198
|
+
*
|
|
199
|
+
* @public
|
|
200
|
+
*/
|
|
201
|
+
export declare interface ResolveWordOptions {
|
|
202
|
+
/**
|
|
203
|
+
* Which Bash grammar position `word` occupies — the two positions Bash
|
|
204
|
+
* §3.5.2 ("Tilde Expansion") documents distinct tilde-expansion triggers
|
|
205
|
+
* for:
|
|
206
|
+
*
|
|
207
|
+
* - `'command-argument'` — an ordinary simple-command argument word (e.g.
|
|
208
|
+
* `echo <word>`). Only a *word-initial* unquoted `~` triggers tilde
|
|
209
|
+
* expansion; an unquoted `~` elsewhere in the word (including right
|
|
210
|
+
* after a `:`) is an ordinary character (`echo a:~b` prints `a:~b`
|
|
211
|
+
* literally in real bash).
|
|
212
|
+
* - `'assignment-value'` — the value side of a shell assignment (e.g.
|
|
213
|
+
* `PATH=<word>`, an `Assign` node's `value`). Bash additionally
|
|
214
|
+
* tilde-expands an unquoted `~` immediately following an unquoted `:`
|
|
215
|
+
* anywhere in the value (`PATH=/foo:~/bar` expands the `~/bar`
|
|
216
|
+
* segment) — this is documented for `PATH`-like colon-separated
|
|
217
|
+
* assignment values specifically.
|
|
218
|
+
*
|
|
219
|
+
* **Default: `'assignment-value'` semantics apply whenever this option is
|
|
220
|
+
* omitted.** `resolveWord` takes a bare `Word` node with no grammar
|
|
221
|
+
* context attached, so it cannot see whether its caller extracted that
|
|
222
|
+
* word from a command argument or an assignment value — omitting this
|
|
223
|
+
* option is deliberately treated as the *more conservative* of the two
|
|
224
|
+
* (the one that reports `'tilde'` in more cases), so a caller that
|
|
225
|
+
* doesn't know or care about the distinction never under-reports a real
|
|
226
|
+
* tilde-expansion site. Pass `'command-argument'` explicitly to opt into
|
|
227
|
+
* the narrower, word-initial-only check that matches real bash's
|
|
228
|
+
* behavior for ordinary arguments.
|
|
229
|
+
*
|
|
230
|
+
* @see https://www.gnu.org/software/bash/manual/bash.html#Tilde-Expansion
|
|
231
|
+
*/
|
|
232
|
+
readonly context?: 'command-argument' | 'assignment-value';
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Thrown by `sh-ast/analyze`'s `enumerateCommands` when a tree's genuinely
|
|
237
|
+
* nested structure — subshells within subshells, chained command/process
|
|
238
|
+
* substitutions, deeply nested `if`/`case`/loop/function/`time`/`{ }`
|
|
239
|
+
* bodies, deeply chained `elif` — exceeds its defensive recursion-depth
|
|
240
|
+
* guard. **Not** thrown for a long *linear* chain (`|`/`|&`/`&&`/`||` of
|
|
241
|
+
* any realistic length): `enumerateCommands` traverses those iteratively,
|
|
242
|
+
* so chain length alone never grows this guard's depth counter — only
|
|
243
|
+
* genuine tree nesting does.
|
|
244
|
+
*
|
|
245
|
+
* `enumerateCommands` deliberately fails closed here rather than returning
|
|
246
|
+
* a truncated, partial result: a partial `CommandSite[]` silently omits
|
|
247
|
+
* real command sites, which is a false negative for a permission-hook-style
|
|
248
|
+
* consumer that treats "command not found in the enumeration" as "nothing
|
|
249
|
+
* to worry about" — an explicit, documented throw is safer than a silent
|
|
250
|
+
* under-report. Gate-style consumers should treat this error as `deny`,
|
|
251
|
+
* not fall back to "no commands found, so allow".
|
|
252
|
+
*
|
|
253
|
+
* @public
|
|
254
|
+
*/
|
|
255
|
+
export declare class ShAnalyzeMaxDepthError extends ShBridgeError {
|
|
256
|
+
readonly code = "ESLINT_SH_ANALYZE_MAX_DEPTH";
|
|
257
|
+
/** The maximum nesting depth `enumerateCommands` supports; the same value every time (not caller-configurable). */
|
|
258
|
+
readonly maxDepth: number;
|
|
259
|
+
constructor(maxDepth: number);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Common base class for every error this package throws — originally just
|
|
264
|
+
* {@link parseSync}'s errors, now also the `sh-ast/analyze` layer's (see
|
|
265
|
+
* {@link ShAnalyzeMaxDepthError}). Provides a stable, documented `code`
|
|
266
|
+
* discriminator (e.g. `"ESLINT_SH_PARSE_ERROR"`) alongside the usual
|
|
267
|
+
* `instanceof` narrowing, so consumers can branch on failure kind
|
|
268
|
+
* programmatically without parsing `.message` strings. Never thrown
|
|
269
|
+
* directly — only via its concrete subclasses ({@link ShParseError},
|
|
270
|
+
* {@link ShInvalidDialectError}, {@link ShBridgeInternalError},
|
|
271
|
+
* {@link ShAnalyzeMaxDepthError}).
|
|
272
|
+
*
|
|
273
|
+
* @public
|
|
274
|
+
*/
|
|
275
|
+
declare abstract class ShBridgeError extends Error {
|
|
276
|
+
/**
|
|
277
|
+
* Stable, machine-readable discriminator for this error kind. Distinct
|
|
278
|
+
* per subclass and will not change once published.
|
|
279
|
+
*/
|
|
280
|
+
abstract readonly code: string;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* A normalized AST node produced by {@link parseSync}.
|
|
285
|
+
*
|
|
286
|
+
* `range` and `loc` are expressed in UTF-16 code units, so
|
|
287
|
+
* `code.slice(node.range[0], node.range[1])` reproduces the node's exact
|
|
288
|
+
* source text — even though mvdan/sh itself reports byte offsets and
|
|
289
|
+
* byte-counting columns internally (see design/ARCHITECTURE.md's
|
|
290
|
+
* "Serialization contract"). Every other field copied over from mvdan/sh's
|
|
291
|
+
* typedjson tree keeps its original value but with the field name
|
|
292
|
+
* lowercased (`Stmts` becomes `stmts`, `CondLast` becomes `condlast`, etc.).
|
|
293
|
+
*
|
|
294
|
+
* @public
|
|
295
|
+
*/
|
|
296
|
+
declare interface ShNode {
|
|
297
|
+
readonly type: string;
|
|
298
|
+
readonly range: readonly [number, number];
|
|
299
|
+
readonly loc: {
|
|
300
|
+
readonly start: Position;
|
|
301
|
+
readonly end: Position;
|
|
302
|
+
};
|
|
303
|
+
[field: string]: unknown;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* The result of statically resolving a `Word` node's text. Reports facts
|
|
308
|
+
* only — never a safety verdict, never a hardcoded command/wrapper list.
|
|
309
|
+
* `static: false` ("unknowable") is a first-class, valid result, not an
|
|
310
|
+
* error condition.
|
|
311
|
+
*
|
|
312
|
+
* @public
|
|
313
|
+
*/
|
|
314
|
+
export declare type WordResolution = {
|
|
315
|
+
/** The word is a compile-time-known string. */
|
|
316
|
+
readonly static: true;
|
|
317
|
+
/** The word's exact resolved text (quotes and escapes removed). */
|
|
318
|
+
readonly text: string;
|
|
319
|
+
} | {
|
|
320
|
+
/** The word's value depends on program state, the environment, or the filesystem. */
|
|
321
|
+
readonly static: false;
|
|
322
|
+
/** Why this word cannot be statically resolved. */
|
|
323
|
+
readonly reason: WordResolutionReason;
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Why a {@link resolveWord} result is `static: false`: the word's exact
|
|
328
|
+
* text cannot be determined from source alone.
|
|
329
|
+
*
|
|
330
|
+
* - `'expansion'` — the word contains a `ParamExp` (`$x`, `${x}`), a
|
|
331
|
+
* `CmdSubst` (command substitution, either `$(...)` or backtick form), an
|
|
332
|
+
* `ArithmExp` (`$((expr))`), or a `ProcSubst` (`<(cmd)`/`>(cmd)`) part:
|
|
333
|
+
* its value depends on program state (variables, command output, process
|
|
334
|
+
* substitution) that only exists at run time.
|
|
335
|
+
* - `'tilde'` — an unquoted, unescaped `~` triggers Bash tilde expansion
|
|
336
|
+
* (Bash Reference Manual §3.5.2, "Tilde Expansion") at the start of the
|
|
337
|
+
* word, or — when `resolveWord`'s `context` option is `'assignment-value'`
|
|
338
|
+
* or omitted — immediately after an unquoted `:` anywhere in the word too
|
|
339
|
+
* (§3.5.2 also documents this for assignment-statement values, e.g.
|
|
340
|
+
* `PATH=/foo:~/bar`). Either way: statically shaped, but its value
|
|
341
|
+
* depends on the filesystem/environment (`$HOME`, NSS user database
|
|
342
|
+
* lookups) at run time. See `resolveWord`'s `context` parameter doc for
|
|
343
|
+
* the full command-argument-vs-assignment-value distinction.
|
|
344
|
+
* - `'glob'` — the word contains an unquoted, unescaped glob metacharacter
|
|
345
|
+
* (`*`/`?`), an unquoted bracket expression (`[...]` — an unquoted `[`
|
|
346
|
+
* with a later unquoted `]` in the same word, POSIX 2.13.1 / Bash
|
|
347
|
+
* Reference Manual §3.5.8.1, "Pattern Matching"), or an `ExtGlob` part
|
|
348
|
+
* (Bash Reference Manual §3.5.8, "Filename Expansion"): its value depends
|
|
349
|
+
* on which filesystem entries exist at run time.
|
|
350
|
+
* - `'brace'` — the word contains a `BraceExp` part (`{a,b}`/`{1..10}`,
|
|
351
|
+
* Bash Reference Manual §3.5.1, "Brace Expansion"). mvdan/sh v3.13.1 only
|
|
352
|
+
* ever produces this node type when its caller applies `syntax.SplitBraces`
|
|
353
|
+
* to the parsed tree; this bridge's shim does not call it, so today a
|
|
354
|
+
* brace expression like `{a,b}` reaches {@link resolveWord} as an
|
|
355
|
+
* ordinary `Lit` (indistinguishable, in the AST this bridge produces,
|
|
356
|
+
* from literal `{`/`,`/`}` characters) rather than as a `BraceExp` part.
|
|
357
|
+
* This reason is handled here for forward-compatibility with that node
|
|
358
|
+
* type and is not reachable through {@link resolveWord} today.
|
|
359
|
+
* - `'locale'` — the word contains a `$"..."` locale-translated string
|
|
360
|
+
* (Bash Reference Manual §3.1.2.5, "Locale-Specific Translation"): its
|
|
361
|
+
* value is looked up in the current locale's translation catalog at run
|
|
362
|
+
* time via `gettext`, regardless of whether its contents also contain an
|
|
363
|
+
* expansion. `'locale'` takes precedence over any `'expansion'` a
|
|
364
|
+
* `$"..."` word's *contents* would otherwise report — the `DblQuoted`
|
|
365
|
+
* node's own `dollar` flag is checked before its children are visited at
|
|
366
|
+
* all, so e.g. `$"hi $x"` reports `'locale'`, never `'expansion'`.
|
|
367
|
+
* - `'unsupported'` — the word contains a `$'...'` (ANSI-C quoted) `\u`/`\U`
|
|
368
|
+
* escape whose value has no corresponding Unicode character (greater than
|
|
369
|
+
* `U+10FFFF`, e.g. `$'\UFFFFFFFF'`): real bash's output for this case is
|
|
370
|
+
* an undocumented, non-UTF-8 byte sequence with no exact representation
|
|
371
|
+
* as a JavaScript string (see `ansi-c-escapes.ts`'s
|
|
372
|
+
* `MAX_UNICODE_CODE_POINT` doc comment for the empirical detail) — this
|
|
373
|
+
* package declines to claim a `static: true` text bash never actually
|
|
374
|
+
* produces.
|
|
375
|
+
*
|
|
376
|
+
* This union may grow in a **minor** release — new mvdan/sh word-part node
|
|
377
|
+
* types, or new statically-shaped-but-dynamic categories, can add new reason
|
|
378
|
+
* strings without that being a breaking change. It is deliberately not
|
|
379
|
+
* sealed against extension elsewhere in the codebase. A reason string this
|
|
380
|
+
* version of the package doesn't yet know about only ever accompanies
|
|
381
|
+
* `static: false`, never changes the meaning of `static: true`, so runtime
|
|
382
|
+
* consumers that only branch on `.static` are safe by construction across
|
|
383
|
+
* such additions; an exhaustive compile-time `switch` over `.reason` should
|
|
384
|
+
* still include a `default` case to stay forward-compatible.
|
|
385
|
+
*
|
|
386
|
+
* @public
|
|
387
|
+
*/
|
|
388
|
+
export declare type WordResolutionReason = 'expansion' | 'tilde' | 'glob' | 'brace' | 'locale' | 'unsupported';
|
|
389
|
+
|
|
390
|
+
export { }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recursively freezes `value` and every object/array it (transitively)
|
|
3
|
+
* contains, so a frozen shared table's immutability holds at every level —
|
|
4
|
+
* not just its own top-level keys — rather than relying on every caller to
|
|
5
|
+
* treat it as read-only by convention.
|
|
6
|
+
*
|
|
7
|
+
* Generic, structural recursion over an unknown shape needs a cast to index
|
|
8
|
+
* into `value`'s own properties; contained to this one small, well-tested
|
|
9
|
+
* utility rather than spread across every table that uses it.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export declare function deepFreeze<T>(value: T): Readonly<T>;
|
|
14
|
+
//# sourceMappingURL=deep-freeze.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deep-freeze.d.ts","sourceRoot":"","sources":["../src/deep-freeze.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAWnD"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recursively freezes `value` and every object/array it (transitively)
|
|
3
|
+
* contains, so a frozen shared table's immutability holds at every level —
|
|
4
|
+
* not just its own top-level keys — rather than relying on every caller to
|
|
5
|
+
* treat it as read-only by convention.
|
|
6
|
+
*
|
|
7
|
+
* Generic, structural recursion over an unknown shape needs a cast to index
|
|
8
|
+
* into `value`'s own properties; contained to this one small, well-tested
|
|
9
|
+
* utility rather than spread across every table that uses it.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export function deepFreeze(value) {
|
|
14
|
+
if (value !== null && (typeof value === 'object' || typeof value === 'function')) {
|
|
15
|
+
for (const key of Object.getOwnPropertyNames(value)) {
|
|
16
|
+
const prop = value[key];
|
|
17
|
+
if (prop !== null && typeof prop === 'object') {
|
|
18
|
+
deepFreeze(prop);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
Object.freeze(value);
|
|
22
|
+
}
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=deep-freeze.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deep-freeze.js","sourceRoot":"","sources":["../src/deep-freeze.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,UAAU,CAAI,KAAQ;IACpC,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,UAAU,CAAC,EAAE,CAAC;QACjF,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,mBAAmB,CAAC,KAAK,CAAC,EAAE,CAAC;YACpD,MAAM,IAAI,GAAI,KAAiC,CAAC,GAAG,CAAC,CAAC;YACrD,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC9C,UAAU,CAAC,IAAI,CAAC,CAAC;YACnB,CAAC;QACH,CAAC;QACD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { ShellDialect } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Common base class for every error this package throws — originally just
|
|
4
|
+
* {@link parseSync}'s errors, now also the `sh-ast/analyze` layer's (see
|
|
5
|
+
* {@link ShAnalyzeMaxDepthError}). Provides a stable, documented `code`
|
|
6
|
+
* discriminator (e.g. `"ESLINT_SH_PARSE_ERROR"`) alongside the usual
|
|
7
|
+
* `instanceof` narrowing, so consumers can branch on failure kind
|
|
8
|
+
* programmatically without parsing `.message` strings. Never thrown
|
|
9
|
+
* directly — only via its concrete subclasses ({@link ShParseError},
|
|
10
|
+
* {@link ShInvalidDialectError}, {@link ShBridgeInternalError},
|
|
11
|
+
* {@link ShAnalyzeMaxDepthError}).
|
|
12
|
+
*
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
export declare abstract class ShBridgeError extends Error {
|
|
16
|
+
/**
|
|
17
|
+
* Stable, machine-readable discriminator for this error kind. Distinct
|
|
18
|
+
* per subclass and will not change once published.
|
|
19
|
+
*/
|
|
20
|
+
abstract readonly code: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Position info attached to a {@link ShParseError}.
|
|
24
|
+
*
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
27
|
+
export interface ShParseErrorInfo {
|
|
28
|
+
/** 1-based line number, exactly as reported by mvdan/sh. */
|
|
29
|
+
line: number;
|
|
30
|
+
/**
|
|
31
|
+
* 1-based column number, in **UTF-16 code units** — converted from
|
|
32
|
+
* mvdan/sh's own byte-counting column so it agrees with
|
|
33
|
+
* {@link ShNode.loc}'s columns (mvdan/sh reports `column` as a byte count
|
|
34
|
+
* on the source line, not a UTF-16 code unit count; see
|
|
35
|
+
* design/ARCHITECTURE.md's "Byte→UTF-16 conversion"). For ASCII-only
|
|
36
|
+
* source text this is numerically identical to mvdan/sh's own column.
|
|
37
|
+
*/
|
|
38
|
+
column: number;
|
|
39
|
+
/** The filename used for the parse (see {@link ParseOptions.filename}). */
|
|
40
|
+
filename: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Thrown by {@link parseSync} when shell source fails to parse. Carries the
|
|
44
|
+
* real position mvdan/sh reported — never 1:1 placeholders: `.line` is
|
|
45
|
+
* 1-based exactly as mvdan/sh reports it, and `.column` is converted to
|
|
46
|
+
* UTF-16 code units so it agrees with {@link ShNode.loc}'s columns (mvdan/sh
|
|
47
|
+
* itself reports `column` as a byte count on the source line — see
|
|
48
|
+
* {@link ShParseErrorInfo.column}). The unconverted, raw byte-column
|
|
49
|
+
* position appears only inside `.message`, mvdan/sh's own formatted string,
|
|
50
|
+
* verbatim.
|
|
51
|
+
*
|
|
52
|
+
* @public
|
|
53
|
+
*/
|
|
54
|
+
export declare class ShParseError extends ShBridgeError {
|
|
55
|
+
readonly code = "ESLINT_SH_PARSE_ERROR";
|
|
56
|
+
readonly line: number;
|
|
57
|
+
readonly column: number;
|
|
58
|
+
readonly filename: string;
|
|
59
|
+
constructor(message: string, info: ShParseErrorInfo);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Thrown by {@link parseSync} when {@link ParseOptions.dialect} is not one
|
|
63
|
+
* of the supported {@link ShellDialect} values. Carries the rejected value
|
|
64
|
+
* as `.dialect`; `.message` lists the supported dialects. A foreseeable,
|
|
65
|
+
* actionable user mistake — never an instance of {@link ShParseError}.
|
|
66
|
+
*
|
|
67
|
+
* @public
|
|
68
|
+
*/
|
|
69
|
+
export declare class ShInvalidDialectError extends ShBridgeError {
|
|
70
|
+
readonly code = "ESLINT_SH_INVALID_DIALECT";
|
|
71
|
+
/** The rejected dialect value, exactly as passed to {@link parseSync}. */
|
|
72
|
+
readonly dialect: string;
|
|
73
|
+
constructor(dialect: string, supportedDialects: readonly ShellDialect[]);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Thrown by {@link parseSync} for failures that should never happen given a
|
|
77
|
+
* correctly-behaving WASM shim: a malformed result envelope, an unexpected
|
|
78
|
+
* root node type, or any other shim contract violation. Distinct from
|
|
79
|
+
* {@link ShInvalidDialectError} and {@link ShParseError}, both of which
|
|
80
|
+
* indicate a foreseeable, actionable user mistake rather than an internal
|
|
81
|
+
* defect.
|
|
82
|
+
*
|
|
83
|
+
* @public
|
|
84
|
+
*/
|
|
85
|
+
export declare class ShBridgeInternalError extends ShBridgeError {
|
|
86
|
+
readonly code = "ESLINT_SH_BRIDGE_INTERNAL";
|
|
87
|
+
constructor(message: string);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Thrown by `sh-ast/analyze`'s `enumerateCommands` when a tree's genuinely
|
|
91
|
+
* nested structure — subshells within subshells, chained command/process
|
|
92
|
+
* substitutions, deeply nested `if`/`case`/loop/function/`time`/`{ }`
|
|
93
|
+
* bodies, deeply chained `elif` — exceeds its defensive recursion-depth
|
|
94
|
+
* guard. **Not** thrown for a long *linear* chain (`|`/`|&`/`&&`/`||` of
|
|
95
|
+
* any realistic length): `enumerateCommands` traverses those iteratively,
|
|
96
|
+
* so chain length alone never grows this guard's depth counter — only
|
|
97
|
+
* genuine tree nesting does.
|
|
98
|
+
*
|
|
99
|
+
* `enumerateCommands` deliberately fails closed here rather than returning
|
|
100
|
+
* a truncated, partial result: a partial `CommandSite[]` silently omits
|
|
101
|
+
* real command sites, which is a false negative for a permission-hook-style
|
|
102
|
+
* consumer that treats "command not found in the enumeration" as "nothing
|
|
103
|
+
* to worry about" — an explicit, documented throw is safer than a silent
|
|
104
|
+
* under-report. Gate-style consumers should treat this error as `deny`,
|
|
105
|
+
* not fall back to "no commands found, so allow".
|
|
106
|
+
*
|
|
107
|
+
* @public
|
|
108
|
+
*/
|
|
109
|
+
export declare class ShAnalyzeMaxDepthError extends ShBridgeError {
|
|
110
|
+
readonly code = "ESLINT_SH_ANALYZE_MAX_DEPTH";
|
|
111
|
+
/** The maximum nesting depth `enumerateCommands` supports; the same value every time (not caller-configurable). */
|
|
112
|
+
readonly maxDepth: number;
|
|
113
|
+
constructor(maxDepth: number);
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C;;;;;;;;;;;;GAYG;AACH,8BAAsB,aAAc,SAAQ,KAAK;IAC/C;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CAChC;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;OAOG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,YAAa,SAAQ,aAAa;IAC7C,QAAQ,CAAC,IAAI,2BAA2B;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAEd,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB;CAOpD;AAED;;;;;;;GAOG;AACH,qBAAa,qBAAsB,SAAQ,aAAa;IACtD,QAAQ,CAAC,IAAI,+BAA+B;IAC5C,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEb,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,SAAS,YAAY,EAAE;CAOxE;AAED;;;;;;;;;GASG;AACH,qBAAa,qBAAsB,SAAQ,aAAa;IACtD,QAAQ,CAAC,IAAI,+BAA+B;gBAEhC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,sBAAuB,SAAQ,aAAa;IACvD,QAAQ,CAAC,IAAI,iCAAiC;IAC9C,mHAAmH;IACnH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAEd,QAAQ,EAAE,MAAM;CAO7B"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common base class for every error this package throws — originally just
|
|
3
|
+
* {@link parseSync}'s errors, now also the `sh-ast/analyze` layer's (see
|
|
4
|
+
* {@link ShAnalyzeMaxDepthError}). Provides a stable, documented `code`
|
|
5
|
+
* discriminator (e.g. `"ESLINT_SH_PARSE_ERROR"`) alongside the usual
|
|
6
|
+
* `instanceof` narrowing, so consumers can branch on failure kind
|
|
7
|
+
* programmatically without parsing `.message` strings. Never thrown
|
|
8
|
+
* directly — only via its concrete subclasses ({@link ShParseError},
|
|
9
|
+
* {@link ShInvalidDialectError}, {@link ShBridgeInternalError},
|
|
10
|
+
* {@link ShAnalyzeMaxDepthError}).
|
|
11
|
+
*
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
14
|
+
export class ShBridgeError extends Error {
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Thrown by {@link parseSync} when shell source fails to parse. Carries the
|
|
18
|
+
* real position mvdan/sh reported — never 1:1 placeholders: `.line` is
|
|
19
|
+
* 1-based exactly as mvdan/sh reports it, and `.column` is converted to
|
|
20
|
+
* UTF-16 code units so it agrees with {@link ShNode.loc}'s columns (mvdan/sh
|
|
21
|
+
* itself reports `column` as a byte count on the source line — see
|
|
22
|
+
* {@link ShParseErrorInfo.column}). The unconverted, raw byte-column
|
|
23
|
+
* position appears only inside `.message`, mvdan/sh's own formatted string,
|
|
24
|
+
* verbatim.
|
|
25
|
+
*
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
export class ShParseError extends ShBridgeError {
|
|
29
|
+
code = 'ESLINT_SH_PARSE_ERROR';
|
|
30
|
+
line;
|
|
31
|
+
column;
|
|
32
|
+
filename;
|
|
33
|
+
constructor(message, info) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = 'ShParseError';
|
|
36
|
+
this.line = info.line;
|
|
37
|
+
this.column = info.column;
|
|
38
|
+
this.filename = info.filename;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Thrown by {@link parseSync} when {@link ParseOptions.dialect} is not one
|
|
43
|
+
* of the supported {@link ShellDialect} values. Carries the rejected value
|
|
44
|
+
* as `.dialect`; `.message` lists the supported dialects. A foreseeable,
|
|
45
|
+
* actionable user mistake — never an instance of {@link ShParseError}.
|
|
46
|
+
*
|
|
47
|
+
* @public
|
|
48
|
+
*/
|
|
49
|
+
export class ShInvalidDialectError extends ShBridgeError {
|
|
50
|
+
code = 'ESLINT_SH_INVALID_DIALECT';
|
|
51
|
+
/** The rejected dialect value, exactly as passed to {@link parseSync}. */
|
|
52
|
+
dialect;
|
|
53
|
+
constructor(dialect, supportedDialects) {
|
|
54
|
+
super(`bridge: unrecognized shell dialect "${dialect}"; supported dialects are: ${supportedDialects.join(', ')}`);
|
|
55
|
+
this.name = 'ShInvalidDialectError';
|
|
56
|
+
this.dialect = dialect;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Thrown by {@link parseSync} for failures that should never happen given a
|
|
61
|
+
* correctly-behaving WASM shim: a malformed result envelope, an unexpected
|
|
62
|
+
* root node type, or any other shim contract violation. Distinct from
|
|
63
|
+
* {@link ShInvalidDialectError} and {@link ShParseError}, both of which
|
|
64
|
+
* indicate a foreseeable, actionable user mistake rather than an internal
|
|
65
|
+
* defect.
|
|
66
|
+
*
|
|
67
|
+
* @public
|
|
68
|
+
*/
|
|
69
|
+
export class ShBridgeInternalError extends ShBridgeError {
|
|
70
|
+
code = 'ESLINT_SH_BRIDGE_INTERNAL';
|
|
71
|
+
constructor(message) {
|
|
72
|
+
super(message);
|
|
73
|
+
this.name = 'ShBridgeInternalError';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Thrown by `sh-ast/analyze`'s `enumerateCommands` when a tree's genuinely
|
|
78
|
+
* nested structure — subshells within subshells, chained command/process
|
|
79
|
+
* substitutions, deeply nested `if`/`case`/loop/function/`time`/`{ }`
|
|
80
|
+
* bodies, deeply chained `elif` — exceeds its defensive recursion-depth
|
|
81
|
+
* guard. **Not** thrown for a long *linear* chain (`|`/`|&`/`&&`/`||` of
|
|
82
|
+
* any realistic length): `enumerateCommands` traverses those iteratively,
|
|
83
|
+
* so chain length alone never grows this guard's depth counter — only
|
|
84
|
+
* genuine tree nesting does.
|
|
85
|
+
*
|
|
86
|
+
* `enumerateCommands` deliberately fails closed here rather than returning
|
|
87
|
+
* a truncated, partial result: a partial `CommandSite[]` silently omits
|
|
88
|
+
* real command sites, which is a false negative for a permission-hook-style
|
|
89
|
+
* consumer that treats "command not found in the enumeration" as "nothing
|
|
90
|
+
* to worry about" — an explicit, documented throw is safer than a silent
|
|
91
|
+
* under-report. Gate-style consumers should treat this error as `deny`,
|
|
92
|
+
* not fall back to "no commands found, so allow".
|
|
93
|
+
*
|
|
94
|
+
* @public
|
|
95
|
+
*/
|
|
96
|
+
export class ShAnalyzeMaxDepthError extends ShBridgeError {
|
|
97
|
+
code = 'ESLINT_SH_ANALYZE_MAX_DEPTH';
|
|
98
|
+
/** The maximum nesting depth `enumerateCommands` supports; the same value every time (not caller-configurable). */
|
|
99
|
+
maxDepth;
|
|
100
|
+
constructor(maxDepth) {
|
|
101
|
+
super(`enumerateCommands: exceeded the maximum supported structural nesting depth (${String(maxDepth)} frames) — refusing to return a partial result. Treat this as a deny signal, not "no commands found".`);
|
|
102
|
+
this.name = 'ShAnalyzeMaxDepthError';
|
|
103
|
+
this.maxDepth = maxDepth;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AACH,MAAM,OAAgB,aAAc,SAAQ,KAAK;CAMhD;AAuBD;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,YAAa,SAAQ,aAAa;IACpC,IAAI,GAAG,uBAAuB,CAAC;IAC/B,IAAI,CAAS;IACb,MAAM,CAAS;IACf,QAAQ,CAAS;IAE1B,YAAY,OAAe,EAAE,IAAsB;QACjD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;QAC3B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;IAChC,CAAC;CACF;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,qBAAsB,SAAQ,aAAa;IAC7C,IAAI,GAAG,2BAA2B,CAAC;IAC5C,0EAA0E;IACjE,OAAO,CAAS;IAEzB,YAAY,OAAe,EAAE,iBAA0C;QACrE,KAAK,CACH,uCAAuC,OAAO,8BAA8B,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC3G,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;QACpC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AAED;;;;;;;;;GASG;AACH,MAAM,OAAO,qBAAsB,SAAQ,aAAa;IAC7C,IAAI,GAAG,2BAA2B,CAAC;IAE5C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,OAAO,sBAAuB,SAAQ,aAAa;IAC9C,IAAI,GAAG,6BAA6B,CAAC;IAC9C,mHAAmH;IAC1G,QAAQ,CAAS;IAE1B,YAAY,QAAgB;QAC1B,KAAK,CACH,+EAA+E,MAAM,CAAC,QAAQ,CAAC,uGAAuG,CACvM,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;QACrC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF"}
|