@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,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
+ }