@mnemonica/tactica 0.4.7 → 0.4.9
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 +7 -4
- package/lib/analyzer.d.ts +18 -1
- package/lib/analyzer.js +56 -2
- package/lib/cli.js +19 -3
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/plugins.d.ts +20 -9
- package/lib/plugins.js +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -131,6 +131,8 @@ You should commit `.tactica/` if you want type information to flow through CI wi
|
|
|
131
131
|
|
|
132
132
|
EDS tracking is **auto-enabled** when `@mnemonica/dive` is present in `package.json` dependencies; otherwise it is off. `--eds` / `--no-eds` override the auto-detection.
|
|
133
133
|
|
|
134
|
+
Analyzer errors (for example the unnamed `lazy()` form, which the runtime rejects with `TYPENAME_MUST_BE_A_STRING`) never abort the run: the offending call is skipped, each error prints once as a `tactica: <file>:<line>:<column> <message>` warning on stderr (not `--verbose`-gated), and the exit code stays 0.
|
|
135
|
+
|
|
134
136
|
## Generated Files
|
|
135
137
|
|
|
136
138
|
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.
|
|
@@ -256,7 +258,7 @@ Chained calls (`define('A').define('B')`) and nested calls via variable referenc
|
|
|
256
258
|
### `lazy()` definitions
|
|
257
259
|
|
|
258
260
|
```ts
|
|
259
|
-
const AdminType = UserType.lazy(() => class AdminType {
|
|
261
|
+
const AdminType = UserType.lazy('AdminType', () => class AdminType {
|
|
260
262
|
role: string;
|
|
261
263
|
constructor (role: string) {
|
|
262
264
|
this.role = role;
|
|
@@ -264,7 +266,7 @@ const AdminType = UserType.lazy(() => class AdminType {
|
|
|
264
266
|
});
|
|
265
267
|
```
|
|
266
268
|
|
|
267
|
-
|
|
269
|
+
The name is required — the runtime throws `TYPENAME_MUST_BE_A_STRING` for the unnamed forms (`lazy(getter)`, `Type.lazy(getter)`, `lazy(source, getter)`), so tactica reports them as analyzer errors naming the call site and generates no type for them. The named forms are all recognized: free `lazy('Name', getter)`, method `Type.lazy('Name', getter)`, chained `define('A').lazy('B', getter)`, and explicit-source `lazy(source, 'Name', getter)`. The getter is followed, and the returned constructor is analyzed like a direct `define()` handler — properties and constructor parameters are extracted and emitted in `types.ts` / `registry.ts` like any other type.
|
|
268
270
|
|
|
269
271
|
### Construction shapes beyond plain `new`
|
|
270
272
|
|
|
@@ -859,7 +861,8 @@ interface TacticaPlugin {
|
|
|
859
861
|
appTokens?: Record<string, InstrumentationKind>;
|
|
860
862
|
// factory calls inside listed decorators' args: factory method name ->
|
|
861
863
|
// kind + which factory-call argument holds the target class (default 0)
|
|
862
|
-
|
|
864
|
+
// + whether the factory constructs that target (instantiation)
|
|
865
|
+
decoratorArgFactories?: Record<string, { kind: InstrumentationKind; targetArg?: number; instantiation?: boolean }>;
|
|
863
866
|
// opt in to shape-based `consumer.apply(Mw).forRoutes(...)` detection
|
|
864
867
|
middlewareWiring?: boolean;
|
|
865
868
|
}
|
|
@@ -869,7 +872,7 @@ Programmatic callers pass plugins directly: `new MnemonicaAnalyzer(program, plug
|
|
|
869
872
|
|
|
870
873
|
- **Heritage** — `class X implements <plugin-listed interface>` (matched by interface identifier name). Bare declarations carry scope `module` (attachment statically unknown).
|
|
871
874
|
- **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).
|
|
872
|
-
- **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.
|
|
875
|
+
- **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. With `instantiation: true` the plugin declares that the factory constructs its target on the caller's behalf (a pipe running `new Target(body)` per request): when the target argument resolves to a defined type — a `lookup()` alias, a type name, or a type path — the factory call is also recorded in `usages.json` and `flow.json` as an `instantiation` of that type, `constructorText` being the argument text.
|
|
873
876
|
- **Global providers** — `{ provide: <plugin-listed token>, useClass: X }` object literals → scope `global`. `useExisting`/`useFactory` without `useClass` are skipped.
|
|
874
877
|
- **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.
|
|
875
878
|
|
package/lib/analyzer.d.ts
CHANGED
|
@@ -477,7 +477,9 @@ export declare class MnemonicaAnalyzer {
|
|
|
477
477
|
/**
|
|
478
478
|
* Extract lazy() call arguments into a normalized shape.
|
|
479
479
|
* Handles named/unnamed and explicit-source forms, both as free calls
|
|
480
|
-
* and as method calls.
|
|
480
|
+
* and as method calls. The unnamed shapes return `name: undefined` —
|
|
481
|
+
* processLazyCall rejects them with an analyzer error (the runtime
|
|
482
|
+
* requires the name), so no type is generated for them.
|
|
481
483
|
*/
|
|
482
484
|
private extractLazyCallArgs;
|
|
483
485
|
/**
|
|
@@ -577,6 +579,13 @@ export declare class MnemonicaAnalyzer {
|
|
|
577
579
|
* chain resolution for property accesses).
|
|
578
580
|
*/
|
|
579
581
|
private resolveConstructionFnTypePath;
|
|
582
|
+
/**
|
|
583
|
+
* Resolve an expression passed where a constructor is expected (the
|
|
584
|
+
* Ctor of call/apply/bind, a plugin factory's constructed target) to a
|
|
585
|
+
* defined type's fullPath: value scope for identifiers, chain
|
|
586
|
+
* resolution for property accesses.
|
|
587
|
+
*/
|
|
588
|
+
private resolveCtorArgTypePath;
|
|
580
589
|
/**
|
|
581
590
|
* instance.fork(...) / instance.clone(...) on a tracked variable —
|
|
582
591
|
* runtime returns `this`, so the result carries the source type.
|
|
@@ -986,6 +995,14 @@ export declare class MnemonicaAnalyzer {
|
|
|
986
995
|
* referenced class identifier.
|
|
987
996
|
*/
|
|
988
997
|
private collectInstrumentationDecorator;
|
|
998
|
+
/**
|
|
999
|
+
* A plugin-listed decorator-arg factory with `instantiation: true`
|
|
1000
|
+
* (`@Body(mvp.forType(Entity, Dto))`) constructs the type at its
|
|
1001
|
+
* target argument later, at runtime: record the factory call as that
|
|
1002
|
+
* type's `instantiation` usage and flow, the same as call/apply. A
|
|
1003
|
+
* target that resolves to no defined type records nothing.
|
|
1004
|
+
*/
|
|
1005
|
+
private recordFactoryConstruction;
|
|
989
1006
|
/**
|
|
990
1007
|
* Detect global registrations: object literals shaped like
|
|
991
1008
|
* `{ provide: <plugin-listed token>, useClass: X }`.
|