@webpieces/nx-webpieces-rules 0.3.236 → 0.3.238

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.
Files changed (39) hide show
  1. package/executors.json +10 -0
  2. package/package.json +6 -6
  3. package/src/di-graph-targets.d.ts +17 -0
  4. package/src/di-graph-targets.js +41 -0
  5. package/src/di-graph-targets.js.map +1 -0
  6. package/src/executors/di-graph-generate/executor.d.ts +25 -0
  7. package/src/executors/di-graph-generate/executor.js +107 -0
  8. package/src/executors/di-graph-generate/executor.js.map +1 -0
  9. package/src/executors/di-graph-generate/schema.json +8 -0
  10. package/src/executors/validate-di-graph-unchanged/executor.d.ts +25 -0
  11. package/src/executors/validate-di-graph-unchanged/executor.js +84 -0
  12. package/src/executors/validate-di-graph-unchanged/executor.js.map +1 -0
  13. package/src/executors/validate-di-graph-unchanged/schema.json +8 -0
  14. package/src/executors/validate-nx-wiring/executor.js +1 -0
  15. package/src/executors/validate-nx-wiring/executor.js.map +1 -1
  16. package/src/lib/di-graph/analyzer.d.ts +22 -0
  17. package/src/lib/di-graph/analyzer.js +0 -0
  18. package/src/lib/di-graph/analyzer.js.map +1 -0
  19. package/src/lib/di-graph/bindings.d.ts +34 -0
  20. package/src/lib/di-graph/bindings.js +194 -0
  21. package/src/lib/di-graph/bindings.js.map +1 -0
  22. package/src/lib/di-graph/mermaid.d.ts +12 -0
  23. package/src/lib/di-graph/mermaid.js +80 -0
  24. package/src/lib/di-graph/mermaid.js.map +1 -0
  25. package/src/lib/di-graph/model.d.ts +78 -0
  26. package/src/lib/di-graph/model.js +114 -0
  27. package/src/lib/di-graph/model.js.map +1 -0
  28. package/src/lib/di-graph/program.d.ts +15 -0
  29. package/src/lib/di-graph/program.js +46 -0
  30. package/src/lib/di-graph/program.js.map +1 -0
  31. package/src/lib/di-graph/serializer.d.ts +12 -0
  32. package/src/lib/di-graph/serializer.js +0 -0
  33. package/src/lib/di-graph/serializer.js.map +1 -0
  34. package/src/lib/di-graph/token-resolver.d.ts +28 -0
  35. package/src/lib/di-graph/token-resolver.js +104 -0
  36. package/src/lib/di-graph/token-resolver.js.map +1 -0
  37. package/src/plugin.d.ts +1 -0
  38. package/src/plugin.js +31 -16
  39. package/src/plugin.js.map +1 -1
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Binding Table (pass 1 of the DI graph analyzer)
3
+ *
4
+ * Scans every source file in the project's TypeScript program (skipping .d.ts and
5
+ * node_modules) and collects all Inversify bindings into a Map<tokenKey, Binding[]>:
6
+ *
7
+ * - ContainerModule bodies: bind(TOKEN).to(Impl) / .toSelf() / .toConstantValue(x)
8
+ * / .toDynamicValue(fn), with .inSingletonScope() etc.
9
+ * - Decorators: @provideSingleton() / @provideTransient() (self-binding)
10
+ * and @provideSingletonAs(TOKEN)
11
+ *
12
+ * Arrays because multiInject tokens (e.g. HEADER_TYPES.PlatformHeadersExtension) are
13
+ * bound once per ContainerModule across several packages.
14
+ */
15
+ import * as ts from 'typescript';
16
+ import { Binding } from './model';
17
+ export declare class BindingTable {
18
+ private readonly byToken;
19
+ add(binding: Binding): void;
20
+ lookup(tokenKey: string): Binding[];
21
+ }
22
+ /**
23
+ * If `expr` is (or resolves through the checker to) a class declaration, return it.
24
+ */
25
+ export declare function resolveClassDeclaration(expr: ts.Expression, checker: ts.TypeChecker): ts.ClassDeclaration | null;
26
+ /** Return the decorator call expression when `decorator` is `@name(...)`, else null. */
27
+ export declare function decoratorCall(decorator: ts.Decorator): ts.CallExpression | null;
28
+ /** The identifier name of a decorator like `@provideSingleton()` or `@inject(X)`. */
29
+ export declare function decoratorName(decorator: ts.Decorator): string | null;
30
+ export declare function classDecorators(cls: ts.ClassDeclaration): ts.Decorator[];
31
+ /**
32
+ * Pass 1: collect every binding in the program into a token-keyed table.
33
+ */
34
+ export declare function collectBindings(program: ts.Program, checker: ts.TypeChecker, workspaceRoot: string): BindingTable;
@@ -0,0 +1,194 @@
1
+ "use strict";
2
+ /**
3
+ * Binding Table (pass 1 of the DI graph analyzer)
4
+ *
5
+ * Scans every source file in the project's TypeScript program (skipping .d.ts and
6
+ * node_modules) and collects all Inversify bindings into a Map<tokenKey, Binding[]>:
7
+ *
8
+ * - ContainerModule bodies: bind(TOKEN).to(Impl) / .toSelf() / .toConstantValue(x)
9
+ * / .toDynamicValue(fn), with .inSingletonScope() etc.
10
+ * - Decorators: @provideSingleton() / @provideTransient() (self-binding)
11
+ * and @provideSingletonAs(TOKEN)
12
+ *
13
+ * Arrays because multiInject tokens (e.g. HEADER_TYPES.PlatformHeadersExtension) are
14
+ * bound once per ContainerModule across several packages.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.BindingTable = void 0;
18
+ exports.resolveClassDeclaration = resolveClassDeclaration;
19
+ exports.decoratorCall = decoratorCall;
20
+ exports.decoratorName = decoratorName;
21
+ exports.classDecorators = classDecorators;
22
+ exports.collectBindings = collectBindings;
23
+ const tslib_1 = require("tslib");
24
+ const ts = tslib_1.__importStar(require("typescript"));
25
+ const model_1 = require("./model");
26
+ const token_resolver_1 = require("./token-resolver");
27
+ const BIND_METHOD_NAMES = new Set(['to', 'toSelf', 'toConstantValue', 'toDynamicValue']);
28
+ class BindingTable {
29
+ byToken = new Map();
30
+ add(binding) {
31
+ const list = this.byToken.get(binding.tokenKey);
32
+ if (list) {
33
+ list.push(binding);
34
+ }
35
+ else {
36
+ this.byToken.set(binding.tokenKey, [binding]);
37
+ }
38
+ }
39
+ lookup(tokenKey) {
40
+ return this.byToken.get(tokenKey) ?? [];
41
+ }
42
+ }
43
+ exports.BindingTable = BindingTable;
44
+ function isAnalyzableFile(sourceFile) {
45
+ if (sourceFile.isDeclarationFile)
46
+ return false;
47
+ if (sourceFile.fileName.includes('/node_modules/'))
48
+ return false;
49
+ return true;
50
+ }
51
+ /** Walk `.inSingletonScope()` / `.inTransientScope()` suffixes above a binding call. */
52
+ function scopeFromChain(bindingCall) {
53
+ let node = bindingCall;
54
+ while (node.parent &&
55
+ ts.isPropertyAccessExpression(node.parent) &&
56
+ node.parent.parent &&
57
+ ts.isCallExpression(node.parent.parent)) {
58
+ const methodName = node.parent.name.text;
59
+ if (methodName === 'inSingletonScope')
60
+ return 'singleton';
61
+ if (methodName === 'inTransientScope')
62
+ return 'transient';
63
+ node = node.parent.parent;
64
+ }
65
+ return 'unknown';
66
+ }
67
+ /**
68
+ * If `expr` is (or resolves through the checker to) a class declaration, return it.
69
+ */
70
+ function resolveClassDeclaration(expr, checker) {
71
+ let symbol = checker.getSymbolAtLocation(expr);
72
+ if (symbol && (symbol.flags & ts.SymbolFlags.Alias) !== 0) {
73
+ symbol = checker.getAliasedSymbol(symbol);
74
+ }
75
+ for (const decl of symbol?.declarations ?? []) {
76
+ if (ts.isClassDeclaration(decl))
77
+ return decl;
78
+ }
79
+ return null;
80
+ }
81
+ /**
82
+ * Recognize `bind(TOKEN)` at the bottom of a fluent chain. Accepts a bare `bind(...)`
83
+ * identifier call or `options.bind(...)` property call.
84
+ */
85
+ function asBindCall(expr) {
86
+ if (!ts.isCallExpression(expr))
87
+ return null;
88
+ const callee = expr.expression;
89
+ if (ts.isIdentifier(callee) && callee.text === 'bind')
90
+ return expr;
91
+ if (ts.isPropertyAccessExpression(callee) && callee.name.text === 'bind')
92
+ return expr;
93
+ return null;
94
+ }
95
+ /**
96
+ * Handle one `<receiver>.to*(...)` call: if the receiver bottoms out at bind(TOKEN),
97
+ * record the binding.
98
+ */
99
+ function collectBindCall(call, checker, workspaceRoot, table) {
100
+ if (!ts.isPropertyAccessExpression(call.expression))
101
+ return;
102
+ const methodName = call.expression.name.text;
103
+ if (!BIND_METHOD_NAMES.has(methodName))
104
+ return;
105
+ const bindCall = asBindCall(call.expression.expression);
106
+ if (!bindCall || bindCall.arguments.length === 0)
107
+ return;
108
+ const tokenExpr = bindCall.arguments[0];
109
+ const file = (0, token_resolver_1.relativeFile)(workspaceRoot, call.getSourceFile());
110
+ const scope = scopeFromChain(call);
111
+ if (methodName === 'toSelf') {
112
+ const cls = resolveClassDeclaration(tokenExpr, checker);
113
+ const token = cls
114
+ ? (0, token_resolver_1.classTokenKey)(cls, workspaceRoot)
115
+ : (0, token_resolver_1.resolveTokenKey)(tokenExpr, checker, workspaceRoot);
116
+ table.add(new model_1.Binding(token.key, tokenExpr.getText(), 'toSelf', scope, cls, '', file));
117
+ return;
118
+ }
119
+ const token = (0, token_resolver_1.resolveTokenKey)(tokenExpr, checker, workspaceRoot);
120
+ if (methodName === 'to') {
121
+ const implExpr = call.arguments[0];
122
+ const cls = implExpr ? resolveClassDeclaration(implExpr, checker) : null;
123
+ const valueText = implExpr ? implExpr.getText() : '';
124
+ table.add(new model_1.Binding(token.key, token.display, 'to', scope, cls, valueText, file));
125
+ return;
126
+ }
127
+ const kind = methodName === 'toConstantValue' ? 'toConstantValue' : 'toDynamicValue';
128
+ const valueExpr = call.arguments[0];
129
+ const valueText = valueExpr ? firstLine(valueExpr.getText()) : '';
130
+ table.add(new model_1.Binding(token.key, token.display, kind, scope, null, valueText, file));
131
+ }
132
+ function firstLine(text) {
133
+ const line = text.split('\n')[0].trim();
134
+ return line.length > 60 ? line.slice(0, 57) + '...' : line;
135
+ }
136
+ /** Return the decorator call expression when `decorator` is `@name(...)`, else null. */
137
+ function decoratorCall(decorator) {
138
+ return ts.isCallExpression(decorator.expression) ? decorator.expression : null;
139
+ }
140
+ /** The identifier name of a decorator like `@provideSingleton()` or `@inject(X)`. */
141
+ function decoratorName(decorator) {
142
+ const call = decoratorCall(decorator);
143
+ const callee = call ? call.expression : decorator.expression;
144
+ if (ts.isIdentifier(callee))
145
+ return callee.text;
146
+ if (ts.isPropertyAccessExpression(callee))
147
+ return callee.name.text;
148
+ return null;
149
+ }
150
+ function classDecorators(cls) {
151
+ const decorators = ts.getDecorators(cls);
152
+ return decorators ? [...decorators] : [];
153
+ }
154
+ function collectDecoratorBindings(cls, checker, workspaceRoot, table) {
155
+ const file = (0, token_resolver_1.relativeFile)(workspaceRoot, cls.getSourceFile());
156
+ for (const decorator of classDecorators(cls)) {
157
+ const name = decoratorName(decorator);
158
+ if (name === 'provideSingleton' || name === 'provideTransient') {
159
+ const token = (0, token_resolver_1.classTokenKey)(cls, workspaceRoot);
160
+ const scope = name === 'provideSingleton' ? 'singleton' : 'transient';
161
+ table.add(new model_1.Binding(token.key, token.display, 'decorator', scope, cls, '', file));
162
+ }
163
+ else if (name === 'provideSingletonAs') {
164
+ const call = decoratorCall(decorator);
165
+ const tokenExpr = call?.arguments[0];
166
+ if (!tokenExpr)
167
+ continue;
168
+ const token = (0, token_resolver_1.resolveTokenKey)(tokenExpr, checker, workspaceRoot);
169
+ table.add(new model_1.Binding(token.key, token.display, 'decorator', 'singleton', cls, '', file));
170
+ }
171
+ }
172
+ }
173
+ /**
174
+ * Pass 1: collect every binding in the program into a token-keyed table.
175
+ */
176
+ function collectBindings(program, checker, workspaceRoot) {
177
+ const table = new BindingTable();
178
+ for (const sourceFile of program.getSourceFiles()) {
179
+ if (!isAnalyzableFile(sourceFile))
180
+ continue;
181
+ const visit = (node) => {
182
+ if (ts.isCallExpression(node)) {
183
+ collectBindCall(node, checker, workspaceRoot, table);
184
+ }
185
+ else if (ts.isClassDeclaration(node)) {
186
+ collectDecoratorBindings(node, checker, workspaceRoot, table);
187
+ }
188
+ ts.forEachChild(node, visit);
189
+ };
190
+ visit(sourceFile);
191
+ }
192
+ return table;
193
+ }
194
+ //# sourceMappingURL=bindings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bindings.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/bindings.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;;AAmDH,0DAYC;AAkED,sCAEC;AAGD,sCAMC;AAED,0CAGC;AA4BD,0CAsBC;;AAjMD,uDAAiC;AACjC,mCAAwD;AACxD,qDAAgF;AAEhF,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,iBAAiB,EAAE,gBAAgB,CAAC,CAAC,CAAC;AAEzF,MAAa,YAAY;IACJ,OAAO,GAAG,IAAI,GAAG,EAAqB,CAAC;IAExD,GAAG,CAAC,OAAgB;QAChB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAChD,IAAI,IAAI,EAAE,CAAC;YACP,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACvB,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;QAClD,CAAC;IACL,CAAC;IAED,MAAM,CAAC,QAAgB;QACnB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;IAC5C,CAAC;CACJ;AAfD,oCAeC;AAED,SAAS,gBAAgB,CAAC,UAAyB;IAC/C,IAAI,UAAU,CAAC,iBAAiB;QAAE,OAAO,KAAK,CAAC;IAC/C,IAAI,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,gBAAgB,CAAC;QAAE,OAAO,KAAK,CAAC;IACjE,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,wFAAwF;AACxF,SAAS,cAAc,CAAC,WAA8B;IAClD,IAAI,IAAI,GAAY,WAAW,CAAC;IAChC,OACI,IAAI,CAAC,MAAM;QACX,EAAE,CAAC,0BAA0B,CAAC,IAAI,CAAC,MAAM,CAAC;QAC1C,IAAI,CAAC,MAAM,CAAC,MAAM;QAClB,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EACzC,CAAC;QACC,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;QACzC,IAAI,UAAU,KAAK,kBAAkB;YAAE,OAAO,WAAW,CAAC;QAC1D,IAAI,UAAU,KAAK,kBAAkB;YAAE,OAAO,WAAW,CAAC;QAC1D,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;IAC9B,CAAC;IACD,OAAO,SAAS,CAAC;AACrB,CAAC;AAED;;GAEG;AACH,SAAgB,uBAAuB,CACnC,IAAmB,EACnB,OAAuB;IAEvB,IAAI,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAC/C,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QACxD,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAC9C,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,YAAY,IAAI,EAAE,EAAE,CAAC;QAC5C,IAAI,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;IACjD,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,SAAS,UAAU,CAAC,IAAmB;IACnC,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC;IAC/B,IAAI,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACnE,IAAI,EAAE,CAAC,0BAA0B,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACtF,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,SAAS,eAAe,CACpB,IAAuB,EACvB,OAAuB,EACvB,aAAqB,EACrB,KAAmB;IAEnB,IAAI,CAAC,EAAE,CAAC,0BAA0B,CAAC,IAAI,CAAC,UAAU,CAAC;QAAE,OAAO;IAC5D,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;IAC7C,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,UAAU,CAAC;QAAE,OAAO;IAE/C,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;IACxD,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAEzD,MAAM,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,IAAA,6BAAY,EAAC,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;IAC/D,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IAEnC,IAAI,UAAU,KAAK,QAAQ,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,uBAAuB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACxD,MAAM,KAAK,GAAG,GAAG;YACb,CAAC,CAAC,IAAA,8BAAa,EAAC,GAAG,EAAE,aAAa,CAAC;YACnC,CAAC,CAAC,IAAA,gCAAe,EAAC,SAAS,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;QACzD,KAAK,CAAC,GAAG,CAAC,IAAI,eAAO,CAAC,KAAK,CAAC,GAAG,EAAE,SAAS,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC;QACvF,OAAO;IACX,CAAC;IAED,MAAM,KAAK,GAAG,IAAA,gCAAe,EAAC,SAAS,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;IAEjE,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;QACtB,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QACnC,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,uBAAuB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACzE,MAAM,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrD,KAAK,CAAC,GAAG,CAAC,IAAI,eAAO,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC;QACpF,OAAO;IACX,CAAC;IAED,MAAM,IAAI,GAAgB,UAAU,KAAK,iBAAiB,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,gBAAgB,CAAC;IAClG,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IACpC,MAAM,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAClE,KAAK,CAAC,GAAG,CAAC,IAAI,eAAO,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC;AACzF,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACxC,OAAO,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAC/D,CAAC;AAED,wFAAwF;AACxF,SAAgB,aAAa,CAAC,SAAuB;IACjD,OAAO,EAAE,CAAC,gBAAgB,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC;AACnF,CAAC;AAED,qFAAqF;AACrF,SAAgB,aAAa,CAAC,SAAuB;IACjD,MAAM,IAAI,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC;IAC7D,IAAI,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC;IAChD,IAAI,EAAE,CAAC,0BAA0B,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;IACnE,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,SAAgB,eAAe,CAAC,GAAwB;IACpD,MAAM,UAAU,GAAG,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IACzC,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAC7C,CAAC;AAED,SAAS,wBAAwB,CAC7B,GAAwB,EACxB,OAAuB,EACvB,aAAqB,EACrB,KAAmB;IAEnB,MAAM,IAAI,GAAG,IAAA,6BAAY,EAAC,aAAa,EAAE,GAAG,CAAC,aAAa,EAAE,CAAC,CAAC;IAC9D,KAAK,MAAM,SAAS,IAAI,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3C,MAAM,IAAI,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;QACtC,IAAI,IAAI,KAAK,kBAAkB,IAAI,IAAI,KAAK,kBAAkB,EAAE,CAAC;YAC7D,MAAM,KAAK,GAAG,IAAA,8BAAa,EAAC,GAAG,EAAE,aAAa,CAAC,CAAC;YAChD,MAAM,KAAK,GAAY,IAAI,KAAK,kBAAkB,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC;YAC/E,KAAK,CAAC,GAAG,CAAC,IAAI,eAAO,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC;QACxF,CAAC;aAAM,IAAI,IAAI,KAAK,oBAAoB,EAAE,CAAC;YACvC,MAAM,IAAI,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;YACtC,MAAM,SAAS,GAAG,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;YACrC,IAAI,CAAC,SAAS;gBAAE,SAAS;YACzB,MAAM,KAAK,GAAG,IAAA,gCAAe,EAAC,SAAS,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;YACjE,KAAK,CAAC,GAAG,CAAC,IAAI,eAAO,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC;QAC9F,CAAC;IACL,CAAC;AACL,CAAC;AAED;;GAEG;AACH,SAAgB,eAAe,CAC3B,OAAmB,EACnB,OAAuB,EACvB,aAAqB;IAErB,MAAM,KAAK,GAAG,IAAI,YAAY,EAAE,CAAC;IAEjC,KAAK,MAAM,UAAU,IAAI,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC;QAChD,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC;YAAE,SAAS;QAE5C,MAAM,KAAK,GAAG,CAAC,IAAa,EAAQ,EAAE;YAClC,IAAI,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC5B,eAAe,CAAC,IAAI,EAAE,OAAO,EAAE,aAAa,EAAE,KAAK,CAAC,CAAC;YACzD,CAAC;iBAAM,IAAI,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;gBACrC,wBAAwB,CAAC,IAAI,EAAE,OAAO,EAAE,aAAa,EAAE,KAAK,CAAC,CAAC;YAClE,CAAC;YACD,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACjC,CAAC,CAAC;QACF,KAAK,CAAC,UAAU,CAAC,CAAC;IACtB,CAAC;IAED,OAAO,KAAK,CAAC;AACjB,CAAC","sourcesContent":["/**\n * Binding Table (pass 1 of the DI graph analyzer)\n *\n * Scans every source file in the project's TypeScript program (skipping .d.ts and\n * node_modules) and collects all Inversify bindings into a Map<tokenKey, Binding[]>:\n *\n * - ContainerModule bodies: bind(TOKEN).to(Impl) / .toSelf() / .toConstantValue(x)\n * / .toDynamicValue(fn), with .inSingletonScope() etc.\n * - Decorators: @provideSingleton() / @provideTransient() (self-binding)\n * and @provideSingletonAs(TOKEN)\n *\n * Arrays because multiInject tokens (e.g. HEADER_TYPES.PlatformHeadersExtension) are\n * bound once per ContainerModule across several packages.\n */\n\nimport * as ts from 'typescript';\nimport { Binding, BindingKind, DiScope } from './model';\nimport { classTokenKey, relativeFile, resolveTokenKey } from './token-resolver';\n\nconst BIND_METHOD_NAMES = new Set(['to', 'toSelf', 'toConstantValue', 'toDynamicValue']);\n\nexport class BindingTable {\n private readonly byToken = new Map<string, Binding[]>();\n\n add(binding: Binding): void {\n const list = this.byToken.get(binding.tokenKey);\n if (list) {\n list.push(binding);\n } else {\n this.byToken.set(binding.tokenKey, [binding]);\n }\n }\n\n lookup(tokenKey: string): Binding[] {\n return this.byToken.get(tokenKey) ?? [];\n }\n}\n\nfunction isAnalyzableFile(sourceFile: ts.SourceFile): boolean {\n if (sourceFile.isDeclarationFile) return false;\n if (sourceFile.fileName.includes('/node_modules/')) return false;\n return true;\n}\n\n/** Walk `.inSingletonScope()` / `.inTransientScope()` suffixes above a binding call. */\nfunction scopeFromChain(bindingCall: ts.CallExpression): DiScope {\n let node: ts.Node = bindingCall;\n while (\n node.parent &&\n ts.isPropertyAccessExpression(node.parent) &&\n node.parent.parent &&\n ts.isCallExpression(node.parent.parent)\n ) {\n const methodName = node.parent.name.text;\n if (methodName === 'inSingletonScope') return 'singleton';\n if (methodName === 'inTransientScope') return 'transient';\n node = node.parent.parent;\n }\n return 'unknown';\n}\n\n/**\n * If `expr` is (or resolves through the checker to) a class declaration, return it.\n */\nexport function resolveClassDeclaration(\n expr: ts.Expression,\n checker: ts.TypeChecker,\n): ts.ClassDeclaration | null {\n let symbol = checker.getSymbolAtLocation(expr);\n if (symbol && (symbol.flags & ts.SymbolFlags.Alias) !== 0) {\n symbol = checker.getAliasedSymbol(symbol);\n }\n for (const decl of symbol?.declarations ?? []) {\n if (ts.isClassDeclaration(decl)) return decl;\n }\n return null;\n}\n\n/**\n * Recognize `bind(TOKEN)` at the bottom of a fluent chain. Accepts a bare `bind(...)`\n * identifier call or `options.bind(...)` property call.\n */\nfunction asBindCall(expr: ts.Expression): ts.CallExpression | null {\n if (!ts.isCallExpression(expr)) return null;\n const callee = expr.expression;\n if (ts.isIdentifier(callee) && callee.text === 'bind') return expr;\n if (ts.isPropertyAccessExpression(callee) && callee.name.text === 'bind') return expr;\n return null;\n}\n\n/**\n * Handle one `<receiver>.to*(...)` call: if the receiver bottoms out at bind(TOKEN),\n * record the binding.\n */\nfunction collectBindCall(\n call: ts.CallExpression,\n checker: ts.TypeChecker,\n workspaceRoot: string,\n table: BindingTable,\n): void {\n if (!ts.isPropertyAccessExpression(call.expression)) return;\n const methodName = call.expression.name.text;\n if (!BIND_METHOD_NAMES.has(methodName)) return;\n\n const bindCall = asBindCall(call.expression.expression);\n if (!bindCall || bindCall.arguments.length === 0) return;\n\n const tokenExpr = bindCall.arguments[0];\n const file = relativeFile(workspaceRoot, call.getSourceFile());\n const scope = scopeFromChain(call);\n\n if (methodName === 'toSelf') {\n const cls = resolveClassDeclaration(tokenExpr, checker);\n const token = cls\n ? classTokenKey(cls, workspaceRoot)\n : resolveTokenKey(tokenExpr, checker, workspaceRoot);\n table.add(new Binding(token.key, tokenExpr.getText(), 'toSelf', scope, cls, '', file));\n return;\n }\n\n const token = resolveTokenKey(tokenExpr, checker, workspaceRoot);\n\n if (methodName === 'to') {\n const implExpr = call.arguments[0];\n const cls = implExpr ? resolveClassDeclaration(implExpr, checker) : null;\n const valueText = implExpr ? implExpr.getText() : '';\n table.add(new Binding(token.key, token.display, 'to', scope, cls, valueText, file));\n return;\n }\n\n const kind: BindingKind = methodName === 'toConstantValue' ? 'toConstantValue' : 'toDynamicValue';\n const valueExpr = call.arguments[0];\n const valueText = valueExpr ? firstLine(valueExpr.getText()) : '';\n table.add(new Binding(token.key, token.display, kind, scope, null, valueText, file));\n}\n\nfunction firstLine(text: string): string {\n const line = text.split('\\n')[0].trim();\n return line.length > 60 ? line.slice(0, 57) + '...' : line;\n}\n\n/** Return the decorator call expression when `decorator` is `@name(...)`, else null. */\nexport function decoratorCall(decorator: ts.Decorator): ts.CallExpression | null {\n return ts.isCallExpression(decorator.expression) ? decorator.expression : null;\n}\n\n/** The identifier name of a decorator like `@provideSingleton()` or `@inject(X)`. */\nexport function decoratorName(decorator: ts.Decorator): string | null {\n const call = decoratorCall(decorator);\n const callee = call ? call.expression : decorator.expression;\n if (ts.isIdentifier(callee)) return callee.text;\n if (ts.isPropertyAccessExpression(callee)) return callee.name.text;\n return null;\n}\n\nexport function classDecorators(cls: ts.ClassDeclaration): ts.Decorator[] {\n const decorators = ts.getDecorators(cls);\n return decorators ? [...decorators] : [];\n}\n\nfunction collectDecoratorBindings(\n cls: ts.ClassDeclaration,\n checker: ts.TypeChecker,\n workspaceRoot: string,\n table: BindingTable,\n): void {\n const file = relativeFile(workspaceRoot, cls.getSourceFile());\n for (const decorator of classDecorators(cls)) {\n const name = decoratorName(decorator);\n if (name === 'provideSingleton' || name === 'provideTransient') {\n const token = classTokenKey(cls, workspaceRoot);\n const scope: DiScope = name === 'provideSingleton' ? 'singleton' : 'transient';\n table.add(new Binding(token.key, token.display, 'decorator', scope, cls, '', file));\n } else if (name === 'provideSingletonAs') {\n const call = decoratorCall(decorator);\n const tokenExpr = call?.arguments[0];\n if (!tokenExpr) continue;\n const token = resolveTokenKey(tokenExpr, checker, workspaceRoot);\n table.add(new Binding(token.key, token.display, 'decorator', 'singleton', cls, '', file));\n }\n }\n}\n\n/**\n * Pass 1: collect every binding in the program into a token-keyed table.\n */\nexport function collectBindings(\n program: ts.Program,\n checker: ts.TypeChecker,\n workspaceRoot: string,\n): BindingTable {\n const table = new BindingTable();\n\n for (const sourceFile of program.getSourceFiles()) {\n if (!isAnalyzableFile(sourceFile)) continue;\n\n const visit = (node: ts.Node): void => {\n if (ts.isCallExpression(node)) {\n collectBindCall(node, checker, workspaceRoot, table);\n } else if (ts.isClassDeclaration(node)) {\n collectDecoratorBindings(node, checker, workspaceRoot, table);\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n }\n\n return table;\n}\n"]}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Mermaid Emitter
3
+ *
4
+ * Renders the DI graph as design.md — a Mermaid `graph TD` diagram that GitHub
5
+ * and IDEs render inline, so the DI design is reviewable in every PR. Emitted
6
+ * in the same sorted order as design.json so diffs are equally stable.
7
+ */
8
+ import { DiGraph } from './model';
9
+ /**
10
+ * Render the design.md content for a project's DI graph.
11
+ */
12
+ export declare function toDesignMarkdown(graph: DiGraph): string;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ /**
3
+ * Mermaid Emitter
4
+ *
5
+ * Renders the DI graph as design.md — a Mermaid `graph TD` diagram that GitHub
6
+ * and IDEs render inline, so the DI design is reviewable in every PR. Emitted
7
+ * in the same sorted order as design.json so diffs are equally stable.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.toDesignMarkdown = toDesignMarkdown;
11
+ const serializer_1 = require("./serializer");
12
+ /** Mermaid node ids must be simple identifiers — map graph ids to safe ids. */
13
+ function mermaidId(id) {
14
+ return id.replace(/[^A-Za-z0-9_]/g, '_');
15
+ }
16
+ /** Escape text destined for a quoted mermaid label. */
17
+ function label(text) {
18
+ return text.replace(/"/g, '#quot;');
19
+ }
20
+ /** Escape an edge label (rendered between pipes). */
21
+ function edgeLabel(text) {
22
+ return text.replace(/\|/g, '/').replace(/"/g, "'");
23
+ }
24
+ function nodeStatement(node) {
25
+ const id = mermaidId(node.id);
26
+ const text = label(node.className);
27
+ if (node.kind === 'controller')
28
+ return ` ${id}["${text}"]:::controller`;
29
+ if (node.kind === 'constant' || node.kind === 'dynamic')
30
+ return ` ${id}(["${text}"])`;
31
+ if (node.kind === 'unresolved')
32
+ return ` ${id}{{"${text} ?"}}:::unresolved`;
33
+ return ` ${id}["${text}"]`;
34
+ }
35
+ function graphBody(graph) {
36
+ const lines = ['graph TD'];
37
+ for (const node of graph.nodes) {
38
+ lines.push(nodeStatement(node));
39
+ }
40
+ for (const edge of graph.edges) {
41
+ const from = mermaidId(edge.from);
42
+ const to = mermaidId(edge.to);
43
+ const token = edge.injection === 'multiInject' ? `multiInject ${edge.token}` : edge.token;
44
+ if (token !== '') {
45
+ lines.push(` ${from} -->|${edgeLabel(token)}| ${to}`);
46
+ }
47
+ else {
48
+ lines.push(` ${from} --> ${to}`);
49
+ }
50
+ }
51
+ lines.push(' classDef controller fill:#1f6feb,color:#ffffff,stroke:#0d419d');
52
+ lines.push(' classDef unresolved fill:#f0ad4e,color:#000000,stroke:#b8860b,stroke-dasharray: 5 5');
53
+ return lines;
54
+ }
55
+ /**
56
+ * Render the design.md content for a project's DI graph.
57
+ */
58
+ function toDesignMarkdown(graph) {
59
+ (0, serializer_1.sortGraph)(graph);
60
+ const header = [
61
+ `# DI Design Graph — ${graph.project}`,
62
+ '',
63
+ `> GENERATED by \`nx run ${graph.project}:di-graph-generate\` — do not edit by hand.`,
64
+ `> Machine-readable version: [design.json](./design.json)`,
65
+ '',
66
+ ];
67
+ if (graph.nodes.length === 0) {
68
+ return header.concat(['No DI-registered classes found in this project.', '']).join('\n');
69
+ }
70
+ const body = ['```mermaid', ...graphBody(graph), '```', ''];
71
+ const legend = [
72
+ '',
73
+ 'Edges are constructor injections: `-->|TOKEN|` for `@inject`/`@multiInject`,',
74
+ 'unlabeled arrows for inject-by-type. Rounded nodes are `toConstantValue`/',
75
+ '`toDynamicValue` leaves; dashed nodes are tokens the analyzer could not resolve.',
76
+ '',
77
+ ];
78
+ return header.concat(body).concat(legend).join('\n');
79
+ }
80
+ //# sourceMappingURL=mermaid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mermaid.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/mermaid.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AAoDH,4CA0BC;AA3ED,6CAAyC;AAEzC,+EAA+E;AAC/E,SAAS,SAAS,CAAC,EAAU;IACzB,OAAO,EAAE,CAAC,OAAO,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC;AAC7C,CAAC;AAED,uDAAuD;AACvD,SAAS,KAAK,CAAC,IAAY;IACvB,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AACxC,CAAC;AAED,qDAAqD;AACrD,SAAS,SAAS,CAAC,IAAY;IAC3B,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AACvD,CAAC;AAED,SAAS,aAAa,CAAC,IAAY;IAC/B,MAAM,EAAE,GAAG,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC9B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACnC,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY;QAAE,OAAO,OAAO,EAAE,KAAK,IAAI,iBAAiB,CAAC;IAC3E,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,OAAO,EAAE,MAAM,IAAI,KAAK,CAAC;IACzF,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY;QAAE,OAAO,OAAO,EAAE,MAAM,IAAI,oBAAoB,CAAC;IAC/E,OAAO,OAAO,EAAE,KAAK,IAAI,IAAI,CAAC;AAClC,CAAC;AAED,SAAS,SAAS,CAAC,KAAc;IAC7B,MAAM,KAAK,GAAa,CAAC,UAAU,CAAC,CAAC;IACrC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAC7B,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC;IACpC,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,EAAE,GAAG,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,KAAK,aAAa,CAAC,CAAC,CAAC,eAAe,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;QAC1F,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;YACf,KAAK,CAAC,IAAI,CAAC,OAAO,IAAI,QAAQ,SAAS,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC7D,CAAC;aAAM,CAAC;YACJ,KAAK,CAAC,IAAI,CAAC,OAAO,IAAI,QAAQ,EAAE,EAAE,CAAC,CAAC;QACxC,CAAC;IACL,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,mEAAmE,CAAC,CAAC;IAChF,KAAK,CAAC,IAAI,CAAC,yFAAyF,CAAC,CAAC;IACtG,OAAO,KAAK,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,SAAgB,gBAAgB,CAAC,KAAc;IAC3C,IAAA,sBAAS,EAAC,KAAK,CAAC,CAAC;IAEjB,MAAM,MAAM,GAAG;QACX,uBAAuB,KAAK,CAAC,OAAO,EAAE;QACtC,EAAE;QACF,2BAA2B,KAAK,CAAC,OAAO,6CAA6C;QACrF,0DAA0D;QAC1D,EAAE;KACL,CAAC;IAEF,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,iDAAiD,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7F,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,YAAY,EAAE,GAAG,SAAS,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;IAE5D,MAAM,MAAM,GAAG;QACX,EAAE;QACF,8EAA8E;QAC9E,2EAA2E;QAC3E,kFAAkF;QAClF,EAAE;KACL,CAAC;IAEF,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACzD,CAAC","sourcesContent":["/**\n * Mermaid Emitter\n *\n * Renders the DI graph as design.md — a Mermaid `graph TD` diagram that GitHub\n * and IDEs render inline, so the DI design is reviewable in every PR. Emitted\n * in the same sorted order as design.json so diffs are equally stable.\n */\n\nimport { DiGraph, DiNode } from './model';\nimport { sortGraph } from './serializer';\n\n/** Mermaid node ids must be simple identifiers — map graph ids to safe ids. */\nfunction mermaidId(id: string): string {\n return id.replace(/[^A-Za-z0-9_]/g, '_');\n}\n\n/** Escape text destined for a quoted mermaid label. */\nfunction label(text: string): string {\n return text.replace(/\"/g, '#quot;');\n}\n\n/** Escape an edge label (rendered between pipes). */\nfunction edgeLabel(text: string): string {\n return text.replace(/\\|/g, '/').replace(/\"/g, \"'\");\n}\n\nfunction nodeStatement(node: DiNode): string {\n const id = mermaidId(node.id);\n const text = label(node.className);\n if (node.kind === 'controller') return ` ${id}[\"${text}\"]:::controller`;\n if (node.kind === 'constant' || node.kind === 'dynamic') return ` ${id}([\"${text}\"])`;\n if (node.kind === 'unresolved') return ` ${id}{{\"${text} ?\"}}:::unresolved`;\n return ` ${id}[\"${text}\"]`;\n}\n\nfunction graphBody(graph: DiGraph): string[] {\n const lines: string[] = ['graph TD'];\n for (const node of graph.nodes) {\n lines.push(nodeStatement(node));\n }\n for (const edge of graph.edges) {\n const from = mermaidId(edge.from);\n const to = mermaidId(edge.to);\n const token = edge.injection === 'multiInject' ? `multiInject ${edge.token}` : edge.token;\n if (token !== '') {\n lines.push(` ${from} -->|${edgeLabel(token)}| ${to}`);\n } else {\n lines.push(` ${from} --> ${to}`);\n }\n }\n lines.push(' classDef controller fill:#1f6feb,color:#ffffff,stroke:#0d419d');\n lines.push(' classDef unresolved fill:#f0ad4e,color:#000000,stroke:#b8860b,stroke-dasharray: 5 5');\n return lines;\n}\n\n/**\n * Render the design.md content for a project's DI graph.\n */\nexport function toDesignMarkdown(graph: DiGraph): string {\n sortGraph(graph);\n\n const header = [\n `# DI Design Graph — ${graph.project}`,\n '',\n `> GENERATED by \\`nx run ${graph.project}:di-graph-generate\\` — do not edit by hand.`,\n `> Machine-readable version: [design.json](./design.json)`,\n '',\n ];\n\n if (graph.nodes.length === 0) {\n return header.concat(['No DI-registered classes found in this project.', '']).join('\\n');\n }\n\n const body = ['```mermaid', ...graphBody(graph), '```', ''];\n\n const legend = [\n '',\n 'Edges are constructor injections: `-->|TOKEN|` for `@inject`/`@multiInject`,',\n 'unlabeled arrows for inject-by-type. Rounded nodes are `toConstantValue`/',\n '`toDynamicValue` leaves; dashed nodes are tokens the analyzer could not resolve.',\n '',\n ];\n\n return header.concat(body).concat(legend).join('\\n');\n}\n"]}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * DI Graph Model
3
+ *
4
+ * Data classes for the per-project Inversify dependency DAG that is generated
5
+ * into <projectRoot>/design.json and <projectRoot>/design.md on every build.
6
+ *
7
+ * All structures are classes (not interfaces) per the repo convention for
8
+ * data-only structures.
9
+ */
10
+ import type * as ts from 'typescript';
11
+ export type DiNodeKind = 'controller' | 'class' | 'constant' | 'dynamic' | 'unresolved';
12
+ export type DiInjectionKind = 'token' | 'type' | 'multiInject';
13
+ export type DiScope = 'singleton' | 'transient' | 'unknown';
14
+ export type BindingKind = 'to' | 'toSelf' | 'toConstantValue' | 'toDynamicValue' | 'decorator';
15
+ /**
16
+ * A node in the DI graph — a class, a constant/dynamic binding leaf, or an
17
+ * unresolved token placeholder.
18
+ */
19
+ export declare class DiNode {
20
+ id: string;
21
+ className: string;
22
+ kind: DiNodeKind;
23
+ scope: DiScope;
24
+ file: string;
25
+ constructor(id: string, className: string, kind: DiNodeKind, scope: DiScope, file: string);
26
+ }
27
+ /**
28
+ * A constructor-injection edge: `from` class injects `to` node.
29
+ */
30
+ export declare class DiEdge {
31
+ from: string;
32
+ to: string;
33
+ injection: DiInjectionKind;
34
+ token: string;
35
+ tokenKey: string;
36
+ paramName: string;
37
+ paramType: string;
38
+ constructor(from: string, to: string, injection: DiInjectionKind, token: string, tokenKey: string, paramName: string, paramType: string);
39
+ }
40
+ /**
41
+ * The full per-project DI graph, serialized to design.json.
42
+ */
43
+ export declare class DiGraph {
44
+ schemaVersion: number;
45
+ project: string;
46
+ roots: string[];
47
+ nodes: DiNode[];
48
+ edges: DiEdge[];
49
+ unresolved: string[];
50
+ constructor(project: string);
51
+ }
52
+ /**
53
+ * Canonical identity of a DI token — the key links `Symbol.for('X')` token
54
+ * definitions to their bind() sites even across packages; display is what a
55
+ * human reads in design.md edge labels (e.g. "TYPES.Counter").
56
+ */
57
+ export declare class TokenRef {
58
+ key: string;
59
+ display: string;
60
+ constructor(key: string, display: string);
61
+ }
62
+ /**
63
+ * One binding discovered in pass 1 — either a ContainerModule bind() call or a
64
+ * @provideSingleton/@provideSingletonAs/@provideTransient decorator.
65
+ */
66
+ export declare class Binding {
67
+ tokenKey: string;
68
+ tokenDisplay: string;
69
+ kind: BindingKind;
70
+ scope: DiScope;
71
+ /** Implementation class for to/toSelf/decorator bindings; null for constant/dynamic. */
72
+ implClass: ts.ClassDeclaration | null;
73
+ /** Source text of the bound expression for constant/dynamic leaves; '' otherwise. */
74
+ valueText: string;
75
+ /** Workspace-relative posix path of the file the binding appears in. */
76
+ file: string;
77
+ constructor(tokenKey: string, tokenDisplay: string, kind: BindingKind, scope: DiScope, implClass: ts.ClassDeclaration | null, valueText: string, file: string);
78
+ }
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ /**
3
+ * DI Graph Model
4
+ *
5
+ * Data classes for the per-project Inversify dependency DAG that is generated
6
+ * into <projectRoot>/design.json and <projectRoot>/design.md on every build.
7
+ *
8
+ * All structures are classes (not interfaces) per the repo convention for
9
+ * data-only structures.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.Binding = exports.TokenRef = exports.DiGraph = exports.DiEdge = exports.DiNode = void 0;
13
+ /**
14
+ * A node in the DI graph — a class, a constant/dynamic binding leaf, or an
15
+ * unresolved token placeholder.
16
+ */
17
+ class DiNode {
18
+ id;
19
+ className;
20
+ kind;
21
+ scope;
22
+ file;
23
+ constructor(id, className, kind, scope, file) {
24
+ this.id = id;
25
+ this.className = className;
26
+ this.kind = kind;
27
+ this.scope = scope;
28
+ this.file = file;
29
+ }
30
+ }
31
+ exports.DiNode = DiNode;
32
+ /**
33
+ * A constructor-injection edge: `from` class injects `to` node.
34
+ */
35
+ class DiEdge {
36
+ from;
37
+ to;
38
+ injection;
39
+ token;
40
+ tokenKey;
41
+ paramName;
42
+ paramType;
43
+ constructor(from, to, injection, token, tokenKey, paramName, paramType) {
44
+ this.from = from;
45
+ this.to = to;
46
+ this.injection = injection;
47
+ this.token = token;
48
+ this.tokenKey = tokenKey;
49
+ this.paramName = paramName;
50
+ this.paramType = paramType;
51
+ }
52
+ }
53
+ exports.DiEdge = DiEdge;
54
+ /**
55
+ * The full per-project DI graph, serialized to design.json.
56
+ */
57
+ class DiGraph {
58
+ schemaVersion;
59
+ project;
60
+ roots;
61
+ nodes;
62
+ edges;
63
+ unresolved;
64
+ constructor(project) {
65
+ this.schemaVersion = 1;
66
+ this.project = project;
67
+ this.roots = [];
68
+ this.nodes = [];
69
+ this.edges = [];
70
+ this.unresolved = [];
71
+ }
72
+ }
73
+ exports.DiGraph = DiGraph;
74
+ /**
75
+ * Canonical identity of a DI token — the key links `Symbol.for('X')` token
76
+ * definitions to their bind() sites even across packages; display is what a
77
+ * human reads in design.md edge labels (e.g. "TYPES.Counter").
78
+ */
79
+ class TokenRef {
80
+ key;
81
+ display;
82
+ constructor(key, display) {
83
+ this.key = key;
84
+ this.display = display;
85
+ }
86
+ }
87
+ exports.TokenRef = TokenRef;
88
+ /**
89
+ * One binding discovered in pass 1 — either a ContainerModule bind() call or a
90
+ * @provideSingleton/@provideSingletonAs/@provideTransient decorator.
91
+ */
92
+ class Binding {
93
+ tokenKey;
94
+ tokenDisplay;
95
+ kind;
96
+ scope;
97
+ /** Implementation class for to/toSelf/decorator bindings; null for constant/dynamic. */
98
+ implClass;
99
+ /** Source text of the bound expression for constant/dynamic leaves; '' otherwise. */
100
+ valueText;
101
+ /** Workspace-relative posix path of the file the binding appears in. */
102
+ file;
103
+ constructor(tokenKey, tokenDisplay, kind, scope, implClass, valueText, file) {
104
+ this.tokenKey = tokenKey;
105
+ this.tokenDisplay = tokenDisplay;
106
+ this.kind = kind;
107
+ this.scope = scope;
108
+ this.implClass = implClass;
109
+ this.valueText = valueText;
110
+ this.file = file;
111
+ }
112
+ }
113
+ exports.Binding = Binding;
114
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/model.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAYH;;;GAGG;AACH,MAAa,MAAM;IACf,EAAE,CAAS;IACX,SAAS,CAAS;IAClB,IAAI,CAAa;IACjB,KAAK,CAAU;IACf,IAAI,CAAS;IAEb,YAAY,EAAU,EAAE,SAAiB,EAAE,IAAgB,EAAE,KAAc,EAAE,IAAY;QACrF,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACrB,CAAC;CACJ;AAdD,wBAcC;AAED;;GAEG;AACH,MAAa,MAAM;IACf,IAAI,CAAS;IACb,EAAE,CAAS;IACX,SAAS,CAAkB;IAC3B,KAAK,CAAS;IACd,QAAQ,CAAS;IACjB,SAAS,CAAS;IAClB,SAAS,CAAS;IAElB,YACI,IAAY,EACZ,EAAU,EACV,SAA0B,EAC1B,KAAa,EACb,QAAgB,EAChB,SAAiB,EACjB,SAAiB;QAEjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC/B,CAAC;CACJ;AA1BD,wBA0BC;AAED;;GAEG;AACH,MAAa,OAAO;IAChB,aAAa,CAAS;IACtB,OAAO,CAAS;IAChB,KAAK,CAAW;IAChB,KAAK,CAAW;IAChB,KAAK,CAAW;IAChB,UAAU,CAAW;IAErB,YAAY,OAAe;QACvB,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC;QACvB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;QAChB,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;QAChB,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;QAChB,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC;IACzB,CAAC;CACJ;AAhBD,0BAgBC;AAED;;;;GAIG;AACH,MAAa,QAAQ;IACjB,GAAG,CAAS;IACZ,OAAO,CAAS;IAEhB,YAAY,GAAW,EAAE,OAAe;QACpC,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IAC3B,CAAC;CACJ;AARD,4BAQC;AAED;;;GAGG;AACH,MAAa,OAAO;IAChB,QAAQ,CAAS;IACjB,YAAY,CAAS;IACrB,IAAI,CAAc;IAClB,KAAK,CAAU;IACf,wFAAwF;IACxF,SAAS,CAA6B;IACtC,qFAAqF;IACrF,SAAS,CAAS;IAClB,wEAAwE;IACxE,IAAI,CAAS;IAEb,YACI,QAAgB,EAChB,YAAoB,EACpB,IAAiB,EACjB,KAAc,EACd,SAAqC,EACrC,SAAiB,EACjB,IAAY;QAEZ,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACrB,CAAC;CACJ;AA7BD,0BA6BC","sourcesContent":["/**\n * DI Graph Model\n *\n * Data classes for the per-project Inversify dependency DAG that is generated\n * into <projectRoot>/design.json and <projectRoot>/design.md on every build.\n *\n * All structures are classes (not interfaces) per the repo convention for\n * data-only structures.\n */\n\nimport type * as ts from 'typescript';\n\nexport type DiNodeKind = 'controller' | 'class' | 'constant' | 'dynamic' | 'unresolved';\n\nexport type DiInjectionKind = 'token' | 'type' | 'multiInject';\n\nexport type DiScope = 'singleton' | 'transient' | 'unknown';\n\nexport type BindingKind = 'to' | 'toSelf' | 'toConstantValue' | 'toDynamicValue' | 'decorator';\n\n/**\n * A node in the DI graph — a class, a constant/dynamic binding leaf, or an\n * unresolved token placeholder.\n */\nexport class DiNode {\n id: string;\n className: string;\n kind: DiNodeKind;\n scope: DiScope;\n file: string;\n\n constructor(id: string, className: string, kind: DiNodeKind, scope: DiScope, file: string) {\n this.id = id;\n this.className = className;\n this.kind = kind;\n this.scope = scope;\n this.file = file;\n }\n}\n\n/**\n * A constructor-injection edge: `from` class injects `to` node.\n */\nexport class DiEdge {\n from: string;\n to: string;\n injection: DiInjectionKind;\n token: string;\n tokenKey: string;\n paramName: string;\n paramType: string;\n\n constructor(\n from: string,\n to: string,\n injection: DiInjectionKind,\n token: string,\n tokenKey: string,\n paramName: string,\n paramType: string,\n ) {\n this.from = from;\n this.to = to;\n this.injection = injection;\n this.token = token;\n this.tokenKey = tokenKey;\n this.paramName = paramName;\n this.paramType = paramType;\n }\n}\n\n/**\n * The full per-project DI graph, serialized to design.json.\n */\nexport class DiGraph {\n schemaVersion: number;\n project: string;\n roots: string[];\n nodes: DiNode[];\n edges: DiEdge[];\n unresolved: string[];\n\n constructor(project: string) {\n this.schemaVersion = 1;\n this.project = project;\n this.roots = [];\n this.nodes = [];\n this.edges = [];\n this.unresolved = [];\n }\n}\n\n/**\n * Canonical identity of a DI token — the key links `Symbol.for('X')` token\n * definitions to their bind() sites even across packages; display is what a\n * human reads in design.md edge labels (e.g. \"TYPES.Counter\").\n */\nexport class TokenRef {\n key: string;\n display: string;\n\n constructor(key: string, display: string) {\n this.key = key;\n this.display = display;\n }\n}\n\n/**\n * One binding discovered in pass 1 — either a ContainerModule bind() call or a\n * @provideSingleton/@provideSingletonAs/@provideTransient decorator.\n */\nexport class Binding {\n tokenKey: string;\n tokenDisplay: string;\n kind: BindingKind;\n scope: DiScope;\n /** Implementation class for to/toSelf/decorator bindings; null for constant/dynamic. */\n implClass: ts.ClassDeclaration | null;\n /** Source text of the bound expression for constant/dynamic leaves; '' otherwise. */\n valueText: string;\n /** Workspace-relative posix path of the file the binding appears in. */\n file: string;\n\n constructor(\n tokenKey: string,\n tokenDisplay: string,\n kind: BindingKind,\n scope: DiScope,\n implClass: ts.ClassDeclaration | null,\n valueText: string,\n file: string,\n ) {\n this.tokenKey = tokenKey;\n this.tokenDisplay = tokenDisplay;\n this.kind = kind;\n this.scope = scope;\n this.implClass = implClass;\n this.valueText = valueText;\n this.file = file;\n }\n}\n"]}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Project Program Factory
3
+ *
4
+ * Creates one ts.Program per project for DI graph analysis. Prefers the compile
5
+ * tsconfig (tsconfig.app.json / tsconfig.lib.json) so the program contains the
6
+ * project's real source set; cross-package classes resolve to SOURCE (not dist
7
+ * d.ts) because tsconfig.base.json paths map @webpieces/* to src/index.ts.
8
+ */
9
+ import * as ts from 'typescript';
10
+ export declare function findProjectTsconfig(projectRootAbs: string): string | null;
11
+ /**
12
+ * Create the TypeScript program for a project, or null when the project has no
13
+ * usable tsconfig / no source files (e.g. a package.json-only project).
14
+ */
15
+ export declare function createProjectProgram(projectRootAbs: string): ts.Program | null;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ /**
3
+ * Project Program Factory
4
+ *
5
+ * Creates one ts.Program per project for DI graph analysis. Prefers the compile
6
+ * tsconfig (tsconfig.app.json / tsconfig.lib.json) so the program contains the
7
+ * project's real source set; cross-package classes resolve to SOURCE (not dist
8
+ * d.ts) because tsconfig.base.json paths map @webpieces/* to src/index.ts.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.findProjectTsconfig = findProjectTsconfig;
12
+ exports.createProjectProgram = createProjectProgram;
13
+ const tslib_1 = require("tslib");
14
+ const ts = tslib_1.__importStar(require("typescript"));
15
+ const fs = tslib_1.__importStar(require("fs"));
16
+ const path = tslib_1.__importStar(require("path"));
17
+ const TSCONFIG_CANDIDATES = ['tsconfig.app.json', 'tsconfig.lib.json', 'tsconfig.json'];
18
+ function findProjectTsconfig(projectRootAbs) {
19
+ for (const candidate of TSCONFIG_CANDIDATES) {
20
+ const candidatePath = path.join(projectRootAbs, candidate);
21
+ if (fs.existsSync(candidatePath))
22
+ return candidatePath;
23
+ }
24
+ return null;
25
+ }
26
+ /**
27
+ * Create the TypeScript program for a project, or null when the project has no
28
+ * usable tsconfig / no source files (e.g. a package.json-only project).
29
+ */
30
+ function createProjectProgram(projectRootAbs) {
31
+ const configPath = findProjectTsconfig(projectRootAbs);
32
+ if (!configPath)
33
+ return null;
34
+ const host = {
35
+ ...ts.sys,
36
+ onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
37
+ const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
38
+ throw new Error(`Failed to parse ${configPath}: ${message}`);
39
+ },
40
+ };
41
+ const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, host);
42
+ if (!parsed || parsed.fileNames.length === 0)
43
+ return null;
44
+ return ts.createProgram(parsed.fileNames, parsed.options);
45
+ }
46
+ //# sourceMappingURL=program.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"program.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/program.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;AAQH,kDAMC;AAMD,oDAgBC;;AAlCD,uDAAiC;AACjC,+CAAyB;AACzB,mDAA6B;AAE7B,MAAM,mBAAmB,GAAG,CAAC,mBAAmB,EAAE,mBAAmB,EAAE,eAAe,CAAC,CAAC;AAExF,SAAgB,mBAAmB,CAAC,cAAsB;IACtD,KAAK,MAAM,SAAS,IAAI,mBAAmB,EAAE,CAAC;QAC1C,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC;QAC3D,IAAI,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC;YAAE,OAAO,aAAa,CAAC;IAC3D,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,SAAgB,oBAAoB,CAAC,cAAsB;IACvD,MAAM,UAAU,GAAG,mBAAmB,CAAC,cAAc,CAAC,CAAC;IACvD,IAAI,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAE7B,MAAM,IAAI,GAA2B;QACjC,GAAG,EAAE,CAAC,GAAG;QACT,mCAAmC,EAAE,CAAC,UAAyB,EAAQ,EAAE;YACrE,MAAM,OAAO,GAAG,EAAE,CAAC,4BAA4B,CAAC,UAAU,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;YAC9E,MAAM,IAAI,KAAK,CAAC,mBAAmB,UAAU,KAAK,OAAO,EAAE,CAAC,CAAC;QACjE,CAAC;KACJ,CAAC;IAEF,MAAM,MAAM,GAAG,EAAE,CAAC,gCAAgC,CAAC,UAAU,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;IACzE,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAE1D,OAAO,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;AAC9D,CAAC","sourcesContent":["/**\n * Project Program Factory\n *\n * Creates one ts.Program per project for DI graph analysis. Prefers the compile\n * tsconfig (tsconfig.app.json / tsconfig.lib.json) so the program contains the\n * project's real source set; cross-package classes resolve to SOURCE (not dist\n * d.ts) because tsconfig.base.json paths map @webpieces/* to src/index.ts.\n */\n\nimport * as ts from 'typescript';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nconst TSCONFIG_CANDIDATES = ['tsconfig.app.json', 'tsconfig.lib.json', 'tsconfig.json'];\n\nexport function findProjectTsconfig(projectRootAbs: string): string | null {\n for (const candidate of TSCONFIG_CANDIDATES) {\n const candidatePath = path.join(projectRootAbs, candidate);\n if (fs.existsSync(candidatePath)) return candidatePath;\n }\n return null;\n}\n\n/**\n * Create the TypeScript program for a project, or null when the project has no\n * usable tsconfig / no source files (e.g. a package.json-only project).\n */\nexport function createProjectProgram(projectRootAbs: string): ts.Program | null {\n const configPath = findProjectTsconfig(projectRootAbs);\n if (!configPath) return null;\n\n const host: ts.ParseConfigFileHost = {\n ...ts.sys,\n onUnRecoverableConfigFileDiagnostic: (diagnostic: ts.Diagnostic): void => {\n const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\\n');\n throw new Error(`Failed to parse ${configPath}: ${message}`);\n },\n };\n\n const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, host);\n if (!parsed || parsed.fileNames.length === 0) return null;\n\n return ts.createProgram(parsed.fileNames, parsed.options);\n}\n"]}