@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,614 @@
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
+ /**
7
+ * @angular-modernizer/api - Template Usage Analyzer
8
+ *
9
+ * Intelligent template analysis for import filtering.
10
+ * Three-tier strategy: Fast path → Angular Compiler → Conservative fallback
11
+ *
12
+ * Philosophy: "Only Import What You Use"
13
+ * - Tier 1 (80%): String analysis for simple templates (~2ms)
14
+ * - Tier 2 (15%): Angular Compiler for complex templates (~15ms)
15
+ * - Tier 3 (5%): Conservative fallback (include all imports)
16
+ *
17
+ * Average overhead: ~15% (vs 30% single-tier approach)
18
+ */
19
+
20
+ import { parseTemplate, InterpolationConfig } from '@angular/compiler';
21
+ import { LRUCache } from 'lru-cache';
22
+ import type { ClassDeclaration, Project } from 'ts-morph';
23
+ import { SelectorMapper } from './selector-mapper.js';
24
+ import { ExternalTemplateLoader } from './external-template-loader.js';
25
+
26
+ /**
27
+ * Parsed template result from Angular Compiler.
28
+ * Using 'any' type as @angular/compiler types are complex and internal.
29
+ */
30
+ type ParsedTemplate = any;
31
+
32
+ /**
33
+ * Standard HTML5 elements that should be ignored.
34
+ * These are not Angular components/directives.
35
+ */
36
+ const STANDARD_HTML_ELEMENTS = new Set([
37
+ 'div',
38
+ 'span',
39
+ 'p',
40
+ 'a',
41
+ 'button',
42
+ 'input',
43
+ 'select',
44
+ 'textarea',
45
+ 'label',
46
+ 'form',
47
+ 'table',
48
+ 'tr',
49
+ 'td',
50
+ 'th',
51
+ 'thead',
52
+ 'tbody',
53
+ 'ul',
54
+ 'ol',
55
+ 'li',
56
+ 'h1',
57
+ 'h2',
58
+ 'h3',
59
+ 'h4',
60
+ 'h5',
61
+ 'h6',
62
+ 'img',
63
+ 'video',
64
+ 'audio',
65
+ 'canvas',
66
+ 'svg',
67
+ 'path',
68
+ 'section',
69
+ 'article',
70
+ 'header',
71
+ 'footer',
72
+ 'nav',
73
+ 'main',
74
+ 'aside',
75
+ 'figure',
76
+ 'figcaption',
77
+ ]);
78
+
79
+ /**
80
+ * CommonModule pipes that are always available.
81
+ */
82
+ const COMMON_MODULE_PIPES = new Set([
83
+ 'AsyncPipe',
84
+ 'CurrencyPipe',
85
+ 'DatePipe',
86
+ 'DecimalPipe',
87
+ 'I18nPluralPipe',
88
+ 'I18nSelectPipe',
89
+ 'JsonPipe',
90
+ 'KeyValuePipe',
91
+ 'LowerCasePipe',
92
+ 'PercentPipe',
93
+ 'SlicePipe',
94
+ 'TitleCasePipe',
95
+ 'UpperCasePipe',
96
+ ]);
97
+
98
+ /**
99
+ * Template Usage Analyzer with Three-Tier Strategy.
100
+ *
101
+ * Analyzes Angular templates to determine which imports are actually used.
102
+ * Uses a progressive enhancement approach for optimal performance.
103
+ */
104
+ export class TemplateUsageAnalyzer {
105
+ private readonly templateParseCache = new LRUCache<string, ParsedTemplate>({
106
+ max: 500, // Cache 500 templates
107
+ ttl: 1000 * 60 * 5, // 5 minutes
108
+ });
109
+
110
+ private readonly selectorMapper = new SelectorMapper();
111
+ private readonly templateLoader = new ExternalTemplateLoader();
112
+
113
+ /**
114
+ * Analyze template and return ONLY what's actually used.
115
+ *
116
+ * Uses three-tier strategy for optimal performance:
117
+ * - Tier 1: Fast path for simple templates (80% of cases, ~2ms)
118
+ * - Tier 2: Angular Compiler for complex templates (15% of cases, ~15ms)
119
+ * - Tier 3: Conservative fallback (5% of cases, include all imports)
120
+ *
121
+ * @param component - Component class declaration
122
+ * @param templateContent - Template HTML content
123
+ * @param availableImports - All imports from parent NgModule
124
+ * @param project - ts-morph Project (for selector mapping)
125
+ * @returns Array of actually-used import names
126
+ */
127
+ analyzeTemplateUsage(
128
+ component: ClassDeclaration,
129
+ templateContent: string,
130
+ availableImports: string[],
131
+ project: Project,
132
+ ): string[] {
133
+ // TIER 1: Fast path for simple templates (80% of cases)
134
+ if (this.isSimpleTemplate(templateContent)) {
135
+ console.info(` Using fast path analysis (simple template)`);
136
+ return this.fastPathAnalysis(templateContent, availableImports, project);
137
+ }
138
+
139
+ // TIER 2: Angular compiler for complex templates (15% of cases)
140
+ try {
141
+ console.info(` Using Angular compiler analysis (complex template)`);
142
+
143
+ // Check cache first
144
+ const cacheKey = this.getCacheKey(component, templateContent);
145
+ const cached = this.templateParseCache.get(cacheKey);
146
+ if (cached) {
147
+ console.info(` Cache hit for ${component.getName()}`);
148
+ return this.extractImportsFromParsed(cached, availableImports, project);
149
+ }
150
+
151
+ // Parse with Angular compiler
152
+ const parsed = parseTemplate(templateContent, 'component.html', {
153
+ preserveWhitespaces: false,
154
+ interpolationConfig: InterpolationConfig.fromArray(['{{', '}}']),
155
+ });
156
+
157
+ // Cache result
158
+ this.templateParseCache.set(cacheKey, parsed);
159
+
160
+ // Extract imports from AST
161
+ return this.extractImportsFromParsed(parsed, availableImports, project);
162
+ } catch (error: unknown) {
163
+ // TIER 3: Conservative fallback (5% of cases)
164
+ const errorMessage =
165
+ error instanceof Error ? error.message : String(error);
166
+ console.warn(
167
+ ` Template parsing failed for ${component.getName()}, using conservative strategy`,
168
+ );
169
+ console.warn(` Error: ${errorMessage}`);
170
+ return this.conservativeFallback(availableImports);
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Analyze external template (from templateUrl) and return ONLY what's used.
176
+ *
177
+ * Loads the external template file and analyzes it using the three-tier strategy.
178
+ *
179
+ * @param component - Component class declaration
180
+ * @param templateUrl - Template URL from `` metadata (e.g., './user.component.html')
181
+ * @param componentPath - Absolute path to the component file
182
+ * @param availableImports - All imports from parent NgModule
183
+ * @param project - ts-morph Project (for selector mapping)
184
+ * @returns Array of actually-used import names
185
+ */
186
+ async analyzeExternalTemplate(
187
+ component: ClassDeclaration,
188
+ templateUrl: string,
189
+ componentPath: string,
190
+ availableImports: string[],
191
+ project: Project,
192
+ ): Promise<string[]> {
193
+ console.info(` Loading external template: ${templateUrl}`);
194
+
195
+ const componentFilePath =
196
+ component.getSourceFile().getFilePath() || componentPath;
197
+
198
+ // Load external template
199
+ const templateContent = await this.templateLoader.loadExternalTemplate(
200
+ templateUrl,
201
+ componentFilePath,
202
+ );
203
+
204
+ // If loading failed, use conservative fallback
205
+ if (!templateContent) {
206
+ console.warn(
207
+ ` External template empty or failed to load, using conservative strategy`,
208
+ );
209
+ return this.conservativeFallback(availableImports);
210
+ }
211
+
212
+ // Analyze the loaded template
213
+ return this.analyzeTemplateUsage(
214
+ component,
215
+ templateContent,
216
+ availableImports,
217
+ project,
218
+ );
219
+ }
220
+
221
+ /**
222
+ * Determine if template is simple (can use fast path).
223
+ *
224
+ * Simple templates:
225
+ * - No interpolation: {{ }}
226
+ * - No structural directives: *ngIf, *ngFor, *ngSwitch
227
+ * - No property/event bindings: [], ()
228
+ * - Short content (< 500 chars)
229
+ */
230
+ private isSimpleTemplate(content: string): boolean {
231
+ return (
232
+ !content.includes('{{') &&
233
+ !content.includes('*ng') &&
234
+ !content.includes('[') &&
235
+ !content.includes('(') &&
236
+ content.length < 500
237
+ );
238
+ }
239
+
240
+ /**
241
+ * Fast path: String-based analysis for simple templates.
242
+ *
243
+ * Uses regex to extract element selectors only.
244
+ * ~2ms per template (very fast).
245
+ */
246
+ private fastPathAnalysis(
247
+ templateContent: string,
248
+ availableImports: string[],
249
+ project: Project,
250
+ ): string[] {
251
+ const requiredImports = new Set<string>();
252
+
253
+ // Always include CommonModule for basic directives
254
+ requiredImports.add('CommonModule');
255
+
256
+ // Extract element selectors: <app-user></app-user>
257
+ const elementRegex = /<([a-z][a-z0-9-]*)/gi;
258
+ const matches = templateContent.matchAll(elementRegex);
259
+
260
+ for (const match of matches) {
261
+ const selector = match[1];
262
+
263
+ // Skip if selector is undefined or empty
264
+ if (!selector) {
265
+ continue;
266
+ }
267
+
268
+ // Skip standard HTML elements
269
+ if (this.isStandardHtmlElement(selector)) {
270
+ continue;
271
+ }
272
+
273
+ // Map selector → class name via Phase 3 selector map
274
+ const className = this.mapSelectorToClassName(
275
+ selector,
276
+ availableImports,
277
+ project,
278
+ );
279
+ if (className) {
280
+ requiredImports.add(className);
281
+ }
282
+ }
283
+
284
+ return Array.from(requiredImports);
285
+ }
286
+
287
+ /**
288
+ * Angular compiler-based analysis for complex templates.
289
+ *
290
+ * Parses template AST and extracts:
291
+ * - Element nodes (components)
292
+ * - Pipe nodes (pipes)
293
+ * - Attribute directives
294
+ * - Structural directives
295
+ *
296
+ * ~15ms per template (slower but accurate).
297
+ */
298
+ private extractImportsFromParsed(
299
+ parsed: ParsedTemplate,
300
+ availableImports: string[],
301
+ project: Project,
302
+ ): string[] {
303
+ const requiredImports = new Set<string>();
304
+
305
+ // Always include CommonModule
306
+ requiredImports.add('CommonModule');
307
+
308
+ // Extract from AST nodes
309
+ const elements = this.extractElementsFromAST(parsed.nodes);
310
+ const pipes = this.extractPipesFromAST(parsed.nodes);
311
+ const directives = this.extractDirectivesFromAST(parsed.nodes);
312
+
313
+ // Map selectors to class names
314
+ for (const selector of elements) {
315
+ const className = this.mapSelectorToClassName(
316
+ selector,
317
+ availableImports,
318
+ project,
319
+ );
320
+ if (className) {
321
+ requiredImports.add(className);
322
+ }
323
+ }
324
+
325
+ // Map pipes to class names
326
+ for (const pipeName of pipes) {
327
+ const className = this.mapPipeToClassName(pipeName, availableImports);
328
+ if (className) {
329
+ requiredImports.add(className);
330
+ }
331
+ }
332
+
333
+ // Map directives to class names
334
+ for (const directiveName of directives) {
335
+ const className = this.mapDirectiveToClassName(
336
+ directiveName,
337
+ availableImports,
338
+ );
339
+ if (className) {
340
+ requiredImports.add(className);
341
+ }
342
+ }
343
+
344
+ return Array.from(requiredImports);
345
+ }
346
+
347
+ /**
348
+ * ⭐ CRITICAL: Selector-to-Class mapping using Phase 3 selector map.
349
+ *
350
+ * Reuses Phase 3's cached selector map for O(1) lookup.
351
+ *
352
+ * Example: 'app-user-profile' → 'UserProfileComponent'
353
+ */
354
+ private mapSelectorToClassName(
355
+ selector: string,
356
+ availableImports: string[],
357
+ project: Project,
358
+ ): string | undefined {
359
+ // Reuse Phase 3's selector map caching (O(1) lookup)
360
+ const className = this.selectorMapper.findComponentBySelector(
361
+ project,
362
+ selector,
363
+ );
364
+
365
+ if (!className) {
366
+ console.warn(` ⚠️ Unknown selector: ${selector}`);
367
+ return undefined;
368
+ }
369
+
370
+ // Verify it's in availableImports
371
+ if (!availableImports.includes(className)) {
372
+ console.info(
373
+ ` Selector ${selector} → ${className} not in available imports (external component)`,
374
+ );
375
+ return undefined;
376
+ }
377
+
378
+ return className;
379
+ }
380
+
381
+ /**
382
+ * Map pipe name to class name.
383
+ *
384
+ * Examples:
385
+ * - 'ngxMask' → 'NgxMaskPipe'
386
+ * - 'currency' → 'CurrencyPipe' (CommonModule)
387
+ * - 'uppercase' → 'UpperCasePipe' (CommonModule)
388
+ */
389
+ private mapPipeToClassName(
390
+ pipeName: string,
391
+ availableImports: string[],
392
+ ): string | undefined {
393
+ // Try exact match first
394
+ if (availableImports.includes(pipeName)) {
395
+ return pipeName;
396
+ }
397
+
398
+ // Try with 'Pipe' suffix
399
+ const withSuffix = `${pipeName.charAt(0).toUpperCase()}${pipeName.slice(1)}Pipe`;
400
+ if (availableImports.includes(withSuffix)) {
401
+ return withSuffix;
402
+ }
403
+
404
+ // CommonModule pipes (always available)
405
+ if (COMMON_MODULE_PIPES.has(withSuffix)) {
406
+ return 'CommonModule'; // Pipe comes from CommonModule
407
+ }
408
+
409
+ console.warn(` Unknown pipe: ${pipeName}`);
410
+ return undefined;
411
+ }
412
+
413
+ /**
414
+ * Map directive name to class name.
415
+ *
416
+ * Examples:
417
+ * - 'ngModel' → 'NgModel'
418
+ * - 'ngIf' → 'NgIf' (CommonModule)
419
+ * - 'myDirective' → 'MyDirectiveDirective'
420
+ */
421
+ private mapDirectiveToClassName(
422
+ directiveName: string,
423
+ availableImports: string[],
424
+ ): string | undefined {
425
+ // Try exact match first
426
+ if (availableImports.includes(directiveName)) {
427
+ return directiveName;
428
+ }
429
+
430
+ // Try with 'Directive' suffix
431
+ const withSuffix = `${directiveName.charAt(0).toUpperCase()}${directiveName.slice(1)}Directive`;
432
+ if (availableImports.includes(withSuffix)) {
433
+ return withSuffix;
434
+ }
435
+
436
+ // CommonModule directives (structural)
437
+ const commonDirectives = [
438
+ 'NgIf',
439
+ 'NgFor',
440
+ 'NgSwitch',
441
+ 'NgSwitchCase',
442
+ 'NgSwitchDefault',
443
+ ];
444
+ if (commonDirectives.includes(withSuffix.replace('Directive', ''))) {
445
+ return 'CommonModule';
446
+ }
447
+
448
+ console.warn(` Unknown directive: ${directiveName}`);
449
+ return undefined;
450
+ }
451
+
452
+ /**
453
+ * Conservative fallback: Include all imports (safe).
454
+ *
455
+ * Used when:
456
+ * - Template parsing fails
457
+ * - Template is invalid
458
+ * - Compilation errors
459
+ *
460
+ * Better to include too much than break the application.
461
+ */
462
+ private conservativeFallback(availableImports: string[]): string[] {
463
+ console.info(
464
+ ` Conservative fallback: including all ${availableImports.length} imports`,
465
+ );
466
+ return availableImports;
467
+ }
468
+
469
+ /**
470
+ * Check if element is a standard HTML element.
471
+ */
472
+ private isStandardHtmlElement(tagName: string): boolean {
473
+ return STANDARD_HTML_ELEMENTS.has(tagName.toLowerCase());
474
+ }
475
+
476
+ /**
477
+ * Generate cache key for template parsing.
478
+ */
479
+ private getCacheKey(
480
+ component: ClassDeclaration,
481
+ templateContent: string,
482
+ ): string {
483
+ const componentName = component.getName() ?? 'unknown';
484
+ const contentHash = this.simpleHash(templateContent);
485
+ return `${componentName}-${contentHash}`;
486
+ }
487
+
488
+ /**
489
+ * Simple hash function for cache keys.
490
+ */
491
+ private simpleHash(str: string): string {
492
+ let hash = 0;
493
+ for (let i = 0; i < str.length; i++) {
494
+ const char = str.codePointAt(i)!;
495
+ hash = (hash << 5) - hash + char;
496
+ hash = hash & hash; // Convert to 32-bit integer
497
+ }
498
+ return hash.toString(36);
499
+ }
500
+
501
+ /**
502
+ * Extract element selectors from Angular Compiler AST.
503
+ */
504
+ private extractElementsFromAST(nodes: unknown[]): string[] {
505
+ const elements: string[] = [];
506
+
507
+ const traverse = (node: any): void => {
508
+ if (!node) {
509
+ return;
510
+ }
511
+
512
+ // Element node
513
+ if (node.name && typeof node.name === 'string') {
514
+ if (!this.isStandardHtmlElement(node.name)) {
515
+ elements.push(node.name);
516
+ }
517
+ }
518
+
519
+ // Traverse children
520
+ if (Array.isArray(node.children)) {
521
+ node.children.forEach(traverse);
522
+ }
523
+ };
524
+
525
+ if (Array.isArray(nodes)) {
526
+ nodes.forEach(traverse);
527
+ }
528
+
529
+ return elements;
530
+ }
531
+
532
+ /**
533
+ * Extract pipe names from Angular Compiler AST.
534
+ */
535
+ private extractPipesFromAST(nodes: unknown[]): string[] {
536
+ const pipes: string[] = [];
537
+
538
+ const traverse = (node: any): void => {
539
+ if (!node) {
540
+ return;
541
+ }
542
+
543
+ // Pipe expression node
544
+ if (node.name && node.exp && typeof node.name === 'string') {
545
+ pipes.push(node.name);
546
+ }
547
+
548
+ // Traverse children and expressions
549
+ if (Array.isArray(node.children)) {
550
+ node.children.forEach(traverse);
551
+ }
552
+ if (node.exp) {
553
+ traverse(node.exp);
554
+ }
555
+ if (node.args) {
556
+ node.args.forEach(traverse);
557
+ }
558
+ };
559
+
560
+ if (Array.isArray(nodes)) {
561
+ nodes.forEach(traverse);
562
+ }
563
+
564
+ return pipes;
565
+ }
566
+
567
+ /**
568
+ * Extract directive names from Angular Compiler AST.
569
+ */
570
+ private extractDirectivesFromAST(nodes: unknown[]): string[] {
571
+ const directives: string[] = [];
572
+
573
+ const traverse = (node: any): void => {
574
+ if (!node) {
575
+ return;
576
+ }
577
+
578
+ // Structural directive (*ngIf, *ngFor, etc.)
579
+ if (node.templateAttrs && Array.isArray(node.templateAttrs)) {
580
+ node.templateAttrs.forEach((attr: any) => {
581
+ if (attr.name && typeof attr.name === 'string') {
582
+ directives.push(attr.name);
583
+ }
584
+ });
585
+ }
586
+
587
+ // Attribute directive
588
+ if (node.attributes && Array.isArray(node.attributes)) {
589
+ node.attributes.forEach((attr: any) => {
590
+ if (attr.name && typeof attr.name === 'string') {
591
+ // Skip standard HTML attributes
592
+ if (
593
+ !this.isStandardHtmlElement(attr.name) &&
594
+ attr.name.includes('[')
595
+ ) {
596
+ directives.push(attr.name.replaceAll(/[[\]]/g, ''));
597
+ }
598
+ }
599
+ });
600
+ }
601
+
602
+ // Traverse children
603
+ if (Array.isArray(node.children)) {
604
+ node.children.forEach(traverse);
605
+ }
606
+ };
607
+
608
+ if (Array.isArray(nodes)) {
609
+ nodes.forEach(traverse);
610
+ }
611
+
612
+ return directives;
613
+ }
614
+ }