@intentius/chant 0.19.0 → 0.20.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/dist/build-params.d.ts +108 -0
- package/dist/build-params.d.ts.map +1 -0
- package/dist/build.d.ts +79 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts +31 -0
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-examples.d.ts +41 -0
- package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -0
- package/dist/cli/commands/check-lexicon-intrinsics.d.ts +54 -0
- package/dist/cli/commands/check-lexicon-intrinsics.d.ts.map +1 -0
- package/dist/cli/commands/check-lexicon.d.ts +1 -1
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/lint.d.ts +27 -0
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/lint.d.ts.map +1 -1
- package/dist/cli/handlers/run-client.d.ts +1 -1
- package/dist/cli/handlers/run-client.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/plugins.d.ts +16 -0
- package/dist/cli/plugins.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +10 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/docs-sections.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +12 -5
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/discover.d.ts +62 -7
- package/dist/components/discover.d.ts.map +1 -1
- package/dist/components/sandbox/driver.d.ts +12 -0
- package/dist/components/sandbox/driver.d.ts.map +1 -0
- package/dist/components/sandbox/run.d.ts +42 -0
- package/dist/components/sandbox/run.d.ts.map +1 -0
- package/dist/composite.d.ts +5 -0
- package/dist/composite.d.ts.map +1 -1
- package/dist/config.d.ts +71 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/discovery/collect.d.ts.map +1 -1
- package/dist/discovery/entity-wire-codec.d.ts +166 -0
- package/dist/discovery/entity-wire-codec.d.ts.map +1 -0
- package/dist/discovery/entity-wire.d.ts +50 -0
- package/dist/discovery/entity-wire.d.ts.map +1 -0
- package/dist/discovery/fold-import.d.ts +210 -0
- package/dist/discovery/fold-import.d.ts.map +1 -0
- package/dist/discovery/index.d.ts +74 -1
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/sandbox/bundle.d.ts +18 -0
- package/dist/discovery/sandbox/bundle.d.ts.map +1 -0
- package/dist/discovery/sandbox/child-errors.d.ts +15 -0
- package/dist/discovery/sandbox/child-errors.d.ts.map +1 -0
- package/dist/discovery/sandbox/driver.d.ts +13 -0
- package/dist/discovery/sandbox/driver.d.ts.map +1 -0
- package/dist/discovery/sandbox/run.d.ts +69 -0
- package/dist/discovery/sandbox/run.d.ts.map +1 -0
- package/dist/errors.d.ts +9 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/fold/fold.d.ts +226 -0
- package/dist/fold/fold.d.ts.map +1 -0
- package/dist/fold/subset.d.ts +102 -0
- package/dist/fold/subset.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lexicon-output.d.ts +7 -2
- package/dist/lexicon-output.d.ts.map +1 -1
- package/dist/lexicon-schema.d.ts +2 -2
- package/dist/lexicon-schema.d.ts.map +1 -1
- package/dist/lexicon.d.ts +81 -1
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.d.ts +11 -0
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lint/component-checks.d.ts +7 -1
- package/dist/lint/component-checks.d.ts.map +1 -1
- package/dist/lint/rules/evl001-non-literal-expression.d.ts.map +1 -1
- package/dist/lint/rules/evl003-dynamic-property-access.d.ts.map +1 -1
- package/dist/params.d.ts +60 -0
- package/dist/params.d.ts.map +1 -0
- package/dist/provenance.d.ts +21 -0
- package/dist/provenance.d.ts.map +1 -1
- package/dist/terraform/aws-resources.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/build-params.test.ts +144 -0
- package/src/build-params.ts +207 -0
- package/src/build.test.ts +38 -0
- package/src/build.ts +144 -7
- package/src/cli/commands/build.test.ts +220 -2
- package/src/cli/commands/build.ts +111 -3
- package/src/cli/commands/check-lexicon-examples.test.ts +92 -0
- package/src/cli/commands/check-lexicon-examples.ts +103 -0
- package/src/cli/commands/check-lexicon-intrinsics.test.ts +145 -0
- package/src/cli/commands/check-lexicon-intrinsics.ts +312 -0
- package/src/cli/commands/check-lexicon.test.ts +34 -0
- package/src/cli/commands/check-lexicon.ts +119 -1
- package/src/cli/commands/lint.ts +31 -3
- package/src/cli/commands/onboard.ts +1 -1
- package/src/cli/component-security-boundary.test.ts +170 -0
- package/src/cli/handlers/build.ts +24 -3
- package/src/cli/handlers/components.ts +9 -2
- package/src/cli/handlers/dev.ts +1 -1
- package/src/cli/handlers/graph.ts +7 -5
- package/src/cli/handlers/lifecycle.ts +2 -2
- package/src/cli/handlers/lint.ts +2 -0
- package/src/cli/handlers/misc.ts +2 -2
- package/src/cli/handlers/run-client.ts +1 -1
- package/src/cli/handlers/run.ts +20 -5
- package/src/cli/main.test.ts +22 -0
- package/src/cli/main.ts +39 -0
- package/src/cli/plugins.ts +20 -4
- package/src/cli/registry.ts +10 -0
- package/src/cli/security-boundary.test.ts +135 -0
- package/src/codegen/docs-sections.test.ts +61 -0
- package/src/codegen/docs-sections.ts +7 -3
- package/src/components/cli-support.ts +22 -10
- package/src/components/discover.ts +127 -25
- package/src/components/sandbox/driver.ts +114 -0
- package/src/components/sandbox/run.test.ts +185 -0
- package/src/components/sandbox/run.ts +177 -0
- package/src/composite.test.ts +21 -0
- package/src/composite.ts +20 -1
- package/src/config.ts +81 -0
- package/src/discovery/collect.ts +17 -3
- package/src/discovery/entity-wire-codec.ts +485 -0
- package/src/discovery/entity-wire.test.ts +240 -0
- package/src/discovery/entity-wire.ts +67 -0
- package/src/discovery/fold-import.test.ts +1026 -0
- package/src/discovery/fold-import.ts +1805 -0
- package/src/discovery/index.test.ts +191 -1
- package/src/discovery/index.ts +233 -1
- package/src/discovery/sandbox/bundle.ts +218 -0
- package/src/discovery/sandbox/child-errors.ts +65 -0
- package/src/discovery/sandbox/driver.ts +147 -0
- package/src/discovery/sandbox/run.test.ts +179 -0
- package/src/discovery/sandbox/run.ts +196 -0
- package/src/errors.ts +9 -1
- package/src/fold/fold.test.ts +535 -0
- package/src/fold/fold.ts +648 -0
- package/src/fold/subset-doc-parity.test.ts +183 -0
- package/src/fold/subset.test.ts +241 -0
- package/src/fold/subset.ts +302 -0
- package/src/index.ts +2 -0
- package/src/lexicon-output.ts +7 -2
- package/src/lexicon-schema.test.ts +14 -0
- package/src/lexicon-schema.ts +3 -1
- package/src/lexicon.ts +82 -1
- package/src/lifecycle/git.test.ts +10 -5
- package/src/lifecycle/release-ledger.test.ts +28 -0
- package/src/lifecycle/release-ledger.ts +11 -0
- package/src/lint/component-checks.ts +8 -1
- package/src/lint/rules/evl001-non-literal-expression.ts +26 -110
- package/src/lint/rules/evl003-dynamic-property-access.ts +11 -2
- package/src/params.test.ts +22 -0
- package/src/params.ts +66 -0
- package/src/provenance.ts +22 -0
- package/src/terraform/adopt-state.test.ts +1 -1
- package/src/terraform/aws-resources.test.ts +11 -4
- package/src/terraform/aws-resources.ts +60 -0
package/src/fold/fold.ts
ADDED
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import { intrinsicFolds, type IntrinsicDef } from "../lexicon";
|
|
3
|
+
import {
|
|
4
|
+
SUPPORTED_BINARY_OPERATORS,
|
|
5
|
+
SUPPORTED_UNARY_OPERATORS,
|
|
6
|
+
UNSUPPORTED_OBJECT_MEMBER_MESSAGE,
|
|
7
|
+
UNSUPPORTED_UNARY_MESSAGE,
|
|
8
|
+
callExpressionMessage,
|
|
9
|
+
computedPropertyNameMessage,
|
|
10
|
+
dynamicElementAccessMessage,
|
|
11
|
+
isLiteralElementKey,
|
|
12
|
+
isLiteralPropertyName,
|
|
13
|
+
resourceCtorArgMessage,
|
|
14
|
+
unsupportedBinaryMessage,
|
|
15
|
+
unsupportedExpressionMessage,
|
|
16
|
+
type SubsetRuleId,
|
|
17
|
+
} from "./subset";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* fold — static AST value reducer (chant #1026/#1021/#1024, part of epic #1019)
|
|
21
|
+
*
|
|
22
|
+
* Reduces a single-file TypeScript expression AST to a value with NO
|
|
23
|
+
* module execution. The node-kind/operator/key subset it covers — literals,
|
|
24
|
+
* template interpolation, object/array literals (incl. spread), `const`
|
|
25
|
+
* identifier resolution, property and element access (incl. the
|
|
26
|
+
* cross-resource `{ __attrRef }` case, literal-key-only), unary `!`/`-`,
|
|
27
|
+
* the binary operators `+ - * / === !== > < >= <=`, short-circuit
|
|
28
|
+
* `&& || ??`, conditional expressions, `as`/`satisfies`/`!`/parenthesized
|
|
29
|
+
* unwrapping, a nested `new Type({...})` resource-as-value, and registered
|
|
30
|
+
* lexicon intrinsic tagged templates — is defined ONCE, in {@link "./subset"}
|
|
31
|
+
* ({@link findSubsetViolation}), and shared with EVL001/EVL003
|
|
32
|
+
* ({@link "../lint/rules/evl001-non-literal-expression"}), so the linted
|
|
33
|
+
* subset and the folded subset can never drift apart (#1024).
|
|
34
|
+
*
|
|
35
|
+
* A `CallExpression` has no case — a function call as a value is
|
|
36
|
+
* structurally unrepresentable, not merely linted against. Composite
|
|
37
|
+
* factory calls are out of scope here (epic Phase 5, #1023).
|
|
38
|
+
*
|
|
39
|
+
* Cross-file identifier resolution (chant #1020): `consts` alone is always
|
|
40
|
+
* this file's own top-level bindings — that part stays single-file, and
|
|
41
|
+
* nothing about the supported node-kind subset changes. But an identifier
|
|
42
|
+
* `fold()` can't find in `consts` isn't necessarily a dead end: the optional
|
|
43
|
+
* `externals` map (populated by ../discovery/fold-import.ts, which owns the
|
|
44
|
+
* module graph traversal) lets a caller pre-resolve an *imported* binding to
|
|
45
|
+
* its real value — a plain value for an imported `const`, or the REAL,
|
|
46
|
+
* already-constructed `Declarable`/`CompositeInstance` for a name bound to a
|
|
47
|
+
* resource/composite in the defining module — and `fold()` just returns it
|
|
48
|
+
* for the identifier, unchanged. This is why a bare object/array bracket
|
|
49
|
+
* index a few lines down (`obj[key]`) is enough to make `network.vpc.VpcId`
|
|
50
|
+
* fold correctly once `network` resolves via `externals` to the real,
|
|
51
|
+
* shared composite instance object: indexing a live class instance the same
|
|
52
|
+
* way as a plain object returns its real getter's real `AttrRef`, wired to
|
|
53
|
+
* the real shared parent — see fold-import.ts's module doc for why that
|
|
54
|
+
* shared identity is the entire hard part of #1020.
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The result of folding an AST node: a plain value, a symbolic reference to
|
|
59
|
+
* a sibling resource's attribute, a folded intrinsic tagged template, an
|
|
60
|
+
* unresolved external symbol chain, or a folded resource spec.
|
|
61
|
+
*/
|
|
62
|
+
export type FoldedValue =
|
|
63
|
+
| string
|
|
64
|
+
| number
|
|
65
|
+
| boolean
|
|
66
|
+
| null
|
|
67
|
+
| undefined
|
|
68
|
+
| FoldedValue[]
|
|
69
|
+
| { [key: string]: FoldedValue }
|
|
70
|
+
| AttrRefValue
|
|
71
|
+
| FoldedIntrinsic
|
|
72
|
+
| SymbolicValue
|
|
73
|
+
| FoldedResource;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Symbolic reference produced when a property/element access resolves to an
|
|
77
|
+
* attribute of another `const`-declared resource in the same file, e.g.
|
|
78
|
+
* `bucket.name` (or `bucket["name"]`) where `bucket` is
|
|
79
|
+
* `const bucket = new S3Bucket({...})`.
|
|
80
|
+
*
|
|
81
|
+
* This is the SAME envelope `AttrRef.prototype.toJSON()` produces at
|
|
82
|
+
* runtime ({@link "../attrref"}) — `serializer-walker.ts`'s `walkValue`
|
|
83
|
+
* already recognizes a plain `{ __attrRef }` object as an AttrRef envelope
|
|
84
|
+
* (it doesn't require a live `AttrRef` instance), so this is not an
|
|
85
|
+
* invented shape: it's the existing envelope, produced without running the
|
|
86
|
+
* module that would otherwise construct the real `AttrRef`.
|
|
87
|
+
*/
|
|
88
|
+
export interface AttrRefValue {
|
|
89
|
+
__attrRef: { entity: string; attribute: string };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The result of folding a registered lexicon intrinsic tagged template
|
|
94
|
+
* (e.g. `Sub\`${AWS.StackName}-x\``) to its node form: tag name, cooked
|
|
95
|
+
* template string parts, and folded interpolated values (in order).
|
|
96
|
+
* Mirrors the runtime call shape `Tag(strings, ...values)` so a later
|
|
97
|
+
* build path can replay it into the real intrinsic object (#1022).
|
|
98
|
+
*/
|
|
99
|
+
export interface FoldedIntrinsic {
|
|
100
|
+
__intrinsic: string;
|
|
101
|
+
strings: string[];
|
|
102
|
+
values: FoldedValue[];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* A sub-expression inside a folded intrinsic that fold could not reduce to
|
|
107
|
+
* a value without resolving an identifier from outside this file — e.g. an
|
|
108
|
+
* imported pseudo-parameter namespace access like `AWS.StackName`.
|
|
109
|
+
* Cross-file import resolution is #1020; until then the raw source text is
|
|
110
|
+
* preserved verbatim (never stringified, never rejected) instead of being
|
|
111
|
+
* treated as an unresolved-identifier error. Only appears inside a folded
|
|
112
|
+
* intrinsic's `values` — see {@link foldIntrinsicValue}.
|
|
113
|
+
*/
|
|
114
|
+
export interface SymbolicValue {
|
|
115
|
+
__symbol: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The result of folding a resource constructor: `new Type({ ...props })`.
|
|
120
|
+
*/
|
|
121
|
+
export interface FoldedResource {
|
|
122
|
+
__resource: string;
|
|
123
|
+
props: { [key: string]: FoldedValue };
|
|
124
|
+
/**
|
|
125
|
+
* The constructor's optional second argument — CFN-style resource-level
|
|
126
|
+
* attributes (`DependsOn`, `Condition`, `DeletionPolicy`,
|
|
127
|
+
* `UpdateReplacePolicy`, `CreationPolicy`, `Metadata`, …) some lexicons
|
|
128
|
+
* accept alongside `props` (see `createResource`'s `attributes` param,
|
|
129
|
+
* ../runtime.ts). Present only when the source actually passed one.
|
|
130
|
+
*/
|
|
131
|
+
attributes?: { [key: string]: FoldedValue };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* One entry per exported `const` resource declaration in {@link foldModule}'s
|
|
136
|
+
* result. The `ok: false` case surfaces the same located, rule-id-tagged
|
|
137
|
+
* shape as {@link FoldError} (#1024) — `error` stays the formatted message
|
|
138
|
+
* string for backward-compat display, while `ruleId`/`line`/`column` let a
|
|
139
|
+
* caller cite the exact same rule id + position an EVL diagnostic for the
|
|
140
|
+
* same construct would.
|
|
141
|
+
*/
|
|
142
|
+
export type FoldModuleEntry =
|
|
143
|
+
| { ok: true; spec: FoldedResource }
|
|
144
|
+
| { ok: false; error: string; ruleId: SubsetRuleId; line: number; column: number };
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Error thrown when a node cannot be folded to a value without executing
|
|
148
|
+
* code. Carries the node's source position (1-based, matching `LintError`)
|
|
149
|
+
* so callers can report a located diagnostic, and the id of the EVL rule
|
|
150
|
+
* that flags the same construct (#1024) — "EVL001" (the general
|
|
151
|
+
* not-statically-evaluable umbrella) unless the rejection is specifically a
|
|
152
|
+
* dynamic element-access key, which is "EVL003"'s construct. A rejection
|
|
153
|
+
* with no EVL equivalent (unresolved identifier, unregistered intrinsic tag,
|
|
154
|
+
* spread of a value that turns out not to be an object/array — all
|
|
155
|
+
* environment/value-dependent, see {@link "./subset"}'s module doc) still
|
|
156
|
+
* defaults to "EVL001" since that's the closest umbrella rule, even though
|
|
157
|
+
* EVL can't actually detect it ahead of a real fold.
|
|
158
|
+
*
|
|
159
|
+
* chant #1020 hang fix — every `FoldError` is thrown for a routine, EXPECTED
|
|
160
|
+
* outcome (this node's shape isn't in the fold subset) and is ALWAYS caught
|
|
161
|
+
* a few frames up (`tryFoldFileCore`'s own top-level catch, ultimately),
|
|
162
|
+
* reduced to `.message`; `.stack` is never read anywhere on this path. V8
|
|
163
|
+
* still eagerly walks live JS frames to populate the (lazy) `.stack` getter's
|
|
164
|
+
* backing data at CONSTRUCTION time regardless of whether it's ever read —
|
|
165
|
+
* cheap for a shallow call stack, but expensive once the surrounding
|
|
166
|
+
* functions are hot enough for V8 to aggressively inline them (every corpus
|
|
167
|
+
* entry re-triggers the same call shapes across `foldFileMemoized` ->
|
|
168
|
+
* `buildExternals` -> `tryFoldFileCore` -> `resolveDeclaratorValue` ->
|
|
169
|
+
* `resolveLiveValue` -> `resolveCallExpression`/`fold`, chant #1020's
|
|
170
|
+
* cross-file resolution making that chain several layers deeper than the
|
|
171
|
+
* pre-#1020 single-file fold ever needed): capturing a stack from deep,
|
|
172
|
+
* optimized/inlined frames requires V8 to reconstruct them from deopt
|
|
173
|
+
* metadata, confirmed via `sample` to dominate CPU during the observed
|
|
174
|
+
* multi-minute stall (`Isolate::CaptureAndSetErrorStack` /
|
|
175
|
+
* `OptimizedJSFrame::Summarize` / `DeoptTranslationIterator`). Most files in
|
|
176
|
+
* the corpus (77/98 entries have at least one run-fallback file) throw one
|
|
177
|
+
* of these, so the cost compounds across a build. `Error.stackTraceLimit = 0`
|
|
178
|
+
* for the duration of `super()` makes V8 capture zero frames — free
|
|
179
|
+
* regardless of stack depth/optimization state — then the limit is restored
|
|
180
|
+
* immediately, so it doesn't suppress a real stack trace anywhere else in
|
|
181
|
+
* the process.
|
|
182
|
+
*/
|
|
183
|
+
export class FoldError extends Error {
|
|
184
|
+
readonly line: number;
|
|
185
|
+
readonly column: number;
|
|
186
|
+
readonly ruleId: SubsetRuleId;
|
|
187
|
+
|
|
188
|
+
constructor(message: string, line: number, column: number, ruleId: SubsetRuleId = "EVL001") {
|
|
189
|
+
const prevStackTraceLimit = Error.stackTraceLimit;
|
|
190
|
+
Error.stackTraceLimit = 0;
|
|
191
|
+
super(`${line}:${column} - ${message}`);
|
|
192
|
+
Error.stackTraceLimit = prevStackTraceLimit;
|
|
193
|
+
this.name = "FoldError";
|
|
194
|
+
this.line = line;
|
|
195
|
+
this.column = column;
|
|
196
|
+
this.ruleId = ruleId;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Resolve a node's 1-based line/column via its owning `SourceFile`. Exported
|
|
202
|
+
* (chant #1020) so fold-import.ts can build its own located `FoldError`s
|
|
203
|
+
* (e.g. an import-cycle diagnostic pointing at the specific `import`
|
|
204
|
+
* statement that closes the cycle) using the exact same position math
|
|
205
|
+
* `foldError` uses here, rather than a second hand-rolled implementation.
|
|
206
|
+
*/
|
|
207
|
+
export function locate(node: ts.Node): { line: number; column: number } {
|
|
208
|
+
const sourceFile = node.getSourceFile();
|
|
209
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
210
|
+
return { line: line + 1, column: character + 1 };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function foldError(node: ts.Node, message: string, ruleId: SubsetRuleId = "EVL001"): FoldError {
|
|
214
|
+
const { line, column } = locate(node);
|
|
215
|
+
return new FoldError(message, line, column, ruleId);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Collect every top-level `const x = <initializer>` in a source file into a
|
|
220
|
+
* name -> initializer map. Single-file only (cross-file is #1020).
|
|
221
|
+
*/
|
|
222
|
+
export function collectConsts(sourceFile: ts.SourceFile): Map<string, ts.Expression> {
|
|
223
|
+
const consts = new Map<string, ts.Expression>();
|
|
224
|
+
for (const statement of sourceFile.statements) {
|
|
225
|
+
if (!ts.isVariableStatement(statement)) continue;
|
|
226
|
+
if ((statement.declarationList.flags & ts.NodeFlags.Const) === 0) continue;
|
|
227
|
+
for (const decl of statement.declarationList.declarations) {
|
|
228
|
+
if (ts.isIdentifier(decl.name) && decl.initializer) {
|
|
229
|
+
consts.set(decl.name.text, decl.initializer);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return consts;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** A property/element key foldable without execution: identifier, string, or numeric literal. */
|
|
237
|
+
function propName(node: ts.PropertyName): string {
|
|
238
|
+
if (isLiteralPropertyName(node)) return node.text;
|
|
239
|
+
throw foldError(node, computedPropertyNameMessage(node));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* An element-access key foldable without execution: a string or numeric
|
|
244
|
+
* LITERAL only (EVL003 semantics — a variable or expression key is a
|
|
245
|
+
* dynamic key and is rejected).
|
|
246
|
+
*/
|
|
247
|
+
function elementKey(node: ts.Expression): string {
|
|
248
|
+
if (isLiteralElementKey(node)) return node.text;
|
|
249
|
+
throw foldError(node, dynamicElementAccessMessage(node), "EVL003");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** True when `ident` is a `const` bound to a `new Type(...)` resource constructor. */
|
|
253
|
+
function resolvesToResource(consts: Map<string, ts.Expression>, ident: ts.Identifier): boolean {
|
|
254
|
+
const init = consts.get(ident.text);
|
|
255
|
+
return init !== undefined && ts.isNewExpression(init);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* True when `node` is an identifier, or a dotted/bracketed access chain
|
|
260
|
+
* rooted at an identifier, that isn't bound in `consts` — e.g.
|
|
261
|
+
* `AWS.StackName` from an imported pseudo-parameter namespace, or a bare
|
|
262
|
+
* imported identifier. Resolving what it actually refers to requires
|
|
263
|
+
* following an import (#1020), which is out of scope here.
|
|
264
|
+
*/
|
|
265
|
+
function isUnresolvedSymbolChain(node: ts.Expression, consts: Map<string, ts.Expression>): boolean {
|
|
266
|
+
if (ts.isIdentifier(node)) return node.text !== "undefined" && !consts.has(node.text);
|
|
267
|
+
if (ts.isPropertyAccessExpression(node)) return isUnresolvedSymbolChain(node.expression, consts);
|
|
268
|
+
if (ts.isElementAccessExpression(node)) return isUnresolvedSymbolChain(node.expression, consts);
|
|
269
|
+
if (ts.isNonNullExpression(node)) return isUnresolvedSymbolChain(node.expression, consts);
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Fold one interpolated sub-expression of a registered intrinsic tagged
|
|
275
|
+
* template. Identical to {@link fold}, except an unresolved external
|
|
276
|
+
* symbol chain (a pseudo-parameter-style access this file can't see the
|
|
277
|
+
* import for) folds to a {@link SymbolicValue} instead of throwing — the
|
|
278
|
+
* run path resolves it once the module actually imports and runs; fold
|
|
279
|
+
* preserves it symbolically rather than stringifying or rejecting it.
|
|
280
|
+
*/
|
|
281
|
+
function foldIntrinsicValue(
|
|
282
|
+
node: ts.Expression,
|
|
283
|
+
consts: Map<string, ts.Expression>,
|
|
284
|
+
intrinsics: readonly IntrinsicDef[],
|
|
285
|
+
externals?: ReadonlyMap<string, unknown>,
|
|
286
|
+
): FoldedValue {
|
|
287
|
+
// Deliberately checks `consts` only, not `externals` — an imported
|
|
288
|
+
// identifier here still folds to a symbolic `{__symbol}` node exactly as
|
|
289
|
+
// before #1020, revived later by fold-import.ts's own
|
|
290
|
+
// `resolveSymbolicValue` (a real, trusted import of the defining module —
|
|
291
|
+
// fine for a plain pseudo-parameter-style namespace, the only shape this
|
|
292
|
+
// path exists for). Wiring `externals` in here too would mean two
|
|
293
|
+
// different mechanisms resolving the same cross-file name inside an
|
|
294
|
+
// intrinsic interpolation; out of scope for #1020, which only needs
|
|
295
|
+
// identifiers/property access OUTSIDE tagged templates to resolve.
|
|
296
|
+
if (isUnresolvedSymbolChain(node, consts)) {
|
|
297
|
+
return { __symbol: node.getText() };
|
|
298
|
+
}
|
|
299
|
+
return fold(node, consts, intrinsics, externals);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Fold a `TaggedTemplateExpression` whose tag is a registered, foldable
|
|
304
|
+
* lexicon intrinsic ({@link intrinsicFolds}, `../lexicon.ts`) to its node
|
|
305
|
+
* form. An unregistered — or registered-but-not-foldable — tag throws a
|
|
306
|
+
* located {@link FoldError}.
|
|
307
|
+
*/
|
|
308
|
+
function foldTaggedTemplate(
|
|
309
|
+
node: ts.TaggedTemplateExpression,
|
|
310
|
+
consts: Map<string, ts.Expression>,
|
|
311
|
+
intrinsics: readonly IntrinsicDef[],
|
|
312
|
+
externals?: ReadonlyMap<string, unknown>,
|
|
313
|
+
): FoldedIntrinsic {
|
|
314
|
+
const tagName = node.tag.getText();
|
|
315
|
+
const isRegistered = intrinsics.some((i) => i.name === tagName && intrinsicFolds(i));
|
|
316
|
+
if (!isRegistered) {
|
|
317
|
+
throw foldError(node, `unregistered tagged template intrinsic: ${tagName}\`...\``);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const template = node.template;
|
|
321
|
+
if (ts.isNoSubstitutionTemplateLiteral(template)) {
|
|
322
|
+
return { __intrinsic: tagName, strings: [template.text], values: [] };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const strings = [template.head.text, ...template.templateSpans.map((span) => span.literal.text)];
|
|
326
|
+
const values = template.templateSpans.map((span) =>
|
|
327
|
+
foldIntrinsicValue(span.expression, consts, intrinsics, externals),
|
|
328
|
+
);
|
|
329
|
+
return { __intrinsic: tagName, strings, values };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Fold a single expression node to a value. Throws {@link FoldError} for
|
|
334
|
+
* anything outside the supported subset — including any `CallExpression`
|
|
335
|
+
* that isn't a registered intrinsic tagged template.
|
|
336
|
+
*
|
|
337
|
+
* @param intrinsics - Lexicon-registered intrinsics that fold
|
|
338
|
+
* ({@link intrinsicFolds}, e.g. `Sub`). A tagged template whose tag isn't
|
|
339
|
+
* in this list, or is registered but not foldable, is rejected. Defaults
|
|
340
|
+
* to none — pass the target lexicon's manifest `intrinsics` to recognize
|
|
341
|
+
* its tags.
|
|
342
|
+
* @param externals - chant #1020: pre-resolved imported bindings, consulted
|
|
343
|
+
* only when an identifier isn't in `consts`. See the module doc above.
|
|
344
|
+
* `undefined` (the default) preserves the exact pre-#1020 single-file
|
|
345
|
+
* behavior — every identifier not in `consts` is unresolved.
|
|
346
|
+
*/
|
|
347
|
+
export function fold(
|
|
348
|
+
node: ts.Expression,
|
|
349
|
+
consts: Map<string, ts.Expression>,
|
|
350
|
+
intrinsics: readonly IntrinsicDef[] = [],
|
|
351
|
+
externals?: ReadonlyMap<string, unknown>,
|
|
352
|
+
): FoldedValue {
|
|
353
|
+
if (
|
|
354
|
+
ts.isParenthesizedExpression(node) ||
|
|
355
|
+
ts.isAsExpression(node) ||
|
|
356
|
+
ts.isSatisfiesExpression(node) ||
|
|
357
|
+
ts.isNonNullExpression(node)
|
|
358
|
+
) {
|
|
359
|
+
return fold(node.expression, consts, intrinsics, externals);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
363
|
+
return node.text;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (ts.isNumericLiteral(node)) {
|
|
367
|
+
return Number(node.text);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword) return true;
|
|
371
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword) return false;
|
|
372
|
+
if (node.kind === ts.SyntaxKind.NullKeyword) return null;
|
|
373
|
+
|
|
374
|
+
if (ts.isIdentifier(node) && node.text === "undefined") return undefined;
|
|
375
|
+
|
|
376
|
+
if (ts.isTaggedTemplateExpression(node)) {
|
|
377
|
+
return foldTaggedTemplate(node, consts, intrinsics, externals);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (ts.isTemplateExpression(node)) {
|
|
381
|
+
let out = node.head.text;
|
|
382
|
+
for (const span of node.templateSpans) {
|
|
383
|
+
out += String(fold(span.expression, consts, intrinsics, externals)) + span.literal.text;
|
|
384
|
+
}
|
|
385
|
+
return out;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
389
|
+
const obj: { [key: string]: FoldedValue } = {};
|
|
390
|
+
for (const prop of node.properties) {
|
|
391
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
392
|
+
obj[propName(prop.name)] = fold(prop.initializer, consts, intrinsics, externals);
|
|
393
|
+
} else if (ts.isShorthandPropertyAssignment(prop)) {
|
|
394
|
+
obj[prop.name.text] = fold(prop.name, consts, intrinsics, externals);
|
|
395
|
+
} else if (ts.isSpreadAssignment(prop)) {
|
|
396
|
+
const src = fold(prop.expression, consts, intrinsics, externals);
|
|
397
|
+
if (src === null || typeof src !== "object") {
|
|
398
|
+
throw foldError(prop, "spread source not an object");
|
|
399
|
+
}
|
|
400
|
+
Object.assign(obj, src);
|
|
401
|
+
} else {
|
|
402
|
+
throw foldError(prop, UNSUPPORTED_OBJECT_MEMBER_MESSAGE);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return obj;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
409
|
+
const arr: FoldedValue[] = [];
|
|
410
|
+
for (const el of node.elements) {
|
|
411
|
+
if (ts.isSpreadElement(el)) {
|
|
412
|
+
const src = fold(el.expression, consts, intrinsics, externals);
|
|
413
|
+
if (!Array.isArray(src)) {
|
|
414
|
+
throw foldError(el, "spread source not an array");
|
|
415
|
+
}
|
|
416
|
+
arr.push(...src);
|
|
417
|
+
} else {
|
|
418
|
+
arr.push(fold(el, consts, intrinsics, externals));
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return arr;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (ts.isIdentifier(node)) {
|
|
425
|
+
if (!consts.has(node.text)) {
|
|
426
|
+
// chant #1020 — an imported binding fold-import.ts already resolved
|
|
427
|
+
// (to a plain value, or the real live Declarable/CompositeInstance a
|
|
428
|
+
// sibling file's own fold produced) is returned as-is: for a plain
|
|
429
|
+
// value this is exactly like resolving a local const; for a real live
|
|
430
|
+
// object, the property-access branches below just index it like any
|
|
431
|
+
// other object, which is what makes a real, correctly-identified
|
|
432
|
+
// `AttrRef` fall out of `network.vpc.VpcId` with zero special-casing
|
|
433
|
+
// here (see fold-import.ts's module doc).
|
|
434
|
+
if (externals?.has(node.text)) {
|
|
435
|
+
return externals.get(node.text) as FoldedValue;
|
|
436
|
+
}
|
|
437
|
+
// chant #1064 — a bare `process` reference is ALWAYS an ambient
|
|
438
|
+
// environment read (`process.env.X`, `process.argv`, …), never
|
|
439
|
+
// something a future resolution pass could fold: it's Node's global,
|
|
440
|
+
// not a local const or an importable module export. Point at the
|
|
441
|
+
// supported alternative instead of leaving the author to infer the fix
|
|
442
|
+
// from the generic "unresolved identifier" message — see
|
|
443
|
+
// ../build-params.ts/../params.ts.
|
|
444
|
+
if (node.text === "process") {
|
|
445
|
+
throw foldError(
|
|
446
|
+
node,
|
|
447
|
+
`ambient "process" read is not foldable — declare a build-time parameter instead ` +
|
|
448
|
+
`(chant.config.ts's buildParams + \`chant build --param name=value\`/\`--params-file\`) and reference ` +
|
|
449
|
+
`it via \`import { params } from "@intentius/chant/params"\`, rather than reading process.env directly`,
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
throw foldError(node, `unresolved identifier: ${node.text}`);
|
|
453
|
+
}
|
|
454
|
+
return fold(consts.get(node.text) as ts.Expression, consts, intrinsics, externals);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
458
|
+
if (ts.isIdentifier(node.expression) && resolvesToResource(consts, node.expression)) {
|
|
459
|
+
return { __attrRef: { entity: node.expression.text, attribute: node.name.text } };
|
|
460
|
+
}
|
|
461
|
+
const obj = fold(node.expression, consts, intrinsics, externals);
|
|
462
|
+
if (obj === null || obj === undefined) return undefined;
|
|
463
|
+
return (obj as { [key: string]: FoldedValue })[node.name.text];
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (ts.isElementAccessExpression(node)) {
|
|
467
|
+
const key = elementKey(node.argumentExpression);
|
|
468
|
+
if (ts.isIdentifier(node.expression) && resolvesToResource(consts, node.expression)) {
|
|
469
|
+
return { __attrRef: { entity: node.expression.text, attribute: key } };
|
|
470
|
+
}
|
|
471
|
+
const obj = fold(node.expression, consts, intrinsics, externals);
|
|
472
|
+
if (obj === null || obj === undefined) return undefined;
|
|
473
|
+
return (obj as { [key: string]: FoldedValue })[key];
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (ts.isPrefixUnaryExpression(node)) {
|
|
477
|
+
if (!SUPPORTED_UNARY_OPERATORS.has(node.operator)) {
|
|
478
|
+
throw foldError(node, UNSUPPORTED_UNARY_MESSAGE);
|
|
479
|
+
}
|
|
480
|
+
const value = fold(node.operand, consts, intrinsics, externals);
|
|
481
|
+
if (node.operator === ts.SyntaxKind.ExclamationToken) return !value;
|
|
482
|
+
return -(value as unknown as number); // ts.SyntaxKind.MinusToken — the only other supported operator
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (ts.isBinaryExpression(node)) {
|
|
486
|
+
const opKind = node.operatorToken.kind;
|
|
487
|
+
const S = ts.SyntaxKind;
|
|
488
|
+
|
|
489
|
+
if (opKind === S.AmpersandAmpersandToken) {
|
|
490
|
+
const left = fold(node.left, consts, intrinsics, externals);
|
|
491
|
+
return left ? fold(node.right, consts, intrinsics, externals) : left;
|
|
492
|
+
}
|
|
493
|
+
if (opKind === S.BarBarToken) {
|
|
494
|
+
const left = fold(node.left, consts, intrinsics, externals);
|
|
495
|
+
return left ? left : fold(node.right, consts, intrinsics, externals);
|
|
496
|
+
}
|
|
497
|
+
if (opKind === S.QuestionQuestionToken) {
|
|
498
|
+
const left = fold(node.left, consts, intrinsics, externals);
|
|
499
|
+
return left === null || left === undefined ? fold(node.right, consts, intrinsics, externals) : left;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (!SUPPORTED_BINARY_OPERATORS.has(opKind)) {
|
|
503
|
+
throw foldError(node, unsupportedBinaryMessage(opKind));
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const left = fold(node.left, consts, intrinsics, externals);
|
|
507
|
+
const right = fold(node.right, consts, intrinsics, externals);
|
|
508
|
+
switch (opKind) {
|
|
509
|
+
case S.PlusToken:
|
|
510
|
+
return (left as unknown as string) + (right as unknown as string);
|
|
511
|
+
case S.MinusToken:
|
|
512
|
+
return (left as unknown as number) - (right as unknown as number);
|
|
513
|
+
case S.AsteriskToken:
|
|
514
|
+
return (left as unknown as number) * (right as unknown as number);
|
|
515
|
+
case S.SlashToken:
|
|
516
|
+
return (left as unknown as number) / (right as unknown as number);
|
|
517
|
+
case S.EqualsEqualsEqualsToken:
|
|
518
|
+
return left === right;
|
|
519
|
+
case S.ExclamationEqualsEqualsToken:
|
|
520
|
+
return left !== right;
|
|
521
|
+
case S.GreaterThanToken:
|
|
522
|
+
return (left as unknown as string) > (right as unknown as string);
|
|
523
|
+
case S.LessThanToken:
|
|
524
|
+
return (left as unknown as string) < (right as unknown as string);
|
|
525
|
+
case S.GreaterThanEqualsToken:
|
|
526
|
+
return (left as unknown as string) >= (right as unknown as string);
|
|
527
|
+
case S.LessThanEqualsToken:
|
|
528
|
+
return (left as unknown as string) <= (right as unknown as string);
|
|
529
|
+
default:
|
|
530
|
+
// Unreachable given the SUPPORTED_BINARY_OPERATORS guard above —
|
|
531
|
+
// kept as a defensive fallback in case that set and this switch
|
|
532
|
+
// ever fall out of sync.
|
|
533
|
+
throw foldError(node, unsupportedBinaryMessage(opKind));
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (ts.isConditionalExpression(node)) {
|
|
538
|
+
return fold(node.condition, consts, intrinsics, externals)
|
|
539
|
+
? fold(node.whenTrue, consts, intrinsics, externals)
|
|
540
|
+
: fold(node.whenFalse, consts, intrinsics, externals);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (ts.isNewExpression(node)) {
|
|
544
|
+
// A nested `new Type({...})` used as a property VALUE is not leaf-foldable.
|
|
545
|
+
// fold can only produce the {__resource, props} envelope, and — unlike a
|
|
546
|
+
// TOP-LEVEL resource, which fold-import constructs into a real Declarable —
|
|
547
|
+
// a nested one is never constructed, so the envelope leaks into serialization
|
|
548
|
+
// as the wrong value (real fold-vs-run drift; the #1025 differential caught
|
|
549
|
+
// this on gitlab/multi-stage-deploy, where `new Image({...})` as a job's
|
|
550
|
+
// `image:` must serialize as `{ name }`, not `{ __resource, props }`).
|
|
551
|
+
// Reject so the file falls back to run, which constructs and serializes it
|
|
552
|
+
// correctly. EVL permits this statically — it's a documented fold/EVL
|
|
553
|
+
// divergence, like identifier resolution and spread runtime type.
|
|
554
|
+
throw foldError(node, `nested \`new ${node.expression.getText()}(...)\` as a value is not foldable — falls back to run`);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (ts.isCallExpression(node)) {
|
|
558
|
+
throw foldError(node, callExpressionMessage(node));
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
throw foldError(node, unsupportedExpressionMessage(node));
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Fold a resource constructor call — `new Type({ ...props }, { ...attributes
|
|
566
|
+
* })` — to its spec. Each argument present must be an object literal
|
|
567
|
+
* (anything else is not statically evaluable and throws a located
|
|
568
|
+
* {@link FoldError}). The second argument (CFN-style resource attributes —
|
|
569
|
+
* `DependsOn`, `Condition`, `DeletionPolicy`, …) is optional, matching
|
|
570
|
+
* `createResource`'s runtime constructor signature (../runtime.ts).
|
|
571
|
+
*/
|
|
572
|
+
export function foldResource(
|
|
573
|
+
node: ts.NewExpression,
|
|
574
|
+
consts: Map<string, ts.Expression>,
|
|
575
|
+
intrinsics: readonly IntrinsicDef[] = [],
|
|
576
|
+
externals?: ReadonlyMap<string, unknown>,
|
|
577
|
+
): FoldedResource {
|
|
578
|
+
const typeName = node.expression.getText();
|
|
579
|
+
const [firstArg, secondArg] = node.arguments ?? [];
|
|
580
|
+
|
|
581
|
+
if (!firstArg) {
|
|
582
|
+
return { __resource: typeName, props: {} };
|
|
583
|
+
}
|
|
584
|
+
if (!ts.isObjectLiteralExpression(firstArg)) {
|
|
585
|
+
throw foldError(firstArg, resourceCtorArgMessage(typeName));
|
|
586
|
+
}
|
|
587
|
+
const props = fold(firstArg, consts, intrinsics, externals) as { [key: string]: FoldedValue };
|
|
588
|
+
|
|
589
|
+
if (!secondArg) {
|
|
590
|
+
return { __resource: typeName, props };
|
|
591
|
+
}
|
|
592
|
+
if (!ts.isObjectLiteralExpression(secondArg)) {
|
|
593
|
+
throw foldError(secondArg, `resource attributes argument must be an object literal: ${typeName}(...)`);
|
|
594
|
+
}
|
|
595
|
+
const attributes = fold(secondArg, consts, intrinsics, externals) as { [key: string]: FoldedValue };
|
|
596
|
+
return { __resource: typeName, props, attributes };
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function hasExportModifier(statement: ts.VariableStatement): boolean {
|
|
600
|
+
return statement.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Fold every exported `const X = new Type({...})` resource declaration in a
|
|
605
|
+
* source file to its spec, with no module execution.
|
|
606
|
+
*
|
|
607
|
+
* Non-resource `const` exports (anything whose initializer isn't a `new`
|
|
608
|
+
* expression) are left out of the result rather than folded or errored —
|
|
609
|
+
* `new`-less resource forms (composite factory calls) are epic Phase 5
|
|
610
|
+
* (#1023) and are not attempted here.
|
|
611
|
+
*
|
|
612
|
+
* @param intrinsics - Lexicon-registered intrinsic tags, forwarded to
|
|
613
|
+
* {@link fold} for every resource. See {@link fold}'s `intrinsics` param.
|
|
614
|
+
*/
|
|
615
|
+
export function foldModule(
|
|
616
|
+
source: string,
|
|
617
|
+
fileName = "module.ts",
|
|
618
|
+
intrinsics: readonly IntrinsicDef[] = [],
|
|
619
|
+
): Record<string, FoldModuleEntry> {
|
|
620
|
+
const sourceFile = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, /* setParentNodes */ true);
|
|
621
|
+
const consts = collectConsts(sourceFile);
|
|
622
|
+
const result: Record<string, FoldModuleEntry> = {};
|
|
623
|
+
|
|
624
|
+
for (const statement of sourceFile.statements) {
|
|
625
|
+
if (!ts.isVariableStatement(statement)) continue;
|
|
626
|
+
if (!hasExportModifier(statement)) continue;
|
|
627
|
+
if ((statement.declarationList.flags & ts.NodeFlags.Const) === 0) continue;
|
|
628
|
+
|
|
629
|
+
for (const decl of statement.declarationList.declarations) {
|
|
630
|
+
if (!ts.isIdentifier(decl.name) || !decl.initializer) continue;
|
|
631
|
+
if (!ts.isNewExpression(decl.initializer)) continue;
|
|
632
|
+
|
|
633
|
+
const name = decl.name.text;
|
|
634
|
+
try {
|
|
635
|
+
const spec = foldResource(decl.initializer, consts, intrinsics);
|
|
636
|
+
result[name] = { ok: true, spec };
|
|
637
|
+
} catch (err) {
|
|
638
|
+
if (err instanceof FoldError) {
|
|
639
|
+
result[name] = { ok: false, error: err.message, ruleId: err.ruleId, line: err.line, column: err.column };
|
|
640
|
+
} else {
|
|
641
|
+
throw err;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
return result;
|
|
648
|
+
}
|