@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.
- package/LICENSE +21 -0
- package/README.md +507 -0
- package/dist/analysis/complexity-metrics-calculator.d.ts +99 -0
- package/dist/analysis/complexity-metrics-calculator.d.ts.map +1 -0
- package/dist/analysis/complexity-metrics-calculator.js +195 -0
- package/dist/analysis/complexity-metrics-calculator.js.map +1 -0
- package/dist/analysis/dependency-analyzer.d.ts +227 -0
- package/dist/analysis/dependency-analyzer.d.ts.map +1 -0
- package/dist/analysis/dependency-analyzer.js +451 -0
- package/dist/analysis/dependency-analyzer.js.map +1 -0
- package/dist/analysis/external-template-loader.d.ts +44 -0
- package/dist/analysis/external-template-loader.d.ts.map +1 -0
- package/dist/analysis/external-template-loader.js +69 -0
- package/dist/analysis/external-template-loader.js.map +1 -0
- package/dist/analysis/selector-mapper.d.ts +77 -0
- package/dist/analysis/selector-mapper.d.ts.map +1 -0
- package/dist/analysis/selector-mapper.js +146 -0
- package/dist/analysis/selector-mapper.js.map +1 -0
- package/dist/analysis/service-detector.d.ts +164 -0
- package/dist/analysis/service-detector.d.ts.map +1 -0
- package/dist/analysis/service-detector.js +231 -0
- package/dist/analysis/service-detector.js.map +1 -0
- package/dist/analysis/service-pattern-recognizer.d.ts +247 -0
- package/dist/analysis/service-pattern-recognizer.d.ts.map +1 -0
- package/dist/analysis/service-pattern-recognizer.js +444 -0
- package/dist/analysis/service-pattern-recognizer.js.map +1 -0
- package/dist/analysis/symbol-locator.d.ts +81 -0
- package/dist/analysis/symbol-locator.d.ts.map +1 -0
- package/dist/analysis/symbol-locator.js +128 -0
- package/dist/analysis/symbol-locator.js.map +1 -0
- package/dist/analysis/template-analyzer-usage.d.ts +144 -0
- package/dist/analysis/template-analyzer-usage.d.ts.map +1 -0
- package/dist/analysis/template-analyzer-usage.js +474 -0
- package/dist/analysis/template-analyzer-usage.js.map +1 -0
- package/dist/analysis/template-analyzer.d.ts +80 -0
- package/dist/analysis/template-analyzer.d.ts.map +1 -0
- package/dist/analysis/template-analyzer.js +146 -0
- package/dist/analysis/template-analyzer.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/investigation/angular-symbol-finder.d.ts +76 -0
- package/dist/investigation/angular-symbol-finder.d.ts.map +1 -0
- package/dist/investigation/angular-symbol-finder.js +199 -0
- package/dist/investigation/angular-symbol-finder.js.map +1 -0
- package/dist/investigation/call-graph-builder.d.ts +85 -0
- package/dist/investigation/call-graph-builder.d.ts.map +1 -0
- package/dist/investigation/call-graph-builder.js +196 -0
- package/dist/investigation/call-graph-builder.js.map +1 -0
- package/dist/investigation/codebase-searcher.d.ts +99 -0
- package/dist/investigation/codebase-searcher.d.ts.map +1 -0
- package/dist/investigation/codebase-searcher.js +201 -0
- package/dist/investigation/codebase-searcher.js.map +1 -0
- package/dist/investigation/stack-trace-parser.d.ts +70 -0
- package/dist/investigation/stack-trace-parser.d.ts.map +1 -0
- package/dist/investigation/stack-trace-parser.js +117 -0
- package/dist/investigation/stack-trace-parser.js.map +1 -0
- package/dist/investigation/type-resolver.d.ts +59 -0
- package/dist/investigation/type-resolver.d.ts.map +1 -0
- package/dist/investigation/type-resolver.js +111 -0
- package/dist/investigation/type-resolver.js.map +1 -0
- package/dist/investigation/usage-finder.d.ts +69 -0
- package/dist/investigation/usage-finder.d.ts.map +1 -0
- package/dist/investigation/usage-finder.js +132 -0
- package/dist/investigation/usage-finder.js.map +1 -0
- package/dist/metrics/complexity-metrics.d.ts +196 -0
- package/dist/metrics/complexity-metrics.d.ts.map +1 -0
- package/dist/metrics/complexity-metrics.js +445 -0
- package/dist/metrics/complexity-metrics.js.map +1 -0
- package/dist/public-api.d.ts +77 -0
- package/dist/public-api.d.ts.map +1 -0
- package/dist/public-api.js +50 -0
- package/dist/public-api.js.map +1 -0
- package/dist/transformation/import-manager.d.ts +276 -0
- package/dist/transformation/import-manager.d.ts.map +1 -0
- package/dist/transformation/import-manager.js +488 -0
- package/dist/transformation/import-manager.js.map +1 -0
- package/dist/transformation/ng-morph-adapter.d.ts +111 -0
- package/dist/transformation/ng-morph-adapter.d.ts.map +1 -0
- package/dist/transformation/ng-morph-adapter.js +179 -0
- package/dist/transformation/ng-morph-adapter.js.map +1 -0
- package/dist/transformation/ngmodule-manager.d.ts +145 -0
- package/dist/transformation/ngmodule-manager.d.ts.map +1 -0
- package/dist/transformation/ngmodule-manager.js +356 -0
- package/dist/transformation/ngmodule-manager.js.map +1 -0
- package/package.json +62 -0
- package/src/analysis/complexity-metrics-calculator.ts +253 -0
- package/src/analysis/dependency-analyzer.ts +720 -0
- package/src/analysis/external-template-loader.ts +79 -0
- package/src/analysis/selector-mapper.ts +186 -0
- package/src/analysis/service-detector.ts +280 -0
- package/src/analysis/service-pattern-recognizer.ts +545 -0
- package/src/analysis/symbol-locator.ts +170 -0
- package/src/analysis/template-analyzer-usage.ts +614 -0
- package/src/analysis/template-analyzer.ts +208 -0
- package/src/index.ts +51 -0
- package/src/investigation/angular-symbol-finder.ts +263 -0
- package/src/investigation/call-graph-builder.ts +298 -0
- package/src/investigation/codebase-searcher.ts +286 -0
- package/src/investigation/stack-trace-parser.ts +166 -0
- package/src/investigation/type-resolver.ts +144 -0
- package/src/investigation/usage-finder.ts +198 -0
- package/src/metrics/complexity-metrics.ts +617 -0
- package/src/public-api.ts +119 -0
- package/src/transformation/import-manager.ts +626 -0
- package/src/transformation/ng-morph-adapter.ts +252 -0
- 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
|
+
}
|