@intentius/chant 0.71.1 → 0.72.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.
@@ -252,7 +252,20 @@ export declare function lexiconPackageName(lexiconName: string): string;
252
252
  * fold may not import or invoke anything outside the trusted allowlist —
253
253
  * see {@link FoldSession.sandbox}.
254
254
  */
255
- export declare function createFoldSession(intrinsics?: readonly IntrinsicDef[], buildParams?: Readonly<Record<string, BuildParamValue>>, lexicons?: readonly string[], sandbox?: boolean): FoldSession;
255
+ export declare function createFoldSession(intrinsics?: readonly IntrinsicDef[], buildParams?: Readonly<Record<string, BuildParamValue>>, lexicons?: readonly string[], sandbox?: boolean,
256
+ /**
257
+ * chant#2438 — package specifiers to allow verbatim, alongside the ones
258
+ * derived from `lexicons`.
259
+ *
260
+ * A chant build's active packages are always `@intentius/chant-lexicon-*`,
261
+ * so naming lexicons is the whole story there. A caller embedding the fold
262
+ * path against a host that is not a chant lexicon has packages the naming
263
+ * convention cannot express: the specification's conformance harness supplies
264
+ * `@tsad/shapes`, which is its host's own specifier. The boundary is
265
+ * unchanged, an explicitly named package of this build, only now it can be
266
+ * named as a specifier rather than only as a lexicon.
267
+ */
268
+ lexiconPackages?: readonly string[]): FoldSession;
256
269
  /**
257
270
  * What the fold pass actually EXECUTED, and what it interpreted instead — the
258
271
  * number chant #1023 is measured by, and the one the #1093/#1111
@@ -515,6 +528,20 @@ export interface FoldProjectOptions {
515
528
  readonly buildParams?: Readonly<Record<string, BuildParamValue>>;
516
529
  /** chant #1093: this build asked for the sandbox, so fold may not reach outside the trusted allowlist. */
517
530
  readonly sandbox?: boolean;
531
+ /**
532
+ * chant#2438 — package specifiers to follow a bare import into, verbatim,
533
+ * alongside whatever `lexicons` names.
534
+ *
535
+ * For a chant project this is never needed: an active package is always
536
+ * `@intentius/chant-lexicon-<name>`, which `lexicons` already expresses. It
537
+ * is here for a caller driving the fold path against a host of its own, where
538
+ * the package has a name chant's convention cannot spell. The specification's
539
+ * conformance harness is the first such caller, with `@tsad/shapes`.
540
+ *
541
+ * The boundary does not move. This is still an allowlist the caller states
542
+ * outright, matched by text, and absent it nothing bare resolves at all.
543
+ */
544
+ readonly lexiconPackages?: readonly string[];
518
545
  }
519
546
  /** One file's place in a whole-build fold, as {@link foldProject} reports it. */
520
547
  export interface FoldProjectVerdict {
@@ -1 +1 @@
1
- {"version":3,"file":"fold-import.d.ts","sourceRoot":"","sources":["../../src/discovery/fold-import.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fold-import.d.ts","sourceRoot":"","sources":["../../src/discovery/fold-import.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAMjC,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAkC,KAAK,iBAAiB,EAAyB,MAAM,cAAc,CAAC;AAyC7G,OAAO,EAA6B,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AAEH,mGAAmG;AACnG,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,iBAAiB,CAAC,CAAC;AAElF,MAAM,MAAM,cAAc,GACtB;IACE,EAAE,EAAE,IAAI,CAAC;IACT;;;;;;;;OAQG;IACH,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB;;;;;;;;;;;;;OAaG;IACH,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC;;;;;;;;;;;;OAYG;IACH,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAClC,GACD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,UAAU,EAAE,SAAS,YAAY,EAAE,CAAC;IAC7C,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC;IACrD,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC;IACzB;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,QAAQ,CAAC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACpE;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/C;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC;IACjE;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC9C;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAAC,CAAC;CAC/E;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,GAAE,SAAS,YAAY,EAAO,EACxC,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,EACvD,QAAQ,GAAE,SAAS,MAAM,EAAO,EAChC,OAAO,UAAQ;AACf;;;;;;;;;;;GAWG;AACH,eAAe,GAAE,SAAS,MAAM,EAAO,GACtC,WAAW,CAYb;AA2ID;;;;;;;;;;GAUG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B;;;;;;OAMG;IACH,yBAAyB,EAAE,MAAM,CAAC;IAClC;;;OAGG;IACH,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAQD,gKAAgK;AAChK,wBAAgB,mBAAmB,IAAI,QAAQ,CAAC,mBAAmB,CAAC,CAEnE;AAED,0EAA0E;AAC1E,wBAAgB,wBAAwB,IAAI,IAAI,CAI/C;AA2RD,oDAAoD;AACpD,UAAU,aAAa;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB;;;yEAGqE;IACrE,aAAa,EAAE,EAAE,CAAC,aAAa,CAAC;CACjC;AAED,4FAA4F;AAC5F,UAAU,sBAAsB;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,EAAE,CAAC,aAAa,CAAC;CACjC;AA09BD;;;;;;;;;;GAUG;AACH,UAAU,kBAAkB;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC;IAC1B;;;;;;;;;;;;;OAaG;IACH,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC;IACnC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACpC,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;IACtD;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAE,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC,CAAC;CACxF;AAqKD;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,EAAE,EAAE,EAAE,CAAC,aAAa,GAAG,EAAE,CAAC,kBAAkB,GAAG,MAAM,GAAG,SAAS,CA0D3G;AAsrDD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,UAAU,GAAE,SAAS,YAAY,EAAO,EACxC,OAAO,CAAC,EAAE,WAAW,GACpB,OAAO,CAAC,cAAc,CAAC,CAEzB;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AACH;;;;;;;;;;;;GAYG;AACH,wBAAsB,uBAAuB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAwCzG;AAED,wBAAsB,aAAa,CACjC,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,EACvC,WAAW,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,GACrD,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAE9B;AAED,yDAAyD;AACzD,MAAM,MAAM,aAAa;AACvB,oFAAoF;AAClF,UAAU;AACZ,+EAA+E;GAC7E,SAAS,CAAC;AAEd,MAAM,WAAW,SAAS;IACxB,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACtC;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,aAAa,CAAA;KAAE,CAAC,CAAC;CAChF;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,EACvC,WAAW,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,GACrD,OAAO,CAAC,SAAS,CAAC,CAkDpB;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,qFAAqF;IACrF,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC;IACjE,0GAA0G;IAC1G,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9C;AAED,iFAAiF;AACjF,MAAM,WAAW,kBAAkB;IACjC,0CAA0C;IAC1C,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,CAAC;IACjC,yEAAyE;IACzE,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,KAAK,CAAC;IACnC,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,QAAQ,CAAC,SAAS,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,aAAa,CAAA;KAAE,CAAC;IAC3D,mFAAmF;IACnF,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjD;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,UAAU,GAAE,SAAS,YAAY,EAAO,EACxC,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAoC1C"}
@@ -411,6 +411,35 @@ export declare class FoldableFunction {
411
411
  */
412
412
  leakedIdentity: boolean;
413
413
  }
414
+ /**
415
+ * True when a module-level `const` is (transitively) bound to a `new Type(...)`.
416
+ * Follows an identifier chain (`const a = new T(); const b = a;`) so aliasing
417
+ * cannot smuggle a module-level resource into a function body.
418
+ *
419
+ * chant#2436 moved this here from ../discovery/fold-import.ts so the two places
420
+ * that build {@link FoldableFunction}s share one implementation rather than two
421
+ * that can drift.
422
+ */
423
+ export declare function constResolvesToResource(consts: ReadonlyMap<string, ts.Expression>, name: string, seen?: Set<string>): boolean;
424
+ /**
425
+ * Every top-level function a file declares, exported or not, as a
426
+ * {@link FoldableFunction}: `function f(...) {...}` and `const f = (...) => …`.
427
+ *
428
+ * The markers carry `externals` BY REFERENCE, so the caller may keep filling it
429
+ * after this returns (which is how a sibling function, or a resource built
430
+ * later, becomes reachable from a body).
431
+ *
432
+ * `consts` drops every `new`-bound const, transitively through aliases: a body
433
+ * that mentions one must read the live instance out of `externals`, the same
434
+ * object the module's own fold registered, never re-fold the initializer and
435
+ * construct a duplicate of a resource discovery already has.
436
+ *
437
+ * chant#2436 — one implementation, called both by ../discovery/fold-import.ts
438
+ * during a build and by {@link fold} when a unit-level fold has no externals of
439
+ * its own. Getting the `new`-bound stripping wrong yields a wrong value rather
440
+ * than a fallback to run, which is why there is only one copy of it.
441
+ */
442
+ export declare function collectSameFileFunctions(sourceFile: ts.SourceFile, consts: ReadonlyMap<string, ts.Expression>, file: string, externals: ReadonlyMap<string, unknown>, failures?: ReadonlyMap<string, string>): Map<string, FoldableFunction>;
414
443
  export declare function isFoldableFunction(value: unknown): value is FoldableFunction;
415
444
  /**
416
445
  * The SHAPE half of what makes a project-local function foldable (chant
@@ -1 +1 @@
1
- {"version":3,"file":"fold.d.ts","sourceRoot":"","sources":["../../src/fold/fold.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fold.d.ts","sourceRoot":"","sources":["../../src/fold/fold.ts"],"names":[],"mappings":"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"}
@@ -46,7 +46,16 @@ import { type IntrinsicDef } from "../lexicon.js";
46
46
  * expression, its operator, and (for keys) its literal-ness. It
47
47
  * deliberately does NOT resolve bindings or perform any evaluation, because
48
48
  * three things a full fold needs are inherently environment-dependent and
49
- * cannot be recovered from shape alone:
49
+ * cannot be recovered from shape alone.
50
+ *
51
+ * One thing it does read beyond the expression itself, since chant#2435: the
52
+ * enclosing file's top-level bindings, for `S-CallLocal`. A call to a
53
+ * project-local function is admitted by NAME (see
54
+ * {@link collectLocalCallables}), which is decidable from that file's syntax
55
+ * and needs no resolution. Before that, this module rejected all four shapes
56
+ * the build folds, which is the one direction `F-Direction` forbids, and it
57
+ * made `chant lint` report EVL001 on a file `chant build --fold` folds
58
+ * cleanly. The three genuinely environment-dependent cases are:
50
59
  *
51
60
  * 1. Identifier *resolution* — is a bare name actually a local `const`,
52
61
  * vs. an unbound name? That needs the file's `consts` map. Both
@@ -196,6 +205,30 @@ export declare function isLiteralElementKey(node: ts.Expression): node is ts.Str
196
205
  * one-line reason either.
197
206
  */
198
207
  export declare function briefNodeText(node: ts.Node, maxLength?: number): string;
208
+ /**
209
+ * Every name a file binds to something a project-local call can reach.
210
+ *
211
+ * Two bindings, both of which the specification's `S-LocalFunction` names and
212
+ * both decidable from this file's own syntax, which is all a shape classifier
213
+ * has:
214
+ *
215
+ * - a top-level `function f(...) {...}`, or a `const f = (...) => ...`
216
+ * (a function expression counts, and parentheses / `as` / `satisfies`
217
+ * wrappers are unwrapped), exported or not;
218
+ * - a name imported from a *project* specifier, one starting `./` or `../`.
219
+ * A bare specifier is a package, and a package's function is not
220
+ * project-local.
221
+ *
222
+ * Deliberately syntax-only, and deliberately not a judgment about the callee's
223
+ * body. Whether the body is admissible is `S-FnBody`'s question and the fold's
224
+ * to answer, exactly as it already is for a call this classifier admits by
225
+ * name (a registered authoring helper, an opted-in intrinsic).
226
+ *
227
+ * Mirrors `collectLocalFunctions` in ../discovery/fold-import.ts, which builds
228
+ * the real `FoldableFunction` for each of these during a build. This one only
229
+ * needs the names.
230
+ */
231
+ export declare function collectLocalCallables(sourceFile: ts.SourceFile): ReadonlySet<string>;
199
232
  export declare function computedPropertyNameMessage(node: ts.PropertyName): string;
200
233
  export declare function dynamicElementAccessMessage(keyNode: ts.Expression): string;
201
234
  export declare const UNSUPPORTED_OBJECT_MEMBER_MESSAGE = "unsupported object member";
@@ -1 +1 @@
1
- {"version":3,"file":"subset.d.ts","sourceRoot":"","sources":["../../src/fold/subset.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAEjC,OAAO,EAAiD,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAE9F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+JG;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,QAAQ,CAAC;AAElC,mEAAmE;AACnE,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE/C,uGAAuG;AACvG,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC;IACd,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB;AAMD,0FAA0F;AAC1F,eAAO,MAAM,0BAA0B,EAAE,WAAW,CAAC,EAAE,CAAC,UAAU,CAchE,CAAC;AAEH,oFAAoF;AACpF,eAAO,MAAM,yBAAyB,EAAE,WAAW,CAAC,EAAE,CAAC,UAAU,CAG/D,CAAC;AAEH,gHAAgH;AAChH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,EAAE,CAAC,YAAY,GACpB,IAAI,IAAI,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,aAAa,GAAG,EAAE,CAAC,cAAc,CAE9D;AAED,6GAA6G;AAC7G,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,GAAG,IAAI,IAAI,EAAE,CAAC,aAAa,GAAG,EAAE,CAAC,cAAc,CAErG;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,SAAS,SAAK,GAAG,MAAM,CAGnE;AAQD,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,GAAG,MAAM,CAEzE;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,GAAG,MAAM,CAE1E;AAED,eAAO,MAAM,iCAAiC,8BAA8B,CAAC;AAE7E,eAAO,MAAM,yBAAyB,sBAAsB,CAAC;AAE7D,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,GAAG,MAAM,CAEtE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,GAAG,MAAM,CAErE;AAED,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,MAAM,CAElE;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,EAAE,CAAC,wBAAwB,EACjC,UAAU,CAAC,EAAE,SAAS,YAAY,EAAE,GACnC,eAAe,GAAG,SAAS,CAc7B;AAWD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,UAAU,CAAC,EAAE,SAAS,YAAY,EAAE,GACnC,eAAe,GAAG,SAAS,CA6M7B"}
1
+ {"version":3,"file":"subset.d.ts","sourceRoot":"","sources":["../../src/fold/subset.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAEjC,OAAO,EAAiD,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAE9F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwKG;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,QAAQ,CAAC;AAElC,mEAAmE;AACnE,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE/C,uGAAuG;AACvG,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC;IACd,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB;AAMD,0FAA0F;AAC1F,eAAO,MAAM,0BAA0B,EAAE,WAAW,CAAC,EAAE,CAAC,UAAU,CAchE,CAAC;AAEH,oFAAoF;AACpF,eAAO,MAAM,yBAAyB,EAAE,WAAW,CAAC,EAAE,CAAC,UAAU,CAG/D,CAAC;AAEH,gHAAgH;AAChH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,EAAE,CAAC,YAAY,GACpB,IAAI,IAAI,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,aAAa,GAAG,EAAE,CAAC,cAAc,CAE9D;AAED,6GAA6G;AAC7G,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,GAAG,IAAI,IAAI,EAAE,CAAC,aAAa,GAAG,EAAE,CAAC,cAAc,CAErG;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,SAAS,SAAK,GAAG,MAAM,CAGnE;AAMD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,GAAG,WAAW,CAAC,MAAM,CAAC,CAoDpF;AAgCD,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,GAAG,MAAM,CAEzE;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,GAAG,MAAM,CAE1E;AAED,eAAO,MAAM,iCAAiC,8BAA8B,CAAC;AAE7E,eAAO,MAAM,yBAAyB,sBAAsB,CAAC;AAE7D,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,GAAG,MAAM,CAEtE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,GAAG,MAAM,CAErE;AAED,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,MAAM,CAElE;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,EAAE,CAAC,wBAAwB,EACjC,UAAU,CAAC,EAAE,SAAS,YAAY,EAAE,GACnC,eAAe,GAAG,SAAS,CAc7B;AAWD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,UAAU,CAAC,EAAE,SAAS,YAAY,EAAE,GACnC,eAAe,GAAG,SAAS,CAkO7B"}
package/dist/index.d.ts CHANGED
@@ -38,7 +38,7 @@ export * from "./graph-layout.js";
38
38
  export * from "./graph-lens.js";
39
39
  export * from "./detectLexicon.js";
40
40
  export * from "./fold/fold.js";
41
- export { findSubsetViolation, checkObjectMember, type SubsetViolation, type SubsetRuleId } from "./fold/subset.js";
41
+ export { findSubsetViolation, checkObjectMember, collectLocalCallables, type SubsetViolation, type SubsetRuleId } from "./fold/subset.js";
42
42
  export { SPEC_VERSION } from "./fold/subset.js";
43
43
  export { foldProject, planFoldTaintWithEdges, type FoldProjectOptions, type FoldProjectVerdict, type TaintPlan, type TaintEdgeKind, } from "./discovery/fold-import.js";
44
44
  export * from "./lint/parser.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant",
3
- "version": "0.71.1",
3
+ "version": "0.72.0",
4
4
  "description": "Declarative infrastructure-as-code toolkit — TypeScript on Node.js",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -494,10 +494,42 @@ export const queue = new Queue({ name: Ref(environment) });
494
494
 
495
495
  test("an unregistered call is still flagged as EVL001 in the same project", async () => {
496
496
  await writeFile(join(testDir, "chant.config.json"), JSON.stringify({ lexicons: ["aws"] }));
497
+ // The call is to a name imported from a PACKAGE, which is what "unregistered"
498
+ // has to mean for this test to be about the registry. It used to be a
499
+ // same-file `function makeName()`, which chant#2435 established is
500
+ // `S-CallLocal` and admissible — the build folds that shape, so flagging it
501
+ // was the divergence, not the point being asserted here.
497
502
  await writeFile(
498
503
  join(testDir, "index.ts"),
499
504
  `
500
505
  import { Ref } from "@intentius/chant-lexicon-aws";
506
+ import { randomUUID } from "node:crypto";
507
+
508
+ class Queue {
509
+ constructor(_props: Record<string, unknown>) {}
510
+ }
511
+
512
+ export const queue = new Queue({ name: randomUUID() });
513
+ `,
514
+ );
515
+
516
+ const result = await lintCommand({ path: testDir, format: "stylish" });
517
+
518
+ const evl001 = result.diagnostics.filter((d) => d.ruleId === "EVL001");
519
+ expect(evl001).toHaveLength(1);
520
+ expect(evl001[0].message).toContain("statically evaluable");
521
+ });
522
+
523
+ test("a call to a project-local function is not flagged (chant#2435)", async () => {
524
+ await writeFile(join(testDir, "chant.config.json"), JSON.stringify({ lexicons: ["aws"] }));
525
+ // All three `S-LocalFunction` bindings in one file: a top-level function
526
+ // declaration, an arrow const, and a name imported from a project
527
+ // specifier. A build folds a call to each, so EVL001 must not flag one.
528
+ await writeFile(join(testDir, "helpers.ts"), `export function suffix(s: string): string { return s + "-x"; }\n`);
529
+ await writeFile(
530
+ join(testDir, "index.ts"),
531
+ `
532
+ import { suffix } from "./helpers";
501
533
 
502
534
  class Queue {
503
535
  constructor(_props: Record<string, unknown>) {}
@@ -507,15 +539,17 @@ function makeName(): string {
507
539
  return "generated";
508
540
  }
509
541
 
510
- export const queue = new Queue({ name: makeName() });
542
+ const label = (s: string): string => s.toUpperCase();
543
+
544
+ export const a = new Queue({ name: makeName() });
545
+ export const b = new Queue({ name: label("k") });
546
+ export const c = new Queue({ name: suffix("q") });
511
547
  `,
512
548
  );
513
549
 
514
550
  const result = await lintCommand({ path: testDir, format: "stylish" });
515
551
 
516
- const evl001 = result.diagnostics.filter((d) => d.ruleId === "EVL001");
517
- expect(evl001).toHaveLength(1);
518
- expect(evl001[0].message).toContain("statically evaluable");
552
+ expect(result.diagnostics.filter((d) => d.ruleId === "EVL001")).toHaveLength(0);
519
553
  });
520
554
  });
521
555
 
@@ -0,0 +1,89 @@
1
+ import { describe, test, expect, beforeEach, afterEach } from "vitest";
2
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { foldProject } from "../index";
6
+
7
+ /**
8
+ * chant#2438 — a host package that is not a chant lexicon.
9
+ *
10
+ * `FoldSession.lexiconPackages` is the entire allowlist a bare import is
11
+ * followed into, matched by text, and it has only ever been built from
12
+ * `@intentius/chant-lexicon-<name>`. That covers every chant project, because
13
+ * a chant project's active packages are exactly that.
14
+ *
15
+ * It does not cover a caller driving the fold path against a host of its own.
16
+ * The specification's conformance harness is the first: its `shapes` host owns
17
+ * `@tsad/shapes`, a name the convention cannot spell, so all twenty-five of its
18
+ * hosted whole-build fixtures were unanswerable at chant and reported skipped.
19
+ *
20
+ * `FoldProjectOptions.lexiconPackages` names such a package outright. The
21
+ * boundary is unchanged: still an allowlist the caller states, still matched by
22
+ * text, still empty by default.
23
+ */
24
+ describe("foldProject — a host package outside the lexicon convention (chant#2438)", () => {
25
+ let root: string;
26
+
27
+ /** A project with a real `@tsad/shapes` in its own `node_modules`. */
28
+ beforeEach(() => {
29
+ root = mkdtempSync(join(tmpdir(), "chant-host-pkg-"));
30
+ const pkg = join(root, "node_modules", "@tsad", "shapes");
31
+ mkdirSync(pkg, { recursive: true });
32
+ writeFileSync(
33
+ join(pkg, "package.json"),
34
+ JSON.stringify({ name: "@tsad/shapes", version: "1.0.0", type: "module", main: "index.js" }),
35
+ );
36
+ writeFileSync(join(pkg, "index.js"), "export const SIZES = { small: 1, large: 3 };\n");
37
+ });
38
+
39
+ afterEach(() => rmSync(root, { recursive: true, force: true }));
40
+
41
+ const app = (source: string): string => {
42
+ const file = join(root, "app.ts");
43
+ writeFileSync(file, source);
44
+ return file;
45
+ };
46
+
47
+ test("is not followed without being named, which is the pre-#2438 answer", async () => {
48
+ const file = app('import { SIZES } from "@tsad/shapes";\nexport const s = SIZES.large;\n');
49
+ const verdict = (await foldProject([file])).get(file)!;
50
+
51
+ expect(verdict.verdict).toBe("run");
52
+ expect(verdict.reason).toContain("unresolved identifier: SIZES");
53
+ });
54
+
55
+ test("is followed when named, and the data export folds as a value", async () => {
56
+ const file = app('import { SIZES } from "@tsad/shapes";\nexport const s = SIZES.large;\n');
57
+ const verdict = (await foldProject([file], [], { lexiconPackages: ["@tsad/shapes"] })).get(file)!;
58
+
59
+ // The specification's `F-Host-DataExports/plain-data-export` fixture, and
60
+ // its `expect.json`, are exactly this.
61
+ expect(verdict.verdict).toBe("fold");
62
+ expect(Object.fromEntries(verdict.exports!)).toEqual({ s: 3 });
63
+ });
64
+
65
+ test("naming one package does not open any other", async () => {
66
+ const other = join(root, "node_modules", "@tsad", "elsewhere");
67
+ mkdirSync(other, { recursive: true });
68
+ writeFileSync(
69
+ join(other, "package.json"),
70
+ JSON.stringify({ name: "@tsad/elsewhere", version: "1.0.0", type: "module", main: "index.js" }),
71
+ );
72
+ writeFileSync(join(other, "index.js"), "export const OTHER = 9;\n");
73
+
74
+ const file = app('import { OTHER } from "@tsad/elsewhere";\nexport const s = OTHER;\n');
75
+ const verdict = (await foldProject([file], [], { lexiconPackages: ["@tsad/shapes"] })).get(file)!;
76
+
77
+ expect(verdict.verdict).toBe("run");
78
+ });
79
+
80
+ test("sits alongside `lexicons`, rather than replacing it", async () => {
81
+ const file = app('import { SIZES } from "@tsad/shapes";\nexport const s = SIZES.small;\n');
82
+ const verdict = (
83
+ await foldProject([file], [], { lexicons: ["aws"], lexiconPackages: ["@tsad/shapes"] })
84
+ ).get(file)!;
85
+
86
+ expect(verdict.verdict).toBe("fold");
87
+ expect(Object.fromEntries(verdict.exports!)).toEqual({ s: 1 });
88
+ });
89
+ });
@@ -17,6 +17,8 @@ import {
17
17
  FoldableFunction,
18
18
  isFoldableFunction,
19
19
  locate,
20
+ collectSameFileFunctions,
21
+ constResolvesToResource,
20
22
  type FoldedResource,
21
23
  type FoldedValue,
22
24
  type FoldedIntrinsic,
@@ -307,6 +309,19 @@ export function createFoldSession(
307
309
  buildParams?: Readonly<Record<string, BuildParamValue>>,
308
310
  lexicons: readonly string[] = [],
309
311
  sandbox = false,
312
+ /**
313
+ * chant#2438 — package specifiers to allow verbatim, alongside the ones
314
+ * derived from `lexicons`.
315
+ *
316
+ * A chant build's active packages are always `@intentius/chant-lexicon-*`,
317
+ * so naming lexicons is the whole story there. A caller embedding the fold
318
+ * path against a host that is not a chant lexicon has packages the naming
319
+ * convention cannot express: the specification's conformance harness supplies
320
+ * `@tsad/shapes`, which is its host's own specifier. The boundary is
321
+ * unchanged, an explicitly named package of this build, only now it can be
322
+ * named as a specifier rather than only as a lexicon.
323
+ */
324
+ lexiconPackages: readonly string[] = [],
310
325
  ): FoldSession {
311
326
  return {
312
327
  intrinsics,
@@ -315,7 +330,7 @@ export function createFoldSession(
315
330
  importCache: new Map(),
316
331
  resolvePathCache: new Map(),
317
332
  buildParams,
318
- lexiconPackages: new Set(lexicons.map(lexiconPackageName)),
333
+ lexiconPackages: new Set([...lexicons.map(lexiconPackageName), ...lexiconPackages]),
319
334
  sandbox,
320
335
  factoryModules: new Map(),
321
336
  };
@@ -764,14 +779,6 @@ function collectLocalBindings(sourceFile: ts.SourceFile): Map<string, LocalBindi
764
779
  }
765
780
 
766
781
  /** `(x) => …` / `function (x) {…}`, possibly wrapped in parens/`as`/`satisfies` — the initializer shapes that make a `const` a function binding. */
767
- function unwrapFunctionInitializer(node: ts.Expression): ts.ArrowFunction | ts.FunctionExpression | undefined {
768
- let current = node;
769
- while (ts.isParenthesizedExpression(current) || ts.isAsExpression(current) || ts.isSatisfiesExpression(current)) {
770
- current = current.expression;
771
- }
772
- return ts.isArrowFunction(current) || ts.isFunctionExpression(current) ? current : undefined;
773
- }
774
-
775
782
  /**
776
783
  * chant #1373 — every top-level function a file declares, exported or not, as
777
784
  * a {@link FoldableFunction}: `function f(...) {...}` and `const f = (...) =>
@@ -786,29 +793,10 @@ function unwrapFunctionInitializer(node: ts.Expression): ts.ArrowFunction | ts.F
786
793
  * module-level resource through `externals` only.
787
794
  */
788
795
  function collectLocalFunctions(sourceFile: ts.SourceFile, ctx: ResolveCtx): Map<string, FoldableFunction> {
789
- const consts = new Map(ctx.consts);
790
- for (const [name] of [...consts]) {
791
- if (constResolvesToResource(ctx.consts, name, new Set())) consts.delete(name);
792
- }
793
- const functions = new Map<string, FoldableFunction>();
794
- const add = (name: string, fn: ts.FunctionDeclaration | ts.ArrowFunction | ts.FunctionExpression): void => {
795
- functions.set(name, new FoldableFunction(name, fn, ctx.file, consts, ctx.externals, ctx.crossFileFailures));
796
- };
797
-
798
- for (const statement of sourceFile.statements) {
799
- if (ts.isFunctionDeclaration(statement)) {
800
- if (statement.name && statement.body) add(statement.name.text, statement);
801
- continue;
802
- }
803
- if (!ts.isVariableStatement(statement)) continue;
804
- if ((statement.declarationList.flags & ts.NodeFlags.Const) === 0) continue;
805
- for (const decl of statement.declarationList.declarations) {
806
- if (!ts.isIdentifier(decl.name) || !decl.initializer) continue;
807
- const fn = unwrapFunctionInitializer(decl.initializer);
808
- if (fn) add(decl.name.text, fn);
809
- }
810
- }
811
- return functions;
796
+ // chant#2436 — the walk itself, and the `new`-bound stripping that makes it
797
+ // correct, live in ../fold/fold.ts so a unit-level fold with no externals of
798
+ // its own binds exactly what a build binds.
799
+ return collectSameFileFunctions(sourceFile, ctx.consts, ctx.file, ctx.externals, ctx.crossFileFailures);
812
800
  }
813
801
 
814
802
  // ─────────────────────────────────────────────────────────────────────────
@@ -1939,27 +1927,6 @@ function factoryModuleScopeResolved(
1939
1927
  return scope.resolved;
1940
1928
  }
1941
1929
 
1942
- /**
1943
- * True when a module-level `const` is (transitively) bound to a
1944
- * `new Type(...)`. Mirrors `fold()`'s own `resolvesToResource`, but follows an
1945
- * identifier chain (`const a = new T(); const b = a;`) so aliasing can't smuggle
1946
- * a module-level resource into a factory body — see
1947
- * {@link FactoryModuleScope.consts}.
1948
- */
1949
- function constResolvesToResource(
1950
- consts: Map<string, ts.Expression>,
1951
- name: string,
1952
- seen: Set<string>,
1953
- ): boolean {
1954
- if (seen.has(name)) return false;
1955
- seen.add(name);
1956
- const init = consts.get(name);
1957
- if (init === undefined) return false;
1958
- if (ts.isNewExpression(init)) return true;
1959
- if (ts.isIdentifier(init)) return constResolvesToResource(consts, init.text, seen);
1960
- return false;
1961
- }
1962
-
1963
1930
  /**
1964
1931
  * Find `export const <exportName> = Composite(<fn>, "<name>")` at the top level
1965
1932
  * of `scope`'s module, verifying that `Composite` really is chant's own in that
@@ -4099,6 +4066,20 @@ export interface FoldProjectOptions {
4099
4066
  readonly buildParams?: Readonly<Record<string, BuildParamValue>>;
4100
4067
  /** chant #1093: this build asked for the sandbox, so fold may not reach outside the trusted allowlist. */
4101
4068
  readonly sandbox?: boolean;
4069
+ /**
4070
+ * chant#2438 — package specifiers to follow a bare import into, verbatim,
4071
+ * alongside whatever `lexicons` names.
4072
+ *
4073
+ * For a chant project this is never needed: an active package is always
4074
+ * `@intentius/chant-lexicon-<name>`, which `lexicons` already expresses. It
4075
+ * is here for a caller driving the fold path against a host of its own, where
4076
+ * the package has a name chant's convention cannot spell. The specification's
4077
+ * conformance harness is the first such caller, with `@tsad/shapes`.
4078
+ *
4079
+ * The boundary does not move. This is still an allowlist the caller states
4080
+ * outright, matched by text, and absent it nothing bare resolves at all.
4081
+ */
4082
+ readonly lexiconPackages?: readonly string[];
4102
4083
  }
4103
4084
 
4104
4085
  /** One file's place in a whole-build fold, as {@link foldProject} reports it. */
@@ -4146,6 +4127,7 @@ export async function foldProject(
4146
4127
  options.buildParams,
4147
4128
  options.lexicons ?? [],
4148
4129
  options.sandbox ?? false,
4130
+ options.lexiconPackages ?? [],
4149
4131
  );
4150
4132
  const attempts = new Map<string, FoldFileResult>();
4151
4133
  for (const file of files) attempts.set(file, await tryFoldFile(file, intrinsics, session));
@@ -688,11 +688,17 @@ describe("fold — registered call-form intrinsics (#1044)", () => {
688
688
  expect(() => fold(expr, consts, [REF])).toThrow(FoldError);
689
689
  });
690
690
 
691
- test("a registered name SHADOWED by a local const is rejected — the local binding wins, and it isn't the lexicon's", () => {
691
+ test("a registered name SHADOWED by a local const folds as the LOCAL function, never the lexicon's", () => {
692
+ // chant#2436 — the local binding wins, which is what this has always
693
+ // asserted. It used to win by rejecting, because `fold()` had no way to
694
+ // call a local function; now it wins by being called, which is what a
695
+ // build does with this file. The registered meaning is still not applied:
696
+ // the lexicon's `Ref` produces `{ __intrinsic: "Ref" }`, this produces the
697
+ // arrow's own object.
692
698
  const consts = parseConsts(`const Ref = (n) => ({ mine: n }); const x = Ref("db");`);
693
699
  const expr = consts.get("x");
694
700
  if (!expr) throw new Error("fixture error");
695
- expect(() => fold(expr, consts, [REF])).toThrow(FoldError);
701
+ expect(fold(expr, consts, [REF])).toEqual({ mine: "db" });
696
702
  });
697
703
 
698
704
  test("an unfoldable argument still rejects the whole call", () => {
@@ -765,11 +771,14 @@ describe("fold — registered authoring helpers (#1082)", () => {
765
771
  expect((error as FoldError).message).toContain("function call as a value is not foldable: compose(...)");
766
772
  });
767
773
 
768
- test("a registered name SHADOWED by a local const is rejected — the local binding wins, and it isn't chant's", () => {
774
+ test("a registered name SHADOWED by a local const folds as the LOCAL function, never chant's", () => {
775
+ // chant#2436 — see the matching case under the intrinsic registry above.
776
+ // chant's own `phase(name, steps)` yields `{ phase, steps }`; the absence
777
+ // of `steps` here is what proves the shadowing arrow was the one called.
769
778
  const consts = parseConsts(`const phase = (n) => ({ phase: n }); const x = phase("Apply");`);
770
779
  const expr = consts.get("x");
771
780
  if (!expr) throw new Error("fixture error");
772
- expect(() => fold(expr, consts)).toThrow(FoldError);
781
+ expect(fold(expr, consts)).toEqual({ phase: "Apply" });
773
782
  });
774
783
 
775
784
  test("a registered name used as a METHOD (`ns.phase(...)`) is rejected — only a bare identifier callee folds", () => {
@@ -835,11 +844,13 @@ describe("fold: the ConvergeOp rule builders are registered helpers (#2171)", ()
835
844
  }
836
845
  });
837
846
 
838
- test("a rule builder SHADOWED by a local const is still rejected", () => {
847
+ test("a rule builder SHADOWED by a local const folds as the LOCAL function", () => {
848
+ // chant#2436 — the builder's own shape is `{ field, op, value }`; this
849
+ // yields the arrow's `{ f, v }`, so the shadowing binding is the one used.
839
850
  const consts = parseConsts(`const gt = (f, v) => ({ f, v }); const x = gt("updateCount", 0);`);
840
851
  const expr = consts.get("x");
841
852
  if (!expr) throw new Error("fixture error");
842
- expect(() => fold(expr, consts)).toThrow(FoldError);
853
+ expect(fold(expr, consts)).toEqual({ f: "updateCount", v: 0 });
843
854
  });
844
855
  });
845
856
 
@@ -1244,3 +1255,85 @@ describe("constructions as values (chant #1169)", () => {
1244
1255
  });
1245
1256
  });
1246
1257
  });
1258
+
1259
+ /**
1260
+ * chant#2436 — a unit-level fold binds the file's own functions.
1261
+ *
1262
+ * The build has bound them since #1373: `fold-import.ts` puts a
1263
+ * {@link FoldableFunction} in `externals` for every top-level declaration
1264
+ * before folding anything. A caller with no module graph, and so no
1265
+ * `externals`, had no way to call one, so `fold()` alone answered `run` for a
1266
+ * shape the build folds. The spec's conformance adapter is exactly such a
1267
+ * caller.
1268
+ *
1269
+ * The binding is derived from the node's own source file, and only when the
1270
+ * caller passed no `externals` at all: a caller that passed one is managing
1271
+ * its own bindings and may have left a name out on purpose.
1272
+ */
1273
+ describe("fold — same-file functions without externals (chant#2436)", () => {
1274
+ const foldSource = (src: string): unknown => {
1275
+ const sourceFile = ts.createSourceFile("m.ts", src, ts.ScriptTarget.Latest, true);
1276
+ const consts = collectConsts(sourceFile);
1277
+ const expr = consts.get("x");
1278
+ if (!expr) throw new Error("fixture has no `x`");
1279
+ return fold(expr, consts);
1280
+ };
1281
+
1282
+ test("a function declaration, exported or not", () => {
1283
+ expect(foldSource("function twice(n) { return n * 2; } const x = twice(21);")).toBe(42);
1284
+ expect(foldSource("export function twice(n) { return n * 2; } const x = twice(21);")).toBe(42);
1285
+ });
1286
+
1287
+ test("an arrow const", () => {
1288
+ expect(foldSource('const label = (s) => ({ label: s }); const x = label("k");')).toEqual({ label: "k" });
1289
+ });
1290
+
1291
+ test("a sibling calling a sibling", () => {
1292
+ expect(foldSource("function a(n) { return b(n) + 1; } function b(n) { return n * 2; } const x = a(10);")).toBe(21);
1293
+ });
1294
+
1295
+ test("recursion terminates on its own base case", () => {
1296
+ expect(foldSource("function down(n) { return n <= 0 ? 0 : down(n - 1); } const x = down(3);")).toBe(0);
1297
+ });
1298
+
1299
+ describe("falls back to run rather than producing a value", () => {
1300
+ const rejects = (src: string) => expect(() => foldSource(src)).toThrow(FoldError);
1301
+
1302
+ test("a body that reads a `new`-bound const, so no duplicate resource is built", () => {
1303
+ // The stripping this relies on is why `collectSameFileFunctions` has one
1304
+ // implementation: the body must read the live instance out of
1305
+ // `externals`, never re-fold the initializer. With no externals there is
1306
+ // no instance to read, so the only correct answer is to fall back.
1307
+ rejects("class B { constructor(_p) {} } const b = new B({}); function f() { return b; } const x = f();");
1308
+ });
1309
+
1310
+ test("through an alias of one, so aliasing cannot smuggle it in", () => {
1311
+ rejects(
1312
+ "class B { constructor(_p) {} } const b = new B({}); const alias = b; function f() { return alias; } const x = f();",
1313
+ );
1314
+ });
1315
+
1316
+ test("a body reading an import, which needs a module graph this entry does not have", () => {
1317
+ rejects('import { thing } from "./other"; function f() { return thing; } const x = f();');
1318
+ });
1319
+
1320
+ test("a body with a statement outside the subset", () => {
1321
+ rejects("function f() { for (;;) {} } const x = f();");
1322
+ });
1323
+
1324
+ test("a name the file never binds", () => {
1325
+ rejects("const x = missing(1);");
1326
+ });
1327
+ });
1328
+
1329
+ test("an imported project-local function is still not called here — that needs the module graph", () => {
1330
+ expect(() => foldSource('import { twice } from "./pure"; const x = twice(21);')).toThrow(FoldError);
1331
+ });
1332
+
1333
+ test("an explicit `externals` is left alone, even when it omits the name", () => {
1334
+ const sourceFile = ts.createSourceFile("m.ts", "function twice(n) { return n * 2; } const x = twice(21);", ts.ScriptTarget.Latest, true);
1335
+ const consts = collectConsts(sourceFile);
1336
+ // The caller said "these are the bindings" and did not include `twice`.
1337
+ expect(() => fold(consts.get("x")!, consts, [], new Map())).toThrow(FoldError);
1338
+ });
1339
+ });
package/src/fold/fold.ts CHANGED
@@ -471,6 +471,131 @@ function carriesLiveObject(value: unknown, seen = new Set<unknown>()): boolean {
471
471
  return false;
472
472
  }
473
473
 
474
+ /**
475
+ * True when a module-level `const` is (transitively) bound to a `new Type(...)`.
476
+ * Follows an identifier chain (`const a = new T(); const b = a;`) so aliasing
477
+ * cannot smuggle a module-level resource into a function body.
478
+ *
479
+ * chant#2436 moved this here from ../discovery/fold-import.ts so the two places
480
+ * that build {@link FoldableFunction}s share one implementation rather than two
481
+ * that can drift.
482
+ */
483
+ export function constResolvesToResource(
484
+ consts: ReadonlyMap<string, ts.Expression>,
485
+ name: string,
486
+ seen: Set<string> = new Set(),
487
+ ): boolean {
488
+ if (seen.has(name)) return false;
489
+ seen.add(name);
490
+ const init = consts.get(name);
491
+ if (init === undefined) return false;
492
+ if (ts.isNewExpression(init)) return true;
493
+ if (ts.isIdentifier(init)) return constResolvesToResource(consts, init.text, seen);
494
+ return false;
495
+ }
496
+
497
+ /** `const f = (…) => …`, through parentheses / `as` / `satisfies`. */
498
+ function unwrapFunctionInitializer(node: ts.Expression): ts.ArrowFunction | ts.FunctionExpression | undefined {
499
+ let current = node;
500
+ while (ts.isParenthesizedExpression(current) || ts.isAsExpression(current) || ts.isSatisfiesExpression(current)) {
501
+ current = current.expression;
502
+ }
503
+ return ts.isArrowFunction(current) || ts.isFunctionExpression(current) ? current : undefined;
504
+ }
505
+
506
+ /**
507
+ * Every top-level function a file declares, exported or not, as a
508
+ * {@link FoldableFunction}: `function f(...) {...}` and `const f = (...) => …`.
509
+ *
510
+ * The markers carry `externals` BY REFERENCE, so the caller may keep filling it
511
+ * after this returns (which is how a sibling function, or a resource built
512
+ * later, becomes reachable from a body).
513
+ *
514
+ * `consts` drops every `new`-bound const, transitively through aliases: a body
515
+ * that mentions one must read the live instance out of `externals`, the same
516
+ * object the module's own fold registered, never re-fold the initializer and
517
+ * construct a duplicate of a resource discovery already has.
518
+ *
519
+ * chant#2436 — one implementation, called both by ../discovery/fold-import.ts
520
+ * during a build and by {@link fold} when a unit-level fold has no externals of
521
+ * its own. Getting the `new`-bound stripping wrong yields a wrong value rather
522
+ * than a fallback to run, which is why there is only one copy of it.
523
+ */
524
+ export function collectSameFileFunctions(
525
+ sourceFile: ts.SourceFile,
526
+ consts: ReadonlyMap<string, ts.Expression>,
527
+ file: string,
528
+ externals: ReadonlyMap<string, unknown>,
529
+ failures?: ReadonlyMap<string, string>,
530
+ ): Map<string, FoldableFunction> {
531
+ const bodyConsts = new Map(consts);
532
+ for (const [name] of [...bodyConsts]) {
533
+ if (constResolvesToResource(consts, name, new Set())) bodyConsts.delete(name);
534
+ }
535
+
536
+ const functions = new Map<string, FoldableFunction>();
537
+ const add = (name: string, fn: ts.FunctionDeclaration | ts.ArrowFunction | ts.FunctionExpression): void => {
538
+ functions.set(name, new FoldableFunction(name, fn, file, bodyConsts, externals, failures));
539
+ };
540
+
541
+ for (const statement of sourceFile.statements) {
542
+ if (ts.isFunctionDeclaration(statement)) {
543
+ if (statement.name && statement.body) add(statement.name.text, statement);
544
+ continue;
545
+ }
546
+ if (!ts.isVariableStatement(statement)) continue;
547
+ if ((statement.declarationList.flags & ts.NodeFlags.Const) === 0) continue;
548
+ for (const decl of statement.declarationList.declarations) {
549
+ if (!ts.isIdentifier(decl.name) || !decl.initializer) continue;
550
+ const fn = unwrapFunctionInitializer(decl.initializer);
551
+ if (fn) add(decl.name.text, fn);
552
+ }
553
+ }
554
+ return functions;
555
+ }
556
+
557
+ /**
558
+ * The same-file functions in scope at `node`, for a fold with no `externals`.
559
+ *
560
+ * Memoized per (source file, consts map), both of which are objects the caller
561
+ * holds for the duration of a fold, so a file with many calls walks its
562
+ * statements once. `undefined` when the node has no file to read, which is the
563
+ * pre-#2436 answer: the call is not foldable.
564
+ *
565
+ * The map is its own `externals`, so a function can call a sibling.
566
+ */
567
+ function sameFileFunctions(
568
+ node: ts.Node,
569
+ consts: Map<string, ts.Expression>,
570
+ ): Map<string, FoldableFunction> | undefined {
571
+ let sourceFile: ts.SourceFile | undefined;
572
+ try {
573
+ sourceFile = node.getSourceFile();
574
+ } catch {
575
+ return undefined;
576
+ }
577
+ if (!sourceFile) return undefined;
578
+
579
+ let byConsts = sameFileFunctionCache.get(sourceFile);
580
+ if (!byConsts) {
581
+ byConsts = new WeakMap();
582
+ sameFileFunctionCache.set(sourceFile, byConsts);
583
+ }
584
+ const cached = byConsts.get(consts);
585
+ if (cached) return cached;
586
+
587
+ const scope = new Map<string, unknown>();
588
+ const functions = collectSameFileFunctions(sourceFile, consts, sourceFile.fileName, scope);
589
+ for (const [name, fn] of functions) scope.set(name, fn);
590
+ byConsts.set(consts, functions);
591
+ return functions;
592
+ }
593
+
594
+ const sameFileFunctionCache = new WeakMap<
595
+ ts.SourceFile,
596
+ WeakMap<Map<string, ts.Expression>, Map<string, FoldableFunction>>
597
+ >();
598
+
474
599
  export function isFoldableFunction(value: unknown): value is FoldableFunction {
475
600
  return value instanceof FoldableFunction;
476
601
  }
@@ -1430,6 +1555,20 @@ export function fold(
1430
1555
  if (isFoldableFunction(callee)) {
1431
1556
  return callFoldableFunction(callee, node, consts, intrinsics, externals);
1432
1557
  }
1558
+ // chant#2436 — the same shape, reached without a build. A caller that
1559
+ // supplied no `externals` has no module graph and so no markers, and a
1560
+ // same-file call fell through to the throw below even though a build
1561
+ // folds it. Derive the file's own functions and call one.
1562
+ //
1563
+ // Only when `externals` is absent. A caller that passed one is managing
1564
+ // its own bindings (a build, which already put the markers there before
1565
+ // folding anything), and second-guessing it here could bind a name it
1566
+ // deliberately left out. A same-file function only: an imported one needs
1567
+ // module resolution, which this module does not do and will not start.
1568
+ if (externals === undefined) {
1569
+ const local = sameFileFunctions(node, consts)?.get(node.expression.text);
1570
+ if (local) return callFoldableFunction(local, node, consts, intrinsics, local.externals);
1571
+ }
1433
1572
  }
1434
1573
 
1435
1574
  // chant #1966 — the fourth call shape: a lexicon-package function its
@@ -1,7 +1,7 @@
1
1
  import { describe, test, expect } from "vitest";
2
2
  import * as ts from "typescript";
3
3
  import { fold, foldResource, collectConsts, FoldError } from "./fold";
4
- import { briefNodeText, callExpressionMessage, findSubsetViolation } from "./subset";
4
+ import { briefNodeText, callExpressionMessage, findSubsetViolation, collectLocalCallables } from "./subset";
5
5
  import { evl001NonLiteralExpressionRule } from "../lint/rules/evl001-non-literal-expression";
6
6
  import { evl003DynamicPropertyAccessRule } from "../lint/rules/evl003-dynamic-property-access";
7
7
  import { evl004SpreadNonConstRule } from "../lint/rules/evl004-spread-non-const";
@@ -137,6 +137,38 @@ describe("subset equivalence — supported cases: fold succeeds AND EVL is clean
137
137
  }
138
138
  });
139
139
 
140
+ /**
141
+ * chant#2436 closed a divergence that used to live in the block below.
142
+ *
143
+ * `phase` is a registered authoring helper, but shadowed by the file's own
144
+ * arrow the local binding wins — which is what #1082 asserted, and still
145
+ * true. It used to win by making `fold()` reject, because `fold()` had no way
146
+ * to call a local function; a build folded the same file. Now both fold it, so
147
+ * the two agree rather than diverging by design.
148
+ */
149
+ describe("authoring-helper shadowing — now unified (chant#2436)", () => {
150
+ const source = `
151
+ const phase = (n) => ({ phase: n });
152
+ const good = new Thing({ x: phase("Apply") });
153
+ `;
154
+
155
+ test("fold calls the local arrow, and does not apply the registered meaning", () => {
156
+ const sourceFile = ts.createSourceFile("t.ts", source, ts.ScriptTarget.Latest, true);
157
+ const consts = collectConsts(sourceFile);
158
+ const folded = foldResource(consts.get("good") as ts.NewExpression, consts, []);
159
+
160
+ // chant's own `phase(name, steps)` yields `{ phase, steps }`; no `steps`
161
+ // here is what proves the shadowing binding was the one called.
162
+ expect((folded.props as { x: unknown }).x).toEqual({ phase: "Apply" });
163
+ });
164
+
165
+ test("and EVL001 is clean, as it always was", () => {
166
+ const sourceFile = ts.createSourceFile("t.ts", source, ts.ScriptTarget.Latest, true);
167
+ const context: LintContext = { sourceFile, entities: [], filePath: "t.ts", lexicon: undefined };
168
+ expect(evl001NonLiteralExpressionRule.check(context)).toHaveLength(0);
169
+ });
170
+ });
171
+
140
172
  describe("subset equivalence — unsupported cases: fold rejects AND EVL flags the same rule id + position", () => {
141
173
  for (const c of UNSUPPORTED_CASES) {
142
174
  test(c.name, () => {
@@ -241,25 +273,6 @@ describe("documented divergences — NOT unified by design (see subset.ts module
241
273
  expect(evl001NonLiteralExpressionRule.check(context)).toHaveLength(0);
242
274
  });
243
275
 
244
- test("authoring-helper shadowing: fold rejects a registered name bound to a local const; EVL001 does not (shape-only, no binding resolution)", () => {
245
- // chant #1082 — `phase` is registered, but here it's the file's own local
246
- // arrow function, so the local binding wins and fold() rejects. EVL has no
247
- // binding resolver (subset.ts module doc, point 1) and stays permissive —
248
- // the same direction as every other divergence here.
249
- const source = `
250
- const phase = (n) => ({ phase: n });
251
- const bad = new Thing({ x: phase("Apply") });
252
- `;
253
- const sourceFile = ts.createSourceFile("t.ts", source, ts.ScriptTarget.Latest, true);
254
- const consts = collectConsts(sourceFile);
255
- const badInit = consts.get("bad") as ts.NewExpression;
256
-
257
- expect(() => foldResource(badInit, consts, [])).toThrow(FoldError);
258
-
259
- const context: LintContext = { sourceFile, entities: [], filePath: "t.ts", lexicon: undefined };
260
- expect(evl001NonLiteralExpressionRule.check(context)).toHaveLength(0);
261
- });
262
-
263
276
  test("method call on an unresolved receiver: fold rejects; EVL001 accepts the shape (chant #1966, resolution-dependent)", () => {
264
277
  // `config.getName()` — chant #1966 widened `findSubsetViolation` to
265
278
  // accept a method call unconditionally (mirroring `.step` and a nested
@@ -550,3 +563,131 @@ describe("callExpressionMessage — one line regardless of the call's own argume
550
563
  expect(message).toBe("function call as a value is not foldable: GkeCluster(...)");
551
564
  });
552
565
  });
566
+
567
+ /**
568
+ * chant#2435 — `S-CallLocal`.
569
+ *
570
+ * The classifier rejected every call to a project-local function while the
571
+ * build folds all of them, which is the one direction `F-Direction` forbids:
572
+ * this module may accept what the fold rejects, never the reverse. In practice
573
+ * it meant `chant lint` reported EVL001 on a file `chant build --fold` folds
574
+ * cleanly.
575
+ *
576
+ * The admission is by NAME and the names come from the node's own source file,
577
+ * so nothing a caller does changes the answer.
578
+ */
579
+ describe("findSubsetViolation — project-local calls (chant#2435)", () => {
580
+ /** The initializer of `export const x = …` in `src`, with its file attached. */
581
+ const initializerOf = (src: string): ts.Expression => {
582
+ const sourceFile = ts.createSourceFile("m.ts", src, ts.ScriptTarget.Latest, true);
583
+ for (const statement of sourceFile.statements) {
584
+ if (!ts.isVariableStatement(statement)) continue;
585
+ for (const decl of statement.declarationList.declarations) {
586
+ if (ts.isIdentifier(decl.name) && decl.name.text === "x" && decl.initializer) return decl.initializer;
587
+ }
588
+ }
589
+ throw new Error("fixture has no `x`");
590
+ };
591
+ const accepts = (src: string) => findSubsetViolation(initializerOf(src)) === undefined;
592
+
593
+ describe("admitted, because a build folds them", () => {
594
+ test("a same-file function declaration, exported or not", () => {
595
+ expect(accepts("function twice(n: number) { return n * 2; }\nexport const x = twice(21);")).toBe(true);
596
+ expect(accepts("export function twice(n: number) { return n * 2; }\nexport const x = twice(21);")).toBe(true);
597
+ });
598
+
599
+ test("a same-file arrow const, through parentheses and `as`", () => {
600
+ expect(accepts('const label = (s: string) => ({ label: s });\nexport const x = label("k");')).toBe(true);
601
+ expect(accepts('const label = ((s: string) => s) as (s: string) => string;\nexport const x = label("k");')).toBe(true);
602
+ });
603
+
604
+ test("a name imported from a project specifier", () => {
605
+ expect(accepts('import { twice } from "./pure";\nexport const x = twice(21);')).toBe(true);
606
+ expect(accepts('import { twice } from "../lib/pure";\nexport const x = twice(21);')).toBe(true);
607
+ });
608
+
609
+ test("a default import from a project specifier", () => {
610
+ expect(accepts('import make from "./make";\nexport const x = make(1);')).toBe(true);
611
+ });
612
+ });
613
+
614
+ describe("still rejected, so the diagnostic keeps its value", () => {
615
+ test("a call to a name imported from a package", () => {
616
+ expect(accepts('import { v4 } from "uuid";\nexport const x = v4();')).toBe(false);
617
+ });
618
+
619
+ test("a call to a name the file never binds", () => {
620
+ expect(accepts("export const x = getId();")).toBe(false);
621
+ });
622
+
623
+ test("a local name that shadows nothing local — the specifier decides, not the name", () => {
624
+ expect(accepts('import { twice } from "some-package";\nexport const x = twice(1);')).toBe(false);
625
+ });
626
+
627
+ test("a type-only import, which is erased before anything can call it", () => {
628
+ expect(accepts('import type { T } from "./t";\nexport const x = T();')).toBe(false);
629
+ expect(accepts('import { type T } from "./t";\nexport const x = T();')).toBe(false);
630
+ });
631
+
632
+ test("a `let`-bound arrow, which `S-LocalFunction` does not admit", () => {
633
+ expect(accepts("let f = (n: number) => n;\nexport const x = f(1);")).toBe(false);
634
+ });
635
+
636
+ test("a function nested inside another, so not a top-level binding", () => {
637
+ expect(accepts("function outer() { function inner() { return 1; } return inner; }\nexport const x = inner();")).toBe(false);
638
+ });
639
+
640
+ test("an argument outside the subset, even when the callee is local", () => {
641
+ const violation = findSubsetViolation(
642
+ initializerOf("function twice(n: number) { return n; }\nexport const x = twice(getId());"),
643
+ );
644
+ expect(violation?.ruleId).toBe("EVL001");
645
+ // The rejection points at the argument, not at the admitted call.
646
+ expect(violation?.node.getText()).toBe("getId()");
647
+ });
648
+ });
649
+
650
+ describe("collectLocalCallables", () => {
651
+ test("collects each binding kind, and nothing else", () => {
652
+ const sourceFile = ts.createSourceFile(
653
+ "m.ts",
654
+ [
655
+ 'import { fromProject } from "./p";',
656
+ 'import fromProjectDefault from "./d";',
657
+ 'import { fromPackage } from "pkg";',
658
+ 'import type { TypeOnly } from "./t";',
659
+ "export function declared() { return 1; }",
660
+ "const arrowConst = () => 1;",
661
+ "let notConst = () => 1;",
662
+ "const notAFunction = 1;",
663
+ ].join("\n"),
664
+ ts.ScriptTarget.Latest,
665
+ true,
666
+ );
667
+
668
+ expect([...collectLocalCallables(sourceFile)].sort()).toEqual([
669
+ "arrowConst",
670
+ "declared",
671
+ "fromProject",
672
+ "fromProjectDefault",
673
+ ]);
674
+ });
675
+
676
+ test("is memoized per source file, so a large file is walked once", () => {
677
+ const sourceFile = ts.createSourceFile("m.ts", "function f() { return 1; }", ts.ScriptTarget.Latest, true);
678
+ expect(collectLocalCallables(sourceFile)).toBe(collectLocalCallables(sourceFile));
679
+ });
680
+ });
681
+
682
+ test("the local-callable lookup is not a new failure mode for a node with no file", () => {
683
+ // A synthetic node has no parent chain, so `getSourceFile()` throws. That
684
+ // is pre-existing: `violation()` renders the offending node with
685
+ // `getText()`, which throws for the same reason, so this shape was never
686
+ // supported. What is asserted here is narrower and is what chant#2435
687
+ // could have broken: the lookup added in front of that rejection swallows
688
+ // the throw instead of turning it into a different error.
689
+ const synthetic = ts.factory.createCallExpression(ts.factory.createIdentifier("twice"), undefined, []);
690
+ expect(() => collectLocalCallables(synthetic.getSourceFile())).toThrow();
691
+ expect(() => findSubsetViolation(synthetic)).toThrow(/Cannot read|undefined|getSourceFile|getText/i);
692
+ });
693
+ });
@@ -48,7 +48,16 @@ import { intrinsicCallFolds, intrinsicCallFoldsEagerly, type IntrinsicDef } from
48
48
  * expression, its operator, and (for keys) its literal-ness. It
49
49
  * deliberately does NOT resolve bindings or perform any evaluation, because
50
50
  * three things a full fold needs are inherently environment-dependent and
51
- * cannot be recovered from shape alone:
51
+ * cannot be recovered from shape alone.
52
+ *
53
+ * One thing it does read beyond the expression itself, since chant#2435: the
54
+ * enclosing file's top-level bindings, for `S-CallLocal`. A call to a
55
+ * project-local function is admitted by NAME (see
56
+ * {@link collectLocalCallables}), which is decidable from that file's syntax
57
+ * and needs no resolution. Before that, this module rejected all four shapes
58
+ * the build folds, which is the one direction `F-Direction` forbids, and it
59
+ * made `chant lint` report EVL001 on a file `chant build --fold` folds
60
+ * cleanly. The three genuinely environment-dependent cases are:
52
61
  *
53
62
  * 1. Identifier *resolution* — is a bare name actually a local `const`,
54
63
  * vs. an unbound name? That needs the file's `consts` map. Both
@@ -237,6 +246,111 @@ export function briefNodeText(node: ts.Node, maxLength = 60): string {
237
246
  return collapsed.length > maxLength ? `${collapsed.slice(0, maxLength - 3)}...` : collapsed;
238
247
  }
239
248
 
249
+ // ---------------------------------------------------------------------------
250
+ // Project-local callables (chant#2435, spec `S-CallLocal`)
251
+ // ---------------------------------------------------------------------------
252
+
253
+ /**
254
+ * Every name a file binds to something a project-local call can reach.
255
+ *
256
+ * Two bindings, both of which the specification's `S-LocalFunction` names and
257
+ * both decidable from this file's own syntax, which is all a shape classifier
258
+ * has:
259
+ *
260
+ * - a top-level `function f(...) {...}`, or a `const f = (...) => ...`
261
+ * (a function expression counts, and parentheses / `as` / `satisfies`
262
+ * wrappers are unwrapped), exported or not;
263
+ * - a name imported from a *project* specifier, one starting `./` or `../`.
264
+ * A bare specifier is a package, and a package's function is not
265
+ * project-local.
266
+ *
267
+ * Deliberately syntax-only, and deliberately not a judgment about the callee's
268
+ * body. Whether the body is admissible is `S-FnBody`'s question and the fold's
269
+ * to answer, exactly as it already is for a call this classifier admits by
270
+ * name (a registered authoring helper, an opted-in intrinsic).
271
+ *
272
+ * Mirrors `collectLocalFunctions` in ../discovery/fold-import.ts, which builds
273
+ * the real `FoldableFunction` for each of these during a build. This one only
274
+ * needs the names.
275
+ */
276
+ export function collectLocalCallables(sourceFile: ts.SourceFile): ReadonlySet<string> {
277
+ const cached = localCallableCache.get(sourceFile);
278
+ if (cached) return cached;
279
+
280
+ const names = new Set<string>();
281
+ const unwrap = (node: ts.Expression): ts.Expression => {
282
+ let current = node;
283
+ while (
284
+ ts.isParenthesizedExpression(current) ||
285
+ ts.isAsExpression(current) ||
286
+ ts.isSatisfiesExpression(current)
287
+ ) {
288
+ current = current.expression;
289
+ }
290
+ return current;
291
+ };
292
+
293
+ for (const statement of sourceFile.statements) {
294
+ if (ts.isFunctionDeclaration(statement)) {
295
+ if (statement.name && statement.body) names.add(statement.name.text);
296
+ continue;
297
+ }
298
+ if (ts.isImportDeclaration(statement)) {
299
+ const specifier = statement.moduleSpecifier;
300
+ if (!ts.isStringLiteral(specifier) || !/^\.\.?\//.test(specifier.text)) continue;
301
+ const clause = statement.importClause;
302
+ // `import type { T } from "./t"` is erased before anything runs, so `T`
303
+ // is not something a call can reach. Both spellings are skipped: the
304
+ // whole-clause one and the per-specifier `import { type T }`.
305
+ if (!clause || clause.isTypeOnly) continue;
306
+ if (clause.name) names.add(clause.name.text);
307
+ const bindings = clause.namedBindings;
308
+ if (bindings && ts.isNamedImports(bindings)) {
309
+ for (const element of bindings.elements) {
310
+ if (!element.isTypeOnly) names.add(element.name.text);
311
+ }
312
+ }
313
+ continue;
314
+ }
315
+ if (!ts.isVariableStatement(statement)) continue;
316
+ if ((statement.declarationList.flags & ts.NodeFlags.Const) === 0) continue;
317
+ for (const decl of statement.declarationList.declarations) {
318
+ if (!ts.isIdentifier(decl.name) || !decl.initializer) continue;
319
+ const initializer = unwrap(decl.initializer);
320
+ if (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)) {
321
+ names.add(decl.name.text);
322
+ }
323
+ }
324
+ }
325
+
326
+ localCallableCache.set(sourceFile, names);
327
+ return names;
328
+ }
329
+
330
+ /** Per-file memo: the classifier asks this once per call expression, and a big file has many. */
331
+ const localCallableCache = new WeakMap<ts.SourceFile, ReadonlySet<string>>();
332
+
333
+ /**
334
+ * The project-local callables in scope at `node`, or an empty set when the node
335
+ * has no file to read.
336
+ *
337
+ * The guard is not a claim that a file-less node is supported: the rejection
338
+ * this sits in front of renders the node with `getText()`, which needs the same
339
+ * parent chain. It is here so that looking the names up never becomes a *new*
340
+ * way for this function to fail, and so the answer for a node whose file cannot
341
+ * be read is the pre-#2435 one, that every call is a violation.
342
+ */
343
+ function localCallablesAt(node: ts.Node): ReadonlySet<string> {
344
+ try {
345
+ const sourceFile = node.getSourceFile();
346
+ return sourceFile ? collectLocalCallables(sourceFile) : EMPTY_NAMES;
347
+ } catch {
348
+ return EMPTY_NAMES;
349
+ }
350
+ }
351
+
352
+ const EMPTY_NAMES: ReadonlySet<string> = new Set<string>();
353
+
240
354
  // ---------------------------------------------------------------------------
241
355
  // Shared message builders — `fold()` and `findSubsetViolation` both call
242
356
  // these so the diagnostic text for the same violation kind is the same
@@ -547,6 +661,27 @@ export function findSubsetViolation(
547
661
  return undefined;
548
662
  }
549
663
 
664
+ // chant#2435 — a call to a project-local function. `fold-import.ts` binds
665
+ // every top-level function a file declares (and every one it imports from
666
+ // a project file) before folding it, so a build folds all four shapes the
667
+ // issue tabulates; this classifier rejected all four. That is the one
668
+ // direction `F-Direction` forbids, and it made `chant lint` report EVL001
669
+ // on a file `chant build --fold` folds cleanly.
670
+ //
671
+ // Name-only, like the authoring-helper case above and for the same reason:
672
+ // this module classifies shape and never resolves bindings. Whether the
673
+ // callee's body is admissible is `S-FnBody`'s question, which the fold
674
+ // answers. The set comes from the node's own source file, so every caller
675
+ // gets the corrected answer without passing anything — a caller holding a
676
+ // synthetic node with no file keeps the old, stricter one.
677
+ if (ts.isIdentifier(node.expression) && localCallablesAt(node).has(node.expression.text)) {
678
+ for (const arg of node.arguments) {
679
+ const v = findSubsetViolation(arg, intrinsics);
680
+ if (v) return v;
681
+ }
682
+ return undefined;
683
+ }
684
+
550
685
  return violation(node, callExpressionMessage(node));
551
686
  }
552
687
 
package/src/index.ts CHANGED
@@ -43,7 +43,7 @@ export * from "./fold/fold";
43
43
  // fold?" of without running a fold (subset.ts module doc, point 2c), and the
44
44
  // half of the fold subset a conformance adapter needs that `fold()` alone
45
45
  // does not expose. INTENTIUS/typescript-as-data#11.
46
- export { findSubsetViolation, checkObjectMember, type SubsetViolation, type SubsetRuleId } from "./fold/subset";
46
+ export { findSubsetViolation, checkObjectMember, collectLocalCallables, type SubsetViolation, type SubsetRuleId } from "./fold/subset";
47
47
  // The version of the TypeScript-as-Data specification chant implements
48
48
  // (INTENTIUS/typescript-as-data#18, chant#2424). The specification's
49
49
  // conformance adapter reads this off the public entry; without it a suite