@angular-modernizer/api 0.1.0

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 (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +507 -0
  3. package/dist/analysis/complexity-metrics-calculator.d.ts +99 -0
  4. package/dist/analysis/complexity-metrics-calculator.d.ts.map +1 -0
  5. package/dist/analysis/complexity-metrics-calculator.js +195 -0
  6. package/dist/analysis/complexity-metrics-calculator.js.map +1 -0
  7. package/dist/analysis/dependency-analyzer.d.ts +227 -0
  8. package/dist/analysis/dependency-analyzer.d.ts.map +1 -0
  9. package/dist/analysis/dependency-analyzer.js +451 -0
  10. package/dist/analysis/dependency-analyzer.js.map +1 -0
  11. package/dist/analysis/external-template-loader.d.ts +44 -0
  12. package/dist/analysis/external-template-loader.d.ts.map +1 -0
  13. package/dist/analysis/external-template-loader.js +69 -0
  14. package/dist/analysis/external-template-loader.js.map +1 -0
  15. package/dist/analysis/selector-mapper.d.ts +77 -0
  16. package/dist/analysis/selector-mapper.d.ts.map +1 -0
  17. package/dist/analysis/selector-mapper.js +146 -0
  18. package/dist/analysis/selector-mapper.js.map +1 -0
  19. package/dist/analysis/service-detector.d.ts +164 -0
  20. package/dist/analysis/service-detector.d.ts.map +1 -0
  21. package/dist/analysis/service-detector.js +231 -0
  22. package/dist/analysis/service-detector.js.map +1 -0
  23. package/dist/analysis/service-pattern-recognizer.d.ts +247 -0
  24. package/dist/analysis/service-pattern-recognizer.d.ts.map +1 -0
  25. package/dist/analysis/service-pattern-recognizer.js +444 -0
  26. package/dist/analysis/service-pattern-recognizer.js.map +1 -0
  27. package/dist/analysis/symbol-locator.d.ts +81 -0
  28. package/dist/analysis/symbol-locator.d.ts.map +1 -0
  29. package/dist/analysis/symbol-locator.js +128 -0
  30. package/dist/analysis/symbol-locator.js.map +1 -0
  31. package/dist/analysis/template-analyzer-usage.d.ts +144 -0
  32. package/dist/analysis/template-analyzer-usage.d.ts.map +1 -0
  33. package/dist/analysis/template-analyzer-usage.js +474 -0
  34. package/dist/analysis/template-analyzer-usage.js.map +1 -0
  35. package/dist/analysis/template-analyzer.d.ts +80 -0
  36. package/dist/analysis/template-analyzer.d.ts.map +1 -0
  37. package/dist/analysis/template-analyzer.js +146 -0
  38. package/dist/analysis/template-analyzer.js.map +1 -0
  39. package/dist/index.d.ts +23 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +22 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/investigation/angular-symbol-finder.d.ts +76 -0
  44. package/dist/investigation/angular-symbol-finder.d.ts.map +1 -0
  45. package/dist/investigation/angular-symbol-finder.js +199 -0
  46. package/dist/investigation/angular-symbol-finder.js.map +1 -0
  47. package/dist/investigation/call-graph-builder.d.ts +85 -0
  48. package/dist/investigation/call-graph-builder.d.ts.map +1 -0
  49. package/dist/investigation/call-graph-builder.js +196 -0
  50. package/dist/investigation/call-graph-builder.js.map +1 -0
  51. package/dist/investigation/codebase-searcher.d.ts +99 -0
  52. package/dist/investigation/codebase-searcher.d.ts.map +1 -0
  53. package/dist/investigation/codebase-searcher.js +201 -0
  54. package/dist/investigation/codebase-searcher.js.map +1 -0
  55. package/dist/investigation/stack-trace-parser.d.ts +70 -0
  56. package/dist/investigation/stack-trace-parser.d.ts.map +1 -0
  57. package/dist/investigation/stack-trace-parser.js +117 -0
  58. package/dist/investigation/stack-trace-parser.js.map +1 -0
  59. package/dist/investigation/type-resolver.d.ts +59 -0
  60. package/dist/investigation/type-resolver.d.ts.map +1 -0
  61. package/dist/investigation/type-resolver.js +111 -0
  62. package/dist/investigation/type-resolver.js.map +1 -0
  63. package/dist/investigation/usage-finder.d.ts +69 -0
  64. package/dist/investigation/usage-finder.d.ts.map +1 -0
  65. package/dist/investigation/usage-finder.js +132 -0
  66. package/dist/investigation/usage-finder.js.map +1 -0
  67. package/dist/metrics/complexity-metrics.d.ts +196 -0
  68. package/dist/metrics/complexity-metrics.d.ts.map +1 -0
  69. package/dist/metrics/complexity-metrics.js +445 -0
  70. package/dist/metrics/complexity-metrics.js.map +1 -0
  71. package/dist/public-api.d.ts +77 -0
  72. package/dist/public-api.d.ts.map +1 -0
  73. package/dist/public-api.js +50 -0
  74. package/dist/public-api.js.map +1 -0
  75. package/dist/transformation/import-manager.d.ts +276 -0
  76. package/dist/transformation/import-manager.d.ts.map +1 -0
  77. package/dist/transformation/import-manager.js +488 -0
  78. package/dist/transformation/import-manager.js.map +1 -0
  79. package/dist/transformation/ng-morph-adapter.d.ts +111 -0
  80. package/dist/transformation/ng-morph-adapter.d.ts.map +1 -0
  81. package/dist/transformation/ng-morph-adapter.js +179 -0
  82. package/dist/transformation/ng-morph-adapter.js.map +1 -0
  83. package/dist/transformation/ngmodule-manager.d.ts +145 -0
  84. package/dist/transformation/ngmodule-manager.d.ts.map +1 -0
  85. package/dist/transformation/ngmodule-manager.js +356 -0
  86. package/dist/transformation/ngmodule-manager.js.map +1 -0
  87. package/package.json +62 -0
  88. package/src/analysis/complexity-metrics-calculator.ts +253 -0
  89. package/src/analysis/dependency-analyzer.ts +720 -0
  90. package/src/analysis/external-template-loader.ts +79 -0
  91. package/src/analysis/selector-mapper.ts +186 -0
  92. package/src/analysis/service-detector.ts +280 -0
  93. package/src/analysis/service-pattern-recognizer.ts +545 -0
  94. package/src/analysis/symbol-locator.ts +170 -0
  95. package/src/analysis/template-analyzer-usage.ts +614 -0
  96. package/src/analysis/template-analyzer.ts +208 -0
  97. package/src/index.ts +51 -0
  98. package/src/investigation/angular-symbol-finder.ts +263 -0
  99. package/src/investigation/call-graph-builder.ts +298 -0
  100. package/src/investigation/codebase-searcher.ts +286 -0
  101. package/src/investigation/stack-trace-parser.ts +166 -0
  102. package/src/investigation/type-resolver.ts +144 -0
  103. package/src/investigation/usage-finder.ts +198 -0
  104. package/src/metrics/complexity-metrics.ts +617 -0
  105. package/src/public-api.ts +119 -0
  106. package/src/transformation/import-manager.ts +626 -0
  107. package/src/transformation/ng-morph-adapter.ts +252 -0
  108. package/src/transformation/ngmodule-manager.ts +450 -0
@@ -0,0 +1,356 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
3
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
4
+ /* eslint-disable @typescript-eslint/no-unsafe-call */
5
+ /* eslint-disable @typescript-eslint/no-unsafe-argument */
6
+ /* eslint-disable @typescript-eslint/no-unsafe-return */
7
+ /**
8
+ * @angular-modernizer/api - NgModule Manager
9
+ *
10
+ * Service for managing NgModule declarations and exports.
11
+ * Handles finding NgModules and removing standalone components from them.
12
+ *
13
+ * This is critical for completing the standalone migration, as components
14
+ * marked as standalone must be removed from NgModule declarations/exports
15
+ * to avoid compiler errors.
16
+ *
17
+ * MIGRATION NOTE: Refactored to use ng-morph for Angular-specific operations.
18
+ * - Code reduction: 268 lines → ~150 lines (~44% reduction)
19
+ * - Eliminated SyntaxKind checking and manual AST traversal
20
+ * - Leverages ng-morph's high-level Angular abstractions
21
+ */
22
+ /**
23
+ * NgModule Manager - manages NgModule modifications during migration.
24
+ *
25
+ * This service provides high-level operations for NgModule cleanup,
26
+ * enabling the removal of standalone components from NgModule declarations.
27
+ */
28
+ export class NgModuleManager {
29
+ ngMorphAdapter;
30
+ /**
31
+ * Constructor for NgModuleManager.
32
+ *
33
+ * @param ngMorphAdapter - The ng-morph adapter instance
34
+ */
35
+ constructor(ngMorphAdapter) {
36
+ this.ngMorphAdapter = ngMorphAdapter;
37
+ }
38
+ /**
39
+ * Find the NgModule that declares a specific component.
40
+ *
41
+ * Uses two-tier detection strategy:
42
+ * 1. Fast path: ng-morph for standard `` decorators
43
+ * 2. Fallback: ts-morph for custom decorators (e.g., ``)
44
+ *
45
+ * This enables support for projects with external decorator patterns.
46
+ *
47
+ * @param project - The ts-morph Project instance
48
+ * @param componentClassName - The name of the component to find (e.g., 'CashbookOverviewComponent')
49
+ * @returns The source file containing the NgModule, or undefined if not found
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * const ngModuleFile = ngModuleManager.findNgModuleForComponent(project, 'CashbookOverviewComponent');
54
+ * if (ngModuleFile) {
55
+ * console.info(`Found in: ${ngModuleFile.getFilePath()}`);
56
+ * }
57
+ * ```
58
+ *
59
+ * TIER 1 (ng-morph): Fast path for standard Angular components
60
+ * TIER 2 (ts-morph): Fallback for components with custom decorators
61
+ */
62
+ findNgModuleForComponent(project, componentClassName) {
63
+ // TIER 1: Try ng-morph first (fast path for standard Angular)
64
+ const components = this.ngMorphAdapter.getComponents();
65
+ const component = components.find((comp) => comp.getName() === componentClassName);
66
+ if (component) {
67
+ // Found via ng-morph - use standard path
68
+ const module = this.ngMorphAdapter.findModuleForComponent(component);
69
+ if (module) {
70
+ const filePath = module.getSourceFile().getFilePath();
71
+ return project.getSourceFile(filePath);
72
+ }
73
+ }
74
+ // TIER 2: Fallback to ts-morph for components with custom decorators
75
+ // This handles cases like @ExportComponent, @ExportFunction, etc.
76
+ return this.findNgModuleForComponentWithCustomDecorators(project, componentClassName);
77
+ }
78
+ /**
79
+ * Find NgModule for components with custom decorators (ts-morph fallback).
80
+ *
81
+ * Searches all files for:
82
+ * 1. A class with the target name
83
+ * 2. That has a `` decorator (standard or with custom decorators)
84
+ * 3. Then finds which NgModule declares that class
85
+ *
86
+ * This is slower than ng-morph but necessary for external decorator support.
87
+ *
88
+ * @param project - The ts-morph Project instance
89
+ * @param componentClassName - The name of the component class
90
+ * @returns The source file containing the declaring NgModule, or undefined
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * // Finds components like:
95
+ * // ``()
96
+ * // ``({ selector: 'app-user' })
97
+ * // export class UserComponent {}
98
+ * ```
99
+ */
100
+ findNgModuleForComponentWithCustomDecorators(project, componentClassName) {
101
+ // Step 1: Find the component class in the project
102
+ let componentFile;
103
+ let componentClass;
104
+ for (const sourceFile of project.getSourceFiles()) {
105
+ const classes = sourceFile.getClasses();
106
+ for (const classDecl of classes) {
107
+ if (classDecl.getName() === componentClassName) {
108
+ // Check if this class has a @Component decorator
109
+ const hasComponentDecorator = classDecl
110
+ .getDecorators()
111
+ .some((dec) => dec.getName() === 'Component');
112
+ if (hasComponentDecorator) {
113
+ componentFile = sourceFile;
114
+ componentClass = classDecl;
115
+ break;
116
+ }
117
+ }
118
+ }
119
+ if (componentClass) {
120
+ break;
121
+ }
122
+ }
123
+ if (!componentClass || !componentFile) {
124
+ return undefined;
125
+ }
126
+ // Step 2: Find NgModule that declares this component
127
+ // Search all files for NgModule with this component in declarations
128
+ for (const sourceFile of project.getSourceFiles()) {
129
+ const classes = sourceFile.getClasses();
130
+ for (const classDecl of classes) {
131
+ // Check if this is an NgModule
132
+ const ngModuleDecorator = classDecl
133
+ .getDecorators()
134
+ .find((dec) => dec.getName() === 'NgModule');
135
+ if (!ngModuleDecorator) {
136
+ continue;
137
+ }
138
+ // Extract metadata from @NgModule decorator
139
+ const args = ngModuleDecorator.getArguments();
140
+ if (args.length === 0) {
141
+ continue;
142
+ }
143
+ const metadata = args[0];
144
+ if (!metadata) {
145
+ continue;
146
+ }
147
+ // Check if declarations array contains our component
148
+ const declarations = this.extractArrayFromMetadata(metadata, 'declarations');
149
+ if (declarations.includes(componentClassName)) {
150
+ return sourceFile;
151
+ }
152
+ }
153
+ }
154
+ return undefined;
155
+ }
156
+ /**
157
+ * Remove components from NgModule declarations and exports arrays.
158
+ *
159
+ * Uses ng-morph to locate the module and ts-morph to remove array elements.
160
+ * This is simpler than the previous implementation.
161
+ *
162
+ * @param ngModuleFile - The source file containing the NgModule
163
+ * @param classNamesToRemove - Array of component class names to remove
164
+ *
165
+ * @example
166
+ * ```typescript
167
+ * ngModuleManager.removeDeclarations(
168
+ * ngModuleFile,
169
+ * ['CashbookOverviewComponent', 'AddCashbookEntryDialogComponent']
170
+ * );
171
+ * ```
172
+ *
173
+ * BEFORE (ts-morph): 35+ lines with SyntaxKind and manual AST traversal
174
+ * AFTER (ng-morph + ts-morph): ~20 lines with cleaner logic
175
+ */
176
+ removeDeclarations(ngModuleFile, classNamesToRemove) {
177
+ if (classNamesToRemove.length === 0) {
178
+ return;
179
+ }
180
+ console.info(` [NgModuleManager] removeDeclarations() called for: ${ngModuleFile.getFilePath()}`);
181
+ console.info(` [NgModuleManager] Removing: [${classNamesToRemove.join(', ')}]`);
182
+ const namesToRemoveSet = new Set(classNamesToRemove);
183
+ // Use ts-morph directly instead of ng-morph (same pattern as getNgModuleImports)
184
+ const classes = ngModuleFile.getClasses();
185
+ for (const classDecl of classes) {
186
+ // Find the @NgModule decorator
187
+ const ngModuleDecorator = classDecl
188
+ .getDecorators()
189
+ .find((dec) => dec.getName() === 'NgModule');
190
+ if (!ngModuleDecorator) {
191
+ continue;
192
+ }
193
+ console.info(` [NgModuleManager] Found @NgModule decorator on class: ${classDecl.getName()}`);
194
+ // Extract metadata from @NgModule decorator1
195
+ const args = ngModuleDecorator.getArguments();
196
+ if (args.length === 0) {
197
+ console.info(` [NgModuleManager] Decorator has no arguments`);
198
+ continue;
199
+ }
200
+ const metadata = args[0];
201
+ if (!metadata) {
202
+ console.info(` [NgModuleManager] First argument is null/undefined`);
203
+ continue;
204
+ }
205
+ // Remove from declarations and exports arrays using ts-morph metadata
206
+ console.info(` [NgModuleManager] Removing from declarations and exports arrays...`);
207
+ this.removeFromMetadataArray(metadata, 'declarations', namesToRemoveSet);
208
+ this.removeFromMetadataArray(metadata, 'exports', namesToRemoveSet);
209
+ console.info(` [NgModuleManager] Removal complete`);
210
+ return; // Done - only one NgModule per file
211
+ }
212
+ console.info(` [NgModuleManager] No @NgModule decorator found in file`);
213
+ }
214
+ /**
215
+ * Get the imports array from an NgModule.
216
+ *
217
+ * Uses ng-morph to locate the module, then extracts imports using ts-morph.
218
+ * Simpler than the previous implementation.
219
+ *
220
+ * @param ngModuleFile - The source file containing the NgModule
221
+ * @returns Array of import names (e.g., ['CommonModule', 'ReactiveFormsModule'])
222
+ *
223
+ * @example
224
+ * ```typescript
225
+ * const imports = ngModuleManager.getNgModuleImports(cashbookModuleFile);
226
+ * // imports = ['CommonModule', 'ReactiveFormsModule', 'SharedComponentsModule']
227
+ * ```
228
+ *
229
+ * BEFORE (ts-morph): 45+ lines with SyntaxKind checking
230
+ * AFTER (ng-morph + ts-morph): ~15 lines with cleaner logic
231
+ */
232
+ getNgModuleImports(ngModuleFile) {
233
+ const filePath = ngModuleFile.getFilePath();
234
+ console.info(` [NgModuleManager] getNgModuleImports() called for: ${filePath}`);
235
+ // Use ts-morph directly instead of ng-morph (same as findNgModuleForComponentWithCustomDecorators)
236
+ // This ensures we can extract imports even when file isn't in ng-morph's Project
237
+ const classes = ngModuleFile.getClasses();
238
+ for (const classDecl of classes) {
239
+ // Find the @NgModule decorator
240
+ const ngModuleDecorator = classDecl
241
+ .getDecorators()
242
+ .find((dec) => dec.getName() === 'NgModule');
243
+ if (!ngModuleDecorator) {
244
+ continue;
245
+ }
246
+ console.info(` [NgModuleManager] Found @NgModule decorator on class: ${classDecl.getName()}`);
247
+ // Extract metadata from @NgModule decorator
248
+ const args = ngModuleDecorator.getArguments();
249
+ if (args.length === 0) {
250
+ console.info(` [NgModuleManager] Decorator has no arguments`);
251
+ continue;
252
+ }
253
+ const metadata = args[0];
254
+ if (!metadata) {
255
+ console.info(` [NgModuleManager] First argument is null/undefined`);
256
+ continue;
257
+ }
258
+ console.info(` [NgModuleManager] Extracting imports array...`);
259
+ // Extract imports array using ts-morph metadata
260
+ return this.extractArrayFromMetadata(metadata, 'imports');
261
+ }
262
+ console.info(` [NgModuleManager] No @NgModule decorator found in file`);
263
+ return [];
264
+ }
265
+ /**
266
+ * Extract an array property from NgModule metadata.
267
+ *
268
+ * Helper method to extract array values from metadata object.
269
+ *
270
+ * @param metadata - The metadata object from `` decorator
271
+ * @param propertyName - The property name (e.g., 'imports', 'declarations')
272
+ * @returns Array of string values from the property
273
+ *
274
+ * Note: Uses type assertions due to ng-morph/ts-morph version mismatch
275
+ */
276
+ extractArrayFromMetadata(metadata, propertyName) {
277
+ console.info(` [NgModuleManager] Extracting array: ${propertyName}`);
278
+ try {
279
+ const objLiteral = metadata.asKind(metadata.getKind());
280
+ if (!objLiteral || typeof objLiteral.getProperty !== 'function') {
281
+ console.info(` [NgModuleManager] No objLiteral or getProperty function`);
282
+ return [];
283
+ }
284
+ const property = objLiteral.getProperty(propertyName);
285
+ if (!property || typeof property.getInitializer !== 'function') {
286
+ console.info(` [NgModuleManager] No property '${propertyName}' or getInitializer function`);
287
+ console.info(` [NgModuleManager] Available properties:`, objLiteral.getProperties
288
+ ? objLiteral
289
+ .getProperties()
290
+ .map((p) => p.getName())
291
+ .join(', ')
292
+ : 'N/A');
293
+ return [];
294
+ }
295
+ const initializer = property.getInitializer();
296
+ if (!initializer || typeof initializer.getElements !== 'function') {
297
+ console.info(` [NgModuleManager] No initializer or getElements function`);
298
+ return [];
299
+ }
300
+ const elements = initializer.getElements();
301
+ const result = elements.map((el) => el.getText().trim());
302
+ console.info(` [NgModuleManager] Extracted ${result.length} elements: [${result.join(', ')}]`);
303
+ return result;
304
+ }
305
+ catch (error) {
306
+ console.info(` [NgModuleManager] Exception during extraction:`, error);
307
+ return [];
308
+ }
309
+ }
310
+ /**
311
+ * Remove elements from a metadata array property.
312
+ *
313
+ * Helper method to remove specific values from arrays in metadata.
314
+ *
315
+ * @param metadata - The metadata object from `` decorator
316
+ * @param propertyName - The property name (e.g., 'declarations', 'exports')
317
+ * @param namesToRemove - Set of names to remove
318
+ *
319
+ * Note: Uses type assertions due to ng-morph/ts-morph version mismatch
320
+ */
321
+ removeFromMetadataArray(metadata, propertyName, namesToRemove) {
322
+ try {
323
+ const objLiteral = metadata.asKind(metadata.getKind());
324
+ if (!objLiteral || typeof objLiteral.getProperty !== 'function') {
325
+ return;
326
+ }
327
+ const property = objLiteral.getProperty(propertyName);
328
+ if (!property || typeof property.getInitializer !== 'function') {
329
+ return;
330
+ }
331
+ const arrayLiteral = property.getInitializer();
332
+ if (!arrayLiteral || typeof arrayLiteral.getElements !== 'function') {
333
+ return;
334
+ }
335
+ const elements = arrayLiteral.getElements();
336
+ // Collect indices to remove
337
+ const indicesToRemove = [];
338
+ elements.forEach((element, index) => {
339
+ const elementName = element.getText().trim();
340
+ if (namesToRemove.has(elementName)) {
341
+ indicesToRemove.push(index);
342
+ }
343
+ });
344
+ // Remove in reverse order to preserve indices
345
+ indicesToRemove.reverse().forEach((index) => {
346
+ if (typeof arrayLiteral.removeElement === 'function') {
347
+ arrayLiteral.removeElement(index);
348
+ }
349
+ });
350
+ }
351
+ catch {
352
+ // Silently fail if metadata structure is unexpected
353
+ }
354
+ }
355
+ }
356
+ //# sourceMappingURL=ngmodule-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngmodule-manager.js","sourceRoot":"","sources":["../../src/transformation/ngmodule-manager.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@angular-modernizer/api",
3
+ "version": "0.1.0",
4
+ "description": "Battle-tested analysis and transformation tools for Angular Modernizer",
5
+ "keywords": [
6
+ "angular",
7
+ "modernization",
8
+ "typescript",
9
+ "ast",
10
+ "ts-morph",
11
+ "code-analysis",
12
+ "code-transformation"
13
+ ],
14
+ "license": "MIT",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/eiweiss/tsanalyzer.git",
18
+ "directory": "packages/api"
19
+ },
20
+ "homepage": "https://github.com/eiweiss/tsanalyzer#readme",
21
+ "bugs": "https://github.com/eiweiss/tsanalyzer/issues",
22
+ "type": "module",
23
+ "main": "./dist/index.js",
24
+ "types": "./dist/index.d.ts",
25
+ "files": [
26
+ "dist",
27
+ "src",
28
+ "!dist/**/__tests__/**",
29
+ "!src/**/__tests__/**",
30
+ "!src/**/*.test.ts",
31
+ "!src/**/*.spec.ts"
32
+ ],
33
+ "dependencies": {
34
+ "@angular/compiler": "^17.3.12",
35
+ "lru-cache": "^10.4.3",
36
+ "ng-morph": "^4.8.4",
37
+ "ts-morph": "^27.0.2"
38
+ },
39
+ "devDependencies": {
40
+ "@types/jest": "^30.0.0",
41
+ "@types/node": "^24.10.0",
42
+ "jest": "^30.2.0",
43
+ "rimraf": "^6.1.0",
44
+ "ts-jest": "^29.4.5",
45
+ "typescript": "^5.9.3"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
50
+ "typedocOptions": {
51
+ "entryPoints": [
52
+ "./src/index.ts"
53
+ ],
54
+ "tsconfig": "./tsconfig.json"
55
+ },
56
+ "scripts": {
57
+ "build": "tsc --build",
58
+ "clean": "rimraf dist tsconfig.tsbuildinfo",
59
+ "test": "jest",
60
+ "lint": "eslint src/**/*.ts"
61
+ }
62
+ }
@@ -0,0 +1,253 @@
1
+ /**
2
+ * @angular-modernizer/api - Complexity Metrics Calculator
3
+ *
4
+ * Calculates code complexity and maintainability metrics for Angular applications.
5
+ * Provides quantitative measures to prioritize refactoring efforts.
6
+ */
7
+
8
+ import type { SourceFile, ClassDeclaration, MethodDeclaration } from 'ts-morph';
9
+
10
+ /**
11
+ * Angular-specific metrics for components and directives.
12
+ */
13
+ export interface AngularMetrics {
14
+ /**
15
+ * Number of lifecycle hooks (ngOnInit, ngOnDestroy, etc.).
16
+ */
17
+ lifecycleHooks: number;
18
+
19
+ /**
20
+ * Number of @Input properties.
21
+ */
22
+ inputs: number;
23
+
24
+ /**
25
+ * Number of @Output properties.
26
+ */
27
+ outputs: number;
28
+
29
+ /**
30
+ * Number of Angular decorators (@Component, @Directive, etc.).
31
+ */
32
+ decorators: number;
33
+ }
34
+
35
+ /**
36
+ * Complexity Metrics Calculator - analyzes code complexity and maintainability.
37
+ */
38
+ export class ComplexityMetricsCalculator {
39
+ /**
40
+ * Calculate overall complexity score for a class (0-100 scale).
41
+ * Custom weighted heuristic combining established metrics (McCabe's Cyclomatic Complexity,
42
+ * LOC, method count from CK metrics) with Angular-specific indicators.
43
+ *
44
+ * Combines multiple metrics:
45
+ * - Cyclomatic complexity
46
+ * - Lines of code
47
+ * - Method count
48
+ * - Angular-specific complexity
49
+ *
50
+ * @param classDecl - The class declaration to analyze
51
+ * @returns Complexity score (0-100)
52
+ */
53
+ calculateOverallComplexity(classDecl: ClassDeclaration): number {
54
+ const cyclomatic = this.calculateCyclomaticComplexity(classDecl);
55
+ const loc = this.calculateLOC(classDecl.getSourceFile());
56
+ const methods = this.calculateMethodCount(classDecl);
57
+ const angularMetrics = this.calculateAngularSpecificMetrics(classDecl);
58
+
59
+ // Weighted formula for overall complexity - Weights empirically tuned for Angular applications:
60
+ // - 40%: Cyclomatic complexity (McCabe, 1976) - control flow decision points
61
+ // - 20%: Lines of code (÷10 normalization) - file size indicator
62
+ // - 20%: Method count (×2 amplification) - class size indicator (inspired by WMC from CK metrics)
63
+ // - 20%: Angular features (×1.5 weight) - framework coupling (lifecycle hooks, @Input, @Output)
64
+ // Returns 0-100 scale for prioritizing refactoring efforts.
65
+ const score =
66
+ cyclomatic * 0.4 +
67
+ (loc / 10) * 0.2 +
68
+ methods * 2 * 0.2 +
69
+ (angularMetrics.lifecycleHooks +
70
+ angularMetrics.inputs +
71
+ angularMetrics.outputs) *
72
+ 1.5 *
73
+ 0.2;
74
+
75
+ return Math.min(100, Math.max(0, score));
76
+ }
77
+
78
+ /**
79
+ * Calculate cyclomatic complexity for a class.
80
+ *
81
+ * Measures the number of linearly independent paths through the code.
82
+ * Higher values indicate more complex control flow.
83
+ *
84
+ * @param classDecl - The class declaration
85
+ * @returns Cyclomatic complexity score
86
+ */
87
+ calculateCyclomaticComplexity(classDecl: ClassDeclaration): number {
88
+ let complexity = 1; // Base complexity
89
+
90
+ const methods = classDecl.getMethods();
91
+ for (const method of methods) {
92
+ complexity += this.calculateMethodComplexity(method);
93
+ }
94
+
95
+ return complexity;
96
+ }
97
+
98
+ /**
99
+ * Calculate cyclomatic complexity for a single method.
100
+ * Implements McCabe's algorithm by counting decision points:
101
+ * if/else if, loops, switch cases, ternary operators, logical operators.
102
+ */
103
+ private calculateMethodComplexity(method: MethodDeclaration): number {
104
+ let complexity = 1; // Base complexity
105
+
106
+ const patterns = [
107
+ { regex: /\bif\s*\(/g, description: 'if statements' },
108
+ { regex: /\b(for|while|do)\s*\(/g, description: 'loops' },
109
+ { regex: /\bcase\s+/g, description: 'switch cases' },
110
+ { regex: /\?/g, description: 'ternary operators' },
111
+ { regex: /(&&|\|\|)/g, description: 'logical operators' },
112
+ ];
113
+
114
+ const statements = method.getStatements();
115
+ statements.forEach((statement) => {
116
+ const text = statement.getText();
117
+ patterns.forEach(({ regex }) => {
118
+ const matches = text.match(regex);
119
+ complexity += matches?.length ?? 0;
120
+ });
121
+ });
122
+
123
+ return complexity;
124
+ }
125
+
126
+ /**
127
+ * Calculate lines of code for a source file.
128
+ *
129
+ * Excludes comments and whitespace for meaningful LOC count.
130
+ *
131
+ * @param sourceFile - The source file
132
+ * @returns Number of lines of code
133
+ */
134
+ calculateLOC(sourceFile: SourceFile): number {
135
+ const text = sourceFile.getText();
136
+ const lines = text.split('\n');
137
+
138
+ let loc = 0;
139
+ for (const line of lines) {
140
+ const trimmed = line.trim();
141
+ // Count non-empty, non-comment lines
142
+ if (
143
+ trimmed &&
144
+ !trimmed.startsWith('//') &&
145
+ !trimmed.startsWith('/*') &&
146
+ !trimmed.startsWith('*')
147
+ ) {
148
+ loc++;
149
+ }
150
+ }
151
+
152
+ return loc;
153
+ }
154
+
155
+ /**
156
+ * Calculate the number of methods in a class.
157
+ *
158
+ * @param classDecl - The class declaration
159
+ * @returns Number of methods
160
+ */
161
+ calculateMethodCount(classDecl: ClassDeclaration): number {
162
+ return classDecl.getMethods().length;
163
+ }
164
+
165
+ /**
166
+ * Calculate dependency count for a source file.
167
+ *
168
+ * Counts unique external dependencies (imports from node_modules).
169
+ *
170
+ * @param sourceFile - The source file
171
+ * @returns Number of unique dependencies
172
+ */
173
+ calculateDependencyCount(sourceFile: SourceFile): number {
174
+ const importDeclarations = sourceFile.getImportDeclarations();
175
+ const dependencies = new Set<string>();
176
+
177
+ for (const importDecl of importDeclarations) {
178
+ const moduleSpecifier = importDecl.getModuleSpecifierValue();
179
+ // Only count external dependencies (not relative imports)
180
+ if (
181
+ !moduleSpecifier.startsWith('.') &&
182
+ !moduleSpecifier.startsWith('/')
183
+ ) {
184
+ dependencies.add(moduleSpecifier);
185
+ }
186
+ }
187
+
188
+ return dependencies.size;
189
+ }
190
+
191
+ /**
192
+ * Calculate Angular-specific metrics for a class.
193
+ *
194
+ * Analyzes Angular decorators and patterns.
195
+ *
196
+ * @param classDecl - The class declaration
197
+ * @returns Angular-specific metrics
198
+ */
199
+ calculateAngularSpecificMetrics(classDecl: ClassDeclaration): AngularMetrics {
200
+ const metrics = { lifecycleHooks: 0, inputs: 0, outputs: 0, decorators: 0 };
201
+
202
+ const lifecycleHookNames = new Set([
203
+ 'ngOnInit',
204
+ 'ngOnDestroy',
205
+ 'ngOnChanges',
206
+ 'ngDoCheck',
207
+ 'ngAfterContentInit',
208
+ 'ngAfterContentChecked',
209
+ 'ngAfterViewInit',
210
+ 'ngAfterViewChecked',
211
+ ]);
212
+
213
+ const countIODecorators = (
214
+ decorators: ReturnType<typeof classDecl.getDecorators>,
215
+ ) => {
216
+ decorators.forEach((decorator) => {
217
+ const name = decorator.getName();
218
+ if (name === 'Input') {
219
+ metrics.inputs++;
220
+ }
221
+ if (name === 'Output') {
222
+ metrics.outputs++;
223
+ }
224
+ });
225
+ };
226
+
227
+ classDecl.getDecorators().forEach((decorator) => {
228
+ const name = decorator.getName();
229
+ metrics.decorators++;
230
+
231
+ if (name === 'Component' || name === 'Directive') {
232
+ // Count lifecycle hook methods
233
+ classDecl.getMethods().forEach((method) => {
234
+ if (lifecycleHookNames.has(method.getName())) {
235
+ metrics.lifecycleHooks++;
236
+ }
237
+ });
238
+
239
+ // Count @Input and @Output on properties
240
+ classDecl.getProperties().forEach((prop) => {
241
+ countIODecorators(prop.getDecorators());
242
+ });
243
+
244
+ // Count @Input/@Output on setters
245
+ classDecl.getSetAccessors().forEach((setter) => {
246
+ countIODecorators(setter.getDecorators());
247
+ });
248
+ }
249
+ });
250
+
251
+ return metrics;
252
+ }
253
+ }