@intentius/chant 0.49.0 → 0.50.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/audit/catalog.d.ts +13 -3
- package/dist/audit/catalog.d.ts.map +1 -1
- package/dist/audit/core.d.ts +9 -0
- package/dist/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts +6 -0
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/fetch.d.ts.map +1 -1
- package/dist/audit/report-html.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +6 -0
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/report.d.ts.map +1 -1
- package/dist/audit/rules-doc.d.ts.map +1 -1
- package/dist/audit/secrets.d.ts +95 -0
- package/dist/audit/secrets.d.ts.map +1 -0
- package/dist/audit/wrangler.d.ts +33 -0
- package/dist/audit/wrangler.d.ts.map +1 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +7 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts +23 -0
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts +31 -0
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts +11 -0
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +32 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -0
- package/dist/cli/handlers/scenario.d.ts +39 -0
- package/dist/cli/handlers/scenario.d.ts.map +1 -0
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/server.d.ts +35 -2
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/types.d.ts +29 -1
- package/dist/cli/mcp/types.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +14 -2
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/docs-rule-scanning.d.ts.map +1 -1
- package/dist/components/capability.d.ts +17 -2
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +7 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/component.d.ts +15 -0
- package/dist/components/component.d.ts.map +1 -1
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/verbs/index.d.ts +6 -1
- package/dist/components/verbs/index.d.ts.map +1 -1
- package/dist/components/verbs/run-agent.d.ts +499 -0
- package/dist/components/verbs/run-agent.d.ts.map +1 -0
- package/dist/components/verbs/sign.d.ts +30 -0
- package/dist/components/verbs/sign.d.ts.map +1 -1
- package/dist/composite.d.ts +6 -1
- package/dist/composite.d.ts.map +1 -1
- package/dist/discovery/collect.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +15 -1
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/fold-rank.d.ts +66 -0
- package/dist/discovery/fold-rank.d.ts.map +1 -0
- package/dist/discovery/index.d.ts +15 -0
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/param-deps.d.ts +17 -0
- package/dist/discovery/param-deps.d.ts.map +1 -0
- package/dist/fold/fold.d.ts +55 -2
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/fold/subset.d.ts +21 -14
- package/dist/fold/subset.d.ts.map +1 -1
- package/dist/lexicon-schema.d.ts +2 -0
- package/dist/lexicon-schema.d.ts.map +1 -1
- package/dist/lexicon.d.ts +93 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/converge-ledger.d.ts +90 -0
- package/dist/lifecycle/converge-ledger.d.ts.map +1 -0
- package/dist/lifecycle/deep-diff.d.ts +18 -0
- package/dist/lifecycle/deep-diff.d.ts.map +1 -1
- package/dist/lifecycle/deep-observe.d.ts +9 -1
- package/dist/lifecycle/deep-observe.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +33 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -0
- package/dist/lifecycle/git.d.ts +145 -21
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/index.d.ts +4 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/lease.d.ts +113 -0
- package/dist/lifecycle/lease.d.ts.map +1 -0
- package/dist/lifecycle/scenario-eval.d.ts +42 -0
- package/dist/lifecycle/scenario-eval.d.ts.map +1 -0
- package/dist/lifecycle/scenario.d.ts +163 -0
- package/dist/lifecycle/scenario.d.ts.map +1 -0
- package/dist/lifecycle/symptoms.d.ts +63 -0
- package/dist/lifecycle/symptoms.d.ts.map +1 -0
- package/dist/lint/output-docs.d.ts +94 -0
- package/dist/lint/output-docs.d.ts.map +1 -0
- package/dist/lint/post-synth.d.ts +29 -0
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts +11 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts.map +1 -0
- package/dist/lsp/lexicon-providers.d.ts +7 -0
- package/dist/lsp/lexicon-providers.d.ts.map +1 -1
- package/dist/op/activity-contract.d.ts +139 -0
- package/dist/op/activity-contract.d.ts.map +1 -0
- package/dist/op/builders.d.ts +42 -2
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/converge-rule.d.ts +161 -0
- package/dist/op/converge-rule.d.ts.map +1 -0
- package/dist/op/generate-pipeline.d.ts +39 -0
- package/dist/op/generate-pipeline.d.ts.map +1 -0
- package/dist/op/index.d.ts +14 -0
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/op-verb-class.d.ts +42 -0
- package/dist/op/op-verb-class.d.ts.map +1 -0
- package/dist/op/operator.d.ts +128 -0
- package/dist/op/operator.d.ts.map +1 -0
- package/dist/op/step-output-ref.d.ts +187 -0
- package/dist/op/step-output-ref.d.ts.map +1 -0
- package/dist/op/types.d.ts +18 -1
- package/dist/op/types.d.ts.map +1 -1
- package/dist/provenance.d.ts +73 -3
- package/dist/provenance.d.ts.map +1 -1
- package/dist/runtime-adapter.d.ts +7 -1
- package/dist/runtime-adapter.d.ts.map +1 -1
- package/dist/serializer.d.ts +18 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/toml.d.ts +40 -5
- package/dist/toml.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/audit/catalog.test.ts +1 -1
- package/src/audit/catalog.ts +75 -3
- package/src/audit/core.ts +9 -0
- package/src/audit/discover.ts +29 -2
- package/src/audit/fetch.test.ts +216 -3
- package/src/audit/fetch.ts +270 -59
- package/src/audit/report-html.ts +5 -2
- package/src/audit/report-model.ts +9 -0
- package/src/audit/report.test.ts +22 -0
- package/src/audit/report.ts +3 -2
- package/src/audit/rules-doc.ts +2 -0
- package/src/audit/secrets.test.ts +303 -0
- package/src/audit/secrets.ts +406 -0
- package/src/audit/wrangler.test.ts +230 -0
- package/src/audit/wrangler.ts +290 -0
- package/src/build.ts +8 -3
- package/src/cli/command-group.ts +1 -1
- package/src/cli/commands/__fixtures__/schemas/sarif-2.1.0.schema.json +2882 -0
- package/src/cli/commands/audit.test.ts +215 -1
- package/src/cli/commands/audit.ts +86 -17
- package/src/cli/commands/build.test.ts +167 -2
- package/src/cli/commands/build.ts +114 -23
- package/src/cli/handlers/build.ts +2 -0
- package/src/cli/handlers/components.test.ts +199 -1
- package/src/cli/handlers/components.ts +160 -3
- package/src/cli/handlers/graph.test.ts +20 -0
- package/src/cli/handlers/graph.ts +10 -1
- package/src/cli/handlers/lifecycle.ts +12 -4
- package/src/cli/handlers/operator.test.ts +255 -0
- package/src/cli/handlers/operator.ts +240 -0
- package/src/cli/handlers/scenario.test.ts +456 -0
- package/src/cli/handlers/scenario.ts +330 -0
- package/src/cli/main.test.ts +23 -0
- package/src/cli/main.ts +72 -1
- package/src/cli/mcp/server.test.ts +265 -2
- package/src/cli/mcp/server.ts +84 -7
- package/src/cli/mcp/types.ts +27 -1
- package/src/cli/registry.ts +14 -2
- package/src/codegen/docs-rule-scanning.test.ts +42 -0
- package/src/codegen/docs-rule-scanning.ts +25 -2
- package/src/components/README.md +7 -0
- package/src/components/capability.ts +17 -2
- package/src/components/cli-support.test.ts +17 -0
- package/src/components/cli-support.ts +13 -1
- package/src/components/component-schema.test.ts +32 -0
- package/src/components/component.schema.json +6 -0
- package/src/components/component.test.ts +21 -0
- package/src/components/component.ts +15 -0
- package/src/components/driver.ts +12 -4
- package/src/components/verbs/index.ts +6 -1
- package/src/components/verbs/run-agent.test.ts +683 -0
- package/src/components/verbs/run-agent.ts +786 -0
- package/src/components/verbs/sign.test.ts +19 -0
- package/src/components/verbs/sign.ts +34 -2
- package/src/composite.ts +31 -2
- package/src/discovery/collect.ts +11 -2
- package/src/discovery/fold-import.test.ts +54 -0
- package/src/discovery/fold-import.ts +178 -38
- package/src/discovery/fold-rank.test.ts +197 -0
- package/src/discovery/fold-rank.ts +346 -0
- package/src/discovery/index.ts +16 -1
- package/src/discovery/param-deps.test.ts +118 -0
- package/src/discovery/param-deps.ts +170 -0
- package/src/fold/fold.test.ts +6 -2
- package/src/fold/fold.ts +184 -3
- package/src/fold/subset.test.ts +82 -19
- package/src/fold/subset.ts +79 -41
- package/src/lexicon-schema.ts +3 -0
- package/src/lexicon.ts +103 -2
- package/src/lifecycle/converge-ledger.test.ts +199 -0
- package/src/lifecycle/converge-ledger.ts +179 -0
- package/src/lifecycle/deep-diff.test.ts +79 -1
- package/src/lifecycle/deep-diff.ts +23 -0
- package/src/lifecycle/deep-observe.ts +13 -2
- package/src/lifecycle/gate-ledger.test.ts +103 -0
- package/src/lifecycle/gate-ledger.ts +140 -0
- package/src/lifecycle/git.test.ts +430 -0
- package/src/lifecycle/git.ts +446 -84
- package/src/lifecycle/index.ts +4 -0
- package/src/lifecycle/lease.test.ts +343 -0
- package/src/lifecycle/lease.ts +270 -0
- package/src/lifecycle/scenario-eval.test.ts +199 -0
- package/src/lifecycle/scenario-eval.ts +158 -0
- package/src/lifecycle/scenario.test.ts +195 -0
- package/src/lifecycle/scenario.ts +321 -0
- package/src/lifecycle/symptoms.test.ts +116 -0
- package/src/lifecycle/symptoms.ts +126 -0
- package/src/lint/output-docs.test.ts +220 -0
- package/src/lint/output-docs.ts +204 -0
- package/src/lint/post-synth.test.ts +97 -0
- package/src/lint/post-synth.ts +45 -0
- package/src/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.ts +26 -0
- package/src/lint/rules/comp/comp.test.ts +49 -1
- package/src/lint/rules/evl001-non-literal-expression.test.ts +8 -3
- package/src/lint/rules/evl001-non-literal-expression.ts +6 -6
- package/src/lsp/lexicon-providers.test.ts +44 -0
- package/src/lsp/lexicon-providers.ts +11 -1
- package/src/op/activity-contract.test.ts +180 -0
- package/src/op/activity-contract.ts +278 -0
- package/src/op/builders-exports.test.ts +17 -1
- package/src/op/builders.ts +59 -5
- package/src/op/converge-rule.test.ts +179 -0
- package/src/op/converge-rule.ts +311 -0
- package/src/op/generate-pipeline.test.ts +53 -0
- package/src/op/generate-pipeline.ts +99 -0
- package/src/op/index.ts +30 -0
- package/src/op/local-executor.test.ts +92 -0
- package/src/op/local-executor.ts +45 -9
- package/src/op/op-verb-class.test.ts +126 -0
- package/src/op/op-verb-class.ts +115 -0
- package/src/op/operator.test.ts +346 -0
- package/src/op/operator.ts +213 -0
- package/src/op/step-output-ref.test.ts +334 -0
- package/src/op/step-output-ref.ts +453 -0
- package/src/op/types.ts +18 -1
- package/src/provenance.test.ts +151 -4
- package/src/provenance.ts +118 -4
- package/src/runtime-adapter.ts +31 -10
- package/src/serializer.ts +18 -0
- package/src/toml.test.ts +157 -384
- package/src/toml.ts +371 -5
|
@@ -122,6 +122,25 @@ describe("buildProvenanceStatement — SLSA in-toto statement from #614 provenan
|
|
|
122
122
|
}),
|
|
123
123
|
).toThrow(SignTargetNotDigestError);
|
|
124
124
|
});
|
|
125
|
+
|
|
126
|
+
it("reserved parameter keys win over caller-supplied collisions (#1943)", () => {
|
|
127
|
+
const statement = buildProvenanceStatement({
|
|
128
|
+
imageRef: DIGEST_REF,
|
|
129
|
+
provenance: { sourceRef: "abc123def", artifactDigest: DIGEST_REF.split("@")[1]! },
|
|
130
|
+
builderId: "builder",
|
|
131
|
+
externalParameters: { sourceRef: "spoofed", agent: "reviewer" },
|
|
132
|
+
internalParameters: { artifactDigest: "spoofed", spriteId: "s-1" },
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
expect(statement.predicate.buildDefinition.externalParameters).toEqual({
|
|
136
|
+
sourceRef: "abc123def",
|
|
137
|
+
agent: "reviewer",
|
|
138
|
+
});
|
|
139
|
+
expect(statement.predicate.buildDefinition.internalParameters).toEqual({
|
|
140
|
+
artifactDigest: DIGEST_REF.split("@")[1]!,
|
|
141
|
+
spriteId: "s-1",
|
|
142
|
+
});
|
|
143
|
+
});
|
|
125
144
|
});
|
|
126
145
|
|
|
127
146
|
describe("attest-provenance — keyless cosign attest (#622)", () => {
|
|
@@ -40,6 +40,19 @@
|
|
|
40
40
|
* `requireTool` throws `ToolNotAvailableError` with an actionable message if
|
|
41
41
|
* `cosign` is absent, and no test here ever spawns a real process or talks
|
|
42
42
|
* to Rekor/Fulcio.
|
|
43
|
+
*
|
|
44
|
+
* **#1943 extension (`run-agent` interop).** `BuildProvenanceStatementInput`
|
|
45
|
+
* gained two optional merge fields, `externalParameters`/`internalParameters`
|
|
46
|
+
* — additive only, every existing call site's statement is byte-identical
|
|
47
|
+
* without them. `run-agent` (./run-agent.ts's `buildRunAgentProvenanceStatement`)
|
|
48
|
+
* is the first caller to use them, plus `buildType` (already overridable,
|
|
49
|
+
* unchanged here) set to a `run-agent`-specific recipe URI. `predicateType`
|
|
50
|
+
* stays `"https://slsa.dev/provenance/v1"` for every caller, `run-agent`
|
|
51
|
+
* included — see ./run-agent.ts's own doc comment ("Attestation interop")
|
|
52
|
+
* for why a turn is attested as an SLSA-shaped provenance predicate rather
|
|
53
|
+
* than under a minted `run-agent`-specific `predicateType`, and why that
|
|
54
|
+
* decision means `sign`/`attestProvenance`/`../verbs/verify.ts` needed zero
|
|
55
|
+
* changes to their `cosign` invocations to attest/verify a turn.
|
|
43
56
|
*/
|
|
44
57
|
|
|
45
58
|
import type { Capability } from "../capability";
|
|
@@ -235,6 +248,23 @@ export interface BuildProvenanceStatementInput {
|
|
|
235
248
|
finishedOn?: string;
|
|
236
249
|
/** CI invocation/run id, when available (e.g. a GitHub Actions run id). */
|
|
237
250
|
invocationId?: string;
|
|
251
|
+
/**
|
|
252
|
+
* Additional `buildDefinition.externalParameters` merged in alongside the
|
|
253
|
+
* `{ sourceRef }` default (#1943: `run-agent`'s provenance statement folds
|
|
254
|
+
* in the fountain-supplied *declared* facts — the agent selector today,
|
|
255
|
+
* more once fountain `Agent` resolution lands — without run-agent needing
|
|
256
|
+
* its own predicate builder). `sourceRef` is reserved by this function: a
|
|
257
|
+
* caller-supplied `sourceRef` is overridden, never signed; any other key
|
|
258
|
+
* name is the caller's choice.
|
|
259
|
+
*/
|
|
260
|
+
externalParameters?: Record<string, unknown>;
|
|
261
|
+
/**
|
|
262
|
+
* Additional `buildDefinition.internalParameters` merged in alongside the
|
|
263
|
+
* `{ artifactDigest }` default (#1943: `run-agent` folds in what actually
|
|
264
|
+
* happened — sprite id, checkpoint id, turn status/exit code — the
|
|
265
|
+
* *imperative* facts a build's `internalParameters` are for).
|
|
266
|
+
*/
|
|
267
|
+
internalParameters?: Record<string, unknown>;
|
|
238
268
|
}
|
|
239
269
|
|
|
240
270
|
const DEFAULT_BUILD_TYPE = "https://chant.dev/build-archive/v1";
|
|
@@ -263,8 +293,10 @@ export function buildProvenanceStatement(input: BuildProvenanceStatementInput):
|
|
|
263
293
|
predicate: {
|
|
264
294
|
buildDefinition: {
|
|
265
295
|
buildType: input.buildType ?? DEFAULT_BUILD_TYPE,
|
|
266
|
-
|
|
267
|
-
|
|
296
|
+
// Reserved keys spread LAST so a caller-supplied key can never clobber
|
|
297
|
+
// them into a signed statement that disagrees with resolvedDependencies.
|
|
298
|
+
externalParameters: { ...input.externalParameters, sourceRef: input.provenance.sourceRef },
|
|
299
|
+
internalParameters: { ...input.internalParameters, artifactDigest: input.provenance.artifactDigest },
|
|
268
300
|
resolvedDependencies: [{ uri: input.provenance.sourceRef, digest: { sha256: digest } }],
|
|
269
301
|
},
|
|
270
302
|
runDetails: {
|
package/src/composite.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isDeclarable, type Declarable } from "./declarable";
|
|
2
|
-
import { setProvenance } from "./provenance";
|
|
2
|
+
import { setPathProvenance, setProvenance } from "./provenance";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Marker symbol for Composite type identification.
|
|
@@ -150,10 +150,16 @@ export function Composite<P, M extends CompositeFactoryMembers = CompositeMember
|
|
|
150
150
|
/**
|
|
151
151
|
* Expands a CompositeInstance into a flat Map of prefixed entity names to Declarables.
|
|
152
152
|
* Handles nested composites recursively.
|
|
153
|
+
*
|
|
154
|
+
* @param instanceName - The composite INSTANCE every member belongs to (chant
|
|
155
|
+
* #1443), which at the top call is the export name and stays that name
|
|
156
|
+
* through nesting — `prefix` grows per member and is not an instance
|
|
157
|
+
* identity. Defaults to `prefix` so the top call needs no extra argument.
|
|
153
158
|
*/
|
|
154
159
|
export function expandComposite(
|
|
155
160
|
prefix: string,
|
|
156
161
|
instance: CompositeInstance,
|
|
162
|
+
instanceName: string = prefix,
|
|
157
163
|
): Map<string, Declarable> {
|
|
158
164
|
const result = new Map<string, Declarable>();
|
|
159
165
|
const shared = (instance as unknown as Record<symbol, unknown>)[SHARED_PROPS] as Record<string, unknown> | undefined;
|
|
@@ -164,7 +170,7 @@ export function expandComposite(
|
|
|
164
170
|
const fullName = `${prefix}${memberName[0].toUpperCase()}${memberName.slice(1)}`;
|
|
165
171
|
|
|
166
172
|
if (isCompositeInstance(member)) {
|
|
167
|
-
const nested = expandComposite(fullName, member);
|
|
173
|
+
const nested = expandComposite(fullName, member, instanceName);
|
|
168
174
|
for (const [nestedName, nestedEntity] of nested) {
|
|
169
175
|
// Inner composite already stamped; `??=` keeps the most-specific one.
|
|
170
176
|
if (compositeName) setProvenance(nestedEntity, { composite: compositeName });
|
|
@@ -176,6 +182,15 @@ export function expandComposite(
|
|
|
176
182
|
}
|
|
177
183
|
}
|
|
178
184
|
|
|
185
|
+
// Whole-entity origin (chant #1443). Recorded after the loop so a nested
|
|
186
|
+
// member carries its INNER composite here — the recursive call above already
|
|
187
|
+
// ran and `setPathProvenance` keeps the first writer.
|
|
188
|
+
if (compositeName) {
|
|
189
|
+
for (const entity of result.values()) {
|
|
190
|
+
setPathProvenance(entity, "", { kind: "composite", composite: compositeName, instance: instanceName });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
179
194
|
if (shared) {
|
|
180
195
|
for (const entity of result.values()) {
|
|
181
196
|
if ("props" in entity) {
|
|
@@ -211,6 +226,20 @@ export function expandComposite(
|
|
|
211
226
|
Object.defineProperty(entity, "props", {
|
|
212
227
|
value: merged, enumerable: false, configurable: true,
|
|
213
228
|
});
|
|
229
|
+
// A key the member did not set is the propagating instance's alone, so
|
|
230
|
+
// it is attributable exactly (chant #1443). A key both sides wrote —
|
|
231
|
+
// an override, or the array concatenation above — is left to the
|
|
232
|
+
// whole-entity origin rather than credited to one contributor.
|
|
233
|
+
if (compositeName) {
|
|
234
|
+
for (const k of Object.keys(shared)) {
|
|
235
|
+
if (shared[k] === undefined || existing[k] !== undefined) continue;
|
|
236
|
+
setPathProvenance(entity, k, {
|
|
237
|
+
kind: "composite",
|
|
238
|
+
composite: compositeName,
|
|
239
|
+
instance: instanceName,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
214
243
|
}
|
|
215
244
|
}
|
|
216
245
|
}
|
package/src/discovery/collect.ts
CHANGED
|
@@ -34,6 +34,15 @@ function stackPrefix(file: string, buildRoot: string | undefined): string {
|
|
|
34
34
|
return segments.map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Whole-entity origin for a declarable a file exported directly (chant #1443).
|
|
39
|
+
* A composite's members get theirs from `expandComposite`, which knows the
|
|
40
|
+
* composite and the instance; a direct export has nothing between the source
|
|
41
|
+
* file and the resource. Merged key-by-key, so a path the fold path already
|
|
42
|
+
* attributed to a build parameter is not overwritten.
|
|
43
|
+
*/
|
|
44
|
+
const AUTHORED_ROOT: EntityProvenance["paths"] = { "": { kind: "authored" } };
|
|
45
|
+
|
|
37
46
|
/** One entity to be placed into the map, produced by {@link enumerateEntries}. */
|
|
38
47
|
interface PendingEntry {
|
|
39
48
|
/** The un-disambiguated map key this entity would take (export name, indexed
|
|
@@ -73,12 +82,12 @@ function enumerateEntries(
|
|
|
73
82
|
for (const [rawName, value] of sortedExports) {
|
|
74
83
|
const name = exportKey(rawName, file);
|
|
75
84
|
if (isDeclarable(value)) {
|
|
76
|
-
entries.push({ bareKey: name, value, file, provenance: { sourceFile: file } });
|
|
85
|
+
entries.push({ bareKey: name, value, file, provenance: { sourceFile: file, paths: AUTHORED_ROOT } });
|
|
77
86
|
} else if (Array.isArray(value)) {
|
|
78
87
|
for (let i = 0; i < value.length; i++) {
|
|
79
88
|
const item = value[i];
|
|
80
89
|
if (isDeclarable(item)) {
|
|
81
|
-
entries.push({ bareKey: `${name}_${i}`, value: item, file, provenance: { sourceFile: file } });
|
|
90
|
+
entries.push({ bareKey: `${name}_${i}`, value: item, file, provenance: { sourceFile: file, paths: AUTHORED_ROOT } });
|
|
82
91
|
} else if (isCompositeInstance(item)) {
|
|
83
92
|
const indexedName = `${name}_${i}`;
|
|
84
93
|
for (const [expandedName, entity] of expandComposite(indexedName, item)) {
|
|
@@ -9,6 +9,7 @@ import { isCompositeInstance } from "../composite";
|
|
|
9
9
|
import { isAttrRefLike } from "../utils";
|
|
10
10
|
import { isIntrinsic } from "../intrinsic";
|
|
11
11
|
import { params as sharedParams } from "../params";
|
|
12
|
+
import { getPathProvenance } from "../provenance";
|
|
12
13
|
import type { AttrRef } from "../attrref";
|
|
13
14
|
import type { IntrinsicDef } from "../lexicon";
|
|
14
15
|
|
|
@@ -1153,6 +1154,59 @@ describe("tryFoldFile — build-time parameters (chant #1064)", () => {
|
|
|
1153
1154
|
|
|
1154
1155
|
setBuildParams({});
|
|
1155
1156
|
});
|
|
1157
|
+
|
|
1158
|
+
// chant #1443 — fold substitutes `params.<name>` away, so the emitted value
|
|
1159
|
+
// is a literal and unattributable. These record which parameters the
|
|
1160
|
+
// AUTHORED expression read, before that happens.
|
|
1161
|
+
test("a folded resource records which build parameters each of its paths reads", async () => {
|
|
1162
|
+
await writeResourceDefs();
|
|
1163
|
+
const file = join(testDir, "main.ts");
|
|
1164
|
+
await writeFile(
|
|
1165
|
+
file,
|
|
1166
|
+
`
|
|
1167
|
+
import { Bucket } from "./resources";
|
|
1168
|
+
import { params } from ${JSON.stringify(paramsPath)};
|
|
1169
|
+
const suffix = params.env === "prod" ? "" : "-dev";
|
|
1170
|
+
export const bucket = new Bucket({
|
|
1171
|
+
name: \`assets\${suffix}\`,
|
|
1172
|
+
fixed: "never-moves",
|
|
1173
|
+
nested: { region: params.region },
|
|
1174
|
+
});
|
|
1175
|
+
`,
|
|
1176
|
+
);
|
|
1177
|
+
|
|
1178
|
+
const session = createFoldSession([], { env: "prod", region: "us-east-1" });
|
|
1179
|
+
const result = await tryFoldFile(file, [], session);
|
|
1180
|
+
|
|
1181
|
+
expect(result.ok).toBe(true);
|
|
1182
|
+
if (!result.ok) return;
|
|
1183
|
+
const [, entity] = result.entities[0];
|
|
1184
|
+
expect(getPathProvenance(entity as object)).toEqual({
|
|
1185
|
+
name: { kind: "build-param", params: ["env"] },
|
|
1186
|
+
"nested.region": { kind: "build-param", params: ["region"] },
|
|
1187
|
+
});
|
|
1188
|
+
});
|
|
1189
|
+
|
|
1190
|
+
test("a folded resource that reads no parameter records no path origins", async () => {
|
|
1191
|
+
await writeResourceDefs();
|
|
1192
|
+
const file = join(testDir, "main.ts");
|
|
1193
|
+
await writeFile(
|
|
1194
|
+
file,
|
|
1195
|
+
`
|
|
1196
|
+
import { Bucket } from "./resources";
|
|
1197
|
+
import { params } from ${JSON.stringify(paramsPath)};
|
|
1198
|
+
export const other = params.env;
|
|
1199
|
+
export const bucket = new Bucket({ name: "static" });
|
|
1200
|
+
`,
|
|
1201
|
+
);
|
|
1202
|
+
|
|
1203
|
+
const result = await tryFoldFile(file, [], createFoldSession([], { env: "prod" }));
|
|
1204
|
+
|
|
1205
|
+
expect(result.ok).toBe(true);
|
|
1206
|
+
if (!result.ok) return;
|
|
1207
|
+
const entity = result.entities.find(([name]) => name === "bucket")![1];
|
|
1208
|
+
expect(getPathProvenance(entity as object)).toBeUndefined();
|
|
1209
|
+
});
|
|
1156
1210
|
});
|
|
1157
1211
|
|
|
1158
1212
|
/**
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
type FoldedIntrinsic,
|
|
23
23
|
type FoldedHelperCall,
|
|
24
24
|
type SymbolicValue,
|
|
25
|
+
type FoldedCompositeStepCall,
|
|
25
26
|
} from "../fold/fold";
|
|
26
27
|
import { isChantOwnedSpecifier, isFoldableHelperName } from "../fold/foldable-helpers";
|
|
27
28
|
import {
|
|
@@ -37,7 +38,9 @@ import {
|
|
|
37
38
|
SUPPORTED_UNARY_OPERATORS,
|
|
38
39
|
} from "../fold/subset";
|
|
39
40
|
import { importModule } from "./import";
|
|
40
|
-
import
|
|
41
|
+
import { collectParamDependencies } from "./param-deps";
|
|
42
|
+
import { setPathProvenance } from "../provenance";
|
|
43
|
+
import { intrinsicCallFoldsEagerly, type IntrinsicDef } from "../lexicon";
|
|
41
44
|
import type { BuildParamValue } from "../build-params";
|
|
42
45
|
|
|
43
46
|
/**
|
|
@@ -1096,19 +1099,25 @@ function activeLexiconPackage(specifier: string, lexiconPackages: ReadonlySet<st
|
|
|
1096
1099
|
* and intrinsic functions; the only thing new is that a plain DATA export is
|
|
1097
1100
|
* now reachable too.
|
|
1098
1101
|
*
|
|
1099
|
-
* Callable exports are
|
|
1102
|
+
* Callable exports are otherwise excluded. A lexicon's functions — its
|
|
1100
1103
|
* resource classes, composite factories, intrinsic implementations — already
|
|
1101
1104
|
* have dedicated resolution paths that know how to INVOKE them
|
|
1102
1105
|
* (`resolveResourceEntity`, `resolveCallExpression`, `reviveFoldedValue`), and
|
|
1103
|
-
* binding
|
|
1104
|
-
*
|
|
1105
|
-
*
|
|
1106
|
+
* binding an arbitrary one as a plain identifier value here would widen what
|
|
1107
|
+
* folds in unmeasured ways. The one exception (chant #1966) is a function its
|
|
1108
|
+
* lexicon registered with {@link IntrinsicDef.foldsEagerly} — the same
|
|
1109
|
+
* per-name, lexicon-declared opt-in `foldsAsCall` is for an intrinsic's
|
|
1110
|
+
* envelope-and-revive form, admitting this one because `fold()`
|
|
1111
|
+
* (../fold/fold.ts) needs to CALL it eagerly — synchronously, before
|
|
1112
|
+
* revival — to support a lexicon's own string-building helpers used inline
|
|
1113
|
+
* (`matrix("os").toString()`, `` `${inputs("x")}` ``), where an envelope
|
|
1114
|
+
* would still be a plain object when the enclosing template folds.
|
|
1106
1115
|
*
|
|
1107
1116
|
* Returns `undefined` — never throws — for a package that isn't an active
|
|
1108
|
-
* lexicon, an export the package doesn't have, a callable
|
|
1109
|
-
* import that fails. The binding is then simply absent from
|
|
1110
|
-
* exactly as before, and `fold()`'s ordinary "unresolved
|
|
1111
|
-
* still fires if the name is actually referenced.
|
|
1117
|
+
* lexicon, an export the package doesn't have, a non-admitted callable
|
|
1118
|
+
* export, or an import that fails. The binding is then simply absent from
|
|
1119
|
+
* `externals`, exactly as before, and `fold()`'s ordinary "unresolved
|
|
1120
|
+
* identifier" failure still fires if the name is actually referenced.
|
|
1112
1121
|
*/
|
|
1113
1122
|
async function resolveActiveLexiconExport(
|
|
1114
1123
|
binding: ImportBinding,
|
|
@@ -1133,7 +1142,10 @@ async function resolveActiveLexiconExport(
|
|
|
1133
1142
|
|
|
1134
1143
|
if (!(binding.imported in mod)) return undefined;
|
|
1135
1144
|
const value = mod[binding.imported];
|
|
1136
|
-
if (typeof value === "function")
|
|
1145
|
+
if (typeof value === "function") {
|
|
1146
|
+
const eager = session.intrinsics.some((i) => i.name === binding.imported && intrinsicCallFoldsEagerly(i));
|
|
1147
|
+
if (!eager) return undefined;
|
|
1148
|
+
}
|
|
1137
1149
|
return { value };
|
|
1138
1150
|
}
|
|
1139
1151
|
|
|
@@ -1415,19 +1427,19 @@ async function resolveImportedCall(
|
|
|
1415
1427
|
}
|
|
1416
1428
|
|
|
1417
1429
|
/**
|
|
1418
|
-
* Arm 2 — import the module the callee came from and call it, in this
|
|
1419
|
-
*
|
|
1420
|
-
*
|
|
1421
|
-
*
|
|
1422
|
-
*
|
|
1423
|
-
*
|
|
1430
|
+
* Arm 2 — import the module the callee came from and call it, in this
|
|
1431
|
+
* process, given ALREADY-RESOLVED argument values. Factored out of
|
|
1432
|
+
* {@link invokeImportedCallee} (chant #1174) so a caller with no raw
|
|
1433
|
+
* `ts.CallExpression` node — {@link resolveCompositeCall}, the nested-value
|
|
1434
|
+
* counterpart to {@link resolveCallExpression} — can reach the identical
|
|
1435
|
+
* refusal/resolve/import/callable-check/call sequence #1022 established,
|
|
1436
|
+
* without inventing a second copy of it.
|
|
1424
1437
|
*/
|
|
1425
|
-
async function
|
|
1426
|
-
node: ts.CallExpression,
|
|
1438
|
+
async function invokeResolvedCallee(
|
|
1427
1439
|
calleeName: string,
|
|
1428
1440
|
binding: ImportBinding,
|
|
1429
1441
|
ctx: ResolveCtx,
|
|
1430
|
-
args
|
|
1442
|
+
args: readonly unknown[],
|
|
1431
1443
|
): Promise<unknown> {
|
|
1432
1444
|
// chant #1093 — THE gap this check exists for. Invoking the callee runs
|
|
1433
1445
|
// project code (the factory body, and its whole module's top level) in the
|
|
@@ -1473,12 +1485,67 @@ async function invokeImportedCallee(
|
|
|
1473
1485
|
throw cheapError(`"${binding.imported}" from "${binding.specifier}" is not a function`);
|
|
1474
1486
|
}
|
|
1475
1487
|
|
|
1476
|
-
const callArgs = args ?? (await resolveCallArguments(node, calleeName, ctx));
|
|
1477
|
-
|
|
1478
1488
|
executionCounts.factoryInvocations += 1;
|
|
1479
1489
|
if (isProjectFileSpecifier(binding.specifier)) executionCounts.projectFactoryInvocations += 1;
|
|
1480
1490
|
|
|
1481
|
-
return (Fn as (...fnArgs: unknown[]) => unknown)(...
|
|
1491
|
+
return (Fn as (...fnArgs: unknown[]) => unknown)(...args);
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
/**
|
|
1495
|
+
* Arm 2's original entry point — a raw call-expression node in hand. Resolves
|
|
1496
|
+
* arguments (unless the caller already has them) and delegates to
|
|
1497
|
+
* {@link invokeResolvedCallee}. Unchanged from #1022 in both behavior and
|
|
1498
|
+
* ORDER (refusal, resolve, import, callable check, arguments, call); #1023
|
|
1499
|
+
* factored the body out once already, #1174 a second time.
|
|
1500
|
+
*
|
|
1501
|
+
* @param args - Already-evaluated arguments, when the caller has them.
|
|
1502
|
+
*/
|
|
1503
|
+
async function invokeImportedCallee(
|
|
1504
|
+
node: ts.CallExpression,
|
|
1505
|
+
calleeName: string,
|
|
1506
|
+
binding: ImportBinding,
|
|
1507
|
+
ctx: ResolveCtx,
|
|
1508
|
+
args?: readonly unknown[],
|
|
1509
|
+
): Promise<unknown> {
|
|
1510
|
+
const callArgs = args ?? (await resolveCallArguments(node, calleeName, ctx));
|
|
1511
|
+
return invokeResolvedCallee(calleeName, binding, ctx, callArgs);
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
/**
|
|
1515
|
+
* Resolve a composite-factory call used as a NESTED value — chant #1174's
|
|
1516
|
+
* `<Identifier>(...).step` idiom ({@link FoldedCompositeStepCall}) — given
|
|
1517
|
+
* its callee NAME and already-REVIVED argument values. The nested-value
|
|
1518
|
+
* counterpart to {@link resolveCallExpression}, which the top-level
|
|
1519
|
+
* declarator path (`export const x = Checkout({...})`) already used: the
|
|
1520
|
+
* raw `ts.CallExpression` node isn't needed here because
|
|
1521
|
+
* {@link resolveInterpretableFactory} only needs the import `binding`, and
|
|
1522
|
+
* {@link interpretCompositeFactory}/{@link invokeResolvedCallee} only need
|
|
1523
|
+
* the resolved argument values — which `fold()`'s envelope already folded
|
|
1524
|
+
* and {@link reviveFoldedValue} has already revived by the time this runs.
|
|
1525
|
+
*
|
|
1526
|
+
* Same two arms, same order, as the top-level path: interpret a project-file
|
|
1527
|
+
* registered `Composite` (chant #1023) when its body qualifies, otherwise
|
|
1528
|
+
* import and invoke for real — which is the arm every lexicon-package
|
|
1529
|
+
* composite (`Checkout`, `SetupNode`, …) always takes, exactly as it does
|
|
1530
|
+
* today for a top-level `export const x = Checkout({...})`. An unbound
|
|
1531
|
+
* callee name throws the same "function call as a value" message `fold()`
|
|
1532
|
+
* itself would have, for a caller with no `FoldedCompositeStepCall` special
|
|
1533
|
+
* case to fall into.
|
|
1534
|
+
*/
|
|
1535
|
+
async function resolveCompositeCall(calleeName: string, args: readonly unknown[], ctx: ResolveCtx): Promise<unknown> {
|
|
1536
|
+
const binding = ctx.imports.get(calleeName);
|
|
1537
|
+
if (!binding) throw cheapError(`unresolved identifier: ${calleeName}`);
|
|
1538
|
+
|
|
1539
|
+
const factory = await resolveInterpretableFactory(binding, ctx);
|
|
1540
|
+
if (factory) {
|
|
1541
|
+
const interpreted = await interpretCompositeFactory(factory, args, ctx);
|
|
1542
|
+
if (interpreted) return interpreted.value;
|
|
1543
|
+
// Declined — falls through to arm 2 with the SAME arguments, so a nested
|
|
1544
|
+
// composite-call argument (or a live cross-file reference) is not
|
|
1545
|
+
// resolved a second time. Identical discipline to `resolveImportedCall`.
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
return invokeResolvedCallee(calleeName, binding, ctx, args);
|
|
1482
1549
|
}
|
|
1483
1550
|
|
|
1484
1551
|
/**
|
|
@@ -2502,6 +2569,24 @@ async function reviveFoldedValue(value: FoldedValue, ctx: ResolveCtx, requireLiv
|
|
|
2502
2569
|
return reviveHelperCall(value as FoldedHelperCall, ctx);
|
|
2503
2570
|
}
|
|
2504
2571
|
|
|
2572
|
+
if ("__compositeStep" in value) {
|
|
2573
|
+
// chant #1174 — `<Identifier>(...).step`, see {@link FoldedCompositeStepCall}.
|
|
2574
|
+
// Arguments revive with `requireLiveRefs: false` — the same rule
|
|
2575
|
+
// `resolveCallArguments` applies to a NON-helper callee's arguments
|
|
2576
|
+
// (a composite factory stores its props, it doesn't inspect them the way
|
|
2577
|
+
// an intrinsic/helper does), so a `{__attrRef}` among them stays the
|
|
2578
|
+
// symbolic envelope the composite's own resource construction resolves
|
|
2579
|
+
// by name, exactly as a top-level resource's props would.
|
|
2580
|
+
const call = value as FoldedCompositeStepCall;
|
|
2581
|
+
const revivedArgs: unknown[] = [];
|
|
2582
|
+
for (const a of call.args) revivedArgs.push(await reviveFoldedValue(a, ctx, false));
|
|
2583
|
+
const result = await resolveCompositeCall(call.__compositeStep, revivedArgs, ctx);
|
|
2584
|
+
if (!isIndexableObject(result)) {
|
|
2585
|
+
throw cheapError(`composite call \`${call.__compositeStep}(...)\` did not resolve to an object with a "step" member`);
|
|
2586
|
+
}
|
|
2587
|
+
return (result as Record<string, unknown>).step;
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2505
2590
|
if ("__attrRef" in value) {
|
|
2506
2591
|
if (requireLiveRefs) {
|
|
2507
2592
|
throw cheapError(
|
|
@@ -2889,6 +2974,46 @@ async function constructFoldedResource(
|
|
|
2889
2974
|
* performs unconditionally for the same file, and through the same
|
|
2890
2975
|
* already-memoized `importModule`.
|
|
2891
2976
|
*/
|
|
2977
|
+
/**
|
|
2978
|
+
* The local names this file bound to the build's parameter object (chant
|
|
2979
|
+
* #1443). Object identity against {@link FoldSession.buildParams}, which
|
|
2980
|
+
* `buildExternals` substituted directly, so an unrelated import that happens to
|
|
2981
|
+
* be called `params` is not mistaken for it.
|
|
2982
|
+
*/
|
|
2983
|
+
function paramLocalNames(ctx: ResolveCtx): Set<string> {
|
|
2984
|
+
const out = new Set<string>();
|
|
2985
|
+
const buildParams = ctx.session.buildParams;
|
|
2986
|
+
if (!buildParams) return out;
|
|
2987
|
+
for (const [name, value] of ctx.externals) {
|
|
2988
|
+
if (value === buildParams) out.add(name);
|
|
2989
|
+
}
|
|
2990
|
+
return out;
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
/**
|
|
2994
|
+
* chant #1443 — record which build parameters each of a resource's authored
|
|
2995
|
+
* property expressions reads, before fold substitutes them away. Best-effort
|
|
2996
|
+
* and additive: a file with no `params` import, or a constructor called with no
|
|
2997
|
+
* object literal, records nothing.
|
|
2998
|
+
*/
|
|
2999
|
+
function stampParamDependencies(entity: unknown, node: ts.NewExpression, ctx: ResolveCtx): void {
|
|
3000
|
+
if (typeof entity !== "object" || entity === null) return;
|
|
3001
|
+
const paramLocals = paramLocalNames(ctx);
|
|
3002
|
+
if (paramLocals.size === 0) return;
|
|
3003
|
+
// The same argument `foldResource` treats as props: the first object literal.
|
|
3004
|
+
let propsArg: ts.ObjectLiteralExpression | undefined;
|
|
3005
|
+
for (const argument of node.arguments ?? []) {
|
|
3006
|
+
if (ts.isObjectLiteralExpression(argument)) {
|
|
3007
|
+
propsArg = argument;
|
|
3008
|
+
break;
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
if (!propsArg) return;
|
|
3012
|
+
for (const [path, origin] of Object.entries(collectParamDependencies(propsArg, ctx.consts, paramLocals))) {
|
|
3013
|
+
setPathProvenance(entity, path, origin);
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
|
|
2892
3017
|
async function preresolveResourceConsts(ctx: ResolveCtx): Promise<Map<ts.Expression, unknown>> {
|
|
2893
3018
|
const built = new Map<ts.Expression, unknown>();
|
|
2894
3019
|
for (const [name, initializer] of ctx.consts) {
|
|
@@ -2896,6 +3021,7 @@ async function preresolveResourceConsts(ctx: ResolveCtx): Promise<Map<ts.Express
|
|
|
2896
3021
|
try {
|
|
2897
3022
|
const spec = foldResource(initializer, ctx.consts, ctx.intrinsics, ctx.externals);
|
|
2898
3023
|
const instance = await constructFoldedResource(spec, ctx, false);
|
|
3024
|
+
stampParamDependencies(instance, initializer, ctx);
|
|
2899
3025
|
built.set(initializer, instance);
|
|
2900
3026
|
ctx.externals.set(name, instance);
|
|
2901
3027
|
} catch {
|
|
@@ -2933,6 +3059,7 @@ async function resolveResourceEntity(
|
|
|
2933
3059
|
|
|
2934
3060
|
try {
|
|
2935
3061
|
const entity = (await instantiateFoldedResource(spec.__resource, ctorArgs, ctx, ` for "${name}"`)) as Declarable;
|
|
3062
|
+
stampParamDependencies(entity, node, ctx);
|
|
2936
3063
|
return { ok: true, entity };
|
|
2937
3064
|
} catch (err) {
|
|
2938
3065
|
return { ok: false, reason: err instanceof Error ? err.message : String(err) };
|
|
@@ -3552,22 +3679,21 @@ export async function tryFoldFile(
|
|
|
3552
3679
|
* so the exact same forced-taint safety net still applies to that remaining
|
|
3553
3680
|
* boundary, unchanged.
|
|
3554
3681
|
*/
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
)
|
|
3682
|
+
/**
|
|
3683
|
+
* file -> set of OTHER files in `files` that it relatively imports OR
|
|
3684
|
+
* re-exports from, regardless of that file's own fold outcome. Extracted
|
|
3685
|
+
* from {@link planFoldTaint} (chant #1083) so a second consumer — the fold
|
|
3686
|
+
* blocker dominator ranking (`./fold-rank.ts`) — can walk the exact same
|
|
3687
|
+
* project-file edges instead of re-parsing every file with its own
|
|
3688
|
+
* specifier-resolution logic. chant #1020 — this counts a namespace import
|
|
3689
|
+
* (`import * as ns from "./x"`, previously skipped — see `collectImports`)
|
|
3690
|
+
* and a re-export (`export { x } from "./y"`, which `collectImports` never
|
|
3691
|
+
* saw at all, since it only looks at `import` declarations): both are real
|
|
3692
|
+
* cross-file value dependencies fold's own resolution follows, so both need
|
|
3693
|
+
* the same edge an ordinary named import already gets.
|
|
3694
|
+
*/
|
|
3695
|
+
export async function buildProjectImportEdges(files: readonly string[]): Promise<Map<string, Set<string>>> {
|
|
3560
3696
|
const fileSet = new Set(files);
|
|
3561
|
-
|
|
3562
|
-
// file -> set of OTHER discovered files it relatively imports OR re-exports
|
|
3563
|
-
// from, regardless of that file's own fold outcome (taint needs the full
|
|
3564
|
-
// edge set to propagate transitively). chant #1020 — this now ALSO counts
|
|
3565
|
-
// a namespace import (`import * as ns from "./x"`, previously skipped —
|
|
3566
|
-
// see `collectImports`) and a re-export (`export { x } from "./y"`, which
|
|
3567
|
-
// `collectImports` never saw at all, since it only looks at `import`
|
|
3568
|
-
// declarations): both are real cross-file value dependencies my own new
|
|
3569
|
-
// resolution follows, so both need the same identity guarantee an
|
|
3570
|
-
// ordinary named import already got.
|
|
3571
3697
|
const edges = new Map<string, Set<string>>();
|
|
3572
3698
|
for (const file of files) {
|
|
3573
3699
|
const targets = new Set<string>();
|
|
@@ -3600,10 +3726,24 @@ export async function planFoldTaint(
|
|
|
3600
3726
|
} catch {
|
|
3601
3727
|
// Unreadable/unparseable — no edges contributed; tryFoldFile's own
|
|
3602
3728
|
// top-level catch already turns this into a "run" decision for the
|
|
3603
|
-
// file itself, which is enough to seed it as tainted below
|
|
3729
|
+
// file itself, which is enough to seed it as tainted below (or, for
|
|
3730
|
+
// the dominator ranking, leaves it a childless node).
|
|
3604
3731
|
}
|
|
3605
3732
|
edges.set(file, targets);
|
|
3606
3733
|
}
|
|
3734
|
+
return edges;
|
|
3735
|
+
}
|
|
3736
|
+
|
|
3737
|
+
export async function planFoldTaint(
|
|
3738
|
+
files: readonly string[],
|
|
3739
|
+
wouldFold: ReadonlyMap<string, boolean>,
|
|
3740
|
+
liveSources?: ReadonlyMap<string, ReadonlySet<string>>,
|
|
3741
|
+
): Promise<Set<string>> {
|
|
3742
|
+
const fileSet = new Set(files);
|
|
3743
|
+
|
|
3744
|
+
// file -> set of OTHER discovered files it relatively imports OR re-exports
|
|
3745
|
+
// from — see {@link buildProjectImportEdges}.
|
|
3746
|
+
const edges = await buildProjectImportEdges(files);
|
|
3607
3747
|
|
|
3608
3748
|
// chant #1044 — reverse edges: consumed-file -> the folded files that
|
|
3609
3749
|
// captured its objects. Same taint set, same fixpoint walk; see this
|