@sun-asterisk/sunlint 1.2.2 → 1.3.1

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 (124) hide show
  1. package/CHANGELOG.md +107 -1
  2. package/CONTRIBUTING.md +1654 -66
  3. package/README.md +19 -6
  4. package/config/ci-cd.json +54 -0
  5. package/config/development.json +56 -0
  6. package/config/engines/engines-enhanced.json +86 -0
  7. package/config/engines/semantic-config.json +114 -0
  8. package/config/eslint-rule-mapping.json +50 -38
  9. package/config/large-project.json +143 -0
  10. package/config/presets/all.json +0 -1
  11. package/config/release.json +70 -0
  12. package/config/rule-analysis-strategies.js +23 -4
  13. package/config/rules/S027-categories.json +122 -0
  14. package/config/rules/enhanced-rules-registry.json +2564 -0
  15. package/config/rules/rules-registry-generated.json +785 -837
  16. package/config/rules/rules-registry.json +13 -1
  17. package/core/adapters/sunlint-rule-adapter.js +25 -30
  18. package/core/analysis-orchestrator.js +42 -2
  19. package/core/categories.js +52 -0
  20. package/core/category-constants.js +39 -0
  21. package/core/cli-action-handler.js +53 -32
  22. package/core/cli-program.js +11 -3
  23. package/core/config-manager.js +111 -0
  24. package/core/config-merger.js +88 -0
  25. package/core/constants/categories.js +168 -0
  26. package/core/constants/defaults.js +165 -0
  27. package/core/constants/engines.js +185 -0
  28. package/core/constants/index.js +30 -0
  29. package/core/constants/rules.js +215 -0
  30. package/core/enhanced-rules-registry.js +3 -3
  31. package/core/file-targeting-service.js +128 -7
  32. package/core/interfaces/rule-plugin.interface.js +207 -0
  33. package/core/plugin-manager.js +448 -0
  34. package/core/rule-selection-service.js +42 -15
  35. package/core/semantic-engine.js +658 -0
  36. package/core/semantic-rule-base.js +433 -0
  37. package/core/unified-rule-registry.js +484 -0
  38. package/docs/COMMAND-EXAMPLES.md +134 -0
  39. package/docs/CONSTANTS-ARCHITECTURE.md +288 -0
  40. package/docs/LARGE-PROJECT-GUIDE.md +324 -0
  41. package/engines/core/base-engine.js +249 -0
  42. package/engines/engine-factory.js +275 -0
  43. package/engines/eslint-engine.js +171 -19
  44. package/engines/heuristic-engine.js +569 -78
  45. package/integrations/eslint/plugin/index.js +26 -28
  46. package/origin-rules/common-en.md +8 -8
  47. package/package.json +10 -6
  48. package/rules/common/C003_no_vague_abbreviations/analyzer.js +1 -1
  49. package/rules/common/C017_constructor_logic/analyzer.js +254 -17
  50. package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -0
  51. package/rules/common/C029_catch_block_logging/analyzer.js +17 -5
  52. package/rules/common/C033_separate_service_repository/README.md +78 -0
  53. package/rules/common/C033_separate_service_repository/analyzer.js +160 -0
  54. package/rules/common/C033_separate_service_repository/config.json +50 -0
  55. package/rules/common/C033_separate_service_repository/regex-based-analyzer.js +585 -0
  56. package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +368 -0
  57. package/rules/common/C035_error_logging_context/STRATEGY.md +99 -0
  58. package/rules/common/C035_error_logging_context/analyzer.js +230 -0
  59. package/rules/common/C035_error_logging_context/config.json +54 -0
  60. package/rules/common/C035_error_logging_context/regex-based-analyzer.js +299 -0
  61. package/rules/common/C035_error_logging_context/symbol-based-analyzer.js +454 -0
  62. package/rules/common/C040_centralized_validation/analyzer.js +165 -0
  63. package/rules/common/C040_centralized_validation/config.json +46 -0
  64. package/rules/common/C040_centralized_validation/regex-based-analyzer.js +243 -0
  65. package/rules/common/C040_centralized_validation/symbol-based-analyzer.js +416 -0
  66. package/rules/common/C047_no_duplicate_retry_logic/c047-semantic-rule.js +278 -0
  67. package/rules/common/C047_no_duplicate_retry_logic/symbol-analyzer-enhanced.js +968 -0
  68. package/rules/common/C047_no_duplicate_retry_logic/symbol-config.json +71 -0
  69. package/rules/common/{C076_single_test_behavior → C072_single_test_behavior}/analyzer.js +6 -6
  70. package/rules/common/C076_explicit_function_types/README.md +30 -0
  71. package/rules/common/C076_explicit_function_types/analyzer.js +172 -0
  72. package/rules/common/C076_explicit_function_types/config.json +15 -0
  73. package/rules/common/C076_explicit_function_types/semantic-analyzer.js +341 -0
  74. package/rules/index.js +8 -0
  75. package/rules/parser/rule-parser.js +13 -2
  76. package/rules/security/S005_no_origin_auth/README.md +226 -0
  77. package/rules/security/S005_no_origin_auth/analyzer.js +184 -0
  78. package/rules/security/S005_no_origin_auth/ast-analyzer.js +406 -0
  79. package/rules/security/S005_no_origin_auth/config.json +85 -0
  80. package/rules/security/S006_no_plaintext_recovery_codes/README.md +139 -0
  81. package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +306 -0
  82. package/rules/security/S006_no_plaintext_recovery_codes/config.json +48 -0
  83. package/rules/security/S007_no_plaintext_otp/README.md +198 -0
  84. package/rules/security/S007_no_plaintext_otp/analyzer.js +406 -0
  85. package/rules/security/S007_no_plaintext_otp/config.json +79 -0
  86. package/rules/security/S007_no_plaintext_otp/semantic-analyzer.js +609 -0
  87. package/rules/security/S007_no_plaintext_otp/semantic-config.json +195 -0
  88. package/rules/security/S007_no_plaintext_otp/semantic-wrapper.js +280 -0
  89. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +180 -366
  90. package/rules/security/S027_no_hardcoded_secrets/categories.json +153 -0
  91. package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +250 -0
  92. package/scripts/category-manager.js +150 -0
  93. package/scripts/generate-rules-registry.js +88 -0
  94. package/scripts/migrate-rule-registry.js +157 -0
  95. package/scripts/prepare-release.sh +1 -1
  96. package/scripts/validate-system.js +48 -0
  97. package/.sunlint.json +0 -35
  98. package/config/README.md +0 -88
  99. package/config/engines/eslint-rule-mapping.json +0 -74
  100. package/config/schemas/sunlint-schema.json +0 -0
  101. package/config/testing/test-s005-working.ts +0 -22
  102. package/core/multi-rule-runner.js +0 -0
  103. package/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
  104. package/docs/FUTURE_PACKAGES.md +0 -83
  105. package/docs/HEURISTIC_VS_AI.md +0 -113
  106. package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +0 -112
  107. package/docs/PRODUCTION_SIZE_IMPACT.md +0 -183
  108. package/docs/RELEASE_GUIDE.md +0 -230
  109. package/docs/STANDARDIZED-CATEGORY-FILTERING.md +0 -156
  110. package/engines/tree-sitter-parser.js +0 -0
  111. package/engines/universal-ast-engine.js +0 -0
  112. package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +0 -254
  113. package/rules/common/C029_catch_block_logging/analyzer-backup.js +0 -426
  114. package/rules/common/C029_catch_block_logging/analyzer-fixed.js +0 -130
  115. package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +0 -487
  116. package/rules/common/C029_catch_block_logging/analyzer-simple.js +0 -110
  117. package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +0 -441
  118. package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +0 -127
  119. package/rules/common/C029_catch_block_logging/ast-analyzer.js +0 -133
  120. package/rules/common/C029_catch_block_logging/cfg-analyzer.js +0 -408
  121. package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +0 -454
  122. package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +0 -700
  123. package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +0 -568
  124. package/rules/common/C029_catch_block_logging/semantic-analyzer.js +0 -459
@@ -0,0 +1,658 @@
1
+ /**
2
+ * SunLint Semantic Engine
3
+ * Core Symbol Table Manager using ts-morph
4
+ *
5
+ * Provides shared semantic analysis capabilities for SunLint rules
6
+ * Manages project-wide Symbol Table and AST caching
7
+ */
8
+
9
+ const path = require('path');
10
+ const fs = require('fs').promises;
11
+ const { Project, SyntaxKind } = require('ts-morph');
12
+
13
+ class SemanticEngine {
14
+ constructor(options = {}) {
15
+ this.options = {
16
+ // Compiler options
17
+ compilerOptions: {
18
+ target: 99, // ScriptTarget.Latest
19
+ allowJs: true,
20
+ checkJs: false,
21
+ skipLibCheck: true,
22
+ skipDefaultLibCheck: true,
23
+ ...options.compilerOptions
24
+ },
25
+
26
+ // Performance options
27
+ enableCaching: options.enableCaching !== false,
28
+ maxCacheSize: options.maxCacheSize || 100, // files
29
+ memoryLimit: options.memoryLimit || 500 * 1024 * 1024, // 500MB
30
+
31
+ // Analysis options
32
+ crossFileAnalysis: options.crossFileAnalysis !== false,
33
+ enableTypeChecker: options.enableTypeChecker || false,
34
+
35
+ ...options
36
+ };
37
+
38
+ this.project = null;
39
+ this.symbolTable = new Map();
40
+ this.fileCache = new Map();
41
+ this.initialized = false;
42
+ this.stats = {
43
+ filesAnalyzed: 0,
44
+ cacheHits: 0,
45
+ cacheMisses: 0,
46
+ memoryUsage: 0
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Initialize ts-morph project with optimized memory configuration
52
+ * Designed for large projects (3000+ files, 800-1000 lines each)
53
+ * OPTIMIZED: Accept targetFiles parameter to avoid loading unnecessary files
54
+ */
55
+ async initialize(projectPath, targetFiles = null) {
56
+ try {
57
+ // Load ts-morph conditionally
58
+ const { Project } = await import('ts-morph');
59
+
60
+ // Discover TypeScript configuration
61
+ const tsConfigPath = await this.findTsConfig(projectPath);
62
+
63
+ // Initialize project with memory-optimized settings
64
+ // When using targetFiles, skip tsconfig to avoid auto-discovery
65
+ const projectOptions = {
66
+ compilerOptions: {
67
+ ...this.options.compilerOptions,
68
+ // Memory optimization flags
69
+ skipLibCheck: true,
70
+ skipDefaultLibCheck: true,
71
+ noLib: true, // Don't load standard libraries
72
+ allowJs: true,
73
+ checkJs: false,
74
+ },
75
+ // Critical memory optimizations for large projects
76
+ skipFileDependencyResolution: true, // Don't resolve dependencies
77
+ skipLoadingLibFiles: true, // Don't load .d.ts lib files
78
+ useInMemoryFileSystem: false, // Use disk for large projects
79
+
80
+ // Performance settings for large codebases
81
+ resolutionHost: undefined, // Disable resolution host
82
+ libFolderPath: undefined, // Don't load TypeScript libs
83
+ };
84
+
85
+ // Only use tsconfig when auto-discovering files (no targetFiles)
86
+ if (!targetFiles && tsConfigPath) {
87
+ projectOptions.tsConfigFilePath = tsConfigPath;
88
+ }
89
+
90
+ this.project = new Project(projectOptions);
91
+
92
+ // Use provided targetFiles if available, otherwise discover
93
+ const sourceFiles = targetFiles || await this.discoverTargetFiles(projectPath);
94
+
95
+ // Filter to TypeScript/JavaScript files only for semantic analysis
96
+ const semanticFiles = sourceFiles.filter(filePath =>
97
+ /\.(ts|tsx|js|jsx)$/i.test(filePath)
98
+ );
99
+
100
+ if (targetFiles) {
101
+ console.log(`🎯 Targeted files received: ${targetFiles.length} total, ${semanticFiles.length} TS/JS files`);
102
+ if (semanticFiles.length < 10) {
103
+ console.log(` Files: ${semanticFiles.map(f => path.basename(f)).join(', ')}`);
104
+ }
105
+ }
106
+
107
+ // Adaptive loading strategy based on project size and user preference
108
+ const userMaxFiles = this.options.maxSemanticFiles;
109
+ let maxFiles;
110
+
111
+ if (userMaxFiles === -1) {
112
+ // Unlimited: Load all files
113
+ maxFiles = semanticFiles.length;
114
+ console.log(`🔧 Semantic Engine config: UNLIMITED analysis (all ${semanticFiles.length} files)`);
115
+ } else if (userMaxFiles === 0) {
116
+ // Disable semantic analysis
117
+ maxFiles = 0;
118
+ console.log(`🔧 Semantic Engine config: DISABLED semantic analysis (heuristic only)`);
119
+ } else if (userMaxFiles > 0) {
120
+ // User-specified limit
121
+ maxFiles = Math.min(userMaxFiles, semanticFiles.length);
122
+ console.log(`🔧 Semantic Engine config: USER limit ${maxFiles} files (requested: ${userMaxFiles})`);
123
+ } else {
124
+ // Auto-detect based on project size
125
+ maxFiles = semanticFiles.length > 1000 ? 1000 : semanticFiles.length;
126
+ console.log(`🔧 Semantic Engine config: AUTO limit ${maxFiles} files (project has ${semanticFiles.length} files)`);
127
+ }
128
+
129
+ if (this.options.verbose) {
130
+ console.log(`🔧 Semantic Engine detailed config:`);
131
+ console.log(` 📊 maxSemanticFiles option: ${this.options.maxSemanticFiles}`);
132
+ console.log(` 📈 Total semantic files: ${semanticFiles.length}`);
133
+ console.log(` 🎯 Files to load: ${maxFiles}`);
134
+ console.log(` 📉 Coverage: ${maxFiles > 0 ? Math.round(maxFiles/semanticFiles.length*100) : 0}%`);
135
+ }
136
+
137
+ // Skip semantic analysis if disabled
138
+ if (maxFiles === 0) {
139
+ console.log(`⚠️ Semantic analysis DISABLED - using heuristic rules only`);
140
+ console.log(`💡 To enable semantic analysis, use --max-semantic-files=1000 (or higher)`);
141
+ this.initialized = true;
142
+ return true;
143
+ }
144
+
145
+ if (semanticFiles.length > maxFiles && maxFiles !== semanticFiles.length) {
146
+ console.warn(`⚠️ Large semantic project detected (${semanticFiles.length} files)`);
147
+ console.warn(`⚠️ Loading ${maxFiles} files for memory optimization (${Math.round(maxFiles/semanticFiles.length*100)}% coverage)`);
148
+ if (userMaxFiles !== -1) {
149
+ console.warn(`⚠️ Use --max-semantic-files=-1 to analyze ALL files (unlimited)`);
150
+ console.warn(`⚠️ Use --max-semantic-files=${semanticFiles.length} to analyze exactly this project`);
151
+ }
152
+
153
+ const filesToLoad = semanticFiles.slice(0, maxFiles);
154
+
155
+ // Load files one by one to handle any parse errors gracefully
156
+ let successCount = 0;
157
+ let errorCount = 0;
158
+
159
+ for (const filePath of filesToLoad) {
160
+ try {
161
+ if (require('fs').existsSync(filePath)) {
162
+ this.project.addSourceFileAtPath(filePath);
163
+ successCount++;
164
+ } else {
165
+ errorCount++;
166
+ }
167
+ } catch (error) {
168
+ if (this.options.verbose) {
169
+ console.warn(`❌ Failed to load: ${path.basename(filePath)} - ${error.message}`);
170
+ }
171
+ errorCount++;
172
+ }
173
+ }
174
+
175
+ console.log(`📊 Semantic analysis: ${successCount} files loaded, ${errorCount} skipped`);
176
+
177
+ } else {
178
+ console.log(`📊 Loading all ${semanticFiles.length} files for complete semantic analysis`);
179
+ // For projects within limits, load all files
180
+ this.project.addSourceFilesAtPaths(semanticFiles);
181
+ }
182
+
183
+ // Debug what ts-morph actually loaded
184
+ const actualFiles = this.project.getSourceFiles();
185
+ console.log(`📊 ts-morph loaded: ${actualFiles.length} files (expected: ${semanticFiles.length})`);
186
+ if (actualFiles.length > semanticFiles.length * 2) {
187
+ console.warn(`⚠️ ts-morph auto-discovered additional files (dependency resolution)`);
188
+ }
189
+
190
+ console.log(`🔧 Semantic Engine initialized (Memory Optimized):`);
191
+ console.log(` 📁 Project: ${projectPath}`);
192
+ console.log(` 📋 TS Config: ${tsConfigPath || 'default (minimal)'}`);
193
+ console.log(` 📄 Files loaded: ${this.project.getSourceFiles().length}`);
194
+ console.log(` 🎯 Targeting mode: ${targetFiles ? 'Filtered files' : 'Auto-discovery'}`);
195
+ console.log(` 💾 Memory mode: Optimized for large projects`);
196
+
197
+ this.initialized = true;
198
+ return true;
199
+
200
+ } catch (error) {
201
+ console.warn(`⚠️ ts-morph not available or initialization failed:`, error.message);
202
+ return false;
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Get or create Symbol Table for a file
208
+ */
209
+ async getSymbolTable(filePath) {
210
+ if (!this.initialized) {
211
+ throw new Error('Semantic Engine not initialized');
212
+ }
213
+
214
+ const absolutePath = path.resolve(filePath);
215
+
216
+ // Check cache first
217
+ if (this.fileCache.has(absolutePath)) {
218
+ this.stats.cacheHits++;
219
+ return this.fileCache.get(absolutePath);
220
+ }
221
+
222
+ this.stats.cacheMisses++;
223
+
224
+ // Get source file
225
+ const sourceFile = this.project.getSourceFile(absolutePath);
226
+ if (!sourceFile) {
227
+ console.warn(`⚠️ File not found in project: ${filePath}`);
228
+ return null;
229
+ }
230
+
231
+ // Build symbol table
232
+ const symbolTable = await this.buildSymbolTable(sourceFile);
233
+
234
+ // Cache the result
235
+ if (this.options.enableCaching) {
236
+ this.cacheSymbolTable(absolutePath, symbolTable);
237
+ }
238
+
239
+ this.stats.filesAnalyzed++;
240
+ return symbolTable;
241
+ }
242
+
243
+ /**
244
+ * Build comprehensive symbol table for a file
245
+ */
246
+ async buildSymbolTable(sourceFile) {
247
+ const symbols = {
248
+ // File metadata
249
+ filePath: sourceFile.getFilePath(),
250
+ fileName: sourceFile.getBaseName(),
251
+
252
+ // Imports and exports
253
+ imports: this.extractImports(sourceFile),
254
+ exports: this.extractExports(sourceFile),
255
+
256
+ // Declarations
257
+ functions: this.extractFunctions(sourceFile),
258
+ classes: this.extractClasses(sourceFile),
259
+ interfaces: this.extractInterfaces(sourceFile),
260
+ variables: this.extractVariables(sourceFile),
261
+ constants: this.extractConstants(sourceFile),
262
+
263
+ // React specific (if applicable)
264
+ hooks: this.extractHooks(sourceFile),
265
+ components: this.extractComponents(sourceFile),
266
+
267
+ // Call analysis
268
+ functionCalls: this.extractFunctionCalls(sourceFile),
269
+ methodCalls: this.extractMethodCalls(sourceFile),
270
+
271
+ // Cross-file references
272
+ crossFileReferences: this.extractCrossFileReferences(sourceFile),
273
+
274
+ // Metadata
275
+ lastModified: Date.now(),
276
+ analysisTime: 0
277
+ };
278
+
279
+ // Add cross-file dependency information
280
+ if (this.options.crossFileAnalysis) {
281
+ symbols.dependencies = await this.analyzeDependencies(sourceFile);
282
+ }
283
+
284
+ return symbols;
285
+ }
286
+
287
+ /**
288
+ * Extract import statements
289
+ */
290
+ extractImports(sourceFile) {
291
+ const imports = [];
292
+
293
+ sourceFile.getImportDeclarations().forEach(importDecl => {
294
+ const moduleSpecifier = importDecl.getModuleSpecifierValue();
295
+
296
+ // Named imports
297
+ const namedImports = importDecl.getNamedImports().map(namedImport => ({
298
+ name: namedImport.getName(),
299
+ alias: namedImport.getAliasNode()?.getText(),
300
+ line: sourceFile.getLineAndColumnAtPos(namedImport.getStart()).line
301
+ }));
302
+
303
+ // Default import
304
+ const defaultImport = importDecl.getDefaultImport();
305
+
306
+ imports.push({
307
+ module: moduleSpecifier,
308
+ defaultImport: defaultImport?.getText(),
309
+ namedImports,
310
+ line: sourceFile.getLineAndColumnAtPos(importDecl.getStart()).line,
311
+ isTypeOnly: importDecl.isTypeOnly(),
312
+ resolvedPath: this.resolveModule(moduleSpecifier, sourceFile)
313
+ });
314
+ });
315
+
316
+ return imports;
317
+ }
318
+
319
+ /**
320
+ * Extract function calls (cho C047 analysis)
321
+ */
322
+ extractFunctionCalls(sourceFile) {
323
+ const calls = [];
324
+
325
+ sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).forEach(callExpr => {
326
+ const expression = callExpr.getExpression();
327
+
328
+ calls.push({
329
+ functionName: expression.getText(),
330
+ arguments: callExpr.getArguments().map(arg => ({
331
+ text: arg.getText(),
332
+ type: this.getExpressionType(arg),
333
+ line: sourceFile.getLineAndColumnAtPos(arg.getStart()).line
334
+ })),
335
+ line: sourceFile.getLineAndColumnAtPos(callExpr.getStart()).line,
336
+ column: sourceFile.getLineAndColumnAtPos(callExpr.getStart()).column,
337
+
338
+ // Detailed analysis for retry patterns
339
+ isRetryPattern: this.isRetryPattern(callExpr),
340
+ isConditionalCall: this.isConditionalCall(callExpr),
341
+ parentContext: this.getParentContext(callExpr)
342
+ });
343
+ });
344
+
345
+ return calls;
346
+ }
347
+
348
+ /**
349
+ * Extract React hooks usage
350
+ */
351
+ extractHooks(sourceFile) {
352
+ const hooks = [];
353
+
354
+ sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).forEach(callExpr => {
355
+ const expression = callExpr.getExpression();
356
+ const functionName = expression.getText();
357
+
358
+ // Detect hook patterns
359
+ if (functionName.startsWith('use') || this.isKnownHook(functionName)) {
360
+ hooks.push({
361
+ hookName: functionName,
362
+ arguments: callExpr.getArguments().map(arg => arg.getText()),
363
+ line: sourceFile.getLineAndColumnAtPos(callExpr.getStart()).line,
364
+
365
+ // Special analysis for useQuery, useMutation, etc.
366
+ isQueryHook: this.isQueryHook(functionName),
367
+ retryConfig: this.extractRetryConfig(callExpr)
368
+ });
369
+ }
370
+ });
371
+
372
+ return hooks;
373
+ }
374
+
375
+ /**
376
+ * Analyze cross-file dependencies
377
+ */
378
+ async analyzeDependencies(sourceFile) {
379
+ const dependencies = [];
380
+
381
+ // Analyze imported symbols usage
382
+ sourceFile.getImportDeclarations().forEach(importDecl => {
383
+ const moduleSpecifier = importDecl.getModuleSpecifierValue();
384
+ const resolvedPath = this.resolveModule(moduleSpecifier, sourceFile);
385
+
386
+ if (resolvedPath && this.project.getSourceFile(resolvedPath)) {
387
+ dependencies.push({
388
+ type: 'import',
389
+ module: moduleSpecifier,
390
+ resolvedPath,
391
+ usages: this.findSymbolUsages(sourceFile, importDecl.getNamedImports())
392
+ });
393
+ }
394
+ });
395
+
396
+ return dependencies;
397
+ }
398
+
399
+ /**
400
+ * Utility methods for pattern detection
401
+ */
402
+ isRetryPattern(callExpr) {
403
+ const functionName = callExpr.getExpression().getText();
404
+
405
+ // Known retry functions
406
+ const retryFunctions = ['retry', 'retries', 'withRetry', 'retryWhen'];
407
+ if (retryFunctions.some(fn => functionName.includes(fn))) {
408
+ return true;
409
+ }
410
+
411
+ // Check for retry configuration in arguments
412
+ const args = callExpr.getArguments();
413
+ return args.some(arg => {
414
+ const argText = arg.getText();
415
+ return /retry|retries/i.test(argText);
416
+ });
417
+ }
418
+
419
+ isQueryHook(functionName) {
420
+ const queryHooks = ['useQuery', 'useMutation', 'useInfiniteQuery', 'useSuspenseQuery'];
421
+ return queryHooks.includes(functionName);
422
+ }
423
+
424
+ extractRetryConfig(callExpr) {
425
+ const args = callExpr.getArguments();
426
+
427
+ // Look for retry configuration in arguments
428
+ for (const arg of args) {
429
+ const argText = arg.getText();
430
+
431
+ // Object literal with retry config
432
+ if (arg.getKind() === 204) { // ObjectLiteralExpression
433
+ const retryProperty = arg.getProperties().find(prop =>
434
+ prop.getName && prop.getName() === 'retry'
435
+ );
436
+
437
+ if (retryProperty) {
438
+ return {
439
+ hasRetryConfig: true,
440
+ retryValue: retryProperty.getValueNode()?.getText(),
441
+ line: retryProperty.getStartLineNumber()
442
+ };
443
+ }
444
+ }
445
+ }
446
+
447
+ return { hasRetryConfig: false };
448
+ }
449
+
450
+ /**
451
+ * Resolve module path
452
+ */
453
+ resolveModule(moduleSpecifier, sourceFile) {
454
+ try {
455
+ // Use ts-morph's resolution if available
456
+ if (this.options.enableTypeChecker && sourceFile.getProject().getTypeChecker) {
457
+ const symbol = sourceFile.getProject().getTypeChecker()
458
+ .getSymbolAtLocation(sourceFile.getImportDeclarations()
459
+ .find(imp => imp.getModuleSpecifierValue() === moduleSpecifier)
460
+ ?.getModuleSpecifier());
461
+
462
+ if (symbol?.getDeclarations()?.[0]) {
463
+ return symbol.getDeclarations()[0].getSourceFile().getFilePath();
464
+ }
465
+ }
466
+
467
+ // Basic resolution
468
+ if (moduleSpecifier.startsWith('.')) {
469
+ const dir = path.dirname(sourceFile.getFilePath());
470
+ return path.resolve(dir, moduleSpecifier);
471
+ }
472
+
473
+ return null;
474
+ } catch (error) {
475
+ return null;
476
+ }
477
+ }
478
+
479
+ /**
480
+ * Memory and cache management
481
+ */
482
+ cacheSymbolTable(filePath, symbolTable) {
483
+ // Check memory limits
484
+ if (this.fileCache.size >= this.options.maxCacheSize) {
485
+ this.evictOldestCache();
486
+ }
487
+
488
+ this.fileCache.set(filePath, symbolTable);
489
+ this.updateMemoryStats();
490
+ }
491
+
492
+ evictOldestCache() {
493
+ // Simple LRU eviction
494
+ const oldest = this.fileCache.keys().next().value;
495
+ this.fileCache.delete(oldest);
496
+ }
497
+
498
+ updateMemoryStats() {
499
+ this.stats.memoryUsage = process.memoryUsage().heapUsed;
500
+ }
501
+
502
+ /**
503
+ * Configuration discovery
504
+ */
505
+ async findTsConfig(projectPath) {
506
+ const candidates = [
507
+ path.join(projectPath, 'tsconfig.json'),
508
+ path.join(projectPath, 'jsconfig.json'),
509
+ path.join(projectPath, '..', 'tsconfig.json')
510
+ ];
511
+
512
+ for (const candidate of candidates) {
513
+ try {
514
+ await fs.access(candidate);
515
+ return candidate;
516
+ } catch (error) {
517
+ continue;
518
+ }
519
+ }
520
+
521
+ return null;
522
+ }
523
+
524
+ /**
525
+ * Discover target files with intelligent filtering for large projects
526
+ * Optimized for projects with 3000+ files, 800-1000 lines each
527
+ */
528
+ async discoverTargetFiles(projectPath) {
529
+ const fs = await import('fs');
530
+ const glob = require('glob');
531
+
532
+ try {
533
+ const patterns = [
534
+ '**/*.ts',
535
+ '**/*.tsx',
536
+ '**/*.js', // Include JS files for semantic analysis
537
+ '**/*.jsx' // Include JSX files
538
+ // Both TS and JS files for comprehensive analysis
539
+ ];
540
+
541
+ // Exclude common directories and large files
542
+ const excludePatterns = [
543
+ '**/node_modules/**',
544
+ '**/dist/**',
545
+ '**/build/**',
546
+ '**/coverage/**',
547
+ '**/.git/**',
548
+ '**/.next/**',
549
+ '**/out/**',
550
+ '**/*.min.js',
551
+ '**/*.min.ts',
552
+ '**/*.d.ts', // Skip declaration files
553
+ '**/vendor/**',
554
+ '**/third-party/**'
555
+ ];
556
+
557
+ // Find all matching files
558
+ const allFiles = [];
559
+ for (const pattern of patterns) {
560
+ const globPattern = path.join(projectPath, pattern);
561
+ const files = glob.sync(globPattern, {
562
+ ignore: excludePatterns.map(exclude => path.join(projectPath, exclude))
563
+ });
564
+ allFiles.push(...files);
565
+ }
566
+
567
+ // Filter by file size for memory optimization
568
+ const targetFiles = [];
569
+ for (const filePath of allFiles) {
570
+ try {
571
+ const stats = await fs.stat(filePath);
572
+ // Skip files larger than 100KB (typically auto-generated)
573
+ if (stats.size < 100 * 1024) {
574
+ targetFiles.push(filePath);
575
+ } else {
576
+ console.debug(`⚠️ Skipping large file: ${path.basename(filePath)} (${Math.round(stats.size / 1024)}KB)`);
577
+ }
578
+ } catch (error) {
579
+ // Skip files that can't be stat'd
580
+ continue;
581
+ }
582
+ }
583
+
584
+ console.log(`📁 File discovery: ${targetFiles.length}/${allFiles.length} files selected (memory optimized)`);
585
+ return targetFiles;
586
+
587
+ } catch (error) {
588
+ console.warn(`⚠️ File discovery failed, using basic patterns:`, error.message);
589
+ return this.discoverSourceFiles(projectPath);
590
+ }
591
+ }
592
+
593
+ async discoverSourceFiles(projectPath) {
594
+ const patterns = [
595
+ '**/*.ts',
596
+ '**/*.tsx',
597
+ '**/*.js',
598
+ '**/*.jsx'
599
+ ];
600
+
601
+ // Exclude common directories
602
+ const excludePatterns = [
603
+ '**/node_modules/**',
604
+ '**/dist/**',
605
+ '**/build/**',
606
+ '**/.git/**'
607
+ ];
608
+
609
+ return patterns.map(pattern => path.join(projectPath, pattern))
610
+ .filter(filePath => !excludePatterns.some(exclude =>
611
+ filePath.includes(exclude.replace('**/', ''))
612
+ ));
613
+ }
614
+
615
+ /**
616
+ * Cleanup and statistics
617
+ */
618
+ async cleanup() {
619
+ if (this.project) {
620
+ // Clear caches
621
+ this.fileCache.clear();
622
+ this.symbolTable.clear();
623
+
624
+ console.log(`📊 Semantic Engine Stats:`);
625
+ console.log(` 📄 Files analyzed: ${this.stats.filesAnalyzed}`);
626
+ console.log(` 🎯 Cache hits: ${this.stats.cacheHits}`);
627
+ console.log(` ❌ Cache misses: ${this.stats.cacheMisses}`);
628
+ console.log(` 💾 Memory usage: ${Math.round(this.stats.memoryUsage / 1024 / 1024)}MB`);
629
+ }
630
+ }
631
+
632
+ getStats() {
633
+ return {
634
+ ...this.stats,
635
+ cacheSize: this.fileCache.size,
636
+ symbolTableSize: this.symbolTable.size,
637
+ isInitialized: this.initialized
638
+ };
639
+ }
640
+
641
+ // Stub methods for full extraction implementation
642
+ extractExports(sourceFile) { return []; }
643
+ extractFunctions(sourceFile) { return []; }
644
+ extractClasses(sourceFile) { return []; }
645
+ extractInterfaces(sourceFile) { return []; }
646
+ extractVariables(sourceFile) { return []; }
647
+ extractConstants(sourceFile) { return []; }
648
+ extractComponents(sourceFile) { return []; }
649
+ extractMethodCalls(sourceFile) { return []; }
650
+ extractCrossFileReferences(sourceFile) { return []; }
651
+ getExpressionType(expr) { return 'unknown'; }
652
+ isConditionalCall(callExpr) { return false; }
653
+ getParentContext(callExpr) { return null; }
654
+ isKnownHook(functionName) { return false; }
655
+ findSymbolUsages(sourceFile, namedImports) { return []; }
656
+ }
657
+
658
+ module.exports = SemanticEngine;