sh-ast 0.0.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 (81) hide show
  1. package/README.md +147 -0
  2. package/dist/analyze/ansi-c-escapes.d.ts +53 -0
  3. package/dist/analyze/ansi-c-escapes.d.ts.map +1 -0
  4. package/dist/analyze/ansi-c-escapes.js +255 -0
  5. package/dist/analyze/ansi-c-escapes.js.map +1 -0
  6. package/dist/analyze/decode-lit.d.ts +74 -0
  7. package/dist/analyze/decode-lit.d.ts.map +1 -0
  8. package/dist/analyze/decode-lit.js +114 -0
  9. package/dist/analyze/decode-lit.js.map +1 -0
  10. package/dist/analyze/enumerate-commands.d.ts +165 -0
  11. package/dist/analyze/enumerate-commands.d.ts.map +1 -0
  12. package/dist/analyze/enumerate-commands.js +396 -0
  13. package/dist/analyze/enumerate-commands.js.map +1 -0
  14. package/dist/analyze/index.d.ts +26 -0
  15. package/dist/analyze/index.d.ts.map +1 -0
  16. package/dist/analyze/index.js +30 -0
  17. package/dist/analyze/index.js.map +1 -0
  18. package/dist/analyze/node-helpers.d.ts +28 -0
  19. package/dist/analyze/node-helpers.d.ts.map +1 -0
  20. package/dist/analyze/node-helpers.js +37 -0
  21. package/dist/analyze/node-helpers.js.map +1 -0
  22. package/dist/analyze/resolve-argv0.d.ts +335 -0
  23. package/dist/analyze/resolve-argv0.d.ts.map +1 -0
  24. package/dist/analyze/resolve-argv0.js +510 -0
  25. package/dist/analyze/resolve-argv0.js.map +1 -0
  26. package/dist/analyze/resolve-word.d.ts +151 -0
  27. package/dist/analyze/resolve-word.d.ts.map +1 -0
  28. package/dist/analyze/resolve-word.js +207 -0
  29. package/dist/analyze/resolve-word.js.map +1 -0
  30. package/dist/analyze.d.ts +771 -0
  31. package/dist/deep-freeze.d.ts +14 -0
  32. package/dist/deep-freeze.d.ts.map +1 -0
  33. package/dist/deep-freeze.js +25 -0
  34. package/dist/deep-freeze.js.map +1 -0
  35. package/dist/errors.d.ts +180 -0
  36. package/dist/errors.d.ts.map +1 -0
  37. package/dist/errors.js +179 -0
  38. package/dist/errors.js.map +1 -0
  39. package/dist/index.d.ts +18 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +5 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/normalize.d.ts +52 -0
  44. package/dist/normalize.d.ts.map +1 -0
  45. package/dist/normalize.js +260 -0
  46. package/dist/normalize.js.map +1 -0
  47. package/dist/parse-depth-guard.d.ts +11 -0
  48. package/dist/parse-depth-guard.d.ts.map +1 -0
  49. package/dist/parse-depth-guard.js +633 -0
  50. package/dist/parse-depth-guard.js.map +1 -0
  51. package/dist/parse.d.ts +50 -0
  52. package/dist/parse.d.ts.map +1 -0
  53. package/dist/parse.js +125 -0
  54. package/dist/parse.js.map +1 -0
  55. package/dist/sh-ast.d.ts +1346 -0
  56. package/dist/types.d.ts +74 -0
  57. package/dist/types.d.ts.map +1 -0
  58. package/dist/types.js +2 -0
  59. package/dist/types.js.map +1 -0
  60. package/dist/visitor-keys.d.ts +21 -0
  61. package/dist/visitor-keys.d.ts.map +1 -0
  62. package/dist/visitor-keys.js +23 -0
  63. package/dist/visitor-keys.js.map +1 -0
  64. package/dist/walk.d.ts +18 -0
  65. package/dist/walk.d.ts.map +1 -0
  66. package/dist/walk.js +40 -0
  67. package/dist/walk.js.map +1 -0
  68. package/dist/wasm-instance.d.ts +13 -0
  69. package/dist/wasm-instance.d.ts.map +1 -0
  70. package/dist/wasm-instance.js +88 -0
  71. package/dist/wasm-instance.js.map +1 -0
  72. package/generated/child-type-schema.d.ts +14 -0
  73. package/generated/child-type-schema.js +188 -0
  74. package/generated/node-types.d.ts +958 -0
  75. package/generated/position-fields.d.ts +21 -0
  76. package/generated/position-fields.js +50 -0
  77. package/generated/visitor-keys.d.ts +11 -0
  78. package/generated/visitor-keys.js +50 -0
  79. package/package.json +93 -10
  80. package/shim/sh-ast.wasm +0 -0
  81. package/shim/wasm_exec.js +654 -0
@@ -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"}
@@ -0,0 +1,180 @@
1
+ import type { ShellDialect } from './types.js';
2
+ /**
3
+ * Common base class for every error this package throws — originally just
4
+ * {@link sh-ast#parseSync | parseSync}'s errors, now also the
5
+ * `sh-ast/analyze` layer's (see {@link ShAnalyzeMaxDepthError},
6
+ * {@link ShAnalyzeInvalidWrapperSpecError}). Provides a stable, documented
7
+ * `code` discriminator (e.g. `"SH_AST_PARSE_ERROR"`) alongside the usual
8
+ * `instanceof` narrowing, so consumers can branch on failure kind
9
+ * programmatically without parsing `.message` strings. Never thrown directly
10
+ * — only via its concrete subclasses
11
+ * ({@link sh-ast#ShParseError | ShParseError},
12
+ * {@link sh-ast#ShInvalidDialectError | ShInvalidDialectError},
13
+ * {@link sh-ast#ShInternalError | ShInternalError},
14
+ * {@link ShAnalyzeMaxDepthError},
15
+ * {@link sh-ast#ShParseMaxDepthError | ShParseMaxDepthError},
16
+ * {@link ShAnalyzeInvalidWrapperSpecError}).
17
+ *
18
+ * Re-exported from `sh-ast/analyze` as well as the root `sh-ast` entry
19
+ * point (see #23) — every subclass thrown by `sh-ast/analyze`'s own
20
+ * functions extends this, so a consumer of that subpath alone can
21
+ * `catch`/reference the base without also importing from root.
22
+ *
23
+ * @public
24
+ */
25
+ export declare abstract class ShAstError extends Error {
26
+ /**
27
+ * Stable, machine-readable discriminator for this error kind. Distinct
28
+ * per subclass and will not change once published.
29
+ */
30
+ abstract readonly code: string;
31
+ }
32
+ /**
33
+ * Position info attached to a {@link ShParseError}.
34
+ *
35
+ * @public
36
+ */
37
+ export interface ShParseErrorInfo {
38
+ /** 1-based line number, exactly as reported by mvdan/sh. */
39
+ line: number;
40
+ /**
41
+ * 1-based column number, in **UTF-16 code units** — converted from
42
+ * mvdan/sh's own byte-counting column so it agrees with
43
+ * {@link ShNode.loc}'s columns (mvdan/sh reports `column` as a byte count
44
+ * on the source line, not a UTF-16 code unit count; see
45
+ * design/ARCHITECTURE.md's "Byte→UTF-16 conversion"). For ASCII-only
46
+ * source text this is numerically identical to mvdan/sh's own column.
47
+ */
48
+ column: number;
49
+ /** The filename used for the parse (see {@link ParseOptions.filename}). */
50
+ filename: string;
51
+ }
52
+ /**
53
+ * Thrown by {@link parseSync} when shell source fails to parse. Carries the
54
+ * real position mvdan/sh reported — never 1:1 placeholders: `.line` is
55
+ * 1-based exactly as mvdan/sh reports it, and `.column` is converted to
56
+ * UTF-16 code units so it agrees with {@link ShNode.loc}'s columns (mvdan/sh
57
+ * itself reports `column` as a byte count on the source line — see
58
+ * {@link ShParseErrorInfo.column}). The unconverted, raw byte-column
59
+ * position appears only inside `.message`, mvdan/sh's own formatted string,
60
+ * verbatim.
61
+ *
62
+ * @public
63
+ */
64
+ export declare class ShParseError extends ShAstError {
65
+ readonly code = "SH_AST_PARSE_ERROR";
66
+ readonly line: number;
67
+ readonly column: number;
68
+ readonly filename: string;
69
+ constructor(message: string, info: ShParseErrorInfo);
70
+ }
71
+ /**
72
+ * Thrown by {@link parseSync} when {@link ParseOptions.dialect} is not one
73
+ * of the supported {@link ShellDialect} values. Carries the rejected value
74
+ * as `.dialect`; `.message` lists the supported dialects. A foreseeable,
75
+ * actionable user mistake — never an instance of {@link ShParseError}.
76
+ *
77
+ * @public
78
+ */
79
+ export declare class ShInvalidDialectError extends ShAstError {
80
+ readonly code = "SH_AST_INVALID_DIALECT";
81
+ /** The rejected dialect value, exactly as passed to {@link parseSync}. */
82
+ readonly dialect: string;
83
+ constructor(dialect: string, supportedDialects: readonly ShellDialect[]);
84
+ }
85
+ /**
86
+ * Thrown by {@link parseSync} for failures that should never happen given a
87
+ * correctly-behaving WASM shim: a malformed result envelope, an unexpected
88
+ * root node type, or any other shim contract violation. Distinct from
89
+ * {@link ShInvalidDialectError} and {@link ShParseError}, both of which
90
+ * indicate a foreseeable, actionable user mistake rather than an internal
91
+ * defect.
92
+ *
93
+ * @public
94
+ */
95
+ export declare class ShInternalError extends ShAstError {
96
+ readonly code = "SH_AST_INTERNAL";
97
+ constructor(message: string);
98
+ }
99
+ /**
100
+ * Thrown by `sh-ast/analyze`'s `enumerateCommands` when a tree's genuinely
101
+ * nested structure — subshells within subshells, chained command/process
102
+ * substitutions, deeply nested `if`/`case`/loop/function/`time`/`{ }`
103
+ * bodies, deeply chained `elif` — exceeds its defensive recursion-depth
104
+ * guard. **Not** thrown for a long *linear* chain (`|`/`|&`/`&&`/`||` of
105
+ * any realistic length): `enumerateCommands` traverses those iteratively,
106
+ * so chain length alone never grows this guard's depth counter — only
107
+ * genuine tree nesting does.
108
+ *
109
+ * `enumerateCommands` deliberately fails closed here rather than returning
110
+ * a truncated, partial result: a partial `CommandSite[]` silently omits
111
+ * real command sites, which is a false negative for a permission-hook-style
112
+ * consumer that treats "command not found in the enumeration" as "nothing
113
+ * to worry about" — an explicit, documented throw is safer than a silent
114
+ * under-report. Gate-style consumers should treat this error as `deny`,
115
+ * not fall back to "no commands found, so allow".
116
+ *
117
+ * @public
118
+ */
119
+ export declare class ShAnalyzeMaxDepthError extends ShAstError {
120
+ readonly code = "SH_AST_ANALYZE_MAX_DEPTH";
121
+ /** The maximum nesting depth `enumerateCommands` supports; the same value every time (not caller-configurable). */
122
+ readonly maxDepth: number;
123
+ constructor(maxDepth: number);
124
+ }
125
+ /**
126
+ * Thrown by {@link parseSync} when `text`'s conservatively estimated
127
+ * structural nesting depth (subshells, command/process substitutions,
128
+ * `if`/`case`/loop/function bodies, `{ }` blocks, backtick or `$(...)`
129
+ * command substitution, `$((...))` arithmetic — see
130
+ * `parse-depth-guard.ts`'s module doc for the exact heuristic) exceeds the
131
+ * limit `parseSync` accepts. Thrown *before* `text` is ever handed to the
132
+ * WASM shim: mvdan/sh's own recursive-descent parser has no recovery path
133
+ * for exhausting its call stack on pathologically deep input (a stack
134
+ * overflow deep inside the shared WASM instance is not a normal, catchable
135
+ * JS error the way a parse syntax error is), so this bridge estimates the
136
+ * risk up front and fails closed with a typed error instead of ever making
137
+ * that call — the shared WASM instance is therefore never put at risk of
138
+ * wedging on this class of input; a `parseSync` call right after catching
139
+ * this error works exactly as it would if this input had never been
140
+ * attempted.
141
+ *
142
+ * Like {@link ShAnalyzeMaxDepthError}, this is pathological-input
143
+ * protection, not a normal-usage ceiling: a legitimately deep (but
144
+ * realistic) script parses fine. The estimate can *over*-count relative to
145
+ * mvdan/sh's real grammar (rejecting some legitimate-but-unusually-deep
146
+ * input) but is designed to never *under*-count relative to it — see
147
+ * `parse-depth-guard.ts` for the documented false-positive sources this
148
+ * trades off against ever letting a genuinely crash-inducing input
149
+ * through.
150
+ *
151
+ * @public
152
+ */
153
+ export declare class ShParseMaxDepthError extends ShAstError {
154
+ readonly code = "SH_AST_PARSE_MAX_DEPTH";
155
+ /** The maximum structural nesting depth `parseSync` accepts; the same value every time (not caller-configurable). */
156
+ readonly maxDepth: number;
157
+ /**
158
+ * The conservatively estimated nesting depth that tripped the guard.
159
+ * Not necessarily `text`'s "true" nesting depth under a real parse — see
160
+ * {@link ShParseMaxDepthError}'s doc comment — only ever `> maxDepth`.
161
+ */
162
+ readonly estimatedDepth: number;
163
+ constructor(maxDepth: number, estimatedDepth: number);
164
+ }
165
+ /**
166
+ * Thrown by `sh-ast/analyze`'s `resolveArgv0` when a caller-supplied
167
+ * `options.transparentWrappers` array contains a malformed `WrapperSpec`
168
+ * entry — e.g. a non-array or empty `names`, or a flag field that isn't an
169
+ * array of strings. Fails closed with a clear, specific message at the
170
+ * point the malformed table is supplied, rather than letting the bad shape
171
+ * reach flag-matching logic and surface as a confusing native `TypeError`
172
+ * (or, worse, a silently wrong match) far from its actual cause.
173
+ *
174
+ * @public
175
+ */
176
+ export declare class ShAnalyzeInvalidWrapperSpecError extends ShAstError {
177
+ readonly code = "SH_AST_ANALYZE_INVALID_WRAPPER_SPEC";
178
+ constructor(message: string);
179
+ }
180
+ //# 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;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,8BAAsB,UAAW,SAAQ,KAAK;IAC5C;;;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,UAAU;IAC1C,QAAQ,CAAC,IAAI,wBAAwB;IACrC,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,UAAU;IACnD,QAAQ,CAAC,IAAI,4BAA4B;IACzC,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEb,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,SAAS,YAAY,EAAE;CAOxE;AAED;;;;;;;;;GASG;AACH,qBAAa,eAAgB,SAAQ,UAAU;IAC7C,QAAQ,CAAC,IAAI,qBAAqB;gBAEtB,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,sBAAuB,SAAQ,UAAU;IACpD,QAAQ,CAAC,IAAI,8BAA8B;IAC3C,mHAAmH;IACnH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAEd,QAAQ,EAAE,MAAM;CAO7B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,oBAAqB,SAAQ,UAAU;IAClD,QAAQ,CAAC,IAAI,4BAA4B;IACzC,qHAAqH;IACrH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;gBAEpB,QAAQ,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM;CAQrD;AAED;;;;;;;;;;GAUG;AACH,qBAAa,gCAAiC,SAAQ,UAAU;IAC9D,QAAQ,CAAC,IAAI,yCAAyC;gBAE1C,OAAO,EAAE,MAAM;CAI5B"}
package/dist/errors.js ADDED
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Common base class for every error this package throws — originally just
3
+ * {@link sh-ast#parseSync | parseSync}'s errors, now also the
4
+ * `sh-ast/analyze` layer's (see {@link ShAnalyzeMaxDepthError},
5
+ * {@link ShAnalyzeInvalidWrapperSpecError}). Provides a stable, documented
6
+ * `code` discriminator (e.g. `"SH_AST_PARSE_ERROR"`) alongside the usual
7
+ * `instanceof` narrowing, so consumers can branch on failure kind
8
+ * programmatically without parsing `.message` strings. Never thrown directly
9
+ * — only via its concrete subclasses
10
+ * ({@link sh-ast#ShParseError | ShParseError},
11
+ * {@link sh-ast#ShInvalidDialectError | ShInvalidDialectError},
12
+ * {@link sh-ast#ShInternalError | ShInternalError},
13
+ * {@link ShAnalyzeMaxDepthError},
14
+ * {@link sh-ast#ShParseMaxDepthError | ShParseMaxDepthError},
15
+ * {@link ShAnalyzeInvalidWrapperSpecError}).
16
+ *
17
+ * Re-exported from `sh-ast/analyze` as well as the root `sh-ast` entry
18
+ * point (see #23) — every subclass thrown by `sh-ast/analyze`'s own
19
+ * functions extends this, so a consumer of that subpath alone can
20
+ * `catch`/reference the base without also importing from root.
21
+ *
22
+ * @public
23
+ */
24
+ export class ShAstError extends Error {
25
+ }
26
+ /**
27
+ * Thrown by {@link parseSync} when shell source fails to parse. Carries the
28
+ * real position mvdan/sh reported — never 1:1 placeholders: `.line` is
29
+ * 1-based exactly as mvdan/sh reports it, and `.column` is converted to
30
+ * UTF-16 code units so it agrees with {@link ShNode.loc}'s columns (mvdan/sh
31
+ * itself reports `column` as a byte count on the source line — see
32
+ * {@link ShParseErrorInfo.column}). The unconverted, raw byte-column
33
+ * position appears only inside `.message`, mvdan/sh's own formatted string,
34
+ * verbatim.
35
+ *
36
+ * @public
37
+ */
38
+ export class ShParseError extends ShAstError {
39
+ code = 'SH_AST_PARSE_ERROR';
40
+ line;
41
+ column;
42
+ filename;
43
+ constructor(message, info) {
44
+ super(message);
45
+ this.name = 'ShParseError';
46
+ this.line = info.line;
47
+ this.column = info.column;
48
+ this.filename = info.filename;
49
+ }
50
+ }
51
+ /**
52
+ * Thrown by {@link parseSync} when {@link ParseOptions.dialect} is not one
53
+ * of the supported {@link ShellDialect} values. Carries the rejected value
54
+ * as `.dialect`; `.message` lists the supported dialects. A foreseeable,
55
+ * actionable user mistake — never an instance of {@link ShParseError}.
56
+ *
57
+ * @public
58
+ */
59
+ export class ShInvalidDialectError extends ShAstError {
60
+ code = 'SH_AST_INVALID_DIALECT';
61
+ /** The rejected dialect value, exactly as passed to {@link parseSync}. */
62
+ dialect;
63
+ constructor(dialect, supportedDialects) {
64
+ super(`bridge: unrecognized shell dialect "${dialect}"; supported dialects are: ${supportedDialects.join(', ')}`);
65
+ this.name = 'ShInvalidDialectError';
66
+ this.dialect = dialect;
67
+ }
68
+ }
69
+ /**
70
+ * Thrown by {@link parseSync} for failures that should never happen given a
71
+ * correctly-behaving WASM shim: a malformed result envelope, an unexpected
72
+ * root node type, or any other shim contract violation. Distinct from
73
+ * {@link ShInvalidDialectError} and {@link ShParseError}, both of which
74
+ * indicate a foreseeable, actionable user mistake rather than an internal
75
+ * defect.
76
+ *
77
+ * @public
78
+ */
79
+ export class ShInternalError extends ShAstError {
80
+ code = 'SH_AST_INTERNAL';
81
+ constructor(message) {
82
+ super(message);
83
+ this.name = 'ShInternalError';
84
+ }
85
+ }
86
+ /**
87
+ * Thrown by `sh-ast/analyze`'s `enumerateCommands` when a tree's genuinely
88
+ * nested structure — subshells within subshells, chained command/process
89
+ * substitutions, deeply nested `if`/`case`/loop/function/`time`/`{ }`
90
+ * bodies, deeply chained `elif` — exceeds its defensive recursion-depth
91
+ * guard. **Not** thrown for a long *linear* chain (`|`/`|&`/`&&`/`||` of
92
+ * any realistic length): `enumerateCommands` traverses those iteratively,
93
+ * so chain length alone never grows this guard's depth counter — only
94
+ * genuine tree nesting does.
95
+ *
96
+ * `enumerateCommands` deliberately fails closed here rather than returning
97
+ * a truncated, partial result: a partial `CommandSite[]` silently omits
98
+ * real command sites, which is a false negative for a permission-hook-style
99
+ * consumer that treats "command not found in the enumeration" as "nothing
100
+ * to worry about" — an explicit, documented throw is safer than a silent
101
+ * under-report. Gate-style consumers should treat this error as `deny`,
102
+ * not fall back to "no commands found, so allow".
103
+ *
104
+ * @public
105
+ */
106
+ export class ShAnalyzeMaxDepthError extends ShAstError {
107
+ code = 'SH_AST_ANALYZE_MAX_DEPTH';
108
+ /** The maximum nesting depth `enumerateCommands` supports; the same value every time (not caller-configurable). */
109
+ maxDepth;
110
+ constructor(maxDepth) {
111
+ 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".`);
112
+ this.name = 'ShAnalyzeMaxDepthError';
113
+ this.maxDepth = maxDepth;
114
+ }
115
+ }
116
+ /**
117
+ * Thrown by {@link parseSync} when `text`'s conservatively estimated
118
+ * structural nesting depth (subshells, command/process substitutions,
119
+ * `if`/`case`/loop/function bodies, `{ }` blocks, backtick or `$(...)`
120
+ * command substitution, `$((...))` arithmetic — see
121
+ * `parse-depth-guard.ts`'s module doc for the exact heuristic) exceeds the
122
+ * limit `parseSync` accepts. Thrown *before* `text` is ever handed to the
123
+ * WASM shim: mvdan/sh's own recursive-descent parser has no recovery path
124
+ * for exhausting its call stack on pathologically deep input (a stack
125
+ * overflow deep inside the shared WASM instance is not a normal, catchable
126
+ * JS error the way a parse syntax error is), so this bridge estimates the
127
+ * risk up front and fails closed with a typed error instead of ever making
128
+ * that call — the shared WASM instance is therefore never put at risk of
129
+ * wedging on this class of input; a `parseSync` call right after catching
130
+ * this error works exactly as it would if this input had never been
131
+ * attempted.
132
+ *
133
+ * Like {@link ShAnalyzeMaxDepthError}, this is pathological-input
134
+ * protection, not a normal-usage ceiling: a legitimately deep (but
135
+ * realistic) script parses fine. The estimate can *over*-count relative to
136
+ * mvdan/sh's real grammar (rejecting some legitimate-but-unusually-deep
137
+ * input) but is designed to never *under*-count relative to it — see
138
+ * `parse-depth-guard.ts` for the documented false-positive sources this
139
+ * trades off against ever letting a genuinely crash-inducing input
140
+ * through.
141
+ *
142
+ * @public
143
+ */
144
+ export class ShParseMaxDepthError extends ShAstError {
145
+ code = 'SH_AST_PARSE_MAX_DEPTH';
146
+ /** The maximum structural nesting depth `parseSync` accepts; the same value every time (not caller-configurable). */
147
+ maxDepth;
148
+ /**
149
+ * The conservatively estimated nesting depth that tripped the guard.
150
+ * Not necessarily `text`'s "true" nesting depth under a real parse — see
151
+ * {@link ShParseMaxDepthError}'s doc comment — only ever `> maxDepth`.
152
+ */
153
+ estimatedDepth;
154
+ constructor(maxDepth, estimatedDepth) {
155
+ super(`parseSync: input's estimated structural nesting depth (${String(estimatedDepth)}+ frames) exceeds the maximum this bridge accepts (${String(maxDepth)}) — refusing to hand this input to the WASM parser, which would risk exhausting its call stack uncatchably. Treat this as a deny signal, not "input looks fine".`);
156
+ this.name = 'ShParseMaxDepthError';
157
+ this.maxDepth = maxDepth;
158
+ this.estimatedDepth = estimatedDepth;
159
+ }
160
+ }
161
+ /**
162
+ * Thrown by `sh-ast/analyze`'s `resolveArgv0` when a caller-supplied
163
+ * `options.transparentWrappers` array contains a malformed `WrapperSpec`
164
+ * entry — e.g. a non-array or empty `names`, or a flag field that isn't an
165
+ * array of strings. Fails closed with a clear, specific message at the
166
+ * point the malformed table is supplied, rather than letting the bad shape
167
+ * reach flag-matching logic and surface as a confusing native `TypeError`
168
+ * (or, worse, a silently wrong match) far from its actual cause.
169
+ *
170
+ * @public
171
+ */
172
+ export class ShAnalyzeInvalidWrapperSpecError extends ShAstError {
173
+ code = 'SH_AST_ANALYZE_INVALID_WRAPPER_SPEC';
174
+ constructor(message) {
175
+ super(`resolveArgv0: invalid transparentWrappers entry — ${message}`);
176
+ this.name = 'ShAnalyzeInvalidWrapperSpecError';
177
+ }
178
+ }
179
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,OAAgB,UAAW,SAAQ,KAAK;CAM7C;AAuBD;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,YAAa,SAAQ,UAAU;IACjC,IAAI,GAAG,oBAAoB,CAAC;IAC5B,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,UAAU;IAC1C,IAAI,GAAG,wBAAwB,CAAC;IACzC,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,eAAgB,SAAQ,UAAU;IACpC,IAAI,GAAG,iBAAiB,CAAC;IAElC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,OAAO,sBAAuB,SAAQ,UAAU;IAC3C,IAAI,GAAG,0BAA0B,CAAC;IAC3C,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;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,OAAO,oBAAqB,SAAQ,UAAU;IACzC,IAAI,GAAG,wBAAwB,CAAC;IACzC,qHAAqH;IAC5G,QAAQ,CAAS;IAC1B;;;;OAIG;IACM,cAAc,CAAS;IAEhC,YAAY,QAAgB,EAAE,cAAsB;QAClD,KAAK,CACH,0DAA0D,MAAM,CAAC,cAAc,CAAC,sDAAsD,MAAM,CAAC,QAAQ,CAAC,kKAAkK,CACzT,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;QACnC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;IACvC,CAAC;CACF;AAED;;;;;;;;;;GAUG;AACH,MAAM,OAAO,gCAAiC,SAAQ,UAAU;IACrD,IAAI,GAAG,qCAAqC,CAAC;IAEtD,YAAY,OAAe;QACzB,KAAK,CAAC,qDAAqD,OAAO,EAAE,CAAC,CAAC;QACtE,IAAI,CAAC,IAAI,GAAG,kCAAkC,CAAC;IACjD,CAAC;CACF"}
@@ -0,0 +1,18 @@
1
+ export { parseSync } from './parse.js';
2
+ export { ShAnalyzeInvalidWrapperSpecError, ShAnalyzeMaxDepthError, ShAstError, ShInternalError, ShInvalidDialectError, ShParseError, ShParseMaxDepthError, } from './errors.js';
3
+ export type { ShParseErrorInfo } from './errors.js';
4
+ export type { ParseOptions, Position, ShellDialect, ShFile, ShNode } from './types.js';
5
+ export { visitorKeys } from './visitor-keys.js';
6
+ export { walk } from './walk.js';
7
+ /**
8
+ * Strongly-typed `ShNode` subtypes for every mvdan/sh node type — generated
9
+ * by `tools/gen-visitor-keys` alongside {@link visitorKeys} (see
10
+ * design/ARCHITECTURE.md, "The schema table is generated, not
11
+ * hand-written"). {@link ShNode}'s index signature remains the base
12
+ * contract; these are an additive, opt-in stronger typing for consumers
13
+ * that want e.g. `ShNodes.ShCallExprNode` instead of a generic `ShNode`.
14
+ *
15
+ * @public
16
+ */
17
+ export type * as ShNodes from '../generated/node-types.js';
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EACL,gCAAgC,EAChC,sBAAsB,EACtB,UAAU,EACV,eAAe,EACf,qBAAqB,EACrB,YAAY,EACZ,oBAAoB,GACrB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACvF,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;;;;;;;;GASG;AACH,YAAY,KAAK,OAAO,MAAM,4BAA4B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { parseSync } from './parse.js';
2
+ export { ShAnalyzeInvalidWrapperSpecError, ShAnalyzeMaxDepthError, ShAstError, ShInternalError, ShInvalidDialectError, ShParseError, ShParseMaxDepthError, } from './errors.js';
3
+ export { visitorKeys } from './visitor-keys.js';
4
+ export { walk } from './walk.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EACL,gCAAgC,EAChC,sBAAsB,EACtB,UAAU,EACV,eAAe,EACf,qBAAqB,EACrB,YAAY,EACZ,oBAAoB,GACrB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,52 @@
1
+ import type { ShNode } from './types.js';
2
+ /**
3
+ * A JSON value as produced by `JSON.parse`.
4
+ *
5
+ * @internal
6
+ */
7
+ export type JsonValue = string | number | boolean | null | JsonValue[] | {
8
+ [key: string]: JsonValue;
9
+ };
10
+ /**
11
+ * Builds a byte-offset -> UTF-16-index lookup for `text`. mvdan/sh reports
12
+ * byte offsets; ESLint (and JavaScript strings generally) index by UTF-16
13
+ * code unit, so every position needs this conversion. Throws if asked to
14
+ * convert an offset that does not fall on a UTF-8 code-point boundary — an
15
+ * invariant every offset the parser emits must satisfy (see
16
+ * design/ARCHITECTURE.md's "Serialization contract").
17
+ *
18
+ * @internal
19
+ */
20
+ export declare function makeByteToUtf16(text: string): (byteOffset: number) => number;
21
+ /**
22
+ * Converts a byte offset and the 1-based line mvdan/sh already computed for
23
+ * it into a 1-based, UTF-16-code-unit column — the same conversion
24
+ * `normalize` applies to every node's `loc.start`/`loc.end` below, extracted
25
+ * so a parse error's reported position (see `errors.ts`'s
26
+ * `ShParseErrorInfo`) can use the identical algorithm instead of the raw
27
+ * byte-counting column mvdan/sh itself reports. See design/ARCHITECTURE.md's
28
+ * "Byte→UTF-16 conversion".
29
+ *
30
+ * @internal
31
+ */
32
+ export declare function toUtf16Column(text: string, byteOffset: number, line: number): number;
33
+ /**
34
+ * Normalizes mvdan/sh's typedjson tree into the shape ESLint expects: every
35
+ * node gets `type`, `range` (UTF-16), `loc` (1-based, UTF-16 columns), and
36
+ * every other field copied over with its name lowercased. See
37
+ * design/ARCHITECTURE.md's "Normalized node shape".
38
+ *
39
+ * Three of mvdan/sh's struct types — `Slice`, `Replace`, `Expansion`, all
40
+ * reachable only via `ParamExp.Slice`/`.Repl`/`.Exp` — do not implement
41
+ * `syntax.Node` (no `Pos()`/`End()`), so typedjson never gives them a `Pos`/
42
+ * `End` pair, let alone a `Type` discriminator. They still hold real child
43
+ * nodes (`Slice.Offset`/`.Length`, `Replace.Orig`/`.With`,
44
+ * `Expansion.Word`), so dropping them would silently lose that subtree (see
45
+ * issue #13 — `${USER:-nobody}`'s `nobody` literal). This function
46
+ * synthesizes a `type`/`range`/`loc` for these from their own children's
47
+ * already-computed positions instead, via `walkSynthetic` below.
48
+ *
49
+ * @internal
50
+ */
51
+ export declare function normalize(root: JsonValue, text: string): ShNode;
52
+ //# sourceMappingURL=normalize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../src/normalize.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAY,MAAM,EAAE,MAAM,YAAY,CAAC;AAkBnD;;;;GAIG;AACH,MAAM,MAAM,SAAS,GACnB,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,EAAE,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAoEhF;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAsB5E;AAkBD;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAIpF;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAsI/D"}