@webpieces/nx-webpieces-rules 0.3.245 → 0.3.246

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.
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+ /**
3
+ * Angular DI Analyzer (pass 2, Angular flavor)
4
+ *
5
+ * Renders each Angular entry component's injection tree "from the page/root
6
+ * component on down". Reuses the shared {@link DiDesignBuilder} base (node ids,
7
+ * leaf/unresolved labeling, provider-table resolution, factory-dep edges, level
8
+ * assignment) via its `collectInjections` hook — only the front-half (roots,
9
+ * providers, injection-site discovery) is Angular-specific.
10
+ *
11
+ * Angular injection sites per class:
12
+ * 1. Constructor params — `@Inject(TOKEN)` (capital I), `forwardRef(() => X)`,
13
+ * `@Optional`/`@Self`/`@SkipSelf`/`@Host`, or a bare typed param (the class
14
+ * itself is the token).
15
+ * 2. Field initializers calling `inject()` — the standalone pattern
16
+ * (`private saveApi = inject(SaveApi)`); the field name is the edge label
17
+ * and the declared/token type labels the box.
18
+ *
19
+ * Every site is an `angularToken` injection: the provider table is consulted
20
+ * first, then the token expression is resolved as a bare `@Injectable` class,
21
+ * else it becomes an `unresolved` node. Generation never fails.
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.buildAngularDiGraph = buildAngularDiGraph;
25
+ const tslib_1 = require("tslib");
26
+ const ts = tslib_1.__importStar(require("typescript"));
27
+ const model_1 = require("./model");
28
+ const bindings_1 = require("./bindings");
29
+ const analyzer_1 = require("./analyzer");
30
+ const angular_providers_1 = require("./angular-providers");
31
+ const angular_roots_1 = require("./angular-roots");
32
+ function hasComponentDecorator(cls) {
33
+ for (const decorator of (0, bindings_1.classDecorators)(cls)) {
34
+ if ((0, bindings_1.decoratorName)(decorator) === 'Component')
35
+ return true;
36
+ }
37
+ return false;
38
+ }
39
+ /** Unwrap `forwardRef(() => X)` to its inner `X`; return `expr` unchanged otherwise. */
40
+ function unwrapForwardRef(expr) {
41
+ if (ts.isCallExpression(expr) && ts.isIdentifier(expr.expression) && expr.expression.text === 'forwardRef') {
42
+ const arrow = expr.arguments[0];
43
+ if (arrow && ts.isArrowFunction(arrow) && ts.isExpression(arrow.body)) {
44
+ return arrow.body;
45
+ }
46
+ }
47
+ return expr;
48
+ }
49
+ /** Read a constructor param's `@Inject(TOKEN)` token and `@Optional` flag. */
50
+ class AngularParam {
51
+ token;
52
+ optional;
53
+ constructor(token, optional) {
54
+ this.token = token;
55
+ this.optional = optional;
56
+ }
57
+ }
58
+ function readAngularParam(param) {
59
+ let token = null;
60
+ let optional = false;
61
+ for (const decorator of ts.getDecorators(param) ?? []) {
62
+ const name = (0, bindings_1.decoratorName)(decorator);
63
+ const call = (0, bindings_1.decoratorCall)(decorator);
64
+ if (name === 'Inject' && call?.arguments[0]) {
65
+ token = unwrapForwardRef(call.arguments[0]);
66
+ }
67
+ else if (name === 'Optional') {
68
+ optional = true;
69
+ }
70
+ // @Self/@SkipSelf/@Host change the resolving injector, not the token —
71
+ // the edge is recorded regardless; the scope nuance is out of scope for v1.
72
+ }
73
+ return new AngularParam(token, optional);
74
+ }
75
+ /** `inject(TOKEN)` call in a field initializer → the token expression, else null. */
76
+ function fieldInjectToken(initializer) {
77
+ if (!initializer || !ts.isCallExpression(initializer))
78
+ return null;
79
+ if (!ts.isIdentifier(initializer.expression) || initializer.expression.text !== 'inject')
80
+ return null;
81
+ return initializer.arguments[0] ? unwrapForwardRef(initializer.arguments[0]) : null;
82
+ }
83
+ /**
84
+ * Angular builder: injection sites are constructor params (`@Inject`/bare typed)
85
+ * plus `inject()` field initializers. Root/reached components render as
86
+ * `component`; services as `class`.
87
+ */
88
+ class AngularDesignBuilder extends analyzer_1.DiDesignBuilder {
89
+ rootKindOf(cls) {
90
+ return hasComponentDecorator(cls) ? 'component' : 'class';
91
+ }
92
+ collectInjections(cls) {
93
+ const injections = [];
94
+ const ctor = (0, analyzer_1.findConstructor)(cls);
95
+ for (const param of ctor?.parameters ?? []) {
96
+ const info = readAngularParam(param);
97
+ const paramName = ts.isIdentifier(param.name) ? param.name.text : param.name.getText();
98
+ const paramType = param.type ? param.type.getText() : '';
99
+ if (info.token) {
100
+ injections.push(new analyzer_1.Injection('angularToken', info.token, paramName, paramType, false, info.optional));
101
+ }
102
+ else {
103
+ const typeRef = param.type && ts.isTypeReferenceNode(param.type) && ts.isIdentifier(param.type.typeName)
104
+ ? param.type.typeName
105
+ : null;
106
+ if (typeRef) {
107
+ injections.push(new analyzer_1.Injection('angularToken', typeRef, paramName, paramType, false, info.optional));
108
+ }
109
+ }
110
+ }
111
+ for (const member of cls.members) {
112
+ if (!ts.isPropertyDeclaration(member) || !ts.isIdentifier(member.name))
113
+ continue;
114
+ const token = fieldInjectToken(member.initializer);
115
+ if (!token)
116
+ continue;
117
+ const paramName = member.name.text;
118
+ // The field usually has no explicit type (`= inject(SaveApi)`) — the
119
+ // token IS the declared type, so label the box with it.
120
+ const paramType = member.type ? member.type.getText() : token.getText();
121
+ injections.push(new analyzer_1.Injection('angularToken', token, paramName, paramType));
122
+ }
123
+ return injections;
124
+ }
125
+ }
126
+ /**
127
+ * Build the full Angular DI graph for one project: one self-contained `DiDesign`
128
+ * per entry component (bootstrap + routed). `projectRoot` is workspace-relative.
129
+ */
130
+ function buildAngularDiGraph(program, workspaceRoot, projectRoot, projectName) {
131
+ const checker = program.getTypeChecker();
132
+ const table = (0, angular_providers_1.collectAngularProviders)(program, checker, workspaceRoot);
133
+ const graph = new model_1.DiGraph(projectName);
134
+ for (const root of (0, angular_roots_1.findAngularRoots)(program, checker, workspaceRoot, projectRoot)) {
135
+ graph.designs.push((0, analyzer_1.buildDesign)(root, 'component', workspaceRoot, (design) => new AngularDesignBuilder(checker, table, workspaceRoot, design)));
136
+ }
137
+ return graph;
138
+ }
139
+ //# sourceMappingURL=angular-analyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"angular-analyzer.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/angular-analyzer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Angular DI Analyzer (pass 2, Angular flavor)\n *\n * Renders each Angular entry component's injection tree \"from the page/root\n * component on down\". Reuses the shared {@link DiDesignBuilder} base (node ids,\n * leaf/unresolved labeling, provider-table resolution, factory-dep edges, level\n * assignment) via its `collectInjections` hook — only the front-half (roots,\n * providers, injection-site discovery) is Angular-specific.\n *\n * Angular injection sites per class:\n * 1. Constructor params — `@Inject(TOKEN)` (capital I), `forwardRef(() => X)`,\n * `@Optional`/`@Self`/`@SkipSelf`/`@Host`, or a bare typed param (the class\n * itself is the token).\n * 2. Field initializers calling `inject()` — the standalone pattern\n * (`private saveApi = inject(SaveApi)`); the field name is the edge label\n * and the declared/token type labels the box.\n *\n * Every site is an `angularToken` injection: the provider table is consulted\n * first, then the token expression is resolved as a bare `@Injectable` class,\n * else it becomes an `unresolved` node. Generation never fails.\n */\n\nimport * as ts from 'typescript';\nimport { DiDesign, DiGraph, DiNodeKind } from './model';\nimport { BindingTable, classDecorators, decoratorCall, decoratorName } from './bindings';\nimport { buildDesign, DiDesignBuilder, findConstructor, Injection } from './analyzer';\nimport { collectAngularProviders } from './angular-providers';\nimport { findAngularRoots } from './angular-roots';\n\nfunction hasComponentDecorator(cls: ts.ClassDeclaration): boolean {\n for (const decorator of classDecorators(cls)) {\n if (decoratorName(decorator) === 'Component') return true;\n }\n return false;\n}\n\n/** Unwrap `forwardRef(() => X)` to its inner `X`; return `expr` unchanged otherwise. */\nfunction unwrapForwardRef(expr: ts.Expression): ts.Expression {\n if (ts.isCallExpression(expr) && ts.isIdentifier(expr.expression) && expr.expression.text === 'forwardRef') {\n const arrow = expr.arguments[0];\n if (arrow && ts.isArrowFunction(arrow) && ts.isExpression(arrow.body)) {\n return arrow.body;\n }\n }\n return expr;\n}\n\n/** Read a constructor param's `@Inject(TOKEN)` token and `@Optional` flag. */\nclass AngularParam {\n token: ts.Expression | null;\n optional: boolean;\n\n constructor(token: ts.Expression | null, optional: boolean) {\n this.token = token;\n this.optional = optional;\n }\n}\n\nfunction readAngularParam(param: ts.ParameterDeclaration): AngularParam {\n let token: ts.Expression | null = null;\n let optional = false;\n for (const decorator of ts.getDecorators(param) ?? []) {\n const name = decoratorName(decorator);\n const call = decoratorCall(decorator);\n if (name === 'Inject' && call?.arguments[0]) {\n token = unwrapForwardRef(call.arguments[0]);\n } else if (name === 'Optional') {\n optional = true;\n }\n // @Self/@SkipSelf/@Host change the resolving injector, not the token —\n // the edge is recorded regardless; the scope nuance is out of scope for v1.\n }\n return new AngularParam(token, optional);\n}\n\n/** `inject(TOKEN)` call in a field initializer → the token expression, else null. */\nfunction fieldInjectToken(initializer: ts.Expression | undefined): ts.Expression | null {\n if (!initializer || !ts.isCallExpression(initializer)) return null;\n if (!ts.isIdentifier(initializer.expression) || initializer.expression.text !== 'inject') return null;\n return initializer.arguments[0] ? unwrapForwardRef(initializer.arguments[0]) : null;\n}\n\n/**\n * Angular builder: injection sites are constructor params (`@Inject`/bare typed)\n * plus `inject()` field initializers. Root/reached components render as\n * `component`; services as `class`.\n */\nclass AngularDesignBuilder extends DiDesignBuilder {\n protected override rootKindOf(cls: ts.ClassDeclaration): DiNodeKind {\n return hasComponentDecorator(cls) ? 'component' : 'class';\n }\n\n protected collectInjections(cls: ts.ClassDeclaration): Injection[] {\n const injections: Injection[] = [];\n\n const ctor = findConstructor(cls);\n for (const param of ctor?.parameters ?? []) {\n const info = readAngularParam(param);\n const paramName = ts.isIdentifier(param.name) ? param.name.text : param.name.getText();\n const paramType = param.type ? param.type.getText() : '';\n\n if (info.token) {\n injections.push(new Injection('angularToken', info.token, paramName, paramType, false, info.optional));\n } else {\n const typeRef =\n param.type && ts.isTypeReferenceNode(param.type) && ts.isIdentifier(param.type.typeName)\n ? param.type.typeName\n : null;\n if (typeRef) {\n injections.push(new Injection('angularToken', typeRef, paramName, paramType, false, info.optional));\n }\n }\n }\n\n for (const member of cls.members) {\n if (!ts.isPropertyDeclaration(member) || !ts.isIdentifier(member.name)) continue;\n const token = fieldInjectToken(member.initializer);\n if (!token) continue;\n const paramName = member.name.text;\n // The field usually has no explicit type (`= inject(SaveApi)`) — the\n // token IS the declared type, so label the box with it.\n const paramType = member.type ? member.type.getText() : token.getText();\n injections.push(new Injection('angularToken', token, paramName, paramType));\n }\n\n return injections;\n }\n}\n\n/**\n * Build the full Angular DI graph for one project: one self-contained `DiDesign`\n * per entry component (bootstrap + routed). `projectRoot` is workspace-relative.\n */\nexport function buildAngularDiGraph(\n program: ts.Program,\n workspaceRoot: string,\n projectRoot: string,\n projectName: string,\n): DiGraph {\n const checker = program.getTypeChecker();\n const table: BindingTable = collectAngularProviders(program, checker, workspaceRoot);\n const graph = new DiGraph(projectName);\n\n for (const root of findAngularRoots(program, checker, workspaceRoot, projectRoot)) {\n graph.designs.push(\n buildDesign(\n root,\n 'component',\n workspaceRoot,\n (design: DiDesign) => new AngularDesignBuilder(checker, table, workspaceRoot, design),\n ),\n );\n }\n\n return graph;\n}\n"]}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Angular Provider Table (pass 1, Angular flavor)
3
+ *
4
+ * The Angular analog of `bindings.ts` (Inversify): scans the program and maps
5
+ * Angular's provider forms onto the SAME token-keyed {@link BindingTable} the
6
+ * shared walker consumes, so `angular-analyzer.ts` reuses every downstream
7
+ * mechanism (leaf labeling, factory-dep edges, unresolved handling).
8
+ *
9
+ * Provider sources (v1 flat global table — component-scoped shadowing is an
10
+ * accepted approximation):
11
+ * - Any `providers: [...]` array (ApplicationConfig, `@Component`, `@Directive`)
12
+ * - `@Injectable({ providedIn: 'root' | 'platform' | 'any' })` self-registration
13
+ *
14
+ * Provider forms per array element:
15
+ * - bare class `Foo` → to/self binding (implClass=Foo)
16
+ * - `{ provide, useClass }` → to binding (implClass)
17
+ * - `{ provide, useValue }` → toConstantValue leaf
18
+ * - `{ provide, useFactory, deps: [A, B] }` → toDynamicValue leaf + factoryDeps
19
+ * - `{ provide, useExisting }` → alias → target impl class
20
+ * - `multi: true` → multiple bindings per token (fan-out)
21
+ *
22
+ * Framework-internal `provideXxx()` calls (`provideRouter`,
23
+ * `provideZoneChangeDetection`, ...) have no DI leaves and are skipped.
24
+ */
25
+ import * as ts from 'typescript';
26
+ import { BindingTable } from './bindings';
27
+ /**
28
+ * Collect every Angular provider in the program into a token-keyed table.
29
+ * `checker` is required so cross-package class tokens (`SaveApi`, `ClientConfig`)
30
+ * resolve to the same declaration the injection sites reference.
31
+ */
32
+ export declare function collectAngularProviders(program: ts.Program, checker: ts.TypeChecker, workspaceRoot: string): BindingTable;
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ /**
3
+ * Angular Provider Table (pass 1, Angular flavor)
4
+ *
5
+ * The Angular analog of `bindings.ts` (Inversify): scans the program and maps
6
+ * Angular's provider forms onto the SAME token-keyed {@link BindingTable} the
7
+ * shared walker consumes, so `angular-analyzer.ts` reuses every downstream
8
+ * mechanism (leaf labeling, factory-dep edges, unresolved handling).
9
+ *
10
+ * Provider sources (v1 flat global table — component-scoped shadowing is an
11
+ * accepted approximation):
12
+ * - Any `providers: [...]` array (ApplicationConfig, `@Component`, `@Directive`)
13
+ * - `@Injectable({ providedIn: 'root' | 'platform' | 'any' })` self-registration
14
+ *
15
+ * Provider forms per array element:
16
+ * - bare class `Foo` → to/self binding (implClass=Foo)
17
+ * - `{ provide, useClass }` → to binding (implClass)
18
+ * - `{ provide, useValue }` → toConstantValue leaf
19
+ * - `{ provide, useFactory, deps: [A, B] }` → toDynamicValue leaf + factoryDeps
20
+ * - `{ provide, useExisting }` → alias → target impl class
21
+ * - `multi: true` → multiple bindings per token (fan-out)
22
+ *
23
+ * Framework-internal `provideXxx()` calls (`provideRouter`,
24
+ * `provideZoneChangeDetection`, ...) have no DI leaves and are skipped.
25
+ */
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.collectAngularProviders = collectAngularProviders;
28
+ const tslib_1 = require("tslib");
29
+ const ts = tslib_1.__importStar(require("typescript"));
30
+ const model_1 = require("./model");
31
+ const bindings_1 = require("./bindings");
32
+ const token_resolver_1 = require("./token-resolver");
33
+ // Angular injector-scoped providers are effectively singletons at their scope.
34
+ const ANGULAR_SCOPE = 'singleton';
35
+ function isAnalyzableFile(sourceFile) {
36
+ if (sourceFile.isDeclarationFile)
37
+ return false;
38
+ if (sourceFile.fileName.includes('/node_modules/'))
39
+ return false;
40
+ return true;
41
+ }
42
+ /** First line of an expression's source text, truncated for leaf labels. */
43
+ function firstLine(text) {
44
+ const line = text.split('\n')[0].trim();
45
+ return line.length > 60 ? line.slice(0, 57) + '...' : line;
46
+ }
47
+ /** Pull `{ name: value }` properties out of an object literal into a map. */
48
+ function objectProps(obj) {
49
+ const props = new Map();
50
+ for (const prop of obj.properties) {
51
+ if (ts.isPropertyAssignment(prop) && (ts.isIdentifier(prop.name) || ts.isStringLiteralLike(prop.name))) {
52
+ props.set(prop.name.text, prop.initializer);
53
+ }
54
+ }
55
+ return props;
56
+ }
57
+ /** Resolve each `deps: [A, B]` element to a token reference for factory-dep edges. */
58
+ function collectDeps(depsExpr, checker, workspaceRoot) {
59
+ if (!depsExpr || !ts.isArrayLiteralExpression(depsExpr))
60
+ return [];
61
+ const deps = [];
62
+ for (const element of depsExpr.elements) {
63
+ // Simple token form (`EnvironmentConfig`); the decorated array form
64
+ // (`[new Optional(), Token]`) is out of scope for v1.
65
+ if (ts.isIdentifier(element) || ts.isPropertyAccessExpression(element)) {
66
+ const cls = (0, bindings_1.resolveClassDeclaration)(element, checker);
67
+ deps.push(cls ? (0, token_resolver_1.classTokenKey)(cls, workspaceRoot) : (0, token_resolver_1.resolveTokenKey)(element, checker, workspaceRoot));
68
+ }
69
+ }
70
+ return deps;
71
+ }
72
+ /** Record one provider-object literal (`{ provide, useX }`) as a binding. */
73
+ function collectProviderObject(obj, checker, workspaceRoot, file, table) {
74
+ const props = objectProps(obj);
75
+ const provideExpr = props.get('provide');
76
+ if (!provideExpr)
77
+ return;
78
+ const provideClass = (0, bindings_1.resolveClassDeclaration)(provideExpr, checker);
79
+ const token = provideClass
80
+ ? (0, token_resolver_1.classTokenKey)(provideClass, workspaceRoot)
81
+ : (0, token_resolver_1.resolveTokenKey)(provideExpr, checker, workspaceRoot);
82
+ const useClass = props.get('useClass');
83
+ const useValue = props.get('useValue');
84
+ const useFactory = props.get('useFactory');
85
+ const useExisting = props.get('useExisting');
86
+ if (useClass) {
87
+ const impl = (0, bindings_1.resolveClassDeclaration)(useClass, checker);
88
+ table.add(new model_1.Binding(token.key, token.display, 'to', ANGULAR_SCOPE, impl, useClass.getText(), file));
89
+ return;
90
+ }
91
+ if (useExisting) {
92
+ // Alias: T resolves to whatever `useExisting` points at — resolve through
93
+ // to the target impl class so the walk continues into its dependencies.
94
+ const impl = (0, bindings_1.resolveClassDeclaration)(useExisting, checker);
95
+ table.add(new model_1.Binding(token.key, token.display, 'to', ANGULAR_SCOPE, impl, useExisting.getText(), file));
96
+ return;
97
+ }
98
+ if (useFactory) {
99
+ const deps = collectDeps(props.get('deps'), checker, workspaceRoot);
100
+ table.add(new model_1.Binding(token.key, token.display, 'toDynamicValue', ANGULAR_SCOPE, null, firstLine(useFactory.getText()), file, deps));
101
+ return;
102
+ }
103
+ if (useValue) {
104
+ table.add(new model_1.Binding(token.key, token.display, 'toConstantValue', ANGULAR_SCOPE, null, firstLine(useValue.getText()), file));
105
+ return;
106
+ }
107
+ // `{ provide: T }` with no recipe — treat the token itself as the impl class.
108
+ if (provideClass) {
109
+ table.add(new model_1.Binding(token.key, token.display, 'to', ANGULAR_SCOPE, provideClass, provideExpr.getText(), file));
110
+ }
111
+ }
112
+ /** Record one element of a `providers: [...]` array. */
113
+ function collectProviderElement(element, checker, workspaceRoot, file, table) {
114
+ // Framework-internal `provideRouter(...)` / `provideZoneChangeDetection(...)`.
115
+ if (ts.isCallExpression(element))
116
+ return;
117
+ // Bare class provider: `providers: [MyService]` → useClass: MyService.
118
+ if (ts.isIdentifier(element) || ts.isPropertyAccessExpression(element)) {
119
+ const cls = (0, bindings_1.resolveClassDeclaration)(element, checker);
120
+ if (cls) {
121
+ const token = (0, token_resolver_1.classTokenKey)(cls, workspaceRoot);
122
+ table.add(new model_1.Binding(token.key, token.display, 'to', ANGULAR_SCOPE, cls, element.getText(), file));
123
+ }
124
+ return;
125
+ }
126
+ if (ts.isObjectLiteralExpression(element)) {
127
+ collectProviderObject(element, checker, workspaceRoot, file, table);
128
+ }
129
+ }
130
+ /** `@Injectable({ providedIn: 'root' | 'platform' | 'any' })` self-registration. */
131
+ function collectInjectableSelfBinding(cls, checker, workspaceRoot, table) {
132
+ for (const decorator of (0, bindings_1.classDecorators)(cls)) {
133
+ if ((0, bindings_1.decoratorName)(decorator) !== 'Injectable')
134
+ continue;
135
+ const call = (0, bindings_1.decoratorCall)(decorator);
136
+ const arg = call?.arguments[0];
137
+ if (!arg || !ts.isObjectLiteralExpression(arg))
138
+ return;
139
+ const providedIn = objectProps(arg).get('providedIn');
140
+ if (providedIn && ts.isStringLiteralLike(providedIn)) {
141
+ const token = (0, token_resolver_1.classTokenKey)(cls, workspaceRoot);
142
+ const file = (0, token_resolver_1.relativeFile)(workspaceRoot, cls.getSourceFile());
143
+ table.add(new model_1.Binding(token.key, token.display, 'decorator', ANGULAR_SCOPE, cls, '', file));
144
+ }
145
+ return;
146
+ }
147
+ }
148
+ /**
149
+ * Collect every Angular provider in the program into a token-keyed table.
150
+ * `checker` is required so cross-package class tokens (`SaveApi`, `ClientConfig`)
151
+ * resolve to the same declaration the injection sites reference.
152
+ */
153
+ function collectAngularProviders(program, checker, workspaceRoot) {
154
+ const table = new bindings_1.BindingTable();
155
+ for (const sourceFile of program.getSourceFiles()) {
156
+ if (!isAnalyzableFile(sourceFile))
157
+ continue;
158
+ const file = (0, token_resolver_1.relativeFile)(workspaceRoot, sourceFile);
159
+ const visit = (node) => {
160
+ if (ts.isClassDeclaration(node)) {
161
+ collectInjectableSelfBinding(node, checker, workspaceRoot, table);
162
+ }
163
+ else if (ts.isPropertyAssignment(node) &&
164
+ (ts.isIdentifier(node.name) || ts.isStringLiteralLike(node.name)) &&
165
+ node.name.text === 'providers' &&
166
+ ts.isArrayLiteralExpression(node.initializer)) {
167
+ for (const element of node.initializer.elements) {
168
+ collectProviderElement(element, checker, workspaceRoot, file, table);
169
+ }
170
+ }
171
+ ts.forEachChild(node, visit);
172
+ };
173
+ visit(sourceFile);
174
+ }
175
+ return table;
176
+ }
177
+ //# sourceMappingURL=angular-providers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"angular-providers.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/angular-providers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Angular Provider Table (pass 1, Angular flavor)\n *\n * The Angular analog of `bindings.ts` (Inversify): scans the program and maps\n * Angular's provider forms onto the SAME token-keyed {@link BindingTable} the\n * shared walker consumes, so `angular-analyzer.ts` reuses every downstream\n * mechanism (leaf labeling, factory-dep edges, unresolved handling).\n *\n * Provider sources (v1 flat global table — component-scoped shadowing is an\n * accepted approximation):\n * - Any `providers: [...]` array (ApplicationConfig, `@Component`, `@Directive`)\n * - `@Injectable({ providedIn: 'root' | 'platform' | 'any' })` self-registration\n *\n * Provider forms per array element:\n * - bare class `Foo` → to/self binding (implClass=Foo)\n * - `{ provide, useClass }` → to binding (implClass)\n * - `{ provide, useValue }` → toConstantValue leaf\n * - `{ provide, useFactory, deps: [A, B] }` → toDynamicValue leaf + factoryDeps\n * - `{ provide, useExisting }` → alias → target impl class\n * - `multi: true` → multiple bindings per token (fan-out)\n *\n * Framework-internal `provideXxx()` calls (`provideRouter`,\n * `provideZoneChangeDetection`, ...) have no DI leaves and are skipped.\n */\n\nimport * as ts from 'typescript';\nimport { Binding, DiScope, TokenRef } from './model';\nimport { BindingTable, classDecorators, decoratorCall, decoratorName, resolveClassDeclaration } from './bindings';\nimport { classTokenKey, relativeFile, resolveTokenKey } from './token-resolver';\n\n// Angular injector-scoped providers are effectively singletons at their scope.\nconst ANGULAR_SCOPE: DiScope = 'singleton';\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/** First line of an expression's source text, truncated for leaf labels. */\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/** Pull `{ name: value }` properties out of an object literal into a map. */\nfunction objectProps(obj: ts.ObjectLiteralExpression): Map<string, ts.Expression> {\n const props = new Map<string, ts.Expression>();\n for (const prop of obj.properties) {\n if (ts.isPropertyAssignment(prop) && (ts.isIdentifier(prop.name) || ts.isStringLiteralLike(prop.name))) {\n props.set(prop.name.text, prop.initializer);\n }\n }\n return props;\n}\n\n/** Resolve each `deps: [A, B]` element to a token reference for factory-dep edges. */\nfunction collectDeps(depsExpr: ts.Expression | undefined, checker: ts.TypeChecker, workspaceRoot: string): TokenRef[] {\n if (!depsExpr || !ts.isArrayLiteralExpression(depsExpr)) return [];\n const deps: TokenRef[] = [];\n for (const element of depsExpr.elements) {\n // Simple token form (`EnvironmentConfig`); the decorated array form\n // (`[new Optional(), Token]`) is out of scope for v1.\n if (ts.isIdentifier(element) || ts.isPropertyAccessExpression(element)) {\n const cls = resolveClassDeclaration(element, checker);\n deps.push(cls ? classTokenKey(cls, workspaceRoot) : resolveTokenKey(element, checker, workspaceRoot));\n }\n }\n return deps;\n}\n\n/** Record one provider-object literal (`{ provide, useX }`) as a binding. */\nfunction collectProviderObject(\n obj: ts.ObjectLiteralExpression,\n checker: ts.TypeChecker,\n workspaceRoot: string,\n file: string,\n table: BindingTable,\n): void {\n const props = objectProps(obj);\n const provideExpr = props.get('provide');\n if (!provideExpr) return;\n\n const provideClass = resolveClassDeclaration(provideExpr, checker);\n const token = provideClass\n ? classTokenKey(provideClass, workspaceRoot)\n : resolveTokenKey(provideExpr, checker, workspaceRoot);\n\n const useClass = props.get('useClass');\n const useValue = props.get('useValue');\n const useFactory = props.get('useFactory');\n const useExisting = props.get('useExisting');\n\n if (useClass) {\n const impl = resolveClassDeclaration(useClass, checker);\n table.add(new Binding(token.key, token.display, 'to', ANGULAR_SCOPE, impl, useClass.getText(), file));\n return;\n }\n if (useExisting) {\n // Alias: T resolves to whatever `useExisting` points at — resolve through\n // to the target impl class so the walk continues into its dependencies.\n const impl = resolveClassDeclaration(useExisting, checker);\n table.add(new Binding(token.key, token.display, 'to', ANGULAR_SCOPE, impl, useExisting.getText(), file));\n return;\n }\n if (useFactory) {\n const deps = collectDeps(props.get('deps'), checker, workspaceRoot);\n table.add(\n new Binding(token.key, token.display, 'toDynamicValue', ANGULAR_SCOPE, null, firstLine(useFactory.getText()), file, deps),\n );\n return;\n }\n if (useValue) {\n table.add(\n new Binding(token.key, token.display, 'toConstantValue', ANGULAR_SCOPE, null, firstLine(useValue.getText()), file),\n );\n return;\n }\n // `{ provide: T }` with no recipe — treat the token itself as the impl class.\n if (provideClass) {\n table.add(new Binding(token.key, token.display, 'to', ANGULAR_SCOPE, provideClass, provideExpr.getText(), file));\n }\n}\n\n/** Record one element of a `providers: [...]` array. */\nfunction collectProviderElement(\n element: ts.Expression,\n checker: ts.TypeChecker,\n workspaceRoot: string,\n file: string,\n table: BindingTable,\n): void {\n // Framework-internal `provideRouter(...)` / `provideZoneChangeDetection(...)`.\n if (ts.isCallExpression(element)) return;\n\n // Bare class provider: `providers: [MyService]` → useClass: MyService.\n if (ts.isIdentifier(element) || ts.isPropertyAccessExpression(element)) {\n const cls = resolveClassDeclaration(element, checker);\n if (cls) {\n const token = classTokenKey(cls, workspaceRoot);\n table.add(new Binding(token.key, token.display, 'to', ANGULAR_SCOPE, cls, element.getText(), file));\n }\n return;\n }\n\n if (ts.isObjectLiteralExpression(element)) {\n collectProviderObject(element, checker, workspaceRoot, file, table);\n }\n}\n\n/** `@Injectable({ providedIn: 'root' | 'platform' | 'any' })` self-registration. */\nfunction collectInjectableSelfBinding(\n cls: ts.ClassDeclaration,\n checker: ts.TypeChecker,\n workspaceRoot: string,\n table: BindingTable,\n): void {\n for (const decorator of classDecorators(cls)) {\n if (decoratorName(decorator) !== 'Injectable') continue;\n const call = decoratorCall(decorator);\n const arg = call?.arguments[0];\n if (!arg || !ts.isObjectLiteralExpression(arg)) return;\n const providedIn = objectProps(arg).get('providedIn');\n if (providedIn && ts.isStringLiteralLike(providedIn)) {\n const token = classTokenKey(cls, workspaceRoot);\n const file = relativeFile(workspaceRoot, cls.getSourceFile());\n table.add(new Binding(token.key, token.display, 'decorator', ANGULAR_SCOPE, cls, '', file));\n }\n return;\n }\n}\n\n/**\n * Collect every Angular provider in the program into a token-keyed table.\n * `checker` is required so cross-package class tokens (`SaveApi`, `ClientConfig`)\n * resolve to the same declaration the injection sites reference.\n */\nexport function collectAngularProviders(\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 const file = relativeFile(workspaceRoot, sourceFile);\n\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node)) {\n collectInjectableSelfBinding(node, checker, workspaceRoot, table);\n } else if (\n ts.isPropertyAssignment(node) &&\n (ts.isIdentifier(node.name) || ts.isStringLiteralLike(node.name)) &&\n node.name.text === 'providers' &&\n ts.isArrayLiteralExpression(node.initializer)\n ) {\n for (const element of node.initializer.elements) {\n collectProviderElement(element, checker, workspaceRoot, file, table);\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n }\n\n return table;\n}\n"]}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Angular Roots
3
+ *
4
+ * The Angular analog of "@Controller classes are the roots". Finds the entry
5
+ * components a DI tree is rendered "from the page/root component on down":
6
+ *
7
+ * - Bootstrap root: `bootstrapApplication(AppComponent, appConfig)` in main.ts
8
+ * — `arguments[0]` is the root component.
9
+ * - Route roots: the `Routes`-typed array (and any array passed to
10
+ * `provideRouter(...)`). Each route object contributes its `component` /
11
+ * `loadComponent` component, recursing through `children`.
12
+ *
13
+ * Each resolved component becomes its own `DiDesign` (one tree per root, like
14
+ * one-design-per-controller). Roots are de-duplicated by class declaration.
15
+ */
16
+ import * as ts from 'typescript';
17
+ /**
18
+ * Find every Angular entry component (bootstrap + routed) in the project, sorted
19
+ * by class name for deterministic output.
20
+ */
21
+ export declare function findAngularRoots(program: ts.Program, checker: ts.TypeChecker, workspaceRoot: string, projectRoot: string): ts.ClassDeclaration[];
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ /**
3
+ * Angular Roots
4
+ *
5
+ * The Angular analog of "@Controller classes are the roots". Finds the entry
6
+ * components a DI tree is rendered "from the page/root component on down":
7
+ *
8
+ * - Bootstrap root: `bootstrapApplication(AppComponent, appConfig)` in main.ts
9
+ * — `arguments[0]` is the root component.
10
+ * - Route roots: the `Routes`-typed array (and any array passed to
11
+ * `provideRouter(...)`). Each route object contributes its `component` /
12
+ * `loadComponent` component, recursing through `children`.
13
+ *
14
+ * Each resolved component becomes its own `DiDesign` (one tree per root, like
15
+ * one-design-per-controller). Roots are de-duplicated by class declaration.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.findAngularRoots = findAngularRoots;
19
+ const tslib_1 = require("tslib");
20
+ const ts = tslib_1.__importStar(require("typescript"));
21
+ const bindings_1 = require("./bindings");
22
+ const token_resolver_1 = require("./token-resolver");
23
+ /** Class declarations under the project root only (roots must be project-owned). */
24
+ function isUnderProject(sourceFile, workspaceRoot, projectRoot) {
25
+ if (sourceFile.isDeclarationFile)
26
+ return false;
27
+ const prefix = projectRoot.endsWith('/') ? projectRoot : projectRoot + '/';
28
+ return (0, token_resolver_1.relativeFile)(workspaceRoot, sourceFile).startsWith(prefix);
29
+ }
30
+ /** Callee identifier name of a call expression (`bootstrapApplication`, `provideRouter`, ...). */
31
+ function calleeName(call) {
32
+ if (ts.isIdentifier(call.expression))
33
+ return call.expression.text;
34
+ if (ts.isPropertyAccessExpression(call.expression))
35
+ return call.expression.name.text;
36
+ return null;
37
+ }
38
+ /**
39
+ * Resolve an expression to an array literal — either an inline `[...]` or an
40
+ * identifier (`routes`) whose declaration initializes to an array literal.
41
+ */
42
+ function asArrayLiteral(expr, checker) {
43
+ if (ts.isArrayLiteralExpression(expr))
44
+ return expr;
45
+ if (ts.isIdentifier(expr)) {
46
+ let symbol = checker.getSymbolAtLocation(expr);
47
+ if (symbol && (symbol.flags & ts.SymbolFlags.Alias) !== 0)
48
+ symbol = checker.getAliasedSymbol(symbol);
49
+ for (const decl of symbol?.declarations ?? []) {
50
+ if (ts.isVariableDeclaration(decl) && decl.initializer && ts.isArrayLiteralExpression(decl.initializer)) {
51
+ return decl.initializer;
52
+ }
53
+ }
54
+ }
55
+ return null;
56
+ }
57
+ /**
58
+ * A `loadComponent: () => import('./x').then(m => m.X)` lazy route — resolve the
59
+ * first property access inside the arrow body that points at a class
60
+ * declaration (the `m.X`). Returns null when the dynamic import can't be
61
+ * followed (falls back to no root, never throws).
62
+ */
63
+ function resolveLazyComponent(arrow, checker) {
64
+ let found = null;
65
+ const visit = (node) => {
66
+ if (found)
67
+ return;
68
+ if (ts.isPropertyAccessExpression(node)) {
69
+ const cls = (0, bindings_1.resolveClassDeclaration)(node, checker);
70
+ if (cls) {
71
+ found = cls;
72
+ return;
73
+ }
74
+ }
75
+ ts.forEachChild(node, visit);
76
+ };
77
+ visit(arrow);
78
+ return found;
79
+ }
80
+ /** Parse a `Routes` array literal, collecting `component`/`loadComponent` roots (recursing `children`). */
81
+ function collectRouteComponents(array, checker, out) {
82
+ for (const element of array.elements) {
83
+ if (!ts.isObjectLiteralExpression(element))
84
+ continue;
85
+ for (const prop of element.properties) {
86
+ if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name))
87
+ continue;
88
+ if (prop.name.text === 'component') {
89
+ const cls = (0, bindings_1.resolveClassDeclaration)(prop.initializer, checker);
90
+ if (cls)
91
+ out.add(cls);
92
+ }
93
+ else if (prop.name.text === 'loadComponent') {
94
+ const cls = resolveLazyComponent(prop.initializer, checker);
95
+ if (cls)
96
+ out.add(cls);
97
+ }
98
+ else if (prop.name.text === 'children') {
99
+ const nested = asArrayLiteral(prop.initializer, checker);
100
+ if (nested)
101
+ collectRouteComponents(nested, checker, out);
102
+ }
103
+ }
104
+ }
105
+ }
106
+ /**
107
+ * Find every Angular entry component (bootstrap + routed) in the project, sorted
108
+ * by class name for deterministic output.
109
+ */
110
+ function findAngularRoots(program, checker, workspaceRoot, projectRoot) {
111
+ const roots = new Set();
112
+ for (const sourceFile of program.getSourceFiles()) {
113
+ if (!isUnderProject(sourceFile, workspaceRoot, projectRoot))
114
+ continue;
115
+ const visit = (node) => {
116
+ if (ts.isCallExpression(node)) {
117
+ const name = calleeName(node);
118
+ if (name === 'bootstrapApplication' && node.arguments[0]) {
119
+ const cls = (0, bindings_1.resolveClassDeclaration)(node.arguments[0], checker);
120
+ if (cls)
121
+ roots.add(cls);
122
+ }
123
+ else if (name === 'provideRouter' && node.arguments[0]) {
124
+ const array = asArrayLiteral(node.arguments[0], checker);
125
+ if (array)
126
+ collectRouteComponents(array, checker, roots);
127
+ }
128
+ }
129
+ else if (ts.isVariableDeclaration(node) &&
130
+ node.type &&
131
+ ts.isTypeReferenceNode(node.type) &&
132
+ ts.isIdentifier(node.type.typeName) &&
133
+ node.type.typeName.text === 'Routes' &&
134
+ node.initializer &&
135
+ ts.isArrayLiteralExpression(node.initializer)) {
136
+ collectRouteComponents(node.initializer, checker, roots);
137
+ }
138
+ ts.forEachChild(node, visit);
139
+ };
140
+ visit(sourceFile);
141
+ }
142
+ return [...roots].sort((a, b) => {
143
+ const nameA = a.name ? a.name.text : '';
144
+ const nameB = b.name ? b.name.text : '';
145
+ if (nameA !== nameB)
146
+ return nameA < nameB ? -1 : 1;
147
+ return a.getSourceFile().fileName < b.getSourceFile().fileName ? -1 : 1;
148
+ });
149
+ }
150
+ //# sourceMappingURL=angular-roots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"angular-roots.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/angular-roots.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;AAyFH,4CA2CC;;AAlID,uDAAiC;AACjC,yCAAqD;AACrD,qDAAgD;AAEhD,oFAAoF;AACpF,SAAS,cAAc,CAAC,UAAyB,EAAE,aAAqB,EAAE,WAAmB;IACzF,IAAI,UAAU,CAAC,iBAAiB;QAAE,OAAO,KAAK,CAAC;IAC/C,MAAM,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,GAAG,GAAG,CAAC;IAC3E,OAAO,IAAA,6BAAY,EAAC,aAAa,EAAE,UAAU,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;AACtE,CAAC;AAED,kGAAkG;AAClG,SAAS,UAAU,CAAC,IAAuB;IACvC,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;IAClE,IAAI,EAAE,CAAC,0BAA0B,CAAC,IAAI,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;IACrF,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,SAAS,cAAc,CAAC,IAAmB,EAAE,OAAuB;IAChE,IAAI,EAAE,CAAC,wBAAwB,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnD,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,IAAI,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;QAC/C,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC;YAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;QACrG,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,YAAY,IAAI,EAAE,EAAE,CAAC;YAC5C,IAAI,EAAE,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC,wBAAwB,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;gBACtG,OAAO,IAAI,CAAC,WAAW,CAAC;YAC5B,CAAC;QACL,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,SAAS,oBAAoB,CAAC,KAAoB,EAAE,OAAuB;IACvE,IAAI,KAAK,GAA+B,IAAI,CAAC;IAC7C,MAAM,KAAK,GAAG,CAAC,IAAa,EAAQ,EAAE;QAClC,IAAI,KAAK;YAAE,OAAO;QAClB,IAAI,EAAE,CAAC,0BAA0B,CAAC,IAAI,CAAC,EAAE,CAAC;YACtC,MAAM,GAAG,GAAG,IAAA,kCAAuB,EAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACnD,IAAI,GAAG,EAAE,CAAC;gBACN,KAAK,GAAG,GAAG,CAAC;gBACZ,OAAO;YACX,CAAC;QACL,CAAC;QACD,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACjC,CAAC,CAAC;IACF,KAAK,CAAC,KAAK,CAAC,CAAC;IACb,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,2GAA2G;AAC3G,SAAS,sBAAsB,CAC3B,KAAgC,EAChC,OAAuB,EACvB,GAA6B;IAE7B,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACnC,IAAI,CAAC,EAAE,CAAC,yBAAyB,CAAC,OAAO,CAAC;YAAE,SAAS;QACrD,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;YACpC,IAAI,CAAC,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,SAAS;YAC5E,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBACjC,MAAM,GAAG,GAAG,IAAA,kCAAuB,EAAC,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;gBAC/D,IAAI,GAAG;oBAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC1B,CAAC;iBAAM,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;gBAC5C,MAAM,GAAG,GAAG,oBAAoB,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;gBAC5D,IAAI,GAAG;oBAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC1B,CAAC;iBAAM,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;gBACvC,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;gBACzD,IAAI,MAAM;oBAAE,sBAAsB,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;YAC7D,CAAC;QACL,CAAC;IACL,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,SAAgB,gBAAgB,CAC5B,OAAmB,EACnB,OAAuB,EACvB,aAAqB,EACrB,WAAmB;IAEnB,MAAM,KAAK,GAAG,IAAI,GAAG,EAAuB,CAAC;IAE7C,KAAK,MAAM,UAAU,IAAI,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC;QAChD,IAAI,CAAC,cAAc,CAAC,UAAU,EAAE,aAAa,EAAE,WAAW,CAAC;YAAE,SAAS;QAEtE,MAAM,KAAK,GAAG,CAAC,IAAa,EAAQ,EAAE;YAClC,IAAI,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC5B,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;gBAC9B,IAAI,IAAI,KAAK,sBAAsB,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;oBACvD,MAAM,GAAG,GAAG,IAAA,kCAAuB,EAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;oBAChE,IAAI,GAAG;wBAAE,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBAC5B,CAAC;qBAAM,IAAI,IAAI,KAAK,eAAe,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;oBACvD,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;oBACzD,IAAI,KAAK;wBAAE,sBAAsB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;gBAC7D,CAAC;YACL,CAAC;iBAAM,IACH,EAAE,CAAC,qBAAqB,CAAC,IAAI,CAAC;gBAC9B,IAAI,CAAC,IAAI;gBACT,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC;gBACjC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC;gBACnC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,KAAK,QAAQ;gBACpC,IAAI,CAAC,WAAW;gBAChB,EAAE,CAAC,wBAAwB,CAAC,IAAI,CAAC,WAAW,CAAC,EAC/C,CAAC;gBACC,sBAAsB,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;YAC7D,CAAC;YACD,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACjC,CAAC,CAAC;QACF,KAAK,CAAC,UAAU,CAAC,CAAC;IACtB,CAAC;IAED,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAsB,EAAE,CAAsB,EAAE,EAAE;QACtE,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QACxC,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QACxC,IAAI,KAAK,KAAK,KAAK;YAAE,OAAO,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACnD,OAAO,CAAC,CAAC,aAAa,EAAE,CAAC,QAAQ,GAAG,CAAC,CAAC,aAAa,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5E,CAAC,CAAC,CAAC;AACP,CAAC","sourcesContent":["/**\n * Angular Roots\n *\n * The Angular analog of \"@Controller classes are the roots\". Finds the entry\n * components a DI tree is rendered \"from the page/root component on down\":\n *\n * - Bootstrap root: `bootstrapApplication(AppComponent, appConfig)` in main.ts\n * — `arguments[0]` is the root component.\n * - Route roots: the `Routes`-typed array (and any array passed to\n * `provideRouter(...)`). Each route object contributes its `component` /\n * `loadComponent` component, recursing through `children`.\n *\n * Each resolved component becomes its own `DiDesign` (one tree per root, like\n * one-design-per-controller). Roots are de-duplicated by class declaration.\n */\n\nimport * as ts from 'typescript';\nimport { resolveClassDeclaration } from './bindings';\nimport { relativeFile } from './token-resolver';\n\n/** Class declarations under the project root only (roots must be project-owned). */\nfunction isUnderProject(sourceFile: ts.SourceFile, workspaceRoot: string, projectRoot: string): boolean {\n if (sourceFile.isDeclarationFile) return false;\n const prefix = projectRoot.endsWith('/') ? projectRoot : projectRoot + '/';\n return relativeFile(workspaceRoot, sourceFile).startsWith(prefix);\n}\n\n/** Callee identifier name of a call expression (`bootstrapApplication`, `provideRouter`, ...). */\nfunction calleeName(call: ts.CallExpression): string | null {\n if (ts.isIdentifier(call.expression)) return call.expression.text;\n if (ts.isPropertyAccessExpression(call.expression)) return call.expression.name.text;\n return null;\n}\n\n/**\n * Resolve an expression to an array literal — either an inline `[...]` or an\n * identifier (`routes`) whose declaration initializes to an array literal.\n */\nfunction asArrayLiteral(expr: ts.Expression, checker: ts.TypeChecker): ts.ArrayLiteralExpression | null {\n if (ts.isArrayLiteralExpression(expr)) return expr;\n if (ts.isIdentifier(expr)) {\n let symbol = checker.getSymbolAtLocation(expr);\n if (symbol && (symbol.flags & ts.SymbolFlags.Alias) !== 0) symbol = checker.getAliasedSymbol(symbol);\n for (const decl of symbol?.declarations ?? []) {\n if (ts.isVariableDeclaration(decl) && decl.initializer && ts.isArrayLiteralExpression(decl.initializer)) {\n return decl.initializer;\n }\n }\n }\n return null;\n}\n\n/**\n * A `loadComponent: () => import('./x').then(m => m.X)` lazy route — resolve the\n * first property access inside the arrow body that points at a class\n * declaration (the `m.X`). Returns null when the dynamic import can't be\n * followed (falls back to no root, never throws).\n */\nfunction resolveLazyComponent(arrow: ts.Expression, checker: ts.TypeChecker): ts.ClassDeclaration | null {\n let found: ts.ClassDeclaration | null = null;\n const visit = (node: ts.Node): void => {\n if (found) return;\n if (ts.isPropertyAccessExpression(node)) {\n const cls = resolveClassDeclaration(node, checker);\n if (cls) {\n found = cls;\n return;\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(arrow);\n return found;\n}\n\n/** Parse a `Routes` array literal, collecting `component`/`loadComponent` roots (recursing `children`). */\nfunction collectRouteComponents(\n array: ts.ArrayLiteralExpression,\n checker: ts.TypeChecker,\n out: Set<ts.ClassDeclaration>,\n): void {\n for (const element of array.elements) {\n if (!ts.isObjectLiteralExpression(element)) continue;\n for (const prop of element.properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;\n if (prop.name.text === 'component') {\n const cls = resolveClassDeclaration(prop.initializer, checker);\n if (cls) out.add(cls);\n } else if (prop.name.text === 'loadComponent') {\n const cls = resolveLazyComponent(prop.initializer, checker);\n if (cls) out.add(cls);\n } else if (prop.name.text === 'children') {\n const nested = asArrayLiteral(prop.initializer, checker);\n if (nested) collectRouteComponents(nested, checker, out);\n }\n }\n }\n}\n\n/**\n * Find every Angular entry component (bootstrap + routed) in the project, sorted\n * by class name for deterministic output.\n */\nexport function findAngularRoots(\n program: ts.Program,\n checker: ts.TypeChecker,\n workspaceRoot: string,\n projectRoot: string,\n): ts.ClassDeclaration[] {\n const roots = new Set<ts.ClassDeclaration>();\n\n for (const sourceFile of program.getSourceFiles()) {\n if (!isUnderProject(sourceFile, workspaceRoot, projectRoot)) continue;\n\n const visit = (node: ts.Node): void => {\n if (ts.isCallExpression(node)) {\n const name = calleeName(node);\n if (name === 'bootstrapApplication' && node.arguments[0]) {\n const cls = resolveClassDeclaration(node.arguments[0], checker);\n if (cls) roots.add(cls);\n } else if (name === 'provideRouter' && node.arguments[0]) {\n const array = asArrayLiteral(node.arguments[0], checker);\n if (array) collectRouteComponents(array, checker, roots);\n }\n } else if (\n ts.isVariableDeclaration(node) &&\n node.type &&\n ts.isTypeReferenceNode(node.type) &&\n ts.isIdentifier(node.type.typeName) &&\n node.type.typeName.text === 'Routes' &&\n node.initializer &&\n ts.isArrayLiteralExpression(node.initializer)\n ) {\n collectRouteComponents(node.initializer, checker, roots);\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n }\n\n return [...roots].sort((a: ts.ClassDeclaration, b: ts.ClassDeclaration) => {\n const nameA = a.name ? a.name.text : '';\n const nameB = b.name ? b.name.text : '';\n if (nameA !== nameB) return nameA < nameB ? -1 : 1;\n return a.getSourceFile().fileName < b.getSourceFile().fileName ? -1 : 1;\n });\n}\n"]}
@@ -13,7 +13,8 @@ exports.generateDesignDot = generateDesignDot;
13
13
  * Node fill colors by DI node kind.
14
14
  */
15
15
  const KIND_COLORS = {
16
- controller: '#E3F2FD', // light blue — the root/entry class
16
+ controller: '#E3F2FD', // light blue — the root/entry class (Inversify)
17
+ component: '#E8F5E9', // light green — the root/entry Angular component
17
18
  class: '#F5F5F5', // neutral — plain injectable class
18
19
  constant: '#FFF3E0', // light orange — toConstantValue leaf
19
20
  dynamic: '#FFF3E0', // light orange — toDynamicValue leaf
@@ -31,7 +32,7 @@ function nodeStatement(node) {
31
32
  styles.push('rounded');
32
33
  if (node.kind === 'unresolved')
33
34
  styles.push('dashed');
34
- const penwidth = node.kind === 'controller' ? ', penwidth=2' : '';
35
+ const penwidth = node.kind === 'controller' || node.kind === 'component' ? ', penwidth=2' : '';
35
36
  return ` "${dotEscape(node.id)}" [fillcolor="${color}", style="${styles.join(',')}", label="${label}"${penwidth}];\n`;
36
37
  }
37
38
  /**
@@ -61,10 +62,12 @@ function generateDesignDot(design) {
61
62
  dot += ` { rank=same; ${ids.map((id) => `"${dotEscape(id)}"`).join('; ')}; }\n`;
62
63
  }
63
64
  dot += '\n';
64
- // Constructor-injection edges; labels mirror design.md (mermaid.ts)
65
+ // Constructor-injection edges; labels are the declared param/field NAME
66
+ // (B0) — the human-meaningful "why", not the raw token expression. The
67
+ // token/tokenKey stay in design.json for tooling. Mirrors design.md.
65
68
  for (const edge of design.edges) {
66
- const token = edge.injection === 'multiInject' ? `multiInject ${edge.token}` : edge.token;
67
- const label = token !== '' ? ` [label="${dotEscape(token)}"]` : '';
69
+ const name = edge.injection === 'multiInject' ? `multiInject ${edge.paramName}` : edge.paramName;
70
+ const label = name !== '' ? ` [label="${dotEscape(name)}"]` : '';
68
71
  dot += ` "${dotEscape(edge.from)}" -> "${dotEscape(edge.to)}"${label};\n`;
69
72
  }
70
73
  dot += '\n labelloc="t";\n';