@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,617 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @angular-modernizer/api - Complexity Metrics
|
|
3
|
+
*
|
|
4
|
+
* Calculates code complexity metrics for Angular components and services.
|
|
5
|
+
* Helps identify candidates for refactoring.
|
|
6
|
+
*
|
|
7
|
+
* Analyzes:
|
|
8
|
+
* - Cyclomatic complexity for control flow complexity
|
|
9
|
+
* - Lines of code and method count for size metrics
|
|
10
|
+
* - Dependency count for coupling analysis
|
|
11
|
+
* - Additional Angular-specific metrics (template complexity, lifecycle method usage)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
type SourceFile,
|
|
16
|
+
SyntaxKind,
|
|
17
|
+
type ClassDeclaration,
|
|
18
|
+
ClassDeclaration as ClassDeclarationClass,
|
|
19
|
+
type MethodDeclaration,
|
|
20
|
+
} from 'ts-morph';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Complexity metrics for a source file.
|
|
24
|
+
*/
|
|
25
|
+
export interface ComplexityMetrics {
|
|
26
|
+
/**
|
|
27
|
+
* Cyclomatic complexity.
|
|
28
|
+
*/
|
|
29
|
+
cyclomaticComplexity: number;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Lines of code.
|
|
33
|
+
*/
|
|
34
|
+
linesOfCode: number;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Number of methods/functions.
|
|
38
|
+
*/
|
|
39
|
+
methodCount: number;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Number of dependencies.
|
|
43
|
+
*/
|
|
44
|
+
dependencyCount: number;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Additional metrics.
|
|
48
|
+
*/
|
|
49
|
+
metadata?: Record<string, unknown>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Angular-specific metrics for components and directives.
|
|
54
|
+
*/
|
|
55
|
+
export interface AngularMetrics {
|
|
56
|
+
/**
|
|
57
|
+
* Number of lifecycle hooks (ngOnInit, ngOnDestroy, etc.).
|
|
58
|
+
*/
|
|
59
|
+
lifecycleHooks: number;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Number of @Input properties.
|
|
63
|
+
*/
|
|
64
|
+
inputs: number;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Number of @Output properties.
|
|
68
|
+
*/
|
|
69
|
+
outputs: number;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Number of Angular decorators (@Component, @Directive, etc.).
|
|
73
|
+
*/
|
|
74
|
+
decorators: number;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Template complexity score (structural directives, bindings, etc.).
|
|
78
|
+
*/
|
|
79
|
+
templateComplexity?: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Complexity Metrics Calculator.
|
|
84
|
+
*/
|
|
85
|
+
export class ComplexityMetricsCalculator {
|
|
86
|
+
/**
|
|
87
|
+
* Calculate overall complexity score for a class (0-100 scale).
|
|
88
|
+
*
|
|
89
|
+
* Combines multiple metrics:
|
|
90
|
+
* - Cyclomatic complexity
|
|
91
|
+
* - Lines of code
|
|
92
|
+
* - Method count
|
|
93
|
+
* - Angular-specific complexity
|
|
94
|
+
*
|
|
95
|
+
* @param classDecl - The class declaration to analyze
|
|
96
|
+
* @returns Complexity score (0-100)
|
|
97
|
+
*/
|
|
98
|
+
calculateOverallComplexity(classDecl: ClassDeclaration): number {
|
|
99
|
+
const cyclomatic = this.calculateCyclomaticComplexity(classDecl);
|
|
100
|
+
const loc = this.calculateLOC(classDecl.getSourceFile());
|
|
101
|
+
const methods = this.calculateMethodCount(classDecl);
|
|
102
|
+
const angularMetrics = this.calculateAngularSpecificMetrics(classDecl);
|
|
103
|
+
|
|
104
|
+
// Weighted formula for overall complexity
|
|
105
|
+
const score =
|
|
106
|
+
cyclomatic * 0.4 +
|
|
107
|
+
(loc / 10) * 0.2 +
|
|
108
|
+
methods * 2 * 0.2 +
|
|
109
|
+
(angularMetrics.lifecycleHooks +
|
|
110
|
+
angularMetrics.inputs +
|
|
111
|
+
angularMetrics.outputs) *
|
|
112
|
+
1.5 *
|
|
113
|
+
0.2;
|
|
114
|
+
|
|
115
|
+
return Math.min(100, Math.max(0, score));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Calculate cyclomatic complexity for a class.
|
|
120
|
+
*
|
|
121
|
+
* Measures the number of linearly independent paths through the code.
|
|
122
|
+
* Higher values indicate more complex control flow.
|
|
123
|
+
*
|
|
124
|
+
* @param classDecl - The class declaration
|
|
125
|
+
* @returns Cyclomatic complexity score
|
|
126
|
+
*/
|
|
127
|
+
calculateCyclomaticComplexity(classDecl: ClassDeclaration): number;
|
|
128
|
+
calculateCyclomaticComplexity(method: MethodDeclaration): number;
|
|
129
|
+
calculateCyclomaticComplexity(
|
|
130
|
+
node: ClassDeclaration | MethodDeclaration,
|
|
131
|
+
): number {
|
|
132
|
+
let complexity = 1; // Base complexity
|
|
133
|
+
|
|
134
|
+
if (node instanceof ClassDeclarationClass) {
|
|
135
|
+
const methods = node.getMethods();
|
|
136
|
+
for (const method of methods) {
|
|
137
|
+
complexity += this.calculateMethodComplexity(method);
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
complexity = this.calculateMethodComplexity(node);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return complexity;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Calculate cyclomatic complexity for a single method.
|
|
148
|
+
*/
|
|
149
|
+
private calculateMethodComplexity(method: MethodDeclaration): number {
|
|
150
|
+
let complexity = 1; // Base complexity
|
|
151
|
+
|
|
152
|
+
// Count control flow statements
|
|
153
|
+
const statements = method.getStatements();
|
|
154
|
+
for (const statement of statements) {
|
|
155
|
+
const text = statement.getText();
|
|
156
|
+
|
|
157
|
+
// Count if statements (including else if)
|
|
158
|
+
const ifMatches = text.match(/\bif\s*\(/g);
|
|
159
|
+
if (ifMatches) {
|
|
160
|
+
complexity += ifMatches.length;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Count loops
|
|
164
|
+
const loopMatches = text.match(/\b(for|while|do)\s*\(/g);
|
|
165
|
+
if (loopMatches) {
|
|
166
|
+
complexity += loopMatches.length;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Count switch cases
|
|
170
|
+
const caseMatches = text.match(/\bcase\s+/g);
|
|
171
|
+
if (caseMatches) {
|
|
172
|
+
complexity += caseMatches.length;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Count ternary operators
|
|
176
|
+
const ternaryMatches = text.match(/\?/g);
|
|
177
|
+
if (ternaryMatches) {
|
|
178
|
+
complexity += ternaryMatches.length;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Count logical operators (&&, ||)
|
|
182
|
+
const logicalMatches = text.match(/(&&|\|\|)/g);
|
|
183
|
+
if (logicalMatches) {
|
|
184
|
+
complexity += logicalMatches.length;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return complexity;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Calculate lines of code for a source file.
|
|
193
|
+
*
|
|
194
|
+
* Excludes comments and whitespace for meaningful LOC count.
|
|
195
|
+
*
|
|
196
|
+
* @param sourceFile - The source file
|
|
197
|
+
* @returns Number of lines of code
|
|
198
|
+
*/
|
|
199
|
+
calculateLOC(sourceFile: SourceFile): number {
|
|
200
|
+
const text = sourceFile.getText();
|
|
201
|
+
const lines = text.split('\n');
|
|
202
|
+
|
|
203
|
+
let loc = 0;
|
|
204
|
+
for (const line of lines) {
|
|
205
|
+
const trimmed = line.trim();
|
|
206
|
+
// Count non-empty, non-comment lines
|
|
207
|
+
if (
|
|
208
|
+
trimmed &&
|
|
209
|
+
!trimmed.startsWith('//') &&
|
|
210
|
+
!trimmed.startsWith('/*') &&
|
|
211
|
+
!trimmed.startsWith('*')
|
|
212
|
+
) {
|
|
213
|
+
loc++;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return loc;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Calculate the number of methods in a class.
|
|
222
|
+
* Includes methods, getters, and setters.
|
|
223
|
+
*
|
|
224
|
+
* @param classDecl - The class declaration
|
|
225
|
+
* @returns Number of methods (including accessors)
|
|
226
|
+
*/
|
|
227
|
+
calculateMethodCount(classDecl: ClassDeclaration): number {
|
|
228
|
+
const methods = classDecl.getMethods().length;
|
|
229
|
+
const getters = classDecl.getGetAccessors().length;
|
|
230
|
+
const setters = classDecl.getSetAccessors().length;
|
|
231
|
+
return methods + getters + setters;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Calculate dependency count for a source file.
|
|
236
|
+
*
|
|
237
|
+
* Counts all import declarations (including duplicates).
|
|
238
|
+
*
|
|
239
|
+
* @param sourceFile - The source file
|
|
240
|
+
* @returns Number of import declarations
|
|
241
|
+
*/
|
|
242
|
+
calculateDependencyCount(sourceFile: SourceFile): number {
|
|
243
|
+
return sourceFile.getImportDeclarations().length;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Calculate Angular-specific metrics for a class.
|
|
248
|
+
*
|
|
249
|
+
* Analyzes Angular decorators and patterns.
|
|
250
|
+
*
|
|
251
|
+
* @param classDecl - The class declaration
|
|
252
|
+
* @returns Angular-specific metrics
|
|
253
|
+
*/
|
|
254
|
+
calculateAngularSpecificMetrics(classDecl: ClassDeclaration): AngularMetrics {
|
|
255
|
+
const metrics = {
|
|
256
|
+
lifecycleHooks: 0,
|
|
257
|
+
inputs: 0,
|
|
258
|
+
outputs: 0,
|
|
259
|
+
decorators: 0,
|
|
260
|
+
templateComplexity: 0,
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const lifecycleHookNames = new Set([
|
|
264
|
+
'ngOnInit',
|
|
265
|
+
'ngOnDestroy',
|
|
266
|
+
'ngOnChanges',
|
|
267
|
+
'ngDoCheck',
|
|
268
|
+
'ngAfterContentInit',
|
|
269
|
+
'ngAfterContentChecked',
|
|
270
|
+
'ngAfterViewInit',
|
|
271
|
+
'ngAfterViewChecked',
|
|
272
|
+
]);
|
|
273
|
+
|
|
274
|
+
const angularTypes = new Set(['Component', 'Directive']);
|
|
275
|
+
|
|
276
|
+
// Count Angular decorators and analyze Component/Directive classes
|
|
277
|
+
classDecl.getDecorators().forEach((decorator) => {
|
|
278
|
+
const name = decorator.getName();
|
|
279
|
+
metrics.decorators++;
|
|
280
|
+
|
|
281
|
+
if (!angularTypes.has(name)) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Count lifecycle hook methods
|
|
286
|
+
metrics.lifecycleHooks += classDecl
|
|
287
|
+
.getMethods()
|
|
288
|
+
.filter((method) => lifecycleHookNames.has(method.getName())).length;
|
|
289
|
+
|
|
290
|
+
// Count @Input decorators on properties and setters
|
|
291
|
+
const countInputOutput = (
|
|
292
|
+
nodes: { getDecorators: () => { getName: () => string }[] }[],
|
|
293
|
+
) => {
|
|
294
|
+
nodes.forEach((node) => {
|
|
295
|
+
node.getDecorators().forEach((dec) => {
|
|
296
|
+
const decName = dec.getName();
|
|
297
|
+
if (decName === 'Input') {
|
|
298
|
+
metrics.inputs++;
|
|
299
|
+
}
|
|
300
|
+
if (decName === 'Output') {
|
|
301
|
+
metrics.outputs++;
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
countInputOutput(classDecl.getProperties());
|
|
308
|
+
countInputOutput(classDecl.getSetAccessors());
|
|
309
|
+
|
|
310
|
+
// Calculate template complexity for components only
|
|
311
|
+
if (name === 'Component') {
|
|
312
|
+
metrics.templateComplexity =
|
|
313
|
+
this.calculateTemplateComplexity(classDecl);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
return metrics;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Calculate complexity metrics for a source file.
|
|
322
|
+
*
|
|
323
|
+
* @param sourceFile - The source file to analyze
|
|
324
|
+
* @returns Complexity metrics
|
|
325
|
+
*
|
|
326
|
+
* Calculates actual metrics using ts-morph AST analysis:
|
|
327
|
+
* - Cyclomatic complexity: count decision points (if, for, while, case, &&, ||, ?)
|
|
328
|
+
* - Lines of code: count non-empty, non-comment lines
|
|
329
|
+
* - Method count: count function/method declarations
|
|
330
|
+
* - Dependency count: analyze constructor parameters and property injections
|
|
331
|
+
*/
|
|
332
|
+
calculate(sourceFile: SourceFile): ComplexityMetrics {
|
|
333
|
+
const cyclomaticComplexity =
|
|
334
|
+
this.calculateCyclomaticComplexitySourceFile(sourceFile);
|
|
335
|
+
const linesOfCode = this.calculateLinesOfCode(sourceFile);
|
|
336
|
+
const methodCount = this.calculateMethodCountSourceFile(sourceFile);
|
|
337
|
+
const dependencyCount = this.calculateDependencyCount(sourceFile);
|
|
338
|
+
|
|
339
|
+
// Angular-specific metrics
|
|
340
|
+
const metadata: Record<string, unknown> = {
|
|
341
|
+
...this.calculateAngularMetrics(sourceFile),
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
cyclomaticComplexity,
|
|
346
|
+
linesOfCode,
|
|
347
|
+
methodCount,
|
|
348
|
+
dependencyCount,
|
|
349
|
+
metadata,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Calculate cyclomatic complexity for a source file.
|
|
355
|
+
* Counts decision points: if, for, while, do-while, case, catch, &&, ||, ?
|
|
356
|
+
*
|
|
357
|
+
* @param sourceFile - The source file
|
|
358
|
+
* @returns Cyclomatic complexity value
|
|
359
|
+
*/
|
|
360
|
+
private calculateCyclomaticComplexitySourceFile(
|
|
361
|
+
sourceFile: SourceFile,
|
|
362
|
+
): number {
|
|
363
|
+
let complexity = 1; // Base complexity
|
|
364
|
+
|
|
365
|
+
// Decision point node types
|
|
366
|
+
const decisionPoints = new Set([
|
|
367
|
+
SyntaxKind.IfStatement,
|
|
368
|
+
SyntaxKind.ForStatement,
|
|
369
|
+
SyntaxKind.ForInStatement,
|
|
370
|
+
SyntaxKind.ForOfStatement,
|
|
371
|
+
SyntaxKind.WhileStatement,
|
|
372
|
+
SyntaxKind.DoStatement,
|
|
373
|
+
SyntaxKind.CaseClause,
|
|
374
|
+
SyntaxKind.CatchClause,
|
|
375
|
+
SyntaxKind.ConditionalExpression, // ternary operator
|
|
376
|
+
]);
|
|
377
|
+
|
|
378
|
+
const logicalOperators = new Set([
|
|
379
|
+
SyntaxKind.AmpersandAmpersandToken,
|
|
380
|
+
SyntaxKind.BarBarToken,
|
|
381
|
+
]);
|
|
382
|
+
|
|
383
|
+
// Count decision points and logical operators
|
|
384
|
+
sourceFile.forEachDescendant((node) => {
|
|
385
|
+
const kind = node.getKind();
|
|
386
|
+
|
|
387
|
+
if (decisionPoints.has(kind)) {
|
|
388
|
+
complexity++;
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Count logical operators (&&, ||) in binary expressions
|
|
393
|
+
if (kind === SyntaxKind.BinaryExpression) {
|
|
394
|
+
const operator = node
|
|
395
|
+
.asKind(SyntaxKind.BinaryExpression)
|
|
396
|
+
?.getOperatorToken()
|
|
397
|
+
.getKind();
|
|
398
|
+
if (operator && logicalOperators.has(operator)) {
|
|
399
|
+
complexity++;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
return complexity;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Calculate lines of code (excluding comments and whitespace).
|
|
409
|
+
*
|
|
410
|
+
* @param sourceFile - The source file
|
|
411
|
+
* @returns Lines of code count
|
|
412
|
+
*/
|
|
413
|
+
private calculateLinesOfCode(sourceFile: SourceFile): number {
|
|
414
|
+
const text = sourceFile.getFullText();
|
|
415
|
+
const lines = text.split('\n');
|
|
416
|
+
|
|
417
|
+
let inBlockComment = false;
|
|
418
|
+
|
|
419
|
+
return lines.reduce((loc, line) => {
|
|
420
|
+
const trimmed = line.trim();
|
|
421
|
+
|
|
422
|
+
// Track block comment state
|
|
423
|
+
if (trimmed.startsWith('/*')) {
|
|
424
|
+
inBlockComment = true;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Skip lines inside block comments
|
|
428
|
+
if (inBlockComment) {
|
|
429
|
+
if (trimmed.endsWith('*/')) {
|
|
430
|
+
inBlockComment = false;
|
|
431
|
+
}
|
|
432
|
+
return loc;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Skip empty lines and single-line comments, count everything else
|
|
436
|
+
return trimmed.length === 0 || trimmed.startsWith('//') ? loc : loc + 1;
|
|
437
|
+
}, 0);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Calculate method/function count.
|
|
442
|
+
*
|
|
443
|
+
* @param sourceFile - The source file
|
|
444
|
+
* @returns Method count
|
|
445
|
+
*/
|
|
446
|
+
private calculateMethodCountSourceFile(sourceFile: SourceFile): number {
|
|
447
|
+
let count = 0;
|
|
448
|
+
|
|
449
|
+
sourceFile.forEachDescendant((node) => {
|
|
450
|
+
const kind = node.getKind();
|
|
451
|
+
if (
|
|
452
|
+
kind === SyntaxKind.MethodDeclaration ||
|
|
453
|
+
kind === SyntaxKind.FunctionDeclaration ||
|
|
454
|
+
kind === SyntaxKind.FunctionExpression ||
|
|
455
|
+
kind === SyntaxKind.ArrowFunction ||
|
|
456
|
+
kind === SyntaxKind.GetAccessor ||
|
|
457
|
+
kind === SyntaxKind.SetAccessor
|
|
458
|
+
) {
|
|
459
|
+
count++;
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
return count;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Calculate Angular-specific metrics.
|
|
468
|
+
*
|
|
469
|
+
* @param sourceFile - The source file
|
|
470
|
+
* @returns Angular-specific metrics
|
|
471
|
+
*/
|
|
472
|
+
private calculateAngularMetrics(
|
|
473
|
+
sourceFile: SourceFile,
|
|
474
|
+
): Record<string, unknown> {
|
|
475
|
+
const metrics: Record<string, unknown> = {
|
|
476
|
+
lifecycleHookCount: 0,
|
|
477
|
+
inputCount: 0,
|
|
478
|
+
outputCount: 0,
|
|
479
|
+
templateComplexity: 0,
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
const classes = sourceFile.getClasses();
|
|
483
|
+
|
|
484
|
+
for (const classDecl of classes) {
|
|
485
|
+
// Count lifecycle hooks
|
|
486
|
+
metrics['lifecycleHookCount'] =
|
|
487
|
+
(metrics['lifecycleHookCount'] as number) +
|
|
488
|
+
this.countLifecycleHooks(classDecl);
|
|
489
|
+
|
|
490
|
+
// Count @Input and @Output decorators
|
|
491
|
+
const { inputCount, outputCount } = this.countInputsAndOutputs(classDecl);
|
|
492
|
+
metrics['inputCount'] = (metrics['inputCount'] as number) + inputCount;
|
|
493
|
+
metrics['outputCount'] = (metrics['outputCount'] as number) + outputCount;
|
|
494
|
+
|
|
495
|
+
// Calculate template complexity (if component)
|
|
496
|
+
const templateComplexity = this.calculateTemplateComplexity(classDecl);
|
|
497
|
+
metrics['templateComplexity'] =
|
|
498
|
+
(metrics['templateComplexity'] as number) + templateComplexity;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return metrics;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Count lifecycle hook methods in a class.
|
|
506
|
+
*
|
|
507
|
+
* @param classDecl - The class declaration
|
|
508
|
+
* @returns Count of lifecycle hooks
|
|
509
|
+
*/
|
|
510
|
+
private countLifecycleHooks(classDecl: ClassDeclaration): number {
|
|
511
|
+
const lifecycleHooks = [
|
|
512
|
+
'ngOnInit',
|
|
513
|
+
'ngOnDestroy',
|
|
514
|
+
'ngOnChanges',
|
|
515
|
+
'ngDoCheck',
|
|
516
|
+
'ngAfterContentInit',
|
|
517
|
+
'ngAfterContentChecked',
|
|
518
|
+
'ngAfterViewInit',
|
|
519
|
+
'ngAfterViewChecked',
|
|
520
|
+
];
|
|
521
|
+
|
|
522
|
+
let count = 0;
|
|
523
|
+
const methods = classDecl.getMethods();
|
|
524
|
+
|
|
525
|
+
for (const method of methods) {
|
|
526
|
+
if (lifecycleHooks.includes(method.getName())) {
|
|
527
|
+
count++;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return count;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Count @Input and @Output decorators in a class.
|
|
536
|
+
*
|
|
537
|
+
* @param classDecl - The class declaration
|
|
538
|
+
* @returns Input and output counts
|
|
539
|
+
*/
|
|
540
|
+
private countInputsAndOutputs(classDecl: ClassDeclaration): {
|
|
541
|
+
inputCount: number;
|
|
542
|
+
outputCount: number;
|
|
543
|
+
} {
|
|
544
|
+
let inputCount = 0;
|
|
545
|
+
let outputCount = 0;
|
|
546
|
+
|
|
547
|
+
// Helper to count decorators by name
|
|
548
|
+
const countDecorators = (
|
|
549
|
+
nodes: { getDecorators: () => { getName: () => string }[] }[],
|
|
550
|
+
decoratorName: string,
|
|
551
|
+
): number => {
|
|
552
|
+
return nodes.reduce((count, node) => {
|
|
553
|
+
return (
|
|
554
|
+
count +
|
|
555
|
+
node.getDecorators().filter((d) => d.getName() === decoratorName)
|
|
556
|
+
.length
|
|
557
|
+
);
|
|
558
|
+
}, 0);
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
// Count @Input on properties and setters
|
|
562
|
+
inputCount =
|
|
563
|
+
countDecorators(classDecl.getProperties(), 'Input') +
|
|
564
|
+
countDecorators(classDecl.getSetAccessors(), 'Input');
|
|
565
|
+
|
|
566
|
+
// Count @Output on properties
|
|
567
|
+
outputCount = countDecorators(classDecl.getProperties(), 'Output');
|
|
568
|
+
|
|
569
|
+
return { inputCount, outputCount };
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Calculate template complexity for a component.
|
|
574
|
+
* Looks for inline templates in @Component decorator.
|
|
575
|
+
*
|
|
576
|
+
* @param classDecl - The class declaration
|
|
577
|
+
* @returns Template complexity score
|
|
578
|
+
*/
|
|
579
|
+
private calculateTemplateComplexity(classDecl: ClassDeclaration): number {
|
|
580
|
+
const componentDecorator = classDecl
|
|
581
|
+
.getDecorators()
|
|
582
|
+
.find((d) => d.getName() === 'Component');
|
|
583
|
+
|
|
584
|
+
if (!componentDecorator) {
|
|
585
|
+
return 0;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const decoratorText = componentDecorator.getText();
|
|
589
|
+
|
|
590
|
+
// Extract inline template
|
|
591
|
+
const templateMatch = /template\s*:\s*`([^`]*)`/s.exec(decoratorText);
|
|
592
|
+
if (!templateMatch?.[1]) {
|
|
593
|
+
return 0;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const template = templateMatch[1];
|
|
597
|
+
|
|
598
|
+
// Count complexity indicators in template
|
|
599
|
+
let complexity = 0;
|
|
600
|
+
|
|
601
|
+
// Structural directives
|
|
602
|
+
complexity += (template.match(/\*ngIf/g) ?? []).length;
|
|
603
|
+
complexity += (template.match(/\*ngFor/g) ?? []).length;
|
|
604
|
+
complexity += (template.match(/\*ngSwitch/g) ?? []).length;
|
|
605
|
+
|
|
606
|
+
// Event bindings
|
|
607
|
+
complexity += (template.match(/\([a-zA-Z]+\)/g) ?? []).length;
|
|
608
|
+
|
|
609
|
+
// Property bindings
|
|
610
|
+
complexity += (template.match(/\[[a-zA-Z]+\]/g) ?? []).length;
|
|
611
|
+
|
|
612
|
+
// Pipes (complexity factor)
|
|
613
|
+
complexity += (template.match(/\|/g) ?? []).length;
|
|
614
|
+
|
|
615
|
+
return complexity;
|
|
616
|
+
}
|
|
617
|
+
}
|