@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,79 @@
1
+ /**
2
+ * @angular-modernizer/api - External Template Loader
3
+ *
4
+ * Loads external Angular templates from templateUrl.
5
+ *
6
+ * Philosophy: "Support Both Inline and External"
7
+ * - Handles templateUrl: './component.html'
8
+ * - Resolves relative paths correctly
9
+ * - Graceful fallback on errors
10
+ */
11
+
12
+ import * as path from 'node:path';
13
+ import * as fs from 'node:fs/promises';
14
+
15
+ /**
16
+ * External Template Loader - Loads templates from templateUrl.
17
+ *
18
+ * Supports both relative and absolute template paths.
19
+ */
20
+ export class ExternalTemplateLoader {
21
+ /**
22
+ * Load external template file.
23
+ *
24
+ * Handles templateUrl: './user.component.html'
25
+ *
26
+ * @param templateUrl - Template URL from `` metadata
27
+ * @param componentPath - Absolute path to the component file
28
+ * @returns Template content or empty string on error
29
+ */
30
+ async loadExternalTemplate(
31
+ templateUrl: string,
32
+ componentPath: string,
33
+ ): Promise<string> {
34
+ // Resolve template path relative to component
35
+ const templatePath = path.resolve(path.dirname(componentPath), templateUrl);
36
+
37
+ try {
38
+ return await fs.readFile(templatePath, 'utf-8');
39
+ } catch (error: unknown) {
40
+ const errorMessage =
41
+ error instanceof Error ? error.message : String(error);
42
+ console.warn(` Failed to load template: ${templatePath}`);
43
+ console.warn(` Error: ${errorMessage}`);
44
+ return ''; // Conservative: no template = include all imports
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Check if a template path exists.
50
+ *
51
+ * @param templateUrl - Template URL from `` metadata
52
+ * @param componentPath - Absolute path to the component file
53
+ * @returns True if template file exists
54
+ */
55
+ async templateExists(
56
+ templateUrl: string,
57
+ componentPath: string,
58
+ ): Promise<boolean> {
59
+ const templatePath = path.resolve(path.dirname(componentPath), templateUrl);
60
+
61
+ try {
62
+ await fs.access(templatePath);
63
+ return true;
64
+ } catch {
65
+ return false;
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Resolve template path from component path and templateUrl.
71
+ *
72
+ * @param templateUrl - Template URL from `` metadata
73
+ * @param componentPath - Absolute path to the component file
74
+ * @returns Absolute path to template file
75
+ */
76
+ resolveTemplatePath(templateUrl: string, componentPath: string): string {
77
+ return path.resolve(path.dirname(componentPath), templateUrl);
78
+ }
79
+ }
@@ -0,0 +1,186 @@
1
+ /**
2
+ * @angular-modernizer/api - Selector Mapper
3
+ *
4
+ * Reusable selector-to-className mapping utility.
5
+ * Extracted from Phase 3's selector map caching for reuse across components.
6
+ *
7
+ * Philosophy: "Build Once, Use Everywhere"
8
+ * - O(n) initial build, O(1) lookups
9
+ * - Lazy initialization (only builds when first needed)
10
+ * - Universal coverage (standard `` + custom decorators)
11
+ */
12
+
13
+ import type {
14
+ ClassDeclaration,
15
+ Decorator,
16
+ Project,
17
+ ObjectLiteralExpression,
18
+ } from 'ts-morph';
19
+ import { SyntaxKind } from 'ts-morph';
20
+
21
+ /**
22
+ * Selector Mapper - Maps component selectors to class names.
23
+ *
24
+ * Provides O(1) selector lookups using a cached map built from project files.
25
+ * Supports both standard `` decorators and custom decorators (e.g., ``).
26
+ */
27
+ export class SelectorMapper {
28
+ private selectorMap: Map<string, string> | undefined;
29
+
30
+ /**
31
+ * Find a component class name by its selector.
32
+ *
33
+ * Builds the selector map lazily on first call, then reuses for O(1) lookups.
34
+ *
35
+ * @param project - ts-morph Project
36
+ * @param selector - Component selector (e.g., 'app-user-profile')
37
+ * @returns Class name (e.g., 'UserProfileComponent') or undefined if not found
38
+ */
39
+ findComponentBySelector(
40
+ project: Project,
41
+ selector: string,
42
+ ): string | undefined {
43
+ // Build selector map lazily on first use
44
+ this.selectorMap ??= this.buildSelectorMap(project);
45
+
46
+ // O(1) lookup in cached map
47
+ return this.selectorMap.get(selector);
48
+ }
49
+
50
+ /**
51
+ * Get the complete selector map.
52
+ *
53
+ * Builds the map lazily if not yet built.
54
+ *
55
+ * @param project - ts-morph Project
56
+ * @returns Map of selector → className
57
+ */
58
+ getSelectorMap(project: Project): Map<string, string> {
59
+ this.selectorMap ??= this.buildSelectorMap(project);
60
+ return this.selectorMap;
61
+ }
62
+
63
+ /**
64
+ * Clear the cached selector map.
65
+ *
66
+ * Useful for testing or when project files have changed significantly.
67
+ */
68
+ clearCache(): void {
69
+ this.selectorMap = undefined;
70
+ }
71
+
72
+ /**
73
+ * Build a comprehensive selector map for the entire project.
74
+ *
75
+ * Scans all source files once to build selector → className mapping.
76
+ * Performance: O(n) initial build, O(1) lookup per selector.
77
+ *
78
+ * @param project - ts-morph Project
79
+ * @returns Map of selector → className
80
+ * @private
81
+ */
82
+ private buildSelectorMap(project: Project): Map<string, string> {
83
+ const selectorMap = new Map<string, string>();
84
+ const sourceFiles = project.getSourceFiles();
85
+
86
+ for (const sourceFile of sourceFiles) {
87
+ // Skip node_modules and test files for performance
88
+ const filePath = sourceFile.getFilePath();
89
+ if (
90
+ filePath.includes('node_modules') ||
91
+ filePath.includes('.spec.ts') ||
92
+ filePath.includes('.d.ts')
93
+ ) {
94
+ continue;
95
+ }
96
+
97
+ const classes = sourceFile.getClasses();
98
+
99
+ for (const classDecl of classes) {
100
+ const className = classDecl.getName();
101
+ if (!className) {
102
+ continue;
103
+ }
104
+
105
+ // Check if this class has a @Component decorator
106
+ // (regardless of other decorators like @ExportComponent)
107
+ const componentDecorator = this.findDecorator(classDecl, 'Component');
108
+
109
+ if (componentDecorator) {
110
+ // Extract selector from @Component metadata
111
+ const selector =
112
+ this.extractSelectorFromDecorator(componentDecorator);
113
+
114
+ if (selector) {
115
+ // Add to map: selector → className
116
+ selectorMap.set(selector, className);
117
+ }
118
+ }
119
+ }
120
+ }
121
+
122
+ return selectorMap;
123
+ }
124
+
125
+ /**
126
+ * Extract the selector from a `` decorator.
127
+ *
128
+ * @param decorator - The `` decorator
129
+ * @returns The selector string or undefined
130
+ * @private
131
+ */
132
+ private extractSelectorFromDecorator(
133
+ decorator: Decorator,
134
+ ): string | undefined {
135
+ const args = decorator.getArguments();
136
+ if (args.length === 0) {
137
+ return undefined;
138
+ }
139
+
140
+ const metadataObj = args[0];
141
+ if (
142
+ !metadataObj ||
143
+ metadataObj.getKind() !== SyntaxKind.ObjectLiteralExpression
144
+ ) {
145
+ return undefined;
146
+ }
147
+
148
+ const objLiteral = metadataObj as ObjectLiteralExpression;
149
+ const selectorProperty = objLiteral.getProperty('selector');
150
+
151
+ if (selectorProperty) {
152
+ const initializer = selectorProperty.getChildAtIndex(2); // Skip name and colon
153
+ const text = initializer.getText();
154
+ // Remove quotes if present
155
+ return text.replaceAll(/^['"`]|['"`]$/g, '');
156
+ }
157
+
158
+ return undefined;
159
+ }
160
+
161
+ /**
162
+ * Find a decorator by name on a class declaration.
163
+ *
164
+ * Supports both standard decorators (``) and custom decorators (``).
165
+ *
166
+ * @param classDecl - Class declaration
167
+ * @param decoratorName - Decorator name (without @)
168
+ * @returns Decorator or undefined
169
+ * @private
170
+ */
171
+ private findDecorator(
172
+ classDecl: ClassDeclaration,
173
+ decoratorName: string,
174
+ ): Decorator | undefined {
175
+ const decorators = classDecl.getDecorators();
176
+
177
+ for (const decorator of decorators) {
178
+ const name = decorator.getName();
179
+ if (name === decoratorName) {
180
+ return decorator;
181
+ }
182
+ }
183
+
184
+ return undefined;
185
+ }
186
+ }
@@ -0,0 +1,280 @@
1
+ /**
2
+ * @angular-modernizer/api - Service Detector
3
+ *
4
+ * Centralized service detection API with caching and bulk operations.
5
+ * Single source of truth for service detection across all analysis rules.
6
+ *
7
+ * ARCHITECTURE:
8
+ * - Wraps ServicePatternRecognizer for consistent detection
9
+ * - Provides caching to avoid redundant AST traversal
10
+ * - Supports bulk detection for performance
11
+ * - Used by all analysis rules that need service detection
12
+ */
13
+
14
+ import type { SourceFile, ClassDeclaration } from 'ts-morph';
15
+ import type { ServicePatternRecognizer, ServiceDetectionResult } from './service-pattern-recognizer.js';
16
+
17
+ /**
18
+ * Bulk detection result for multiple classes.
19
+ */
20
+ export interface BulkDetectionResult {
21
+ /** Map of class name to detection result */
22
+ results: Map<string, ServiceDetectionResult>;
23
+
24
+ /** Total number of services detected */
25
+ serviceCount: number;
26
+
27
+ /** Total number of classes analyzed */
28
+ totalCount: number;
29
+
30
+ /** Detection time in milliseconds */
31
+ detectionTimeMs: number;
32
+ }
33
+
34
+ /**
35
+ * ServiceDetector - Centralized service detection with caching.
36
+ *
37
+ * SINGLE SOURCE OF TRUTH:
38
+ * - All analysis rules should use this detector
39
+ * - Ensures consistent service detection across rules
40
+ * - Provides performance optimization through caching
41
+ *
42
+ * USAGE:
43
+ * ```typescript
44
+ * const detector = new ServiceDetector(recognizer);
45
+ *
46
+ * // Single detection
47
+ * const result = detector.detectService(classDecl);
48
+ *
49
+ * // Bulk detection
50
+ * const bulk = detector.detectServicesInFile(sourceFile);
51
+ *
52
+ * // Cache management
53
+ * detector.clearCache();
54
+ * ```
55
+ */
56
+ export class ServiceDetector {
57
+ private readonly recognizer: ServicePatternRecognizer;
58
+ private readonly cache: Map<string, ServiceDetectionResult>;
59
+
60
+ /**
61
+ * Create a new ServiceDetector.
62
+ *
63
+ * @param recognizer - ServicePatternRecognizer instance
64
+ */
65
+ constructor(recognizer: ServicePatternRecognizer) {
66
+ this.recognizer = recognizer;
67
+ this.cache = new Map();
68
+ }
69
+
70
+ /**
71
+ * Detect if a class is a service with caching.
72
+ *
73
+ * Results are cached by file path + class name for performance.
74
+ *
75
+ * @param classDecl - The class declaration to analyze
76
+ * @returns Service detection result with confidence
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * const result = detector.detectService(classDecl);
81
+ * if (result.isService && result.confidence >= 0.7) {
82
+ * console.info(`High confidence service detected via ${result.detectionMethod}`);
83
+ * }
84
+ * ```
85
+ */
86
+ detectService(classDecl: ClassDeclaration): ServiceDetectionResult {
87
+ const cacheKey = this.getCacheKey(classDecl);
88
+
89
+ // Check cache first
90
+ const cached = this.cache.get(cacheKey);
91
+ if (cached) {
92
+ return cached;
93
+ }
94
+
95
+ // Detect and cache result
96
+ const result = this.recognizer.detectService(classDecl);
97
+ this.cache.set(cacheKey, result);
98
+
99
+ return result;
100
+ }
101
+
102
+ /**
103
+ * Simple check if a class is a service (cached).
104
+ *
105
+ * @param classDecl - The class declaration to check
106
+ * @returns True if detected as service
107
+ */
108
+ isService(classDecl: ClassDeclaration): boolean {
109
+ return this.detectService(classDecl).isService;
110
+ }
111
+
112
+ /**
113
+ * Detect all services in a source file (bulk operation).
114
+ *
115
+ * More efficient than detecting each class individually.
116
+ *
117
+ * @param sourceFile - The source file to analyze
118
+ * @returns Bulk detection result with statistics
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * const bulk = detector.detectServicesInFile(sourceFile);
123
+ * console.info(`Found ${bulk.serviceCount} services in ${bulk.detectionTimeMs}ms`);
124
+ *
125
+ * for (const [className, result] of bulk.results) {
126
+ * if (result.isService) {
127
+ * console.info(`${className}: ${result.detectionMethod} (${result.confidence})`);
128
+ * }
129
+ * }
130
+ * ```
131
+ */
132
+ detectServicesInFile(sourceFile: SourceFile): BulkDetectionResult {
133
+ const startTime = Date.now();
134
+ const results = new Map<string, ServiceDetectionResult>();
135
+ const classes = sourceFile.getClasses();
136
+
137
+ let serviceCount = 0;
138
+
139
+ for (const classDecl of classes) {
140
+ const className = classDecl.getName();
141
+ if (!className) {
142
+ continue;
143
+ }
144
+
145
+ const result = this.detectService(classDecl);
146
+ results.set(className, result);
147
+
148
+ if (result.isService) {
149
+ serviceCount++;
150
+ }
151
+ }
152
+
153
+ const endTime = Date.now();
154
+
155
+ return {
156
+ results,
157
+ serviceCount,
158
+ totalCount: classes.length,
159
+ detectionTimeMs: endTime - startTime,
160
+ };
161
+ }
162
+
163
+ /**
164
+ * Detect services in multiple source files (bulk operation).
165
+ *
166
+ * @param sourceFiles - Array of source files to analyze
167
+ * @returns Bulk detection result with combined statistics
168
+ *
169
+ * @example
170
+ * ```typescript
171
+ * const files = project.getSourceFiles();
172
+ * const bulk = detector.detectServicesInFiles(files);
173
+ * console.info(`Found ${bulk.serviceCount} services across ${files.length} files`);
174
+ * ```
175
+ */
176
+ detectServicesInFiles(sourceFiles: SourceFile[]): BulkDetectionResult {
177
+ const startTime = Date.now();
178
+ const results = new Map<string, ServiceDetectionResult>();
179
+
180
+ let serviceCount = 0;
181
+ let totalCount = 0;
182
+
183
+ for (const sourceFile of sourceFiles) {
184
+ const fileResult = this.detectServicesInFile(sourceFile);
185
+
186
+ // Merge results with class name prefixed by file path for uniqueness
187
+ const filePath = sourceFile.getFilePath();
188
+ for (const [className, result] of fileResult.results) {
189
+ const key = `${filePath}:${className}`;
190
+ results.set(key, result);
191
+ }
192
+
193
+ serviceCount += fileResult.serviceCount;
194
+ totalCount += fileResult.totalCount;
195
+ }
196
+
197
+ const endTime = Date.now();
198
+
199
+ return {
200
+ results,
201
+ serviceCount,
202
+ totalCount,
203
+ detectionTimeMs: endTime - startTime,
204
+ };
205
+ }
206
+
207
+ /**
208
+ * Filter classes to only services.
209
+ *
210
+ * @param classes - Array of class declarations
211
+ * @returns Array of service class declarations
212
+ *
213
+ * @example
214
+ * ```typescript
215
+ * const allClasses = sourceFile.getClasses();
216
+ * const services = detector.filterServices(allClasses);
217
+ * console.info(`Found ${services.length} services out of ${allClasses.length} classes`);
218
+ * ```
219
+ */
220
+ filterServices(classes: ClassDeclaration[]): ClassDeclaration[] {
221
+ return classes.filter((cls) => this.isService(cls));
222
+ }
223
+
224
+ /**
225
+ * Get detection statistics from cache.
226
+ *
227
+ * @returns Statistics about cached detections
228
+ */
229
+ getCacheStatistics(): {
230
+ totalEntries: number;
231
+ serviceEntries: number;
232
+ nonServiceEntries: number;
233
+ } {
234
+ let serviceEntries = 0;
235
+ let nonServiceEntries = 0;
236
+
237
+ for (const result of this.cache.values()) {
238
+ if (result.isService) {
239
+ serviceEntries++;
240
+ } else {
241
+ nonServiceEntries++;
242
+ }
243
+ }
244
+
245
+ return {
246
+ totalEntries: this.cache.size,
247
+ serviceEntries,
248
+ nonServiceEntries,
249
+ };
250
+ }
251
+
252
+ /**
253
+ * Clear the detection cache.
254
+ *
255
+ * Call this when the AST has been modified and cached results
256
+ * may be stale.
257
+ *
258
+ * @example
259
+ * ```typescript
260
+ * // After modifying files
261
+ * await project.save();
262
+ * detector.clearCache();
263
+ * ```
264
+ */
265
+ clearCache(): void {
266
+ this.cache.clear();
267
+ }
268
+
269
+ /**
270
+ * Get cache key for a class declaration.
271
+ *
272
+ * @param classDecl - The class declaration
273
+ * @returns Cache key string
274
+ */
275
+ private getCacheKey(classDecl: ClassDeclaration): string {
276
+ const filePath = classDecl.getSourceFile().getFilePath();
277
+ const className = classDecl.getName() ?? 'anonymous';
278
+ return `${filePath}:${className}`;
279
+ }
280
+ }