@sun-asterisk/sunlint 1.2.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -1
- package/CONTRIBUTING.md +533 -70
- package/README.md +16 -2
- package/config/engines/engines-enhanced.json +86 -0
- package/config/engines/semantic-config.json +114 -0
- package/config/eslint-rule-mapping.json +50 -38
- package/config/rules/enhanced-rules-registry.json +2503 -0
- package/config/rules/rules-registry-generated.json +785 -837
- package/core/adapters/sunlint-rule-adapter.js +25 -30
- package/core/analysis-orchestrator.js +42 -2
- package/core/categories.js +52 -0
- package/core/category-constants.js +39 -0
- package/core/cli-action-handler.js +32 -5
- package/core/config-manager.js +111 -0
- package/core/config-merger.js +61 -0
- package/core/constants/categories.js +168 -0
- package/core/constants/defaults.js +165 -0
- package/core/constants/engines.js +185 -0
- package/core/constants/index.js +30 -0
- package/core/constants/rules.js +215 -0
- package/core/file-targeting-service.js +128 -7
- package/core/interfaces/rule-plugin.interface.js +207 -0
- package/core/plugin-manager.js +448 -0
- package/core/rule-selection-service.js +42 -15
- package/core/semantic-engine.js +560 -0
- package/core/semantic-rule-base.js +433 -0
- package/core/unified-rule-registry.js +484 -0
- package/docs/CONSTANTS-ARCHITECTURE.md +288 -0
- package/engines/core/base-engine.js +249 -0
- package/engines/engine-factory.js +275 -0
- package/engines/eslint-engine.js +171 -19
- package/engines/heuristic-engine.js +511 -78
- package/integrations/eslint/plugin/index.js +27 -27
- package/package.json +10 -6
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +1 -1
- package/rules/common/C029_catch_block_logging/analyzer.js +17 -5
- package/rules/common/C047_no_duplicate_retry_logic/c047-semantic-rule.js +278 -0
- package/rules/common/C047_no_duplicate_retry_logic/symbol-analyzer-enhanced.js +968 -0
- package/rules/common/C047_no_duplicate_retry_logic/symbol-config.json +71 -0
- package/rules/index.js +7 -0
- package/scripts/category-manager.js +150 -0
- package/scripts/generate-rules-registry.js +88 -0
- package/scripts/migrate-rule-registry.js +157 -0
- package/scripts/validate-system.js +48 -0
- package/.sunlint.json +0 -35
- package/config/README.md +0 -88
- package/config/engines/eslint-rule-mapping.json +0 -74
- package/config/schemas/sunlint-schema.json +0 -0
- package/config/testing/test-s005-working.ts +0 -22
- package/core/multi-rule-runner.js +0 -0
- package/engines/tree-sitter-parser.js +0 -0
- package/engines/universal-ast-engine.js +0 -0
- package/rules/common/C029_catch_block_logging/analyzer-backup.js +0 -426
- package/rules/common/C029_catch_block_logging/analyzer-fixed.js +0 -130
- package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +0 -487
- package/rules/common/C029_catch_block_logging/analyzer-simple.js +0 -110
- package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +0 -441
- package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +0 -127
- package/rules/common/C029_catch_block_logging/ast-analyzer.js +0 -133
- package/rules/common/C029_catch_block_logging/cfg-analyzer.js +0 -408
- package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +0 -454
- package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +0 -700
- package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +0 -568
- package/rules/common/C029_catch_block_logging/semantic-analyzer.js +0 -459
|
@@ -0,0 +1,560 @@
|
|
|
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
|
+
|
|
12
|
+
class SemanticEngine {
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
this.options = {
|
|
15
|
+
// Compiler options
|
|
16
|
+
compilerOptions: {
|
|
17
|
+
target: 99, // ScriptTarget.Latest
|
|
18
|
+
allowJs: true,
|
|
19
|
+
checkJs: false,
|
|
20
|
+
skipLibCheck: true,
|
|
21
|
+
skipDefaultLibCheck: true,
|
|
22
|
+
...options.compilerOptions
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
// Performance options
|
|
26
|
+
enableCaching: options.enableCaching !== false,
|
|
27
|
+
maxCacheSize: options.maxCacheSize || 100, // files
|
|
28
|
+
memoryLimit: options.memoryLimit || 500 * 1024 * 1024, // 500MB
|
|
29
|
+
|
|
30
|
+
// Analysis options
|
|
31
|
+
crossFileAnalysis: options.crossFileAnalysis !== false,
|
|
32
|
+
enableTypeChecker: options.enableTypeChecker || false,
|
|
33
|
+
|
|
34
|
+
...options
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
this.project = null;
|
|
38
|
+
this.symbolTable = new Map();
|
|
39
|
+
this.fileCache = new Map();
|
|
40
|
+
this.initialized = false;
|
|
41
|
+
this.stats = {
|
|
42
|
+
filesAnalyzed: 0,
|
|
43
|
+
cacheHits: 0,
|
|
44
|
+
cacheMisses: 0,
|
|
45
|
+
memoryUsage: 0
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Initialize ts-morph project with optimized memory configuration
|
|
51
|
+
* Designed for large projects (3000+ files, 800-1000 lines each)
|
|
52
|
+
*/
|
|
53
|
+
async initialize(projectPath) {
|
|
54
|
+
try {
|
|
55
|
+
// Load ts-morph conditionally
|
|
56
|
+
const { Project } = await import('ts-morph');
|
|
57
|
+
|
|
58
|
+
// Discover TypeScript configuration
|
|
59
|
+
const tsConfigPath = await this.findTsConfig(projectPath);
|
|
60
|
+
|
|
61
|
+
// Initialize project with memory-optimized settings
|
|
62
|
+
this.project = new Project({
|
|
63
|
+
tsConfigFilePath: tsConfigPath,
|
|
64
|
+
compilerOptions: {
|
|
65
|
+
...this.options.compilerOptions,
|
|
66
|
+
// Memory optimization flags
|
|
67
|
+
skipLibCheck: true,
|
|
68
|
+
skipDefaultLibCheck: true,
|
|
69
|
+
noLib: true, // Don't load standard libraries
|
|
70
|
+
allowJs: true,
|
|
71
|
+
checkJs: false,
|
|
72
|
+
},
|
|
73
|
+
// Critical memory optimizations for large projects
|
|
74
|
+
skipFileDependencyResolution: true, // Don't resolve dependencies
|
|
75
|
+
skipLoadingLibFiles: true, // Don't load .d.ts lib files
|
|
76
|
+
useInMemoryFileSystem: false, // Use disk for large projects
|
|
77
|
+
|
|
78
|
+
// Performance settings for large codebases
|
|
79
|
+
resolutionHost: undefined, // Disable resolution host
|
|
80
|
+
libFolderPath: undefined, // Don't load TypeScript libs
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Only add target files, not entire project
|
|
84
|
+
const sourceFiles = await this.discoverTargetFiles(projectPath);
|
|
85
|
+
if (sourceFiles.length > 100) {
|
|
86
|
+
console.warn(`⚠️ Large project detected (${sourceFiles.length} files) - limited analysis mode`);
|
|
87
|
+
// For large projects, only add first 50 files to avoid memory issues
|
|
88
|
+
this.project.addSourceFilesAtPaths(sourceFiles.slice(0, 50));
|
|
89
|
+
} else {
|
|
90
|
+
this.project.addSourceFilesAtPaths(sourceFiles);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
console.log(`🔧 Semantic Engine initialized (Memory Optimized):`);
|
|
94
|
+
console.log(` 📁 Project: ${projectPath}`);
|
|
95
|
+
console.log(` 📋 TS Config: ${tsConfigPath || 'default (minimal)'}`);
|
|
96
|
+
console.log(` 📄 Files loaded: ${this.project.getSourceFiles().length}`);
|
|
97
|
+
console.log(` 💾 Memory mode: Optimized for large projects`);
|
|
98
|
+
|
|
99
|
+
this.initialized = true;
|
|
100
|
+
return true;
|
|
101
|
+
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.warn(`⚠️ ts-morph not available or initialization failed:`, error.message);
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get or create Symbol Table for a file
|
|
110
|
+
*/
|
|
111
|
+
async getSymbolTable(filePath) {
|
|
112
|
+
if (!this.initialized) {
|
|
113
|
+
throw new Error('Semantic Engine not initialized');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const absolutePath = path.resolve(filePath);
|
|
117
|
+
|
|
118
|
+
// Check cache first
|
|
119
|
+
if (this.fileCache.has(absolutePath)) {
|
|
120
|
+
this.stats.cacheHits++;
|
|
121
|
+
return this.fileCache.get(absolutePath);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
this.stats.cacheMisses++;
|
|
125
|
+
|
|
126
|
+
// Get source file
|
|
127
|
+
const sourceFile = this.project.getSourceFile(absolutePath);
|
|
128
|
+
if (!sourceFile) {
|
|
129
|
+
console.warn(`⚠️ File not found in project: ${filePath}`);
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Build symbol table
|
|
134
|
+
const symbolTable = await this.buildSymbolTable(sourceFile);
|
|
135
|
+
|
|
136
|
+
// Cache the result
|
|
137
|
+
if (this.options.enableCaching) {
|
|
138
|
+
this.cacheSymbolTable(absolutePath, symbolTable);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
this.stats.filesAnalyzed++;
|
|
142
|
+
return symbolTable;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Build comprehensive symbol table for a file
|
|
147
|
+
*/
|
|
148
|
+
async buildSymbolTable(sourceFile) {
|
|
149
|
+
const symbols = {
|
|
150
|
+
// File metadata
|
|
151
|
+
filePath: sourceFile.getFilePath(),
|
|
152
|
+
fileName: sourceFile.getBaseName(),
|
|
153
|
+
|
|
154
|
+
// Imports and exports
|
|
155
|
+
imports: this.extractImports(sourceFile),
|
|
156
|
+
exports: this.extractExports(sourceFile),
|
|
157
|
+
|
|
158
|
+
// Declarations
|
|
159
|
+
functions: this.extractFunctions(sourceFile),
|
|
160
|
+
classes: this.extractClasses(sourceFile),
|
|
161
|
+
interfaces: this.extractInterfaces(sourceFile),
|
|
162
|
+
variables: this.extractVariables(sourceFile),
|
|
163
|
+
constants: this.extractConstants(sourceFile),
|
|
164
|
+
|
|
165
|
+
// React specific (if applicable)
|
|
166
|
+
hooks: this.extractHooks(sourceFile),
|
|
167
|
+
components: this.extractComponents(sourceFile),
|
|
168
|
+
|
|
169
|
+
// Call analysis
|
|
170
|
+
functionCalls: this.extractFunctionCalls(sourceFile),
|
|
171
|
+
methodCalls: this.extractMethodCalls(sourceFile),
|
|
172
|
+
|
|
173
|
+
// Cross-file references
|
|
174
|
+
crossFileReferences: this.extractCrossFileReferences(sourceFile),
|
|
175
|
+
|
|
176
|
+
// Metadata
|
|
177
|
+
lastModified: Date.now(),
|
|
178
|
+
analysisTime: 0
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// Add cross-file dependency information
|
|
182
|
+
if (this.options.crossFileAnalysis) {
|
|
183
|
+
symbols.dependencies = await this.analyzeDependencies(sourceFile);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return symbols;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Extract import statements
|
|
191
|
+
*/
|
|
192
|
+
extractImports(sourceFile) {
|
|
193
|
+
const imports = [];
|
|
194
|
+
|
|
195
|
+
sourceFile.getImportDeclarations().forEach(importDecl => {
|
|
196
|
+
const moduleSpecifier = importDecl.getModuleSpecifierValue();
|
|
197
|
+
|
|
198
|
+
// Named imports
|
|
199
|
+
const namedImports = importDecl.getNamedImports().map(namedImport => ({
|
|
200
|
+
name: namedImport.getName(),
|
|
201
|
+
alias: namedImport.getAliasNode()?.getText(),
|
|
202
|
+
line: namedImport.getStartLineNumber()
|
|
203
|
+
}));
|
|
204
|
+
|
|
205
|
+
// Default import
|
|
206
|
+
const defaultImport = importDecl.getDefaultImport();
|
|
207
|
+
|
|
208
|
+
imports.push({
|
|
209
|
+
module: moduleSpecifier,
|
|
210
|
+
defaultImport: defaultImport?.getText(),
|
|
211
|
+
namedImports,
|
|
212
|
+
line: importDecl.getStartLineNumber(),
|
|
213
|
+
isTypeOnly: importDecl.isTypeOnly(),
|
|
214
|
+
resolvedPath: this.resolveModule(moduleSpecifier, sourceFile)
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
return imports;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Extract function calls (cho C047 analysis)
|
|
223
|
+
*/
|
|
224
|
+
extractFunctionCalls(sourceFile) {
|
|
225
|
+
const calls = [];
|
|
226
|
+
|
|
227
|
+
sourceFile.getDescendantsOfKind(sourceFile.getKindName().CallExpression || 210).forEach(callExpr => {
|
|
228
|
+
const expression = callExpr.getExpression();
|
|
229
|
+
|
|
230
|
+
calls.push({
|
|
231
|
+
functionName: expression.getText(),
|
|
232
|
+
arguments: callExpr.getArguments().map(arg => ({
|
|
233
|
+
text: arg.getText(),
|
|
234
|
+
type: this.getExpressionType(arg),
|
|
235
|
+
line: arg.getStartLineNumber()
|
|
236
|
+
})),
|
|
237
|
+
line: callExpr.getStartLineNumber(),
|
|
238
|
+
column: callExpr.getStartColumnNumber(),
|
|
239
|
+
|
|
240
|
+
// Detailed analysis for retry patterns
|
|
241
|
+
isRetryPattern: this.isRetryPattern(callExpr),
|
|
242
|
+
isConditionalCall: this.isConditionalCall(callExpr),
|
|
243
|
+
parentContext: this.getParentContext(callExpr)
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
return calls;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Extract React hooks usage
|
|
252
|
+
*/
|
|
253
|
+
extractHooks(sourceFile) {
|
|
254
|
+
const hooks = [];
|
|
255
|
+
|
|
256
|
+
sourceFile.getDescendantsOfKind(sourceFile.getKindName().CallExpression || 210).forEach(callExpr => {
|
|
257
|
+
const expression = callExpr.getExpression();
|
|
258
|
+
const functionName = expression.getText();
|
|
259
|
+
|
|
260
|
+
// Detect hook patterns
|
|
261
|
+
if (functionName.startsWith('use') || this.isKnownHook(functionName)) {
|
|
262
|
+
hooks.push({
|
|
263
|
+
hookName: functionName,
|
|
264
|
+
arguments: callExpr.getArguments().map(arg => arg.getText()),
|
|
265
|
+
line: callExpr.getStartLineNumber(),
|
|
266
|
+
|
|
267
|
+
// Special analysis for useQuery, useMutation, etc.
|
|
268
|
+
isQueryHook: this.isQueryHook(functionName),
|
|
269
|
+
retryConfig: this.extractRetryConfig(callExpr)
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
return hooks;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Analyze cross-file dependencies
|
|
279
|
+
*/
|
|
280
|
+
async analyzeDependencies(sourceFile) {
|
|
281
|
+
const dependencies = [];
|
|
282
|
+
|
|
283
|
+
// Analyze imported symbols usage
|
|
284
|
+
sourceFile.getImportDeclarations().forEach(importDecl => {
|
|
285
|
+
const moduleSpecifier = importDecl.getModuleSpecifierValue();
|
|
286
|
+
const resolvedPath = this.resolveModule(moduleSpecifier, sourceFile);
|
|
287
|
+
|
|
288
|
+
if (resolvedPath && this.project.getSourceFile(resolvedPath)) {
|
|
289
|
+
dependencies.push({
|
|
290
|
+
type: 'import',
|
|
291
|
+
module: moduleSpecifier,
|
|
292
|
+
resolvedPath,
|
|
293
|
+
usages: this.findSymbolUsages(sourceFile, importDecl.getNamedImports())
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
return dependencies;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Utility methods for pattern detection
|
|
303
|
+
*/
|
|
304
|
+
isRetryPattern(callExpr) {
|
|
305
|
+
const functionName = callExpr.getExpression().getText();
|
|
306
|
+
|
|
307
|
+
// Known retry functions
|
|
308
|
+
const retryFunctions = ['retry', 'retries', 'withRetry', 'retryWhen'];
|
|
309
|
+
if (retryFunctions.some(fn => functionName.includes(fn))) {
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Check for retry configuration in arguments
|
|
314
|
+
const args = callExpr.getArguments();
|
|
315
|
+
return args.some(arg => {
|
|
316
|
+
const argText = arg.getText();
|
|
317
|
+
return /retry|retries/i.test(argText);
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
isQueryHook(functionName) {
|
|
322
|
+
const queryHooks = ['useQuery', 'useMutation', 'useInfiniteQuery', 'useSuspenseQuery'];
|
|
323
|
+
return queryHooks.includes(functionName);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
extractRetryConfig(callExpr) {
|
|
327
|
+
const args = callExpr.getArguments();
|
|
328
|
+
|
|
329
|
+
// Look for retry configuration in arguments
|
|
330
|
+
for (const arg of args) {
|
|
331
|
+
const argText = arg.getText();
|
|
332
|
+
|
|
333
|
+
// Object literal with retry config
|
|
334
|
+
if (arg.getKind() === 204) { // ObjectLiteralExpression
|
|
335
|
+
const retryProperty = arg.getProperties().find(prop =>
|
|
336
|
+
prop.getName && prop.getName() === 'retry'
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
if (retryProperty) {
|
|
340
|
+
return {
|
|
341
|
+
hasRetryConfig: true,
|
|
342
|
+
retryValue: retryProperty.getValueNode()?.getText(),
|
|
343
|
+
line: retryProperty.getStartLineNumber()
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return { hasRetryConfig: false };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Resolve module path
|
|
354
|
+
*/
|
|
355
|
+
resolveModule(moduleSpecifier, sourceFile) {
|
|
356
|
+
try {
|
|
357
|
+
// Use ts-morph's resolution if available
|
|
358
|
+
if (this.options.enableTypeChecker && sourceFile.getProject().getTypeChecker) {
|
|
359
|
+
const symbol = sourceFile.getProject().getTypeChecker()
|
|
360
|
+
.getSymbolAtLocation(sourceFile.getImportDeclarations()
|
|
361
|
+
.find(imp => imp.getModuleSpecifierValue() === moduleSpecifier)
|
|
362
|
+
?.getModuleSpecifier());
|
|
363
|
+
|
|
364
|
+
if (symbol?.getDeclarations()?.[0]) {
|
|
365
|
+
return symbol.getDeclarations()[0].getSourceFile().getFilePath();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Basic resolution
|
|
370
|
+
if (moduleSpecifier.startsWith('.')) {
|
|
371
|
+
const dir = path.dirname(sourceFile.getFilePath());
|
|
372
|
+
return path.resolve(dir, moduleSpecifier);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return null;
|
|
376
|
+
} catch (error) {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Memory and cache management
|
|
383
|
+
*/
|
|
384
|
+
cacheSymbolTable(filePath, symbolTable) {
|
|
385
|
+
// Check memory limits
|
|
386
|
+
if (this.fileCache.size >= this.options.maxCacheSize) {
|
|
387
|
+
this.evictOldestCache();
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
this.fileCache.set(filePath, symbolTable);
|
|
391
|
+
this.updateMemoryStats();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
evictOldestCache() {
|
|
395
|
+
// Simple LRU eviction
|
|
396
|
+
const oldest = this.fileCache.keys().next().value;
|
|
397
|
+
this.fileCache.delete(oldest);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
updateMemoryStats() {
|
|
401
|
+
this.stats.memoryUsage = process.memoryUsage().heapUsed;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Configuration discovery
|
|
406
|
+
*/
|
|
407
|
+
async findTsConfig(projectPath) {
|
|
408
|
+
const candidates = [
|
|
409
|
+
path.join(projectPath, 'tsconfig.json'),
|
|
410
|
+
path.join(projectPath, 'jsconfig.json'),
|
|
411
|
+
path.join(projectPath, '..', 'tsconfig.json')
|
|
412
|
+
];
|
|
413
|
+
|
|
414
|
+
for (const candidate of candidates) {
|
|
415
|
+
try {
|
|
416
|
+
await fs.access(candidate);
|
|
417
|
+
return candidate;
|
|
418
|
+
} catch (error) {
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Discover target files with intelligent filtering for large projects
|
|
428
|
+
* Optimized for projects with 3000+ files, 800-1000 lines each
|
|
429
|
+
*/
|
|
430
|
+
async discoverTargetFiles(projectPath) {
|
|
431
|
+
const fs = await import('fs');
|
|
432
|
+
const glob = require('glob');
|
|
433
|
+
|
|
434
|
+
try {
|
|
435
|
+
const patterns = [
|
|
436
|
+
'**/*.ts',
|
|
437
|
+
'**/*.tsx',
|
|
438
|
+
'**/*.js', // Include JS files for semantic analysis
|
|
439
|
+
'**/*.jsx' // Include JSX files
|
|
440
|
+
// Both TS and JS files for comprehensive analysis
|
|
441
|
+
];
|
|
442
|
+
|
|
443
|
+
// Exclude common directories and large files
|
|
444
|
+
const excludePatterns = [
|
|
445
|
+
'**/node_modules/**',
|
|
446
|
+
'**/dist/**',
|
|
447
|
+
'**/build/**',
|
|
448
|
+
'**/coverage/**',
|
|
449
|
+
'**/.git/**',
|
|
450
|
+
'**/.next/**',
|
|
451
|
+
'**/out/**',
|
|
452
|
+
'**/*.min.js',
|
|
453
|
+
'**/*.min.ts',
|
|
454
|
+
'**/*.d.ts', // Skip declaration files
|
|
455
|
+
'**/vendor/**',
|
|
456
|
+
'**/third-party/**'
|
|
457
|
+
];
|
|
458
|
+
|
|
459
|
+
// Find all matching files
|
|
460
|
+
const allFiles = [];
|
|
461
|
+
for (const pattern of patterns) {
|
|
462
|
+
const globPattern = path.join(projectPath, pattern);
|
|
463
|
+
const files = glob.sync(globPattern, {
|
|
464
|
+
ignore: excludePatterns.map(exclude => path.join(projectPath, exclude))
|
|
465
|
+
});
|
|
466
|
+
allFiles.push(...files);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Filter by file size for memory optimization
|
|
470
|
+
const targetFiles = [];
|
|
471
|
+
for (const filePath of allFiles) {
|
|
472
|
+
try {
|
|
473
|
+
const stats = await fs.stat(filePath);
|
|
474
|
+
// Skip files larger than 100KB (typically auto-generated)
|
|
475
|
+
if (stats.size < 100 * 1024) {
|
|
476
|
+
targetFiles.push(filePath);
|
|
477
|
+
} else {
|
|
478
|
+
console.debug(`⚠️ Skipping large file: ${path.basename(filePath)} (${Math.round(stats.size / 1024)}KB)`);
|
|
479
|
+
}
|
|
480
|
+
} catch (error) {
|
|
481
|
+
// Skip files that can't be stat'd
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
console.log(`📁 File discovery: ${targetFiles.length}/${allFiles.length} files selected (memory optimized)`);
|
|
487
|
+
return targetFiles;
|
|
488
|
+
|
|
489
|
+
} catch (error) {
|
|
490
|
+
console.warn(`⚠️ File discovery failed, using basic patterns:`, error.message);
|
|
491
|
+
return this.discoverSourceFiles(projectPath);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
async discoverSourceFiles(projectPath) {
|
|
496
|
+
const patterns = [
|
|
497
|
+
'**/*.ts',
|
|
498
|
+
'**/*.tsx',
|
|
499
|
+
'**/*.js',
|
|
500
|
+
'**/*.jsx'
|
|
501
|
+
];
|
|
502
|
+
|
|
503
|
+
// Exclude common directories
|
|
504
|
+
const excludePatterns = [
|
|
505
|
+
'**/node_modules/**',
|
|
506
|
+
'**/dist/**',
|
|
507
|
+
'**/build/**',
|
|
508
|
+
'**/.git/**'
|
|
509
|
+
];
|
|
510
|
+
|
|
511
|
+
return patterns.map(pattern => path.join(projectPath, pattern))
|
|
512
|
+
.filter(filePath => !excludePatterns.some(exclude =>
|
|
513
|
+
filePath.includes(exclude.replace('**/', ''))
|
|
514
|
+
));
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Cleanup and statistics
|
|
519
|
+
*/
|
|
520
|
+
async cleanup() {
|
|
521
|
+
if (this.project) {
|
|
522
|
+
// Clear caches
|
|
523
|
+
this.fileCache.clear();
|
|
524
|
+
this.symbolTable.clear();
|
|
525
|
+
|
|
526
|
+
console.log(`📊 Semantic Engine Stats:`);
|
|
527
|
+
console.log(` 📄 Files analyzed: ${this.stats.filesAnalyzed}`);
|
|
528
|
+
console.log(` 🎯 Cache hits: ${this.stats.cacheHits}`);
|
|
529
|
+
console.log(` ❌ Cache misses: ${this.stats.cacheMisses}`);
|
|
530
|
+
console.log(` 💾 Memory usage: ${Math.round(this.stats.memoryUsage / 1024 / 1024)}MB`);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
getStats() {
|
|
535
|
+
return {
|
|
536
|
+
...this.stats,
|
|
537
|
+
cacheSize: this.fileCache.size,
|
|
538
|
+
symbolTableSize: this.symbolTable.size,
|
|
539
|
+
isInitialized: this.initialized
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Stub methods for full extraction implementation
|
|
544
|
+
extractExports(sourceFile) { return []; }
|
|
545
|
+
extractFunctions(sourceFile) { return []; }
|
|
546
|
+
extractClasses(sourceFile) { return []; }
|
|
547
|
+
extractInterfaces(sourceFile) { return []; }
|
|
548
|
+
extractVariables(sourceFile) { return []; }
|
|
549
|
+
extractConstants(sourceFile) { return []; }
|
|
550
|
+
extractComponents(sourceFile) { return []; }
|
|
551
|
+
extractMethodCalls(sourceFile) { return []; }
|
|
552
|
+
extractCrossFileReferences(sourceFile) { return []; }
|
|
553
|
+
getExpressionType(expr) { return 'unknown'; }
|
|
554
|
+
isConditionalCall(callExpr) { return false; }
|
|
555
|
+
getParentContext(callExpr) { return null; }
|
|
556
|
+
isKnownHook(functionName) { return false; }
|
|
557
|
+
findSymbolUsages(sourceFile, namedImports) { return []; }
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
module.exports = SemanticEngine;
|