@intentius/chant 0.44.13 → 0.45.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/discover.d.ts +26 -2
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +10 -0
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/rules-doc.d.ts.map +1 -1
- package/dist/cli/build-params-cli.d.ts +16 -7
- package/dist/cli/build-params-cli.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +20 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts +9 -0
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-docs.d.ts +13 -0
- package/dist/cli/commands/check-lexicon-docs.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-examples.d.ts +24 -14
- package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/codegen.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/docs.d.ts +5 -0
- package/dist/cli/commands/init-lexicon/templates/docs.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/onboard.d.ts +28 -0
- package/dist/cli/commands/onboard.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/search.d.ts +27 -0
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/codegen/docs-pages.d.ts +39 -0
- package/dist/codegen/docs-pages.d.ts.map +1 -0
- package/dist/codegen/docs-sections.d.ts.map +1 -1
- package/dist/codegen/docs-sidebar.d.ts +13 -2
- package/dist/codegen/docs-sidebar.d.ts.map +1 -1
- package/dist/codegen/docs-types.d.ts +30 -10
- package/dist/codegen/docs-types.d.ts.map +1 -1
- package/dist/codegen/docs.d.ts +14 -2
- package/dist/codegen/docs.d.ts.map +1 -1
- package/dist/codegen/surface-snapshot.d.ts +27 -3
- package/dist/codegen/surface-snapshot.d.ts.map +1 -1
- package/dist/codegen/validate.d.ts +11 -3
- package/dist/codegen/validate.d.ts.map +1 -1
- package/dist/components/capability-plugin.d.ts +13 -2
- package/dist/components/capability-plugin.d.ts.map +1 -1
- package/dist/components/driver.d.ts +38 -0
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/starter-plugin.d.ts.map +1 -1
- package/dist/config.d.ts +57 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/fold/fold.d.ts +100 -1
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/graph-ir.d.ts +12 -0
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-ops.d.ts +17 -0
- package/dist/graph-ops.d.ts.map +1 -0
- package/dist/lifecycle/change-set.d.ts +23 -2
- package/dist/lifecycle/change-set.d.ts.map +1 -1
- package/dist/lifecycle/deep-observe.d.ts.map +1 -1
- package/dist/lifecycle/observe.d.ts +8 -0
- package/dist/lifecycle/observe.d.ts.map +1 -1
- package/dist/lifecycle/replay.d.ts.map +1 -1
- package/dist/lifecycle/snapshot.d.ts.map +1 -1
- package/dist/lifecycle/types.d.ts +7 -0
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/lint/policy.d.ts.map +1 -1
- package/dist/managed-fields.d.ts +33 -21
- package/dist/managed-fields.d.ts.map +1 -1
- package/dist/observation.d.ts +23 -1
- package/dist/observation.d.ts.map +1 -1
- package/dist/op/index.d.ts +1 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/yaml.d.ts +0 -8
- package/dist/yaml.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__snapshots__/okf.test.ts.snap +20 -0
- package/src/audit/discover.test.ts +55 -1
- package/src/audit/discover.ts +70 -2
- package/src/audit/edge-init-safety.test.ts +149 -18
- package/src/audit/report-model.ts +12 -1
- package/src/audit/rules-doc.ts +1 -0
- package/src/build-params.test.ts +52 -0
- package/src/cli/build-params-cli.test.ts +25 -8
- package/src/cli/build-params-cli.ts +37 -23
- package/src/cli/commands/__fixtures__/audit-coverage/.github/workflows/ci.yml +5 -0
- package/src/cli/commands/__fixtures__/audit-coverage/Dockerfile +2 -0
- package/src/cli/commands/__fixtures__/audit-coverage/infra/main.tf +3 -0
- package/src/cli/commands/__fixtures__/audit-coverage/infra/stack.json +6 -0
- package/src/cli/commands/__fixtures__/audit-coverage/k8s/deploy.yaml +12 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/pages/getting-started.mdx +16 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +10 -2
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate-cli.ts +3 -1
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate.ts +2 -1
- package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +2 -1
- package/src/cli/commands/audit.test.ts +83 -1
- package/src/cli/commands/audit.ts +121 -28
- package/src/cli/commands/build.test.ts +144 -5
- package/src/cli/commands/build.ts +66 -14
- package/src/cli/commands/check-lexicon-docs.ts +30 -0
- package/src/cli/commands/check-lexicon-examples.test.ts +73 -7
- package/src/cli/commands/check-lexicon-examples.ts +66 -17
- package/src/cli/commands/check-lexicon.test.ts +1 -1
- package/src/cli/commands/check-lexicon.ts +19 -4
- package/src/cli/commands/init-lexicon/templates/codegen.ts +5 -2
- package/src/cli/commands/init-lexicon/templates/docs.ts +35 -2
- package/src/cli/commands/init-lexicon.test.ts +42 -1
- package/src/cli/commands/init-lexicon.ts +3 -1
- package/src/cli/commands/lexicon-rolling-upgrade.ts +1 -1
- package/src/cli/commands/lexicon-surface-diff.test.ts +1 -1
- package/src/cli/commands/lexicon-surface-diff.ts +1 -1
- package/src/cli/commands/onboard.test.ts +319 -225
- package/src/cli/commands/onboard.ts +144 -58
- package/src/cli/handlers/build.test.ts +3 -3
- package/src/cli/handlers/build.ts +2 -0
- package/src/cli/handlers/components.ts +2 -2
- package/src/cli/handlers/graph.test.ts +44 -0
- package/src/cli/handlers/graph.ts +22 -2
- package/src/cli/handlers/lifecycle.test.ts +117 -0
- package/src/cli/handlers/lifecycle.ts +16 -0
- package/src/cli/handlers/run.test.ts +5 -5
- package/src/cli/handlers/run.ts +1 -0
- package/src/cli/handlers/search-live.test.ts +217 -0
- package/src/cli/handlers/search.test.ts +41 -1
- package/src/cli/handlers/search.ts +55 -6
- package/src/cli/main.ts +5 -3
- package/src/cli/security-boundary.test.ts +2 -1
- package/src/codegen/docs-pages.test.ts +194 -0
- package/src/codegen/docs-pages.ts +138 -0
- package/src/codegen/docs-sections.ts +0 -8
- package/src/codegen/docs-sidebar.ts +50 -37
- package/src/codegen/docs-types.ts +32 -4
- package/src/codegen/docs.ts +78 -40
- package/src/codegen/lexicon-regen.ts +3 -1
- package/src/codegen/rolling-upgrade.test.ts +1 -1
- package/src/codegen/rolling-upgrade.ts +1 -1
- package/src/codegen/surface-snapshot.test.ts +139 -0
- package/src/codegen/surface-snapshot.ts +297 -122
- package/src/codegen/validate.test.ts +16 -3
- package/src/codegen/validate.ts +17 -7
- package/src/components/auto-release.ts +1 -1
- package/src/components/capability-plugin.ts +23 -2
- package/src/components/driver.test.ts +45 -0
- package/src/components/driver.ts +64 -27
- package/src/components/index.ts +2 -0
- package/src/components/starter-plugin.ts +5 -2
- package/src/config.test.ts +56 -1
- package/src/config.ts +152 -8
- package/src/discovery/fold-import.test.ts +427 -2
- package/src/discovery/fold-import.ts +163 -7
- package/src/discovery/sandbox/driver.test.ts +11 -0
- package/src/fold/fold.test.ts +41 -0
- package/src/fold/fold.ts +372 -2
- package/src/graph-ir-live.test.ts +42 -0
- package/src/graph-ir.ts +42 -2
- package/src/graph-ops.test.ts +69 -0
- package/src/graph-ops.ts +42 -0
- package/src/lifecycle/change-set.test.ts +41 -0
- package/src/lifecycle/change-set.ts +30 -2
- package/src/lifecycle/deep-observe.ts +4 -0
- package/src/lifecycle/observe.ts +29 -2
- package/src/lifecycle/replay.ts +3 -0
- package/src/lifecycle/snapshot.ts +3 -0
- package/src/lifecycle/types.ts +7 -0
- package/src/lint/policy.ts +14 -3
- package/src/managed-fields.test.ts +15 -42
- package/src/managed-fields.ts +43 -26
- package/src/observation.test.ts +24 -8
- package/src/observation.ts +48 -4
- package/src/op/builders-exports.test.ts +60 -0
- package/src/op/index.ts +1 -1
- package/src/ownership.test.ts +63 -1
- package/src/yaml.test.ts +15 -0
- package/src/yaml.ts +19 -2
|
@@ -14,6 +14,8 @@ import {
|
|
|
14
14
|
fold,
|
|
15
15
|
propName,
|
|
16
16
|
FoldError,
|
|
17
|
+
FoldableFunction,
|
|
18
|
+
isFoldableFunction,
|
|
17
19
|
locate,
|
|
18
20
|
type FoldedResource,
|
|
19
21
|
type FoldedValue,
|
|
@@ -545,8 +547,15 @@ interface ReExportDeclarator {
|
|
|
545
547
|
elements: Array<{ imported: string; exportedName: string }>;
|
|
546
548
|
}
|
|
547
549
|
|
|
550
|
+
/** `export function name(...) {...}` (chant #1373) — exported as a {@link FoldableFunction} marker so an importer can fold a call to it. The body's own foldability is judged at the CALL, never here: an unfoldable helper nobody calls from foldable source costs its module nothing. */
|
|
551
|
+
interface FunctionDeclarator {
|
|
552
|
+
kind: "function";
|
|
553
|
+
name: string;
|
|
554
|
+
}
|
|
555
|
+
|
|
548
556
|
type ScanDeclarator =
|
|
549
557
|
| ResourceDeclarator
|
|
558
|
+
| FunctionDeclarator
|
|
550
559
|
| SingleDeclarator
|
|
551
560
|
| DestructureDeclarator
|
|
552
561
|
| NamedExportDeclarator
|
|
@@ -563,8 +572,9 @@ interface ScanResult {
|
|
|
563
572
|
* `export const X = <expr>` / `export const { a, b } = <expr>` (composite
|
|
564
573
|
* calls and member access on them, #1023), `export { a, b }` (a local
|
|
565
574
|
* named-export list), and `export { a, b } from "./other"` (a re-export
|
|
566
|
-
* chain, #1020)
|
|
567
|
-
*
|
|
575
|
+
* chain, #1020), and `export function name(...) {...}` (a foldable-function
|
|
576
|
+
* export, #1373). Any OTHER export construct (`export default`, `export *
|
|
577
|
+
* from`, an exported class, `let`/`var`, a destructured export with
|
|
568
578
|
* a rest/nested/defaulted element) makes the whole file ineligible: the
|
|
569
579
|
* module must run so that construct is actually evaluated. This mirrors the
|
|
570
580
|
* epic's hybrid design — fallback is per-module, not per-declaration,
|
|
@@ -629,10 +639,17 @@ function scanExports(sourceFile: ts.SourceFile): ScanResult {
|
|
|
629
639
|
}
|
|
630
640
|
|
|
631
641
|
if (ts.isFunctionDeclaration(statement) && hasExportModifier(statement)) {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
642
|
+
// chant #1373 — a named, bodied exported function is a foldable-function
|
|
643
|
+
// export (see {@link FunctionDeclarator}). An overload signature has no
|
|
644
|
+
// body and no value of its own — skipped, the implementation that
|
|
645
|
+
// follows it is the export. `export default function` has no name an
|
|
646
|
+
// importer could bind and stays a disqualifier, like `export default`.
|
|
647
|
+
if (statement.modifiers?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword) || !statement.name) {
|
|
648
|
+
return { declarators, unfoldableReason: "`export default` is not foldable" };
|
|
649
|
+
}
|
|
650
|
+
if (!statement.body) continue;
|
|
651
|
+
declarators.push({ kind: "function", name: statement.name.text });
|
|
652
|
+
continue;
|
|
636
653
|
}
|
|
637
654
|
if (ts.isClassDeclaration(statement) && hasExportModifier(statement)) {
|
|
638
655
|
return {
|
|
@@ -727,6 +744,54 @@ function collectLocalBindings(sourceFile: ts.SourceFile): Map<string, LocalBindi
|
|
|
727
744
|
return bindings;
|
|
728
745
|
}
|
|
729
746
|
|
|
747
|
+
/** `(x) => …` / `function (x) {…}`, possibly wrapped in parens/`as`/`satisfies` — the initializer shapes that make a `const` a function binding. */
|
|
748
|
+
function unwrapFunctionInitializer(node: ts.Expression): ts.ArrowFunction | ts.FunctionExpression | undefined {
|
|
749
|
+
let current = node;
|
|
750
|
+
while (ts.isParenthesizedExpression(current) || ts.isAsExpression(current) || ts.isSatisfiesExpression(current)) {
|
|
751
|
+
current = current.expression;
|
|
752
|
+
}
|
|
753
|
+
return ts.isArrowFunction(current) || ts.isFunctionExpression(current) ? current : undefined;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* chant #1373 — every top-level function a file declares, exported or not, as
|
|
758
|
+
* a {@link FoldableFunction}: `function f(...) {...}` and `const f = (...) =>
|
|
759
|
+
* …`. Placed in the file's own `externals` (so a same-file call folds, and a
|
|
760
|
+
* sibling function can call it) and exported under its name (so an importer's
|
|
761
|
+
* `buildExternals` can). The marker carries the file's scope BY REFERENCE:
|
|
762
|
+
* `ctx.externals` is still being filled (pre-built resources land in it after
|
|
763
|
+
* this runs) and a body reads it only when called.
|
|
764
|
+
*
|
|
765
|
+
* The marker's `consts` drops every `new`-bound const (transitively, through
|
|
766
|
+
* aliases) — see {@link FoldableFunction.consts} for why a body must reach a
|
|
767
|
+
* module-level resource through `externals` only.
|
|
768
|
+
*/
|
|
769
|
+
function collectLocalFunctions(sourceFile: ts.SourceFile, ctx: ResolveCtx): Map<string, FoldableFunction> {
|
|
770
|
+
const consts = new Map(ctx.consts);
|
|
771
|
+
for (const [name] of [...consts]) {
|
|
772
|
+
if (constResolvesToResource(ctx.consts, name, new Set())) consts.delete(name);
|
|
773
|
+
}
|
|
774
|
+
const functions = new Map<string, FoldableFunction>();
|
|
775
|
+
const add = (name: string, fn: ts.FunctionDeclaration | ts.ArrowFunction | ts.FunctionExpression): void => {
|
|
776
|
+
functions.set(name, new FoldableFunction(name, fn, ctx.file, consts, ctx.externals, ctx.crossFileFailures));
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
for (const statement of sourceFile.statements) {
|
|
780
|
+
if (ts.isFunctionDeclaration(statement)) {
|
|
781
|
+
if (statement.name && statement.body) add(statement.name.text, statement);
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
if (!ts.isVariableStatement(statement)) continue;
|
|
785
|
+
if ((statement.declarationList.flags & ts.NodeFlags.Const) === 0) continue;
|
|
786
|
+
for (const decl of statement.declarationList.declarations) {
|
|
787
|
+
if (!ts.isIdentifier(decl.name) || !decl.initializer) continue;
|
|
788
|
+
const fn = unwrapFunctionInitializer(decl.initializer);
|
|
789
|
+
if (fn) add(decl.name.text, fn);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return functions;
|
|
793
|
+
}
|
|
794
|
+
|
|
730
795
|
// ─────────────────────────────────────────────────────────────────────────
|
|
731
796
|
// Import resolution — unchanged from #1022, shared by the resource and
|
|
732
797
|
// composite-call resolution paths.
|
|
@@ -1208,7 +1273,14 @@ async function resolveLiveValue(node: ts.Expression, ctx: ResolveCtx): Promise<L
|
|
|
1208
1273
|
// exports object). Absent from `externals` falls through to
|
|
1209
1274
|
// `undefined`, exactly the pre-#1020 behavior for any unbound name.
|
|
1210
1275
|
if (ctx.externals.has(node.text)) {
|
|
1211
|
-
|
|
1276
|
+
const external = ctx.externals.get(node.text);
|
|
1277
|
+
// chant #1373 — a function marker is callable, never a value. The
|
|
1278
|
+
// export-by-name cases (`export const g = f`, `export { f }`) are
|
|
1279
|
+
// handled by the declarator loop before it gets here.
|
|
1280
|
+
if (isFoldableFunction(external)) {
|
|
1281
|
+
throw cheapError(`function "${node.text}" used as a value is not foldable`);
|
|
1282
|
+
}
|
|
1283
|
+
return { value: external };
|
|
1212
1284
|
}
|
|
1213
1285
|
return undefined;
|
|
1214
1286
|
}
|
|
@@ -1278,10 +1350,48 @@ async function resolveCallExpression(node: ts.CallExpression, ctx: ResolveCtx):
|
|
|
1278
1350
|
}
|
|
1279
1351
|
const calleeName = node.expression.text;
|
|
1280
1352
|
const binding = ctx.imports.get(calleeName);
|
|
1353
|
+
|
|
1354
|
+
// chant #1373 — a project-local function with a foldable body (declared in
|
|
1355
|
+
// this file, or imported from a sibling project file) is evaluated
|
|
1356
|
+
// statically, through the same `fold()` branch a NESTED call to it takes.
|
|
1357
|
+
// When its body turns out not to fold, an IMPORTED callee still has the
|
|
1358
|
+
// pre-#1373 path below (interpret as a composite, else import and invoke) —
|
|
1359
|
+
// a helper that wraps a composite call, say, keeps folding by invocation
|
|
1360
|
+
// exactly as it did. A same-file callee has nothing to invoke without
|
|
1361
|
+
// running the file, so the fold diagnostic is the verdict.
|
|
1362
|
+
const local = ctx.externals.get(calleeName);
|
|
1363
|
+
if (isFoldableFunction(local)) {
|
|
1364
|
+
let foldFailure: Error | undefined;
|
|
1365
|
+
try {
|
|
1366
|
+
const folded = fold(node, ctx.consts, ctx.intrinsics, ctx.externals);
|
|
1367
|
+
return await reviveFoldedValue(folded, ctx, false);
|
|
1368
|
+
} catch (err) {
|
|
1369
|
+
if (!(err instanceof Error)) throw err;
|
|
1370
|
+
foldFailure = err;
|
|
1371
|
+
}
|
|
1372
|
+
if (!binding) throw foldFailure;
|
|
1373
|
+
try {
|
|
1374
|
+
return await resolveImportedCall(node, calleeName, binding, ctx);
|
|
1375
|
+
} catch (err) {
|
|
1376
|
+
throw cheapError(
|
|
1377
|
+
`${foldFailure.message}; invoking it instead failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1378
|
+
);
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1281
1382
|
if (!binding) {
|
|
1282
1383
|
throw cheapError(callExpressionMessage(node));
|
|
1283
1384
|
}
|
|
1385
|
+
return resolveImportedCall(node, calleeName, binding, ctx);
|
|
1386
|
+
}
|
|
1284
1387
|
|
|
1388
|
+
/** The pre-#1373 two-arm resolution of an IMPORTED callee — see {@link resolveCallExpression}. */
|
|
1389
|
+
async function resolveImportedCall(
|
|
1390
|
+
node: ts.CallExpression,
|
|
1391
|
+
calleeName: string,
|
|
1392
|
+
binding: ImportBinding,
|
|
1393
|
+
ctx: ResolveCtx,
|
|
1394
|
+
): Promise<unknown> {
|
|
1285
1395
|
// chant #1023 — is this callee a composite whose body can be INTERPRETED
|
|
1286
1396
|
// rather than run? Purely a static question (does the defining module parse,
|
|
1287
1397
|
// does it declare this export as `Composite(<fn>, "<name>")`, is `<fn>`'s
|
|
@@ -3108,6 +3218,10 @@ async function buildExternals(
|
|
|
3108
3218
|
* falls back. See {@link FoldFileResult.liveSources}.
|
|
3109
3219
|
*/
|
|
3110
3220
|
function hasObjectIdentity(value: unknown): boolean {
|
|
3221
|
+
// chant #1373 — a foldable-function marker is a description of source, not
|
|
3222
|
+
// an object the run path could hold a different copy of: a call to it
|
|
3223
|
+
// produces the same value folded or run. No identity to disagree about.
|
|
3224
|
+
if (isFoldableFunction(value)) return false;
|
|
3111
3225
|
return value !== null && (typeof value === "object" || typeof value === "function");
|
|
3112
3226
|
}
|
|
3113
3227
|
|
|
@@ -3143,6 +3257,19 @@ async function resolveDeclaratorValue(node: ts.Expression, ctx: ResolveCtx): Pro
|
|
|
3143
3257
|
* entry point below.
|
|
3144
3258
|
*/
|
|
3145
3259
|
async function tryFoldFileCore(file: string, session: FoldSession): Promise<FoldFileResult> {
|
|
3260
|
+
// chant #1373 — chant-core's own modules are library code, not project
|
|
3261
|
+
// source: their exports are reached by importing them (a helper, a
|
|
3262
|
+
// constructor, `params`), never by folding their bodies. Before #1373 every
|
|
3263
|
+
// one of them happened to disqualify itself at the scan (each exports a
|
|
3264
|
+
// function); now that an exported function is a foldable shape, the
|
|
3265
|
+
// exclusion has to be explicit, or a fixture/in-repo import by path would
|
|
3266
|
+
// walk fold into chant's own tree and fold `params` to an empty object.
|
|
3267
|
+
{
|
|
3268
|
+
const root = chantCoreRoot();
|
|
3269
|
+
if (file === root || file.startsWith(root + sep)) {
|
|
3270
|
+
return { ok: false, reason: "chant's own module is not project source" };
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3146
3273
|
try {
|
|
3147
3274
|
const source = await readFile(file, "utf-8");
|
|
3148
3275
|
const sourceFile = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, /* setParentNodes */ true);
|
|
@@ -3177,10 +3304,24 @@ async function tryFoldFileCore(file: string, session: FoldSession): Promise<Fold
|
|
|
3177
3304
|
// {@link preresolveResourceConsts}.
|
|
3178
3305
|
const prebuiltResources = await preresolveResourceConsts(ctx);
|
|
3179
3306
|
|
|
3307
|
+
// chant #1373 — this file's own functions, callable from its own folds
|
|
3308
|
+
// (and, once exported, from an importer's). Registered after the
|
|
3309
|
+
// resource pre-pass so a marker never shadows a pre-built instance; a
|
|
3310
|
+
// function and a const cannot share a name in valid TypeScript anyway.
|
|
3311
|
+
const localFunctions = collectLocalFunctions(sourceFile, ctx);
|
|
3312
|
+
for (const [name, marker] of localFunctions) {
|
|
3313
|
+
if (!ctx.externals.has(name)) ctx.externals.set(name, marker);
|
|
3314
|
+
}
|
|
3315
|
+
|
|
3180
3316
|
const entities: FoldedEntity[] = [];
|
|
3181
3317
|
const exportedValues = new Map<string, unknown>();
|
|
3182
3318
|
|
|
3183
3319
|
for (const decl of scan.declarators) {
|
|
3320
|
+
if (decl.kind === "function") {
|
|
3321
|
+
applyResolvedValue(decl.name, localFunctions.get(decl.name), entities, exportedValues);
|
|
3322
|
+
continue;
|
|
3323
|
+
}
|
|
3324
|
+
|
|
3184
3325
|
if (decl.kind === "resource") {
|
|
3185
3326
|
// chant #1169 — the pre-pass already built this exact node. Reuse that
|
|
3186
3327
|
// instance rather than constructing a second one: a sibling prop that
|
|
@@ -3199,6 +3340,15 @@ async function tryFoldFileCore(file: string, session: FoldSession): Promise<Fold
|
|
|
3199
3340
|
}
|
|
3200
3341
|
|
|
3201
3342
|
if (decl.kind === "single") {
|
|
3343
|
+
// chant #1373 — `export const f = (…) => …` exports the function
|
|
3344
|
+
// marker, exactly like `export function f`; so does an alias of one
|
|
3345
|
+
// (`export const g = f`, `f` declared here or imported).
|
|
3346
|
+
const aliased = ts.isIdentifier(decl.node) ? ctx.externals.get(decl.node.text) : undefined;
|
|
3347
|
+
const marker = localFunctions.get(decl.name) ?? (isFoldableFunction(aliased) ? aliased : undefined);
|
|
3348
|
+
if (marker) {
|
|
3349
|
+
applyResolvedValue(decl.name, marker, entities, exportedValues);
|
|
3350
|
+
continue;
|
|
3351
|
+
}
|
|
3202
3352
|
let value: unknown;
|
|
3203
3353
|
try {
|
|
3204
3354
|
value = (await resolveDeclaratorValue(decl.node, ctx)).value;
|
|
@@ -3244,6 +3394,12 @@ async function tryFoldFileCore(file: string, session: FoldSession): Promise<Fold
|
|
|
3244
3394
|
// (ctx.locals/ctx.externals lookup + memoized destructured-member
|
|
3245
3395
|
// extraction) rather than duplicating it here.
|
|
3246
3396
|
for (const { localNameNode, exportedName } of decl.elements) {
|
|
3397
|
+
// chant #1373 — `function f() {…}; export { f }`.
|
|
3398
|
+
const marker = localFunctions.get(localNameNode.text);
|
|
3399
|
+
if (marker) {
|
|
3400
|
+
applyResolvedValue(exportedName, marker, entities, exportedValues);
|
|
3401
|
+
continue;
|
|
3402
|
+
}
|
|
3247
3403
|
let value: unknown;
|
|
3248
3404
|
try {
|
|
3249
3405
|
value = (await resolveDeclaratorValue(localNameNode, ctx)).value;
|
|
@@ -27,6 +27,17 @@ describe("generateDriverSource — build-time parameters (#1108)", () => {
|
|
|
27
27
|
expect(bindAt).toBeLessThan(firstImportAt);
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
+
test("an unset optional parameter is absent from the snapshot, not carried as null (#1371)", () => {
|
|
31
|
+
// The snapshot crosses into the child as JSON. JSON has no `undefined`,
|
|
32
|
+
// so "unset" must be expressed by omitting the key — a `null` here would
|
|
33
|
+
// bind `params.<name>` to a value in the child while the parent's fold
|
|
34
|
+
// path reads `undefined`.
|
|
35
|
+
setBuildParams({ tier: "light" });
|
|
36
|
+
const source = generateDriverSource({ files: ["/tmp/project/a.ts"], buildRoot: "/tmp/project" });
|
|
37
|
+
expect(source).toContain(`setBuildParams(${JSON.stringify({ tier: "light" })});`);
|
|
38
|
+
expect(source).not.toContain("null");
|
|
39
|
+
});
|
|
40
|
+
|
|
30
41
|
test("with no params resolved, binds an empty snapshot (explicit, not absent)", () => {
|
|
31
42
|
const source = generateDriverSource({ files: ["/tmp/project/a.ts"], buildRoot: "/tmp/project" });
|
|
32
43
|
expect(source).toContain("setBuildParams({});");
|
package/src/fold/fold.test.ts
CHANGED
|
@@ -151,6 +151,47 @@ describe("fold — cross-resource attribute refs", () => {
|
|
|
151
151
|
expect(foldResult).toEqual(runResult);
|
|
152
152
|
expect(foldResult).toEqual({ __attrRef: { entity: "bucket", attribute: "arn" } });
|
|
153
153
|
});
|
|
154
|
+
|
|
155
|
+
// chant #1535 — the const is not a bare `new` but a conditional that yields
|
|
156
|
+
// one. Indexing the folded `{__resource}` envelope by the attribute name
|
|
157
|
+
// returned `undefined`, so `Principal: { Federated: provider.Arn }` emitted
|
|
158
|
+
// `Principal: {}` with no diagnostic.
|
|
159
|
+
test("property access on a const bound to a conditional `new` becomes the same symbolic ref", () => {
|
|
160
|
+
const src = `
|
|
161
|
+
const flag = true;
|
|
162
|
+
const provider = flag ? new OIDCProvider({ Url: "https://x" }) : undefined;
|
|
163
|
+
const ref = provider.Arn;
|
|
164
|
+
const nested = { Principal: { Federated: provider ? provider.Arn : "fallback" } };
|
|
165
|
+
`;
|
|
166
|
+
expect(foldConst(src, "ref")).toEqual({ __attrRef: { entity: "provider", attribute: "Arn" } });
|
|
167
|
+
expect(foldConst(src, "nested")).toEqual({
|
|
168
|
+
Principal: { Federated: { __attrRef: { entity: "provider", attribute: "Arn" } } },
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("element access on a const bound to a conditional `new` becomes the same symbolic ref", () => {
|
|
173
|
+
const src = `
|
|
174
|
+
const provider = true ? new OIDCProvider({}) : undefined;
|
|
175
|
+
const ref = provider["Arn"];
|
|
176
|
+
`;
|
|
177
|
+
expect(foldConst(src, "ref")).toEqual({ __attrRef: { entity: "provider", attribute: "Arn" } });
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("the untaken branch still folds to undefined, as the run path would see it", () => {
|
|
181
|
+
const src = `
|
|
182
|
+
const provider = false ? new OIDCProvider({}) : undefined;
|
|
183
|
+
const ref = provider ? provider.Arn : "fallback";
|
|
184
|
+
`;
|
|
185
|
+
expect(foldConst(src, "ref")).toBe("fallback");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("attribute read on an inline resource expression throws a located FoldError rather than folding to undefined", () => {
|
|
189
|
+
const src = `
|
|
190
|
+
const ref = (true ? new OIDCProvider({}) : undefined).Arn;
|
|
191
|
+
`;
|
|
192
|
+
expect(() => foldConst(src, "ref")).toThrow(FoldError);
|
|
193
|
+
expect(() => foldConst(src, "ref")).toThrow(/attribute "Arn" read on an inline resource expression is not foldable/);
|
|
194
|
+
});
|
|
154
195
|
});
|
|
155
196
|
|
|
156
197
|
describe("fold — element access", () => {
|