@mnemonica/tactica 0.3.11 → 0.3.15
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 +14 -12
- package/lib/analyzer.d.ts +8 -0
- package/lib/analyzer.js +65 -3
- package/lib/cli.js +6 -2
- package/lib/writer.d.ts +11 -1
- package/lib/writer.js +60 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -125,7 +125,7 @@ EDS tracking is **auto-enabled** when `@mnemonica/dive` is present in `package.j
|
|
|
125
125
|
|
|
126
126
|
## Generated Files
|
|
127
127
|
|
|
128
|
-
Tactica writes everything under the `--output` directory (default `.tactica/`)
|
|
128
|
+
Tactica writes everything under the `--output` directory (default `.tactica/`). All file paths inside the JSON files are emitted **project-relative** (anchored at the tsconfig directory) so the output is portable across machines; consumers resolve them against the directory holding `.tactica`. Earlier versions emitted absolute paths, so consumers should accept both.
|
|
129
129
|
|
|
130
130
|
| File | When | Purpose |
|
|
131
131
|
|---|---|---|
|
|
@@ -490,6 +490,8 @@ The analyzer infers property types from constructor bodies and class members. Su
|
|
|
490
490
|
|
|
491
491
|
An existing annotation always beats a weaker inference: `unknown` (and `unknown`-bearing guesses like `Array<unknown>`) never overwrites a known type — `Record<string, unknown>` IS a known type — and an overwrite never drops an optionality modifier.
|
|
492
492
|
|
|
493
|
+
**Method/member return annotations** follow an emission law (0.2.0 behavior, restored after the F10 bare-name ban overshot): an annotation — or any type argument inside it — that resolves to a mnemonica graph type emits the generated alias (`SomeEntry[]` → `Array<EDSRoot_SomeEntry>`; generated-alias spellings resolve via the underscore→dotted law). `InstanceType<typeof X>` — directly or through a local alias (`XInstance = InstanceType<typeof X>`) — drops the wrapper and emits the generated alias (`Definitions_DefinitionEntry`), since the alias already IS the instance type; when X does not resolve to a graph type the WHOLE expression degrades to `unknown` — `InstanceType<unknown>` is invalid TypeScript (TS2344) and is never emitted. An outer generic that is a non-graph **ambient/lib** construct (`MapIterator`, lib helpers — not declared in any analyzed project file) is emitted **verbatim** with inner graph aliases resolved, since it needs no import in the generated file. A **project-local** non-graph name stays `unknown` — the self-contained `types.ts` can carry neither the bare name nor an import (ambiguity between same-named project declarations is the fatal class, as elsewhere). This law covers method/member return annotations; constructor-signature parameters keep their accepted mirror policy.
|
|
494
|
+
|
|
493
495
|
When inference fails the property's type falls back to `unknown` (or `any` in some Topologica paths) — safe, and you can refine manually.
|
|
494
496
|
|
|
495
497
|
### Referenced types in constructor signatures
|
|
@@ -703,7 +705,7 @@ class ModuleGraphBuilder {
|
|
|
703
705
|
|
|
704
706
|
`ModuleGraph` = `{ modules: Map<absPath, ModuleInfo>, edges: CrossModuleUsage[], cycles: string[][] }`.
|
|
705
707
|
Each `ModuleBinding` carries `{ name, kind: 'function'|'class'|'const'|'type'|'unknown',
|
|
706
|
-
sourceModule (resolved
|
|
708
|
+
sourceModule (resolved project-relative path), importKind?, importAlias?, isReExport, external? }`.
|
|
707
709
|
Node.js builtins (`'path'`, `'node:fs'`, …) are skipped entirely and recorded
|
|
708
710
|
only in the module's `builtinSpecifiers`; bindings resolved into `node_modules`
|
|
709
711
|
carry `external: true` and never enter `dependencies`. Re-export chains
|
|
@@ -787,7 +789,7 @@ When enabled, tactica detects execution-flow patterns alongside type definitions
|
|
|
787
789
|
"eds": {
|
|
788
790
|
"UserEntity": [
|
|
789
791
|
{
|
|
790
|
-
"location": "
|
|
792
|
+
"location": "src/queue.ts:45:12",
|
|
791
793
|
"kind": "wrap",
|
|
792
794
|
"code": "wrap(process)",
|
|
793
795
|
"targetType": "UserEntity"
|
|
@@ -857,7 +859,7 @@ When a referenced class is declared in the analyzed project, the point's `locati
|
|
|
857
859
|
{
|
|
858
860
|
"kind": "pipe",
|
|
859
861
|
"className": "PayloadPipe",
|
|
860
|
-
"location": "
|
|
862
|
+
"location": "src/user.controller.ts:49:2",
|
|
861
863
|
"code": "@RegisterPipe(new PayloadPipe({ transform: true }))",
|
|
862
864
|
"scope": "method:UserController.createUser",
|
|
863
865
|
"targets": ["UserController"]
|
|
@@ -866,21 +868,21 @@ When a referenced class is declared in the analyzed project, the point's `locati
|
|
|
866
868
|
"creationGraph": {
|
|
867
869
|
"nodes": [
|
|
868
870
|
{
|
|
869
|
-
"scopeId": "
|
|
870
|
-
"name": "
|
|
871
|
+
"scopeId": "src/main.ts",
|
|
872
|
+
"name": "src/main.ts",
|
|
871
873
|
"kind": "module",
|
|
872
|
-
"filePath": "
|
|
873
|
-
"location": "
|
|
874
|
+
"filePath": "src/main.ts",
|
|
875
|
+
"location": "src/main.ts:1:1",
|
|
874
876
|
"starter": true
|
|
875
877
|
}
|
|
876
878
|
],
|
|
877
879
|
"edges": [
|
|
878
|
-
{ "caller": "
|
|
880
|
+
{ "caller": "src/main.ts", "callee": "src/user.service.ts:26:2" }
|
|
879
881
|
],
|
|
880
882
|
"anchors": [
|
|
881
883
|
{
|
|
882
|
-
"location": "
|
|
883
|
-
"holderScopeId": "
|
|
884
|
+
"location": "src/user.service.ts:29:24",
|
|
885
|
+
"holderScopeId": "src/user.service.ts:26:2",
|
|
884
886
|
"typePath": "UserEntity.UserResponse",
|
|
885
887
|
"constructorText": "user.UserResponse",
|
|
886
888
|
"variable": "userResponse"
|
|
@@ -960,7 +962,7 @@ Mnemonica accepts the `exposeInstanceMethods` option at runtime, but tactica's `
|
|
|
960
962
|
|
|
961
963
|
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`.
|
|
962
964
|
|
|
963
|
-
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, with the index suffix consumed entirely (`typeof statusList[number]` emits exactly `'active' | 'not_active' | 'hold'`, never `'hold'[number]`). The value-level twin — element access over a const-asserted literal array (`(<const>[…])[0]`, `([…] as const)[1]`, tracked `x[0]`) — infers the element literal under the same rules; non-numeric, out-of-range, spread, or non-literal elements and general `<T>` assertions (as opposed to `<const>`) stay `unknown`. Any assertion spelling is tracked (`[…] as const`, `satisfies`, `<const>[…]`), and signed numeric literals keep their sign (`-1 | 1`). Cross-file only when the const is imported by the declaring file. Every other `typeof` source — and any indexed-access target that fails to resolve — degrades the whole expression to `unknown`: an index suffix glued onto a fallback type (`unknown[number]`) is invalid TypeScript and is never emitted.
|
|
965
|
+
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, with the index suffix consumed entirely (`typeof statusList[number]` emits exactly `'active' | 'not_active' | 'hold'`, never `'hold'[number]`). The value-level twin — element access over a const-asserted literal array (`(<const>[…])[0]`, `([…] as const)[1]`, tracked `x[0]`) — infers the element literal under the same rules; non-numeric, out-of-range, spread, or non-literal elements and general `<T>` assertions (as opposed to `<const>`) stay `unknown`. Any assertion spelling is tracked (`[…] as const`, `satisfies`, `<const>[…]`), and signed numeric literals keep their sign (`-1 | 1`). Cross-file only when the const is imported by the declaring file. Every other `typeof` source — and any indexed-access target that fails to resolve — degrades the whole expression to `unknown`: an index suffix glued onto a fallback type (`unknown[number]`) is invalid TypeScript and is never emitted. A sibling emission law governs method/member return annotations (0.2.0 behavior restored after the F10 bare-name ban overshot): inner type arguments resolving to graph types emit the generated aliases; `InstanceType<typeof X>` (directly or via a local alias) drops the wrapper and emits the alias — X unresolvable degrades the WHOLE expression to `unknown`, never `InstanceType<unknown>` (invalid TS, TS2344); a non-graph **ambient/lib** outer generic (`MapIterator`, lib helpers — not declared in any analyzed project file) emits verbatim with inner aliases resolved; a **project-local** non-graph name stays `unknown` (no imports in the generated file; same-named ambiguity is the fatal class). Constructor-signature parameters keep their accepted mirror policy and are not covered.
|
|
964
966
|
|
|
965
967
|
### Same-file interface merging is last-wins
|
|
966
968
|
|
package/lib/analyzer.d.ts
CHANGED
|
@@ -776,6 +776,14 @@ export declare class MnemonicaAnalyzer {
|
|
|
776
776
|
* metadata heuristic, not the identity-law surface.
|
|
777
777
|
*/
|
|
778
778
|
private resolveWrapInstanceTypePath;
|
|
779
|
+
/**
|
|
780
|
+
* Emission-law helper (0.2.0 restoration): is `name` declared in any
|
|
781
|
+
* ANALYZED PROJECT file? External/ambient files (.d.ts, node_modules)
|
|
782
|
+
* do not count. A name with no project declaration is an ambient/lib
|
|
783
|
+
* construct — safe to emit verbatim into the self-contained types.ts;
|
|
784
|
+
* a project-local name is not (no imports in the generated file).
|
|
785
|
+
*/
|
|
786
|
+
private isProjectDeclaredTypeName;
|
|
779
787
|
/**
|
|
780
788
|
* F24: resolve a bare-identifier annotation to a graph fullPath. The
|
|
781
789
|
* annotation may name the type directly (`LedgerUpdate`) or carry
|