@mnemonica/tactica 0.3.0 → 0.3.7
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/README.md +148 -3
- package/lib/analyzer.d.ts +301 -6
- package/lib/analyzer.js +1545 -125
- package/lib/cli.d.ts +3 -2
- package/lib/cli.js +70 -9
- package/lib/generator.d.ts +21 -2
- package/lib/generator.js +43 -2
- package/lib/graph.d.ts +28 -0
- package/lib/graph.js +78 -1
- package/lib/plugins.d.ts +14 -0
- package/lib/plugins.js +3 -1
- package/lib/scopes.d.ts +8 -0
- package/lib/scopes.js +13 -2
- package/lib/types.d.ts +18 -1
- package/lib/types.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -138,7 +138,7 @@ Tactica writes everything under the `--output` directory (default `.tactica/`):
|
|
|
138
138
|
| `flow.json` | always | Native instance-flow patterns (property reads/writes, method calls, destructuring, returns, spreads, etc.) per type. |
|
|
139
139
|
| `instrumentation.json` | always | v2 envelope. `points`: framework lifecycle crossroads (interceptors, guards, pipes, filters, middleware) detected via **plugin-supplied vocabulary** — heritage declarations, decorator sites, provider-token registrations, `consumer.apply()` wiring. Syntactic only — no dive dependency; with no plugins loaded, `points` is `[]`. `creationGraph`: the inside-out walk from every instantiation site out to the starters — see "Creation graph" below. |
|
|
140
140
|
| `modules.json` | always | Module-scope graph: every module's `exportedBindings`/`importedBindings` (functions, classes, consts, types — not only mnemonica types), project-internal `dependencies`, `builtinSpecifiers` (Node builtins are skipped entirely — both `'path'` and `'node:path'` forms), `unresolvedSpecifiers`, circular-import `cycles`, and cross-module mnemonica-type `edges`. Resolution uses `ts.resolveModuleName` with the project's compilerOptions (tsconfig `paths`, extensionless imports, index files) — no type checker. Bindings resolved into `node_modules` are marked `external: true` and never enter `dependencies`. |
|
|
141
|
-
| `scopes.json` | always | Local-scope graph: function/method/arrow scopes only (no block scopes) plus one module scope per file; variables with `typePath` (mnemonica type when known), `isParameter`, `isMutable`, and `reassignments` — each reassignment of a mutable binding is a flow-termination point. |
|
|
141
|
+
| `scopes.json` | always | Local-scope graph: function/method/arrow scopes only (no block scopes) plus one module scope per file; variables with `typePath` (mnemonica type when known), `isParameter`, `isMutable`, and `reassignments` — each reassignment of a mutable binding is a flow-termination point. `lookup()` initializers resolve through the same tier law as the analyzer (scope-chain receiver first, then the analyzer's source-relative/root law), so scopes metadata never disagrees with the hard-fail verdicts. |
|
|
142
142
|
| `eds.json` | when EDS enabled | Execution-flow patterns (`wrap`, `current`, `getFlow`, `attachHooks` lifecycle wiring). Consumed by tools that visualize execution chains. |
|
|
143
143
|
|
|
144
144
|
### Default mode (types.ts + registry.ts)
|
|
@@ -471,6 +471,131 @@ The analyzer infers property types from constructor bodies and class members. Su
|
|
|
471
471
|
|
|
472
472
|
When inference fails the property's type falls back to `unknown` (or `any` in some Topologica paths) — safe, and you can refine manually.
|
|
473
473
|
|
|
474
|
+
### Referenced types in constructor signatures
|
|
475
|
+
|
|
476
|
+
A constructor parameter that references a named type (`record: SharedShape`) resolves **through the importing file's own import statements** — relative and tsconfig-paths imports are followed to the declaration (chasing re-export barrels), and imported classes/interfaces/type aliases with extractable fields are expanded inline, **inherited members included**: `extends` chains are walked (depth-bounded, cycle-guarded) and parent fields merge with the declaration's own fields overriding on name clash. A field typed by a `typeof` query over a module-level const array (`status?: typeof statusList[number]`) expands to the element literal union (`status?: 'active' | 'closed'`); any other `typeof` source degrades that field to `unknown` — a bare `typeof name` is never emitted, since the generated file has no imports to resolve it against. An import is never shadowed by a same-named declaration elsewhere in the program. A reference that resolves not at all (a ghost name, declared nowhere) emits `unknown` — generated `.tactica/types.ts` carries no import statements of its own, so a bare unresolvable name is never emitted. A reference that resolves *ambiguously* — several of your project's files declare the name and no import chooses between them — is **fatal** like the mnemonica-graph identity law below: tactica prints the reference site and every declaration site, exits non-zero, and writes no output. An import anchoring the reference is never ambiguity (the import wins, duplicate declarations or not), and package-declared same-named types never count — a user-local declaration always wins over a node_modules `.d.ts`. Two same-named interfaces in one file are legal TypeScript merging, not ambiguity (see Known Limitations for the last-wins caveat).
|
|
477
|
+
|
|
478
|
+
**Ambiguous plain-TypeScript reference — fails, and how to fix it.** Two plain interfaces named `SharedShape` exist in different files, and a third file references a bare `SharedShape` with nothing anchoring it:
|
|
479
|
+
|
|
480
|
+
```ts
|
|
481
|
+
// a.ts // b.ts
|
|
482
|
+
export interface SharedShape { export interface SharedShape {
|
|
483
|
+
a: string; b: number;
|
|
484
|
+
} }
|
|
485
|
+
|
|
486
|
+
// consumer.ts — FATAL: which SharedShape?
|
|
487
|
+
import { define } from 'mnemonica';
|
|
488
|
+
|
|
489
|
+
export const Crate = define('Crate', function (this: Crate, data: SharedShape) {
|
|
490
|
+
this.item = data;
|
|
491
|
+
});
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
```
|
|
495
|
+
tactica: Ambiguous reference to type 'SharedShape': 2 declarations share the name and no import disambiguates — import the one you mean
|
|
496
|
+
at src/consumer.ts:6:67
|
|
497
|
+
at /abs/path/src/a.ts:1:1
|
|
498
|
+
at /abs/path/src/b.ts:1:1
|
|
499
|
+
tactica: aborting — 1 resolution failure(s); no .tactica output written
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
Fix it by importing the declaration you mean — the file's own import wins over every other tier:
|
|
503
|
+
|
|
504
|
+
```ts
|
|
505
|
+
// consumer.ts — OK: the import anchors SharedShape → a.ts
|
|
506
|
+
import { define } from 'mnemonica';
|
|
507
|
+
import { SharedShape } from './a';
|
|
508
|
+
|
|
509
|
+
export const Crate = define('Crate', function (this: Crate, data: SharedShape) {
|
|
510
|
+
this.item = data; // item: { a: string }
|
|
511
|
+
});
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
Qualified references through a namespace import resolve one level per segment: `models.SharedShape` finds the declaration in the namespace import's module; multi-level paths (`holders.Inner.Crate`, `barrel.Deep.Gadget`) descend through namespace declarations, `export * as ns from '…'` barrels, and named re-exports of namespaces — each segment disambiguates, so two namespaces may declare the same name without colliding. What is *not* followed: ambient string-named modules (`declare module '…'`), default-export shapes behind a namespace segment, and type-only constructs with no extractable fields (unions of literals, mapped types) — those emit `unknown` like any other unresolvable reference.
|
|
515
|
+
|
|
516
|
+
References to **mnemonica graph types** (types declared via `define()`, as opposed to plain TypeScript declarations) follow the runtime lookup law instead: the file's own value bindings and imports anchor the name first, then the defining type's own chain relative-first, then its collection's roots, then a program-wide search that must be unique. Same-namespace duplicate definitions (which the mnemonica runtime rejects with `ALREADY_DECLARED`) and graph references that stay ambiguous or unresolved are fatal: tactica prints every location, exits non-zero, and writes no `.tactica` output at all.
|
|
517
|
+
|
|
518
|
+
**Ambiguous reference — fails, and how to fix it.** Two types named `Token` exist under different parents, and a third file references a bare `Token` with nothing anchoring it:
|
|
519
|
+
|
|
520
|
+
```ts
|
|
521
|
+
// defs.ts
|
|
522
|
+
export const Holder = define('Holder', function (this: Holder) { /* … */ });
|
|
523
|
+
export const Token = Holder.define('Token', function (this: Token, data: { mark: string }) { /* … */ });
|
|
524
|
+
|
|
525
|
+
// other.ts
|
|
526
|
+
export const Other = define('Other', function (this: Other) { /* … */ });
|
|
527
|
+
Other.define('Token', function (this: Token, data: { hue: string }) { /* … */ });
|
|
528
|
+
|
|
529
|
+
// consumer.ts — FATAL: which Token?
|
|
530
|
+
export const Crate = define('Crate', function (this: Crate, data: Token) {
|
|
531
|
+
this.item = data;
|
|
532
|
+
});
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
```
|
|
536
|
+
tactica: Ambiguous reference to mnemonica type 'Token': 2 types share the name and neither the parent chain nor the imports disambiguate
|
|
537
|
+
at src/consumer.ts:7:67
|
|
538
|
+
at src/defs.ts:8:29
|
|
539
|
+
at src/other.ts:9:7
|
|
540
|
+
tactica: aborting — 1 resolution failure(s); no .tactica output written
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
Fix it by anchoring the reference — either import the constructor you mean (the import wins over every other tier), or use the full generated name:
|
|
544
|
+
|
|
545
|
+
```ts
|
|
546
|
+
// consumer.ts — OK: the import anchors Token → Holder.Token
|
|
547
|
+
import { Token } from './defs';
|
|
548
|
+
export const Crate = define('Crate', function (this: Crate, data: Token) {
|
|
549
|
+
this.item = data;
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
// or — OK: fully-qualified generated type name
|
|
553
|
+
export const Crate = define('Crate', function (this: Crate, data: Holder_Token) {
|
|
554
|
+
this.item = data;
|
|
555
|
+
});
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
**Duplicate in one namespace — fails, and there is only one fix.** Two types with the same name under the same parent (or two same-named roots) are what the mnemonica runtime rejects with `ALREADY_DECLARED`; tactica fails the generation instead of emitting a wrong graph:
|
|
559
|
+
|
|
560
|
+
```ts
|
|
561
|
+
// dup-a.ts // dup-b.ts
|
|
562
|
+
export const A = define( export const B = define(
|
|
563
|
+
'DupRoot', …); 'DupRoot', …);
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
```
|
|
567
|
+
tactica: Duplicate definition of 'DupRoot' in one namespace — the mnemonica runtime would throw ALREADY_DECLARED
|
|
568
|
+
at src/dup-a.ts:3:22
|
|
569
|
+
at src/dup-b.ts:6:23
|
|
570
|
+
tactica: aborting — 1 resolution failure(s); no .tactica output written
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
Rename one of them. Same-named types under **different** parents (`User.Address` and `Company.Address`) are legal mnemonica and never reported.
|
|
574
|
+
|
|
575
|
+
**Unresolved `lookup()` path — fails, with a did-you-mean.** Literal `lookup()` paths are validated against the complete graph after analysis. A path matching no type is what the runtime answers with `undefined` — the `TypeError` arrives one line later at the `new` — so it is fatal too, and same-named types elsewhere in the graph are listed as candidates:
|
|
576
|
+
|
|
577
|
+
```ts
|
|
578
|
+
// consumer.ts — FATAL: no root 'Token'; Holder.Token and Other.Token exist
|
|
579
|
+
const TokenCtor = lookup('Token');
|
|
580
|
+
export const instance = new TokenCtor({ mark: 'x' });
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
```
|
|
584
|
+
tactica: Unresolved lookup of mnemonica type 'Token': the runtime would return undefined — 2 graph type(s) carry the name off-root (Holder.Token, Other.Token); use the full dotted path
|
|
585
|
+
at src/consumer.ts:6:19
|
|
586
|
+
at src/defs.ts:8:29
|
|
587
|
+
at src/other.ts:9:7
|
|
588
|
+
tactica: aborting — 1 resolution failure(s); no .tactica output written
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
```ts
|
|
592
|
+
// OK: dotted absolute path, or receiver-relative resolution
|
|
593
|
+
const ByPath = lookup('Holder.Token');
|
|
594
|
+
const ByReceiver = Holder.lookup('Token');
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
Non-literal lookup arguments (computed strings) stay best-effort and are not validated.
|
|
598
|
+
|
|
474
599
|
## API Reference
|
|
475
600
|
|
|
476
601
|
### `MnemonicaAnalyzer`
|
|
@@ -491,6 +616,7 @@ class MnemonicaAnalyzer {
|
|
|
491
616
|
getEDSUsages(): Map<string, EDSInfo[]>;
|
|
492
617
|
getFlowUsages(): Map<string, FlowInfo[]>;
|
|
493
618
|
getInstrumentationPoints(): InstrumentationPoint[];
|
|
619
|
+
getResolutionErrors(): ResolutionError[]; // fatal duplicate/ambiguous graph + plain-TS references (hard-fail law)
|
|
494
620
|
}
|
|
495
621
|
```
|
|
496
622
|
|
|
@@ -658,6 +784,7 @@ When enabled, tactica detects execution-flow patterns alongside type definitions
|
|
|
658
784
|
- `scopeId` — the scopeId of the scope holding the wrap call site (fallback join).
|
|
659
785
|
- `wrapsTypePath` — the mnemonica fullPath of the instance argument, resolved through the scope-variable chain (innermost binding wins; an untyped local shadows a typed outer one rather than being guessed).
|
|
660
786
|
- `via` — the location of the enclosing wrap site when the call is nested inside another wrapped body (or returns a function): the wrappers-graph generation chain is built from it.
|
|
787
|
+
- `scope` — the enclosing mnemonica type path. Lexical scoping (the owning `define()`/`lazy()` handler or `@decorate()`-ed class) wins; a wrap site outside any handler — the fire-and-forget-wrapper shape — is attributed through its instance/context argument (a tracked assignment like `const holder = new Holder(…)`, or the enclosing function's parameter annotation resolved through the graph law), and nested sites (function-valued returns, lexically nested wraps) inherit the causing wrap site's scope down the `via` chain. Sites attributable to nothing stay under the `unknown` key with `scope` absent.
|
|
661
788
|
|
|
662
789
|
## Instrumentation Points
|
|
663
790
|
|
|
@@ -681,6 +808,9 @@ interface TacticaPlugin {
|
|
|
681
808
|
useDecorators?: Record<string, InstrumentationKind>;
|
|
682
809
|
// `{ provide: TOKEN, useClass: Impl }` registrations: token identifier -> kind
|
|
683
810
|
appTokens?: Record<string, InstrumentationKind>;
|
|
811
|
+
// factory calls inside listed decorators' args: factory method name ->
|
|
812
|
+
// kind + which factory-call argument holds the target class (default 0)
|
|
813
|
+
decoratorArgFactories?: Record<string, { kind: InstrumentationKind; targetArg?: number }>;
|
|
684
814
|
// opt in to shape-based `consumer.apply(Mw).forRoutes(...)` detection
|
|
685
815
|
middlewareWiring?: boolean;
|
|
686
816
|
}
|
|
@@ -689,7 +819,8 @@ interface TacticaPlugin {
|
|
|
689
819
|
Programmatic callers pass plugins directly: `new MnemonicaAnalyzer(program, plugins)` or `run({ …, plugins })`; config-file plugins append after programmatic ones, and later plugins override earlier ones on the same identifier key. Detection covers:
|
|
690
820
|
|
|
691
821
|
- **Heritage** — `class X implements <plugin-listed interface>` (matched by interface identifier name). Bare declarations carry scope `module` (attachment statically unknown).
|
|
692
|
-
- **Decorator sites** — plugin-listed decorators on classes or
|
|
822
|
+
- **Decorator sites** — plugin-listed decorators on classes, methods, or method parameters, including inline instances (`@Register(new Impl(…))`). One point per referenced class; scope is `controller:<Name>` or `method:<Class>.<method>` (parameter-decorated sites take the enclosing method's scope — the attachment is the handler, not the argument name).
|
|
823
|
+
- **Decorator-arg factories** — a `CallExpression` argument of the form `<anything>.<factory>(…)` inside a listed decorator's args (class, method, or parameter position), e.g. `@UsePipes(mvp.forType(Dto))` and the parameter form `@Body(mvp.forType(Entity, Dto))`: when `<factory>` is plugin-listed, a point of the configured `kind` is emitted with the class in `targetArg` (default 0) as the target. Factory calls whose method name is not listed are ignored, as are listed factories whose configured position is not an identifier. Points resolve `location`/`code` to the target's declaration and dedupe exactly like other decorator sites.
|
|
693
824
|
- **Global providers** — `{ provide: <plugin-listed token>, useClass: X }` object literals → scope `global`. `useExisting`/`useFactory` without `useClass` are skipped.
|
|
694
825
|
- **Middleware wiring** — `consumer.apply(Mw).forRoutes(...)` inside a class's `configure()` method → scope `module`, targets from `forRoutes` arguments when statically readable. Requires `middlewareWiring: true` from any loaded plugin.
|
|
695
826
|
|
|
@@ -806,7 +937,21 @@ Mnemonica accepts the `exposeInstanceMethods` option at runtime, but tactica's `
|
|
|
806
937
|
|
|
807
938
|
### Single-pass analysis without binding
|
|
808
939
|
|
|
809
|
-
The analyzer does not use `ts.Program.getTypeChecker()` for resolution, so
|
|
940
|
+
The analyzer does not use `ts.Program.getTypeChecker()` for resolution. Plain TypeScript references in constructor signatures resolve through the importing file's import statements first, then its local declarations, and only then a unique same-named declaration across the scanned files — so a reference whose name is genuinely absent (or lives behind unresolvable specifiers) resolves to `unknown`, while a name several of your own files declare with no import to disambiguate fails hard (see above). References to mnemonica graph types follow the path-aware lookup law described above and fail hard instead of degrading to `unknown`.
|
|
941
|
+
|
|
942
|
+
Two expansion boundaries stay name-based and deliberate: **heritage** — `extends` chains are walked through the same import-aware machinery (depth-capped, cycle-guarded), but bases that resolve to an external/node_modules declaration or to a non-identifier expression (mixin calls like `extends mixin(X)`) are not followed — their inherited fields simply stay absent, same as before the walk existed. And **`typeof` fields** — only module-level const arrays with statically visible literal elements expand to the literal union (cross-file only when the const is imported by the declaring file); every other `typeof` source degrades the field to `unknown`.
|
|
943
|
+
|
|
944
|
+
### Same-file interface merging is last-wins
|
|
945
|
+
|
|
946
|
+
Two same-named interfaces in ONE module are legal TypeScript declaration merging, and tactica does not treat them as ambiguity (the fatal class is same-named declarations across several files, unanchored by an import). Resolution keeps a single declaration per name per file, and it is the last one — members from earlier declarations are not combined:
|
|
947
|
+
|
|
948
|
+
```ts
|
|
949
|
+
// merged.ts — resolves to { b: number }; the `a: string` member is not picked up
|
|
950
|
+
export interface SharedShape { a: string; }
|
|
951
|
+
export interface SharedShape { b: number; }
|
|
952
|
+
```
|
|
953
|
+
|
|
954
|
+
**Workaround:** declare the merged shape once, or split the interfaces under distinct names and import the one you mean.
|
|
810
955
|
|
|
811
956
|
### Custom collection name conflicts
|
|
812
957
|
|
package/lib/analyzer.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import { AnalyzeResult, DefinitionInfo, UsageInfo, EDSInfo, FlowInfo, InstrumentationPoint } from './types';
|
|
2
|
+
import { AnalyzeResult, DefinitionInfo, UsageInfo, EDSInfo, FlowInfo, InstrumentationPoint, ResolutionError } from './types';
|
|
3
3
|
import { TypeGraphImpl } from './graph';
|
|
4
4
|
import { TacticaPlugin } from './plugins';
|
|
5
5
|
/**
|
|
@@ -20,7 +20,6 @@ export declare class MnemonicaAnalyzer {
|
|
|
20
20
|
private functionBindings;
|
|
21
21
|
private nestedWrapVia;
|
|
22
22
|
private wrapEntryByNode;
|
|
23
|
-
private typeAliases;
|
|
24
23
|
private variableToTypeMap;
|
|
25
24
|
private moduleObjectVariables;
|
|
26
25
|
private createTypesCollectionVariables;
|
|
@@ -30,6 +29,27 @@ export declare class MnemonicaAnalyzer {
|
|
|
30
29
|
private instrumentationClassDecls;
|
|
31
30
|
private instrumentationSites;
|
|
32
31
|
private instrumentationVocabulary;
|
|
32
|
+
private referencedTypeDecls;
|
|
33
|
+
private referencedTypeImports;
|
|
34
|
+
private referencedTypeReExports;
|
|
35
|
+
private referencedTypeExportStars;
|
|
36
|
+
private referencedTypeExportAliases;
|
|
37
|
+
private referencedTypeNamespaces;
|
|
38
|
+
private referencedTypeNamespaceStars;
|
|
39
|
+
private referencedTypeResolutionCache;
|
|
40
|
+
private referencedTypeConstArrays;
|
|
41
|
+
private referencedTypeCompilerOptions;
|
|
42
|
+
private currentReferencedTypeFile;
|
|
43
|
+
private expandingReferencedAliases;
|
|
44
|
+
private defineSites;
|
|
45
|
+
private graphReferenceErrors;
|
|
46
|
+
private lookupReferencesValidated;
|
|
47
|
+
private lookupReferences;
|
|
48
|
+
private plainTypeReferencesValidated;
|
|
49
|
+
private plainTypeReferences;
|
|
50
|
+
private fileGraphBindings;
|
|
51
|
+
private currentGraphAnchor;
|
|
52
|
+
private processedCalls;
|
|
33
53
|
constructor(program?: ts.Program, plugins?: TacticaPlugin[]);
|
|
34
54
|
/**
|
|
35
55
|
* Reset usage-related state for a fresh pass.
|
|
@@ -93,6 +113,250 @@ export declare class MnemonicaAnalyzer {
|
|
|
93
113
|
* createTypesCollection are recognized without relying on the type checker.
|
|
94
114
|
*/
|
|
95
115
|
private trackImports;
|
|
116
|
+
/**
|
|
117
|
+
* Record a named referenced-type declaration (type alias, class, or
|
|
118
|
+
* interface) for the file currently being visited.
|
|
119
|
+
*/
|
|
120
|
+
private trackReferencedTypeDeclaration;
|
|
121
|
+
/**
|
|
122
|
+
* Record consts initialized with an array literal (optionally wrapped in
|
|
123
|
+
* `as const` / `satisfies`), so a `typeof statusList[number]` field type
|
|
124
|
+
* expands to the element literal union — the generated file carries no
|
|
125
|
+
* imports, so emitting the bare `typeof statusList` query would be an
|
|
126
|
+
* unresolvable name downstream. First binding wins: a nested shadow
|
|
127
|
+
* must not replace the module-level const the typeof refers to.
|
|
128
|
+
*/
|
|
129
|
+
private trackReferencedTypeConstArray;
|
|
130
|
+
/**
|
|
131
|
+
* Find the array literal behind a module const referenced through
|
|
132
|
+
* `typeof`: the declaring file's own consts first (the F13 case is a
|
|
133
|
+
* NON-exported const in the same module as the expanded class), then —
|
|
134
|
+
* when the file imports the name — the imported module's consts.
|
|
135
|
+
* External modules are never analyzed, so those yield nothing.
|
|
136
|
+
*/
|
|
137
|
+
private findReferencedConstArray;
|
|
138
|
+
/**
|
|
139
|
+
* Element literal types of a tracked const array: every element must be
|
|
140
|
+
* a plain literal (optionally wrapped in `as const` / `satisfies`) —
|
|
141
|
+
* string, numeric, boolean, or null. Spreads, identifiers, and nested
|
|
142
|
+
* arrays mean the union is not statically visible and yield undefined,
|
|
143
|
+
* so the caller degrades the field to `unknown` rather than guessing.
|
|
144
|
+
*/
|
|
145
|
+
private literalTypesOfArray;
|
|
146
|
+
/**
|
|
147
|
+
* Emit-type for `typeof name` when `name` is a tracked const array: the
|
|
148
|
+
* union of its element literal types (`'active' | 'closed'`). Every
|
|
149
|
+
* other typeof source — non-array consts, functions, classes, names not
|
|
150
|
+
* tracked at all — yields undefined, so the caller degrades the field
|
|
151
|
+
* to `unknown`: a bare `typeof name` emitted into types.ts has no
|
|
152
|
+
* import to resolve against downstream.
|
|
153
|
+
*/
|
|
154
|
+
private typeOfConstArrayUnion;
|
|
155
|
+
/**
|
|
156
|
+
* Record the importing file's named/namespace/default import bindings so
|
|
157
|
+
* referenced-type names resolve through the file's own import statements
|
|
158
|
+
* (F10) rather than a program-wide name map.
|
|
159
|
+
*/
|
|
160
|
+
private trackReferencedTypeImport;
|
|
161
|
+
/**
|
|
162
|
+
* Record re-export wiring (`export { X } from '…'`, `export * from '…'`,
|
|
163
|
+
* `export { X as Y }`) so resolution can chase barrels to the origin
|
|
164
|
+
* module. Mirrors ModuleGraphBuilder.resolveOrigin, name-based only.
|
|
165
|
+
*/
|
|
166
|
+
private trackReferencedTypeReExport;
|
|
167
|
+
/**
|
|
168
|
+
* Resolve a module specifier from a containing file with the program's
|
|
169
|
+
* compilerOptions (tsconfig `paths`, extensionless imports, index files).
|
|
170
|
+
* Module resolution only — the no-getTypeChecker() precedent stays.
|
|
171
|
+
*/
|
|
172
|
+
private resolveReferencedTypeModule;
|
|
173
|
+
/**
|
|
174
|
+
* Look up a name in one resolved module, chasing re-export barrels with a
|
|
175
|
+
* bounded depth. External (node_modules) modules hold no in-project
|
|
176
|
+
* declarations and stop the chase.
|
|
177
|
+
*/
|
|
178
|
+
private findReferencedTypeInModule;
|
|
179
|
+
/**
|
|
180
|
+
* Resolve a referenced type name as used in fromFile, import-aware:
|
|
181
|
+
* 1. the file's own import statements (relative + tsconfig paths,
|
|
182
|
+
* chased through re-export barrels),
|
|
183
|
+
* 2. the file's local declarations,
|
|
184
|
+
* 3. the unique same-named declaration across scanned files.
|
|
185
|
+
* Returns undefined when nothing matches (or the match is ambiguous),
|
|
186
|
+
* in which case the caller falls back to `unknown`.
|
|
187
|
+
*/
|
|
188
|
+
private resolveReferencedTypeDeclaration;
|
|
189
|
+
/**
|
|
190
|
+
* External/ambient declaration files (.d.ts, anything under
|
|
191
|
+
* node_modules) never participate in plain-TS referenced-type
|
|
192
|
+
* resolution or the ambiguity law: they are not project source, the
|
|
193
|
+
* CLI never analyzes them, and a user-local declaration always wins
|
|
194
|
+
* over a package-declared same-named type.
|
|
195
|
+
*/
|
|
196
|
+
private isExternalDeclFile;
|
|
197
|
+
/**
|
|
198
|
+
* Properties of a referenced class/interface/alias-of-literal declaration,
|
|
199
|
+
* shared by `this:`-parameter expansion and inline type emission.
|
|
200
|
+
* Inherited members are included: the extends chain is walked
|
|
201
|
+
* (depth-capped, cycle-guarded) and parent fields merge first, the
|
|
202
|
+
* declaration's own fields overriding on name clash.
|
|
203
|
+
*/
|
|
204
|
+
private referencedDeclarationProperties;
|
|
205
|
+
private referencedDeclarationPropertiesInner;
|
|
206
|
+
/**
|
|
207
|
+
* Property signatures of interface/alias type-literal members, into
|
|
208
|
+
* the given map.
|
|
209
|
+
*/
|
|
210
|
+
private collectTypeElementProperties;
|
|
211
|
+
/**
|
|
212
|
+
* Resolve the heritage clause of a class (`extends Base`) or interface
|
|
213
|
+
* (`extends A, B`) to referenced-type declarations through the SAME
|
|
214
|
+
* import-aware machinery as plain references (the declaring file's own
|
|
215
|
+
* imports first, then its locals, then the unique program-wide
|
|
216
|
+
* declaration). Unresolvable or external bases yield nothing — their
|
|
217
|
+
* inherited fields simply stay absent, same as before this walk
|
|
218
|
+
* existed. Mixin calls (`extends mixin(X)`) and namespace access are
|
|
219
|
+
* not followed.
|
|
220
|
+
*/
|
|
221
|
+
private resolveHeritageDeclarations;
|
|
222
|
+
/**
|
|
223
|
+
* Expand a referenced-type declaration to a self-contained type string
|
|
224
|
+
* for emission into generated files: type aliases through inferType,
|
|
225
|
+
* classes and interfaces through their (public, non-method) fields.
|
|
226
|
+
* Nested references resolve against the declaring file while expanding.
|
|
227
|
+
*/
|
|
228
|
+
private expandReferencedTypeDeclaration;
|
|
229
|
+
private expandReferencedTypeDeclarationInner;
|
|
230
|
+
/**
|
|
231
|
+
* Resolve a simple (non-qualified) type reference: import-aware
|
|
232
|
+
* declaration expansion first, then the InstanceType<typeof X> pattern,
|
|
233
|
+
* then mnemonica graph types; known globals keep their bare name and
|
|
234
|
+
* anything else falls back to `unknown` so generated files never carry
|
|
235
|
+
* an unresolvable bare name. Returns undefined when the caller should
|
|
236
|
+
* keep the generic spelling (handled separately).
|
|
237
|
+
*/
|
|
238
|
+
private resolveSimpleTypeReference;
|
|
239
|
+
/**
|
|
240
|
+
* Resolve a qualified type reference (models.Inner.Crate) through the
|
|
241
|
+
* current file's namespace imports. The chain's head must be a namespace
|
|
242
|
+
* import; middle segments descend through namespace declarations, named
|
|
243
|
+
* re-exports of namespaces, and `export * as ns from '…'` barrels (each
|
|
244
|
+
* segment consumed exactly once, so the walk cannot cycle); the final
|
|
245
|
+
* segment resolves to a declaration which is expanded inline. When the
|
|
246
|
+
* precise walk finds nothing, the legacy rightmost-name lookup in the
|
|
247
|
+
* head module keeps one-level forms (models.Type) working — nested
|
|
248
|
+
* declarations are recorded by plain name there too. Returns undefined
|
|
249
|
+
* when the head is not a namespace import or nothing resolves.
|
|
250
|
+
*/
|
|
251
|
+
private inferQualifiedTypeReference;
|
|
252
|
+
/**
|
|
253
|
+
* Find a namespace declaration by name directly inside a module block.
|
|
254
|
+
*/
|
|
255
|
+
private findNamespaceInBlock;
|
|
256
|
+
/**
|
|
257
|
+
* Find a named type declaration (alias, class, interface) directly inside
|
|
258
|
+
* a namespace block — the final segment of a descended qualified chain.
|
|
259
|
+
*/
|
|
260
|
+
private findReferencedTypeInBlock;
|
|
261
|
+
/**
|
|
262
|
+
* Fallback for a type-reference name that resolves to no declaration and
|
|
263
|
+
* no graph type: known globals keep their bare name (they resolve without
|
|
264
|
+
* an import); everything else becomes `unknown` so generated types.ts
|
|
265
|
+
* never carries an unresolvable bare name (README's documented behavior)
|
|
266
|
+
* and the site is recorded for the plain-TS ambiguity validation.
|
|
267
|
+
*/
|
|
268
|
+
private unresolvedTypeReferenceFallback;
|
|
269
|
+
/**
|
|
270
|
+
* Record one define()/lazy()/@decorate() site under its runtime
|
|
271
|
+
* namespace key. Two sites in one namespace are a same-namespace
|
|
272
|
+
* duplicate (the runtime throws ALREADY_DECLARED); every site is kept
|
|
273
|
+
* so the failure can report all locations.
|
|
274
|
+
*/
|
|
275
|
+
private recordDefineSite;
|
|
276
|
+
/**
|
|
277
|
+
* Fatal resolution failures (hard-fail law): same-namespace duplicate
|
|
278
|
+
* mnemonica definitions plus ambiguous/unresolved mnemonica-graph
|
|
279
|
+
* references. The CLI prints every location and writes no output.
|
|
280
|
+
*/
|
|
281
|
+
getResolutionErrors(): ResolutionError[];
|
|
282
|
+
/**
|
|
283
|
+
* Resolve a reference to a mnemonica graph type name, import-aware and
|
|
284
|
+
* path-aware (the hard-fail identity law, mirroring the runtime):
|
|
285
|
+
* 1. value scope — a tracked top-level binding in the referencing file
|
|
286
|
+
* (`const Address = User.define('Address', …)`),
|
|
287
|
+
* 2. import scope — a binding exported from a module this file imports
|
|
288
|
+
* (barrels chased),
|
|
289
|
+
* 3. nearest-chain — the anchor type's own subtypes first, then each
|
|
290
|
+
* ancestor level (relative-first),
|
|
291
|
+
* 4. root — roots of the anchor's collection,
|
|
292
|
+
* 5. program-wide — only when exactly one type carries the name.
|
|
293
|
+
* Ambiguity (several candidates and nothing disambiguates) and absence
|
|
294
|
+
* are both returned as such — the caller records a hard failure; a bare
|
|
295
|
+
* first-match name is never emitted.
|
|
296
|
+
*/
|
|
297
|
+
private resolveGraphTypeName;
|
|
298
|
+
/**
|
|
299
|
+
* Find a graph constructor binding exported by a resolved module,
|
|
300
|
+
* chasing re-export barrels with a bounded depth.
|
|
301
|
+
*/
|
|
302
|
+
private findGraphBindingInModule;
|
|
303
|
+
/**
|
|
304
|
+
* Validate literal lookup() paths recorded during the usages pass
|
|
305
|
+
* against the complete graph. A lookup path matching no type is what the
|
|
306
|
+
* runtime answers with `undefined` — the TypeError arrives one line
|
|
307
|
+
* later at the `new` — so it joins the hard-fail law. The relative-first
|
|
308
|
+
* step already ran inside resolveLookupPath; whatever was recorded is
|
|
309
|
+
* the root-resolution result, so a plain findType check is the exact
|
|
310
|
+
* runtime law. Same-named types elsewhere in the graph are listed as
|
|
311
|
+
* did-you-mean candidates. Runs once per usages pass (re-armed by
|
|
312
|
+
* resetUsages); non-literal lookup arguments are never recorded and
|
|
313
|
+
* stay best-effort.
|
|
314
|
+
*/
|
|
315
|
+
private validateLookupReferences;
|
|
316
|
+
/**
|
|
317
|
+
* Record a plain-TS type reference site that resolved to nothing and
|
|
318
|
+
* fell back to `unknown`, for the lazily-run ambiguity validation.
|
|
319
|
+
* Deduped by (name, location): inferType can visit the same node more
|
|
320
|
+
* than once per pass (constructor params + property inference).
|
|
321
|
+
*/
|
|
322
|
+
private recordPlainTypeReferenceSite;
|
|
323
|
+
/**
|
|
324
|
+
* Project-source declaration files carrying `name` — one entry per
|
|
325
|
+
* file, so same-file interface merging counts once (not ambiguous).
|
|
326
|
+
* External/ambient declarations (.d.ts, anything under node_modules)
|
|
327
|
+
* never count: a user-local declaration always wins over a package-
|
|
328
|
+
* declared same-named type, so an external collision stays soft.
|
|
329
|
+
*/
|
|
330
|
+
private plainTypeDeclarationFiles;
|
|
331
|
+
/**
|
|
332
|
+
* Validate plain-TS type reference sites recorded during the usages
|
|
333
|
+
* pass against the complete declaration map. A name declared in
|
|
334
|
+
* several project-source files — with no import in the referencing
|
|
335
|
+
* file to anchor it — is ambiguous: silently emitting `unknown` would
|
|
336
|
+
* hide a real type the author meant, so it joins the hard-fail law
|
|
337
|
+
* (the plain-TS tier of the same identity law as graph references).
|
|
338
|
+
* Absence (ghost names) and external collisions stay soft `unknown`.
|
|
339
|
+
* Runs once per usages pass (re-armed by resetUsages), mirroring
|
|
340
|
+
* validateLookupReferences: recording happens on every pass, but only
|
|
341
|
+
* the usages pass sees the complete declaration map.
|
|
342
|
+
*/
|
|
343
|
+
private validatePlainTypeReferences;
|
|
344
|
+
/**
|
|
345
|
+
* `file:line:column` of a recorded declaration, for the ambiguity
|
|
346
|
+
* report. Nodes recorded during traversal keep their positions; a
|
|
347
|
+
* synthetic/unpositioned node falls back to the file itself.
|
|
348
|
+
*/
|
|
349
|
+
private plainDeclLocation;
|
|
350
|
+
/**
|
|
351
|
+
* Record a hard-fail graph reference error with the reference site and
|
|
352
|
+
* every candidate location.
|
|
353
|
+
*/
|
|
354
|
+
private recordGraphReferenceError;
|
|
355
|
+
/**
|
|
356
|
+
* Location (`file:line:column`) of an AST node, derived without parent
|
|
357
|
+
* pointers when necessary.
|
|
358
|
+
*/
|
|
359
|
+
private nodeLocation;
|
|
96
360
|
/**
|
|
97
361
|
* Track aliases of the mnemonica module object, e.g.:
|
|
98
362
|
* const m = mnemonica;
|
|
@@ -203,6 +467,12 @@ export declare class MnemonicaAnalyzer {
|
|
|
203
467
|
* For chained calls like const X = define('A').define('B'), we map X -> A (the root type)
|
|
204
468
|
*/
|
|
205
469
|
private trackVariableAssignment;
|
|
470
|
+
/**
|
|
471
|
+
* Mirror a variable -> mnemonica fullPath binding into the per-file
|
|
472
|
+
* value-scope map (graph identity law: `typeof X` and bare references
|
|
473
|
+
* resolve through the file's own bindings first).
|
|
474
|
+
*/
|
|
475
|
+
private trackFileGraphBinding;
|
|
206
476
|
/**
|
|
207
477
|
* Track variable assignments from lookup() calls
|
|
208
478
|
* e.g., const SentienceConstructor = lookup('Sentience') maps "SentienceConstructor" -> "Sentience"
|
|
@@ -255,6 +525,15 @@ export declare class MnemonicaAnalyzer {
|
|
|
255
525
|
* collection.lookup('User.Admin')
|
|
256
526
|
*/
|
|
257
527
|
private resolveLookupPath;
|
|
528
|
+
/**
|
|
529
|
+
* Lookup-law delegate for the local-scope walker (scopes.json typePath
|
|
530
|
+
* metadata): resolve a lookup() initializer call through exactly the
|
|
531
|
+
* tiers the usages pass resolved it against (same source resolution,
|
|
532
|
+
* same complete graph). The walker runs its own scope-chain value-scope
|
|
533
|
+
* tier before delegating; everything above value scope lands here, so
|
|
534
|
+
* scopes.json never disagrees with the hard-fail-law verdicts.
|
|
535
|
+
*/
|
|
536
|
+
resolveLookupCallPath(call: ts.CallExpression): string | undefined;
|
|
258
537
|
/**
|
|
259
538
|
* Find a parent type by its name, searching in the graph.
|
|
260
539
|
* When collectionId is provided, only types from that collection are considered.
|
|
@@ -338,10 +617,6 @@ export declare class MnemonicaAnalyzer {
|
|
|
338
617
|
* Infer return type by analyzing return statements in the method body
|
|
339
618
|
*/
|
|
340
619
|
private inferReturnTypeFromBody;
|
|
341
|
-
/**
|
|
342
|
-
* Get full text from a qualified name (e.g., Namespace.Type)
|
|
343
|
-
*/
|
|
344
|
-
private getQualifiedNameText;
|
|
345
620
|
/**
|
|
346
621
|
* Infer type from initializer
|
|
347
622
|
*/
|
|
@@ -373,6 +648,24 @@ export declare class MnemonicaAnalyzer {
|
|
|
373
648
|
* returns undefined for calls outside any type scope (module top level).
|
|
374
649
|
*/
|
|
375
650
|
private resolveEDSScope;
|
|
651
|
+
/**
|
|
652
|
+
* Resolve a wrap site's instance/context argument to a mnemonica type
|
|
653
|
+
* path — the fire-and-forget-wrapper attribution fallback when the call
|
|
654
|
+
* sits outside any define()/lazy() handler: a tracked assignment
|
|
655
|
+
* (`const holder = new Holder(...)`), else the root identifier's
|
|
656
|
+
* (property-access roots included) parameter annotation resolved
|
|
657
|
+
* through the graph law. Ambiguity or absence stays silent — this is a
|
|
658
|
+
* metadata heuristic, not the identity-law surface.
|
|
659
|
+
*/
|
|
660
|
+
private resolveWrapInstanceTypePath;
|
|
661
|
+
/**
|
|
662
|
+
* Resolve a bare-identifier type annotation of the nearest enclosing
|
|
663
|
+
* function's parameter through the mnemonica-graph tiers (value scope,
|
|
664
|
+
* imports, roots, program-wide-unique). Non-identifier and generic
|
|
665
|
+
* annotations are not graph references; ambiguity and absence yield
|
|
666
|
+
* undefined.
|
|
667
|
+
*/
|
|
668
|
+
private resolveParameterAnnotationTypePath;
|
|
376
669
|
/**
|
|
377
670
|
* Resolve a wrap() argument to its function node without the type
|
|
378
671
|
* checker: direct function expressions/arrows, or same-file bindings
|
|
@@ -397,6 +690,8 @@ export declare class MnemonicaAnalyzer {
|
|
|
397
690
|
* site (`via` = the site whose wrapping caused it) and recurse into
|
|
398
691
|
* its own returns. Returns through identifiers resolve through the
|
|
399
692
|
* same-file bindings table; unresolvable returns are simply skipped.
|
|
693
|
+
* A return declared outside any type scope inherits the causing wrap
|
|
694
|
+
* site's scope attribution (the generation chain is the only holder).
|
|
400
695
|
*/
|
|
401
696
|
private recordWrappedReturn;
|
|
402
697
|
/**
|