@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
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import { describe, expect, test } from "vitest";
|
|
3
|
+
import { collectConsts } from "../fold/fold";
|
|
4
|
+
import { collectParamDependencies } from "./param-deps";
|
|
5
|
+
import type { PathOrigin } from "../provenance";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Collect dependencies for the props of the file's single
|
|
9
|
+
* `export const x = new Type({...})`, which is the shape every case here uses.
|
|
10
|
+
*/
|
|
11
|
+
function depsOf(source: string, paramLocals = ["params"]): Record<string, PathOrigin> {
|
|
12
|
+
const file = ts.createSourceFile("fixture.ts", source, ts.ScriptTarget.Latest, true);
|
|
13
|
+
const consts = collectConsts(file);
|
|
14
|
+
let props: ts.ObjectLiteralExpression | undefined;
|
|
15
|
+
for (const statement of file.statements) {
|
|
16
|
+
if (!ts.isVariableStatement(statement)) continue;
|
|
17
|
+
for (const decl of statement.declarationList.declarations) {
|
|
18
|
+
if (decl.name.getText() !== "x" || !decl.initializer) continue;
|
|
19
|
+
if (!ts.isNewExpression(decl.initializer)) continue;
|
|
20
|
+
for (const argument of decl.initializer.arguments ?? []) {
|
|
21
|
+
if (ts.isObjectLiteralExpression(argument)) {
|
|
22
|
+
props = argument;
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (!props) throw new Error("fixture has no `const x = new Type({...})`");
|
|
29
|
+
return collectParamDependencies(props, consts, new Set(paramLocals));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const param = (...names: string[]): PathOrigin => ({ kind: "build-param", params: names });
|
|
33
|
+
|
|
34
|
+
describe("collectParamDependencies", () => {
|
|
35
|
+
test("a direct read is attributed to its own path", () => {
|
|
36
|
+
expect(depsOf(`export const x = new Thing({ replicas: params.replicas, name: "fixed" });`)).toEqual({
|
|
37
|
+
replicas: param("replicas"),
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("nested object literals are descended into, dotted", () => {
|
|
42
|
+
const source = `export const x = new Thing({ spec: { template: { image: params.image } } });`;
|
|
43
|
+
expect(depsOf(source)).toEqual({ "spec.template.image": param("image") });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("an expression is attributed to every parameter it can read, not to its value", () => {
|
|
47
|
+
const source = `export const x = new Thing({ replicas: params.tier === "prod" ? params.big : 1 });`;
|
|
48
|
+
expect(depsOf(source)).toEqual({ replicas: param("big", "tier") });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("a parameter hoisted into a const is followed to the field that uses it", () => {
|
|
52
|
+
const source = [
|
|
53
|
+
`const replicas = params.tier === "prod" ? 5 : 1;`,
|
|
54
|
+
`export const x = new Thing({ replicas });`,
|
|
55
|
+
].join("\n");
|
|
56
|
+
expect(depsOf(source)).toEqual({ replicas: param("tier") });
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("const chains are followed transitively, and a cycle terminates", () => {
|
|
60
|
+
const source = [
|
|
61
|
+
`const a = b;`,
|
|
62
|
+
`const b = \`\${params.region}-\${a}\`;`,
|
|
63
|
+
`export const x = new Thing({ zone: a });`,
|
|
64
|
+
].join("\n");
|
|
65
|
+
expect(depsOf(source)).toEqual({ zone: param("region") });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("template literals and calls are walked", () => {
|
|
69
|
+
const source = `export const x = new Thing({ bucket: \`\${params.env}-assets\`.toLowerCase() });`;
|
|
70
|
+
expect(depsOf(source)).toEqual({ bucket: param("env") });
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("an array is attributed whole, never per index", () => {
|
|
74
|
+
const source = `export const x = new Thing({ containers: [{ image: params.image }, { image: "sidecar" }] });`;
|
|
75
|
+
expect(depsOf(source)).toEqual({ containers: param("image") });
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("a spread is attributed to the object it spreads into", () => {
|
|
79
|
+
const source = [
|
|
80
|
+
`const base = { region: params.region };`,
|
|
81
|
+
`export const x = new Thing({ ...base, spec: { ...base, replicas: 1 } });`,
|
|
82
|
+
].join("\n");
|
|
83
|
+
expect(depsOf(source)).toEqual({ "": param("region"), spec: param("region") });
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("bracket access with a literal key names the parameter", () => {
|
|
87
|
+
expect(depsOf(`export const x = new Thing({ zone: params["region"] });`)).toEqual({ zone: param("region") });
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("a property KEY that happens to match a const is not a reference", () => {
|
|
91
|
+
const source = [`const tier = params.tier;`, `export const x = new Thing({ spec: { tier: "fixed" } });`].join("\n");
|
|
92
|
+
expect(depsOf(source)).toEqual({});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("a bare reference to the whole params object records nothing", () => {
|
|
96
|
+
// It names no single parameter; under-reporting is the safe direction.
|
|
97
|
+
expect(depsOf(`export const x = new Thing({ all: params });`)).toEqual({});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("a file that never imported params records nothing", () => {
|
|
101
|
+
expect(depsOf(`export const x = new Thing({ replicas: params.replicas });`, [])).toEqual({});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("the local name the import bound is what counts, not the word 'params'", () => {
|
|
105
|
+
const source = `export const x = new Thing({ replicas: p.replicas, other: params.replicas });`;
|
|
106
|
+
expect(depsOf(source, ["p"])).toEqual({ replicas: param("replicas") });
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("parameter names are sorted and de-duplicated", () => {
|
|
110
|
+
const source = `export const x = new Thing({ n: params.z + params.a + params.z });`;
|
|
111
|
+
expect(depsOf(source)).toEqual({ n: param("a", "z") });
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("a computed key is skipped rather than guessed at", () => {
|
|
115
|
+
const source = `export const x = new Thing({ [params.key]: 1, name: params.name });`;
|
|
116
|
+
expect(depsOf(source)).toEqual({ name: param("name") });
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import type { PathOrigin } from "../provenance";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Which build parameters an authored props expression READS, per path (chant
|
|
6
|
+
* #1443) — the declared-side counterpart of `managed-fields.ts`'s per-path live
|
|
7
|
+
* `owners`.
|
|
8
|
+
*
|
|
9
|
+
* This is a syntactic dependency, not a value taint. Fold substitutes
|
|
10
|
+
* `params.<name>` before anything is emitted, so by output time the value is a
|
|
11
|
+
* literal and indistinguishable from one typed by hand; tainting the value
|
|
12
|
+
* through fold's evaluator would mean propagating a tag through every operator
|
|
13
|
+
* it supports, and one operator that forgot would produce silently wrong
|
|
14
|
+
* provenance. Recording which parameters the *expression* mentions cannot
|
|
15
|
+
* degrade that way: a shape this walk does not follow drops a dependency, it
|
|
16
|
+
* never invents one.
|
|
17
|
+
*
|
|
18
|
+
* It also answers the more useful question. For
|
|
19
|
+
* `replicas: params.tier === "prod" ? 5 : 1`, a taint reports that the value
|
|
20
|
+
* came from the literal `5` — true, and no help to anyone about to edit the
|
|
21
|
+
* field. The dependency reports that the field is governed by `tier`.
|
|
22
|
+
*
|
|
23
|
+
* Known under-reporting, all in the safe direction: destructured parameters
|
|
24
|
+
* (`const { tier } = params`) are not tracked, because `collectConsts` only
|
|
25
|
+
* records identifier bindings; a bare reference to the whole `params` object
|
|
26
|
+
* names no single parameter and records nothing; and the run path has no
|
|
27
|
+
* expression to walk at all.
|
|
28
|
+
*
|
|
29
|
+
* The one direction it can over-report is a local binding that shadows a
|
|
30
|
+
* top-level `const` of the same name, since names resolve against the file's
|
|
31
|
+
* consts without scope tracking. That reports a parameter the field could
|
|
32
|
+
* plausibly be governed by rather than one it definitely is, which is the
|
|
33
|
+
* failure the "could affect" reading is written to absorb.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/** The property name a member declares, when it is a literal one. */
|
|
37
|
+
function literalName(name: ts.PropertyName | undefined): string | undefined {
|
|
38
|
+
if (!name) return undefined;
|
|
39
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) return name.text;
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Strip the wrappers that do not change what an initializer reads. */
|
|
44
|
+
function unwrap(expr: ts.Expression): ts.Expression {
|
|
45
|
+
let current = expr;
|
|
46
|
+
for (;;) {
|
|
47
|
+
if (ts.isParenthesizedExpression(current) || ts.isAsExpression(current) || ts.isNonNullExpression(current)) {
|
|
48
|
+
current = current.expression;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
return current;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Every parameter name `expr` reads, following this file's own top-level
|
|
57
|
+
* `const` bindings so a value hoisted into a local is still attributed
|
|
58
|
+
* (`const replicas = params.tier === "prod" ? 5 : 1` used as `replicas`).
|
|
59
|
+
*/
|
|
60
|
+
function readParams(
|
|
61
|
+
expr: ts.Expression,
|
|
62
|
+
consts: ReadonlyMap<string, ts.Expression>,
|
|
63
|
+
paramLocals: ReadonlySet<string>,
|
|
64
|
+
out: Set<string>,
|
|
65
|
+
): void {
|
|
66
|
+
const followed = new Set<string>();
|
|
67
|
+
|
|
68
|
+
const visit = (node: ts.Node): void => {
|
|
69
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
70
|
+
if (ts.isIdentifier(node.expression) && paramLocals.has(node.expression.text)) {
|
|
71
|
+
out.add(node.name.text);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
visit(node.expression);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (ts.isElementAccessExpression(node)) {
|
|
79
|
+
if (ts.isIdentifier(node.expression) && paramLocals.has(node.expression.text)) {
|
|
80
|
+
if (ts.isStringLiteralLike(node.argumentExpression)) out.add(node.argumentExpression.text);
|
|
81
|
+
} else {
|
|
82
|
+
visit(node.expression);
|
|
83
|
+
}
|
|
84
|
+
visit(node.argumentExpression);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (ts.isIdentifier(node)) {
|
|
89
|
+
// A bare `params` names no single parameter — see the under-reporting
|
|
90
|
+
// note above.
|
|
91
|
+
if (paramLocals.has(node.text)) return;
|
|
92
|
+
const initializer = consts.get(node.text);
|
|
93
|
+
if (initializer && !followed.has(node.text)) {
|
|
94
|
+
followed.add(node.text);
|
|
95
|
+
visit(initializer);
|
|
96
|
+
}
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// A property KEY is not a reference; without this a nested `{ tier: 1 }`
|
|
101
|
+
// would resolve `tier` against `consts` and manufacture a dependency.
|
|
102
|
+
if (ts.isPropertyAssignment(node)) {
|
|
103
|
+
visit(node.initializer);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
ts.forEachChild(node, visit);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
visit(expr);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Path → build-parameter origin for one resource's props object literal.
|
|
115
|
+
*
|
|
116
|
+
* Paths are dotted property names only, per `EntityProvenance.paths`: an object
|
|
117
|
+
* literal is descended into, and anything else — an array literal included — is
|
|
118
|
+
* attributed whole at its own path. An array's elements are deliberately not
|
|
119
|
+
* indexed, because an index-shaped key would not survive an element moving and
|
|
120
|
+
* would not match the `[#key]` addressing a diff uses.
|
|
121
|
+
*
|
|
122
|
+
* A spread is attributed to the object it spreads INTO, at that object's own
|
|
123
|
+
* path (the entity root for a top-level spread), since which keys it
|
|
124
|
+
* contributes is not knowable here.
|
|
125
|
+
*/
|
|
126
|
+
export function collectParamDependencies(
|
|
127
|
+
props: ts.ObjectLiteralExpression,
|
|
128
|
+
consts: ReadonlyMap<string, ts.Expression>,
|
|
129
|
+
paramLocals: ReadonlySet<string>,
|
|
130
|
+
): Record<string, PathOrigin> {
|
|
131
|
+
const out: Record<string, PathOrigin> = {};
|
|
132
|
+
if (paramLocals.size === 0) return out;
|
|
133
|
+
|
|
134
|
+
const record = (path: string, expr: ts.Expression): void => {
|
|
135
|
+
const found = new Set<string>();
|
|
136
|
+
readParams(expr, consts, paramLocals, found);
|
|
137
|
+
if (found.size === 0) return;
|
|
138
|
+
const existing = out[path];
|
|
139
|
+
const merged =
|
|
140
|
+
existing && existing.kind === "build-param" ? new Set([...existing.params, ...found]) : found;
|
|
141
|
+
out[path] = { kind: "build-param", params: [...merged].sort() };
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const walk = (object: ts.ObjectLiteralExpression, prefix: string): void => {
|
|
145
|
+
for (const member of object.properties) {
|
|
146
|
+
if (ts.isSpreadAssignment(member)) {
|
|
147
|
+
record(prefix, member.expression);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (ts.isShorthandPropertyAssignment(member)) {
|
|
151
|
+
const key = member.name.text;
|
|
152
|
+
record(prefix ? `${prefix}.${key}` : key, member.name);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (!ts.isPropertyAssignment(member)) continue;
|
|
156
|
+
const key = literalName(member.name);
|
|
157
|
+
if (key === undefined) continue;
|
|
158
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
159
|
+
const initializer = unwrap(member.initializer);
|
|
160
|
+
if (ts.isObjectLiteralExpression(initializer)) {
|
|
161
|
+
walk(initializer, path);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
record(path, member.initializer);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
walk(props, "");
|
|
169
|
+
return out;
|
|
170
|
+
}
|
package/src/fold/fold.test.ts
CHANGED
|
@@ -435,7 +435,11 @@ describe("fold — registered call-form intrinsics (#1044)", () => {
|
|
|
435
435
|
expect(() => fold(expr, consts, [REF])).toThrow(FoldError);
|
|
436
436
|
});
|
|
437
437
|
|
|
438
|
-
test("an array method taking a closure is rejected — .map is
|
|
438
|
+
test("an array method taking a closure is rejected — the closure argument, not .map itself, is what fails (chant #1966)", () => {
|
|
439
|
+
// chant #1966 — `.map` is now attempted (a real method on a real folded
|
|
440
|
+
// array), so the rejection moves from "no case for this call" to the
|
|
441
|
+
// closure argument itself: a function used as a value is never foldable,
|
|
442
|
+
// with or without a registered intrinsic inside it.
|
|
439
443
|
const consts = parseConsts(`const cidrs = ["10.0.0.0/24"]; const x = cidrs.map((c) => Ref(c));`);
|
|
440
444
|
const expr = consts.get("x");
|
|
441
445
|
if (!expr) throw new Error("fixture error");
|
|
@@ -446,7 +450,7 @@ describe("fold — registered call-form intrinsics (#1044)", () => {
|
|
|
446
450
|
error = e;
|
|
447
451
|
}
|
|
448
452
|
expect(error).toBeInstanceOf(FoldError);
|
|
449
|
-
expect((error as FoldError).message).toContain("
|
|
453
|
+
expect((error as FoldError).message).toContain("a function used as a value is not foldable");
|
|
450
454
|
});
|
|
451
455
|
|
|
452
456
|
test("a user-defined function is rejected however it's named", () => {
|
package/src/fold/fold.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as ts from "typescript";
|
|
2
2
|
import { relative } from "node:path";
|
|
3
|
-
import { intrinsicCallFolds, intrinsicTagFolds, type IntrinsicDef } from "../lexicon";
|
|
3
|
+
import { intrinsicCallFolds, intrinsicCallFoldsEagerly, intrinsicTagFolds, type IntrinsicDef } from "../lexicon";
|
|
4
4
|
import {
|
|
5
5
|
SUPPORTED_BINARY_OPERATORS,
|
|
6
6
|
SUPPORTED_UNARY_OPERATORS,
|
|
@@ -67,7 +67,27 @@ import { isFoldableHelperName } from "./foldable-helpers";
|
|
|
67
67
|
* folds its body against the defining module's scope with the folded
|
|
68
68
|
* arguments bound. Still nothing is imported or run.
|
|
69
69
|
*
|
|
70
|
-
*
|
|
70
|
+
* A bare composite factory call (`Checkout({...})` on its own) is still out
|
|
71
|
+
* of scope (epic Phase 5, #1023 covers only interpreting a factory's OWN
|
|
72
|
+
* body, not consuming its result as a value elsewhere). chant #1174 adds one
|
|
73
|
+
* narrow exception at the PROPERTY-ACCESS level rather than here: the
|
|
74
|
+
* `<Identifier>(...).step` idiom — see {@link FoldedCompositeStepCall} on the
|
|
75
|
+
* property-access branch below. A call with no `.step` narrowing, or any
|
|
76
|
+
* other member, still throws from this branch exactly as before.
|
|
77
|
+
*
|
|
78
|
+
* chant #1966 adds a fourth call shape, and a method call on top of any of
|
|
79
|
+
* the four: a lexicon-package function its lexicon registered with
|
|
80
|
+
* {@link intrinsicCallFoldsEagerly} (../lexicon.ts) evaluates eagerly —
|
|
81
|
+
* unlike the other three, which envelope for later revival — because its
|
|
82
|
+
* usual use (`` `${matrix("os")}` ``) coerces the result via `String()` at
|
|
83
|
+
* fold time, before any revival would run. A `CallExpression` whose callee is
|
|
84
|
+
* a property access — `github.actor.toString()`, `[...].join(",")`,
|
|
85
|
+
* `matrix("os").toString()` — folds its receiver and calls the named method
|
|
86
|
+
* on it directly, PROVIDED the receiver is a real value and not one of
|
|
87
|
+
* fold's own symbolic envelopes (see {@link isFoldSymbolicEnvelope}); nothing
|
|
88
|
+
* about the method name is otherwise restricted.
|
|
89
|
+
*
|
|
90
|
+
* Everything else — an ordinary package function, an array `.map`, a
|
|
71
91
|
* registered name shadowed by a local binding — still throws.
|
|
72
92
|
*
|
|
73
93
|
* Cross-file identifier resolution (chant #1020): `consts` alone is always
|
|
@@ -105,7 +125,8 @@ export type FoldedValue =
|
|
|
105
125
|
| FoldedIntrinsic
|
|
106
126
|
| FoldedHelperCall
|
|
107
127
|
| SymbolicValue
|
|
108
|
-
| FoldedResource
|
|
128
|
+
| FoldedResource
|
|
129
|
+
| FoldedCompositeStepCall;
|
|
109
130
|
|
|
110
131
|
/**
|
|
111
132
|
* Symbolic reference produced when a property/element access resolves to an
|
|
@@ -225,6 +246,40 @@ export interface FoldedResource {
|
|
|
225
246
|
args?: FoldedValue[];
|
|
226
247
|
}
|
|
227
248
|
|
|
249
|
+
/**
|
|
250
|
+
* The result of folding the `<Identifier>(...).step` composite-consumer
|
|
251
|
+
* idiom (chant #1174) — `Checkout({...}).step`, `SetupNode({...}).step`,
|
|
252
|
+
* every lexicon's single-action `Composite()` wrapper embedded inline in a
|
|
253
|
+
* `Job`'s `steps` array, exactly as composites.mdx documents it. This is the
|
|
254
|
+
* SAME shape chant #1544 already carved out of EVL001 as a documented,
|
|
255
|
+
* correct fallback rather than a lint error
|
|
256
|
+
* ({@link "./subset"}'s `allowCompositeStepAccess`) — `fold()` never set
|
|
257
|
+
* that flag, so before this it fell back to run every time, as designed.
|
|
258
|
+
* This is the fold-side counterpart that actually reduces it instead.
|
|
259
|
+
*
|
|
260
|
+
* Symbolic, exactly like {@link FoldedIntrinsic}/{@link FoldedHelperCall}:
|
|
261
|
+
* `fold()` executes nothing here. It records which composite factory the
|
|
262
|
+
* source named, its folded arguments (in source order), and that the result
|
|
263
|
+
* was immediately narrowed to `.step`. ../discovery/fold-import.ts's bridge
|
|
264
|
+
* resolves the callee through the folding file's own imports — a
|
|
265
|
+
* project-file registered `Composite` is interpreted (chant #1023's existing
|
|
266
|
+
* machinery), anything else (every lexicon-package composite, which is what
|
|
267
|
+
* `Checkout`/`SetupNode` are) is imported and invoked for real, exactly as a
|
|
268
|
+
* top-level `export const x = Checkout({...})` already does via
|
|
269
|
+
* `resolveCallExpression` — and then reads `.step` off the REAL result.
|
|
270
|
+
*
|
|
271
|
+
* Deliberately narrower than "any call, any member access": the member name
|
|
272
|
+
* is fixed to `"step"`, matching the one idiom ../fold/subset.ts's EVL
|
|
273
|
+
* carve-out already permits. `fold()` may never accept a shape EVL doesn't
|
|
274
|
+
* (see that module's doc, point 2c, for the direction it is not allowed to
|
|
275
|
+
* be wrong in) — widening past `.step` here without widening the shared
|
|
276
|
+
* predicate in lockstep would open exactly that gap.
|
|
277
|
+
*/
|
|
278
|
+
export interface FoldedCompositeStepCall {
|
|
279
|
+
__compositeStep: string;
|
|
280
|
+
args: FoldedValue[];
|
|
281
|
+
}
|
|
282
|
+
|
|
228
283
|
/**
|
|
229
284
|
* One entry per exported `const` resource declaration in {@link foldModule}'s
|
|
230
285
|
* result. The `ok: false` case surfaces the same located, rule-id-tagged
|
|
@@ -648,11 +703,57 @@ function resolvesToResource(consts: Map<string, ts.Expression>, ident: ts.Identi
|
|
|
648
703
|
return init !== undefined && ts.isNewExpression(init);
|
|
649
704
|
}
|
|
650
705
|
|
|
706
|
+
/**
|
|
707
|
+
* True when `node` is a call through a bare identifier that isn't ALREADY
|
|
708
|
+
* one of `fold()`'s other three call shapes — a registered authoring helper,
|
|
709
|
+
* a registered call-form intrinsic, or a project-local {@link FoldableFunction}
|
|
710
|
+
* — i.e. exactly the callee the `CallExpression` branch below would
|
|
711
|
+
* otherwise throw {@link callExpressionMessage} for. Used only by the
|
|
712
|
+
* `.step` narrowing in the property-access branch (chant #1174,
|
|
713
|
+
* {@link FoldedCompositeStepCall}): checked from the OUTSIDE, at the
|
|
714
|
+
* property-access node, so `Checkout({...})` alone (no `.step`) still falls
|
|
715
|
+
* through to the ordinary `CallExpression` throw, unchanged.
|
|
716
|
+
*/
|
|
717
|
+
function isUnclaimedBareCall(
|
|
718
|
+
node: ts.Expression,
|
|
719
|
+
consts: Map<string, ts.Expression>,
|
|
720
|
+
intrinsics: readonly IntrinsicDef[],
|
|
721
|
+
externals?: ReadonlyMap<string, unknown>,
|
|
722
|
+
): node is ts.CallExpression {
|
|
723
|
+
if (!ts.isCallExpression(node) || !ts.isIdentifier(node.expression)) return false;
|
|
724
|
+
const name = node.expression.text;
|
|
725
|
+
if (consts.has(name)) return false;
|
|
726
|
+
if (isFoldableHelperName(name)) return false;
|
|
727
|
+
if (intrinsics.some((i) => i.name === name && intrinsicCallFolds(i))) return false;
|
|
728
|
+
if (isFoldableFunction(externals?.get(name))) return false;
|
|
729
|
+
return true;
|
|
730
|
+
}
|
|
731
|
+
|
|
651
732
|
/** True when a folded value is a {@link FoldedResource} envelope (a `new Type(...)` that nothing constructed yet). */
|
|
652
733
|
function isFoldedResource(value: FoldedValue): value is FoldedResource {
|
|
653
734
|
return typeof value === "object" && value !== null && !Array.isArray(value) && "__resource" in value;
|
|
654
735
|
}
|
|
655
736
|
|
|
737
|
+
/**
|
|
738
|
+
* True when `value` is one of `fold()`'s own symbolic envelope shapes — a
|
|
739
|
+
* stand-in for a value nothing has constructed or revived yet, not the value
|
|
740
|
+
* itself. A method call (see the `CallExpression` branch's property-access
|
|
741
|
+
* case below) must refuse one rather than silently falling through to
|
|
742
|
+
* `Object.prototype`'s own inherited methods — `toString` chief among them —
|
|
743
|
+
* which would answer with the placeholder's shape instead of what the real,
|
|
744
|
+
* eventually-revived value would produce.
|
|
745
|
+
*/
|
|
746
|
+
function isFoldSymbolicEnvelope(value: FoldedValue): boolean {
|
|
747
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
748
|
+
return (
|
|
749
|
+
"__attrRef" in value ||
|
|
750
|
+
"__intrinsic" in value ||
|
|
751
|
+
"__helper" in value ||
|
|
752
|
+
"__resource" in value ||
|
|
753
|
+
"__compositeStep" in value
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
|
|
656
757
|
/**
|
|
657
758
|
* chant #1535 — an attribute read whose object folded to a resource ENVELOPE
|
|
658
759
|
* rather than resolving through {@link resolvesToResource}. That happens when
|
|
@@ -892,6 +993,15 @@ export function fold(
|
|
|
892
993
|
if (isFoldableFunction(external)) {
|
|
893
994
|
throw foldError(node, `function "${node.text}" used as a value is not foldable`);
|
|
894
995
|
}
|
|
996
|
+
// chant #1966 — a registered eager-fold lexicon helper (see the
|
|
997
|
+
// CallExpression branch below) is callable, never a bare value:
|
|
998
|
+
// nothing downstream can serialize a function.
|
|
999
|
+
if (
|
|
1000
|
+
typeof external === "function" &&
|
|
1001
|
+
intrinsics.some((i) => i.name === node.text && intrinsicCallFoldsEagerly(i))
|
|
1002
|
+
) {
|
|
1003
|
+
throw foldError(node, `function "${node.text}" used as a value is not foldable — call it instead`);
|
|
1004
|
+
}
|
|
895
1005
|
return external as FoldedValue;
|
|
896
1006
|
}
|
|
897
1007
|
// chant #1064 — a bare `process` reference is ALWAYS an ambient
|
|
@@ -948,6 +1058,21 @@ export function fold(
|
|
|
948
1058
|
if (ts.isIdentifier(node.expression) && resolvesToResource(consts, node.expression)) {
|
|
949
1059
|
return { __attrRef: { entity: node.expression.text, attribute: node.name.text } };
|
|
950
1060
|
}
|
|
1061
|
+
// chant #1174 — `<Identifier>(...).step`, the composite-consumer idiom
|
|
1062
|
+
// (`Checkout({...}).step`) — see FoldedCompositeStepCall's doc. Checked
|
|
1063
|
+
// here, at the property-access node, rather than inside the
|
|
1064
|
+
// CallExpression branch: a bare `Checkout({...})` with no `.step` still
|
|
1065
|
+
// has no case there and throws exactly as before.
|
|
1066
|
+
if (node.name.text === "step" && isUnclaimedBareCall(node.expression, consts, intrinsics, externals)) {
|
|
1067
|
+
const call = node.expression;
|
|
1068
|
+
const calleeName = (call.expression as ts.Identifier).text;
|
|
1069
|
+
const inside = insideFunctionBody(node, `composite call \`${calleeName}(...).step\``);
|
|
1070
|
+
if (inside) throw inside;
|
|
1071
|
+
return {
|
|
1072
|
+
__compositeStep: calleeName,
|
|
1073
|
+
args: call.arguments.map((arg) => fold(arg, consts, intrinsics, externals)),
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
951
1076
|
const obj = fold(node.expression, consts, intrinsics, externals);
|
|
952
1077
|
if (obj === null || obj === undefined) return undefined;
|
|
953
1078
|
if (isFoldedResource(obj)) return attrRefOnFoldedResource(node, node.name.text);
|
|
@@ -1146,6 +1271,62 @@ export function fold(
|
|
|
1146
1271
|
}
|
|
1147
1272
|
}
|
|
1148
1273
|
|
|
1274
|
+
// chant #1966 — the fourth call shape: a lexicon-package function its
|
|
1275
|
+
// lexicon registered with {@link intrinsicCallFoldsEagerly} (../lexicon.ts),
|
|
1276
|
+
// resolved into `externals` by ../discovery/fold-import.ts's
|
|
1277
|
+
// `resolveActiveLexiconExport` exactly like a plain data export
|
|
1278
|
+
// (`Azure.ResourceGroupLocation`, chant #1063), except callable. Unlike
|
|
1279
|
+
// the intrinsic-call shape above, this one is EVALUATED right here rather
|
|
1280
|
+
// than enveloped: a lexicon's own string-building helper (`matrix("os")`,
|
|
1281
|
+
// github lexicon) is typically embedded directly in a template literal
|
|
1282
|
+
// (`` `${matrix("os")}` ``), which coerces its result via native
|
|
1283
|
+
// `String()` at fold time — an envelope deferred to later revival would
|
|
1284
|
+
// stringify as "[object Object]" there. Evaluating eagerly, with the
|
|
1285
|
+
// folded arguments, produces the real, already-live return value instead
|
|
1286
|
+
// — the same guarantee a live external's own getter execution already
|
|
1287
|
+
// gives {@link fold}'s property-access branch.
|
|
1288
|
+
if (
|
|
1289
|
+
ts.isIdentifier(node.expression) &&
|
|
1290
|
+
!consts.has(node.expression.text) &&
|
|
1291
|
+
intrinsics.some((i) => i.name === (node.expression as ts.Identifier).text && intrinsicCallFoldsEagerly(i))
|
|
1292
|
+
) {
|
|
1293
|
+
const callee = externals?.get(node.expression.text);
|
|
1294
|
+
if (typeof callee !== "function") {
|
|
1295
|
+
throw foldError(node, `"${node.expression.text}" did not resolve to a function — falls back to run`);
|
|
1296
|
+
}
|
|
1297
|
+
const args = node.arguments.map((arg) => fold(arg, consts, intrinsics, externals));
|
|
1298
|
+
return (callee as (...callArgs: unknown[]) => unknown)(...args) as FoldedValue;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
// chant #1966 — a method call whose RECEIVER is itself foldable: property
|
|
1302
|
+
// access on a live external (`github.actor.toString()`), a call folded by
|
|
1303
|
+
// one of the shapes above (`matrix("os").toString()`), or fold's own
|
|
1304
|
+
// array/object literal (`[...].join(",")`). The method is never checked
|
|
1305
|
+
// by name — only that the receiver is a REAL value (not one of fold's own
|
|
1306
|
+
// symbolic envelopes, see {@link isFoldSymbolicEnvelope}) and that the
|
|
1307
|
+
// named property on it is actually a function. Calling it with the folded
|
|
1308
|
+
// arguments is then no different from what running the file would do:
|
|
1309
|
+
// the receiver is the same real object either way.
|
|
1310
|
+
if (ts.isPropertyAccessExpression(node.expression)) {
|
|
1311
|
+
const methodName = node.expression.name.text;
|
|
1312
|
+
const receiver = fold(node.expression.expression, consts, intrinsics, externals);
|
|
1313
|
+
if (receiver === null || receiver === undefined) {
|
|
1314
|
+
throw foldError(node, `cannot call ".${methodName}(...)" on ${String(receiver)}`);
|
|
1315
|
+
}
|
|
1316
|
+
if (isFoldSymbolicEnvelope(receiver)) {
|
|
1317
|
+
throw foldError(
|
|
1318
|
+
node,
|
|
1319
|
+
`method call \`.${methodName}(...)\` on an unresolved value is not foldable — falls back to run`,
|
|
1320
|
+
);
|
|
1321
|
+
}
|
|
1322
|
+
const method = (receiver as Record<string, unknown>)[methodName];
|
|
1323
|
+
if (typeof method !== "function") {
|
|
1324
|
+
throw foldError(node, `"${methodName}" is not a callable method on the folded value — falls back to run`);
|
|
1325
|
+
}
|
|
1326
|
+
const args = node.arguments.map((arg) => fold(arg, consts, intrinsics, externals));
|
|
1327
|
+
return (method as (...methodArgs: unknown[]) => unknown).apply(receiver, args) as FoldedValue;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1149
1330
|
throw foldError(node, callExpressionMessage(node));
|
|
1150
1331
|
}
|
|
1151
1332
|
|