@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,433 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SunLint Semantic Rule Base
|
|
3
|
+
* Base class for semantic analysis rules using shared Symbol Table
|
|
4
|
+
*
|
|
5
|
+
* Provides common functionality for semantic rules in SunLint
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
class SemanticRuleBase {
|
|
11
|
+
constructor(ruleId, config = {}) {
|
|
12
|
+
this.ruleId = ruleId;
|
|
13
|
+
this.config = {
|
|
14
|
+
// Rule metadata
|
|
15
|
+
category: config.category || 'semantic',
|
|
16
|
+
severity: config.severity || 'warning',
|
|
17
|
+
description: config.description || '',
|
|
18
|
+
|
|
19
|
+
// Analysis options
|
|
20
|
+
crossFileAnalysis: config.crossFileAnalysis !== false,
|
|
21
|
+
requiresTypeChecker: config.requiresTypeChecker || false,
|
|
22
|
+
cacheResults: config.cacheResults !== false,
|
|
23
|
+
|
|
24
|
+
// Performance
|
|
25
|
+
timeout: config.timeout || 30000, // 30 seconds
|
|
26
|
+
maxFiles: config.maxFiles || 1000,
|
|
27
|
+
|
|
28
|
+
...config
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
this.semanticEngine = null;
|
|
32
|
+
this.violations = [];
|
|
33
|
+
this.stats = {
|
|
34
|
+
filesAnalyzed: 0,
|
|
35
|
+
violationsFound: 0,
|
|
36
|
+
analysisTime: 0,
|
|
37
|
+
cacheHits: 0
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Initialize rule with SemanticEngine instance
|
|
43
|
+
*/
|
|
44
|
+
initialize(semanticEngine) {
|
|
45
|
+
this.semanticEngine = semanticEngine;
|
|
46
|
+
|
|
47
|
+
if (!this.semanticEngine || !this.semanticEngine.initialized) {
|
|
48
|
+
throw new Error(`${this.ruleId}: SemanticEngine is required and must be initialized`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log(`🔧 Rule ${this.ruleId} initialized with semantic analysis`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Main analysis method - to be overridden by specific rules
|
|
56
|
+
*/
|
|
57
|
+
async analyze(filePaths, options = {}) {
|
|
58
|
+
const startTime = Date.now();
|
|
59
|
+
this.violations = [];
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
console.log(`🔍 ${this.ruleId}: Starting semantic analysis...`);
|
|
63
|
+
|
|
64
|
+
// Filter and validate files
|
|
65
|
+
const validFiles = await this.filterFiles(filePaths);
|
|
66
|
+
|
|
67
|
+
if (validFiles.length === 0) {
|
|
68
|
+
console.log(`ℹ️ ${this.ruleId}: No valid files to analyze`);
|
|
69
|
+
return this.generateReport();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Analyze each file
|
|
73
|
+
for (const filePath of validFiles) {
|
|
74
|
+
await this.analyzeFile(filePath, options);
|
|
75
|
+
this.stats.filesAnalyzed++;
|
|
76
|
+
|
|
77
|
+
// Check timeout
|
|
78
|
+
if (Date.now() - startTime > this.config.timeout) {
|
|
79
|
+
console.warn(`⚠️ ${this.ruleId}: Analysis timeout reached`);
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this.stats.analysisTime = Date.now() - startTime;
|
|
85
|
+
this.stats.violationsFound = this.violations.length;
|
|
86
|
+
|
|
87
|
+
console.log(`✅ ${this.ruleId}: Analysis complete - ${this.violations.length} violations found`);
|
|
88
|
+
|
|
89
|
+
return this.generateReport();
|
|
90
|
+
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error(`❌ ${this.ruleId}: Analysis failed:`, error.message);
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Analyze single file - to be overridden by specific rules
|
|
99
|
+
*/
|
|
100
|
+
async analyzeFile(filePath, options = {}) {
|
|
101
|
+
throw new Error(`${this.ruleId}: analyzeFile() method must be implemented by subclass`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Filter files based on rule requirements
|
|
106
|
+
*/
|
|
107
|
+
async filterFiles(filePaths) {
|
|
108
|
+
const filtered = [];
|
|
109
|
+
|
|
110
|
+
for (const filePath of filePaths) {
|
|
111
|
+
// Check file extension
|
|
112
|
+
if (this.isValidFileType(filePath)) {
|
|
113
|
+
// Check if file exists in Symbol Table
|
|
114
|
+
try {
|
|
115
|
+
const symbolTable = await this.semanticEngine.getSymbolTable(filePath);
|
|
116
|
+
if (symbolTable) {
|
|
117
|
+
filtered.push(filePath);
|
|
118
|
+
}
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.warn(`⚠️ ${this.ruleId}: Cannot analyze ${filePath}:`, error.message);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return filtered.slice(0, this.config.maxFiles);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Check if file type is supported by this rule
|
|
130
|
+
*/
|
|
131
|
+
isValidFileType(filePath) {
|
|
132
|
+
const supportedExtensions = ['.ts', '.tsx', '.js', '.jsx'];
|
|
133
|
+
const ext = path.extname(filePath);
|
|
134
|
+
return supportedExtensions.includes(ext);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Get Symbol Table for a file with caching
|
|
139
|
+
*/
|
|
140
|
+
async getSymbolTable(filePath) {
|
|
141
|
+
const startTime = Date.now();
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const symbolTable = await this.semanticEngine.getSymbolTable(filePath);
|
|
145
|
+
|
|
146
|
+
if (symbolTable) {
|
|
147
|
+
this.stats.cacheHits++;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return symbolTable;
|
|
151
|
+
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.warn(`⚠️ ${this.ruleId}: Failed to get symbol table for ${filePath}:`, error.message);
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Add a violation
|
|
160
|
+
*/
|
|
161
|
+
addViolation(violation) {
|
|
162
|
+
const enhancedViolation = {
|
|
163
|
+
ruleId: this.ruleId,
|
|
164
|
+
category: this.config.category,
|
|
165
|
+
severity: this.config.severity,
|
|
166
|
+
timestamp: Date.now(),
|
|
167
|
+
|
|
168
|
+
// Required fields
|
|
169
|
+
filePath: violation.filePath,
|
|
170
|
+
line: violation.line,
|
|
171
|
+
column: violation.column || 1,
|
|
172
|
+
message: violation.message,
|
|
173
|
+
|
|
174
|
+
// Optional fields
|
|
175
|
+
endLine: violation.endLine,
|
|
176
|
+
endColumn: violation.endColumn,
|
|
177
|
+
suggestion: violation.suggestion,
|
|
178
|
+
codeSnippet: violation.codeSnippet,
|
|
179
|
+
|
|
180
|
+
// Semantic analysis context
|
|
181
|
+
symbolContext: violation.symbolContext,
|
|
182
|
+
crossFileReferences: violation.crossFileReferences,
|
|
183
|
+
semanticDetails: violation.semanticDetails,
|
|
184
|
+
|
|
185
|
+
...violation
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
this.violations.push(enhancedViolation);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Common semantic analysis utilities
|
|
193
|
+
*/
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Find function calls by name with semantic context
|
|
197
|
+
*/
|
|
198
|
+
findFunctionCalls(symbolTable, functionName) {
|
|
199
|
+
return symbolTable.functionCalls.filter(call =>
|
|
200
|
+
call.functionName === functionName ||
|
|
201
|
+
call.functionName.includes(functionName)
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Find method calls on specific objects
|
|
207
|
+
*/
|
|
208
|
+
findMethodCalls(symbolTable, objectName, methodName) {
|
|
209
|
+
return symbolTable.methodCalls.filter(call =>
|
|
210
|
+
call.objectName === objectName && call.methodName === methodName
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Check if a function call is within a retry context
|
|
216
|
+
*/
|
|
217
|
+
isInRetryContext(symbolTable, functionCall) {
|
|
218
|
+
// Check parent call stack for retry patterns
|
|
219
|
+
const retryPatterns = ['retry', 'retries', 'withRetry', 'retryWhen'];
|
|
220
|
+
|
|
221
|
+
if (functionCall.parentContext) {
|
|
222
|
+
return retryPatterns.some(pattern =>
|
|
223
|
+
functionCall.parentContext.includes(pattern)
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Check nearby calls (previous/next lines)
|
|
228
|
+
const nearbyLines = this.getNearbyLines(symbolTable, functionCall.line, 5);
|
|
229
|
+
return nearbyLines.some(line =>
|
|
230
|
+
retryPatterns.some(pattern => line.includes(pattern))
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Get nearby lines for context analysis
|
|
236
|
+
*/
|
|
237
|
+
getNearbyLines(symbolTable, targetLine, range = 3) {
|
|
238
|
+
const lines = [];
|
|
239
|
+
|
|
240
|
+
// Collect all calls around target line
|
|
241
|
+
const allCalls = [
|
|
242
|
+
...symbolTable.functionCalls,
|
|
243
|
+
...symbolTable.methodCalls,
|
|
244
|
+
...symbolTable.hooks
|
|
245
|
+
];
|
|
246
|
+
|
|
247
|
+
const nearbyCalls = allCalls.filter(call =>
|
|
248
|
+
Math.abs(call.line - targetLine) <= range
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
return nearbyCalls.map(call => ({
|
|
252
|
+
line: call.line,
|
|
253
|
+
text: call.functionName || call.methodName || call.hookName
|
|
254
|
+
}));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Analyze React hooks for retry patterns
|
|
259
|
+
*/
|
|
260
|
+
analyzeHooksForRetry(symbolTable) {
|
|
261
|
+
const retryHooks = [];
|
|
262
|
+
|
|
263
|
+
symbolTable.hooks.forEach(hook => {
|
|
264
|
+
if (hook.isQueryHook && hook.retryConfig.hasRetryConfig) {
|
|
265
|
+
retryHooks.push({
|
|
266
|
+
...hook,
|
|
267
|
+
hasMultiLayerRetry: this.checkMultiLayerRetry(symbolTable, hook)
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
return retryHooks;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Check for multi-layer retry patterns
|
|
277
|
+
*/
|
|
278
|
+
checkMultiLayerRetry(symbolTable, queryHook) {
|
|
279
|
+
// Look for additional retry mechanisms near the query hook
|
|
280
|
+
const nearbyLines = this.getNearbyLines(symbolTable, queryHook.line, 10);
|
|
281
|
+
|
|
282
|
+
// Check for retry patterns in nearby code
|
|
283
|
+
const retryPatterns = nearbyLines.filter(line =>
|
|
284
|
+
/retry|retries|attempt/i.test(line.text)
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
return retryPatterns.length > 1; // Multiple retry mechanisms
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Cross-file analysis utilities
|
|
292
|
+
*/
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Find symbol usages across files
|
|
296
|
+
*/
|
|
297
|
+
async findCrossFileUsages(symbolName, excludeFiles = []) {
|
|
298
|
+
if (!this.config.crossFileAnalysis) {
|
|
299
|
+
return [];
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const usages = [];
|
|
303
|
+
const allFiles = this.semanticEngine.project.getSourceFiles();
|
|
304
|
+
|
|
305
|
+
for (const sourceFile of allFiles) {
|
|
306
|
+
const filePath = sourceFile.getFilePath();
|
|
307
|
+
|
|
308
|
+
if (excludeFiles.includes(filePath)) {
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const symbolTable = await this.getSymbolTable(filePath);
|
|
313
|
+
if (!symbolTable) continue;
|
|
314
|
+
|
|
315
|
+
// Search in various symbol collections
|
|
316
|
+
const foundUsages = [
|
|
317
|
+
...this.searchInCollection(symbolTable.functionCalls, symbolName),
|
|
318
|
+
...this.searchInCollection(symbolTable.methodCalls, symbolName),
|
|
319
|
+
...this.searchInCollection(symbolTable.imports, symbolName),
|
|
320
|
+
...this.searchInCollection(symbolTable.variables, symbolName)
|
|
321
|
+
];
|
|
322
|
+
|
|
323
|
+
foundUsages.forEach(usage => {
|
|
324
|
+
usages.push({
|
|
325
|
+
...usage,
|
|
326
|
+
filePath,
|
|
327
|
+
crossFileReference: true
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return usages;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
searchInCollection(collection, symbolName) {
|
|
336
|
+
return collection.filter(item =>
|
|
337
|
+
item.name === symbolName ||
|
|
338
|
+
item.functionName === symbolName ||
|
|
339
|
+
item.methodName === symbolName ||
|
|
340
|
+
(item.namedImports && item.namedImports.some(imp => imp.name === symbolName))
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Generate analysis report
|
|
346
|
+
*/
|
|
347
|
+
generateReport() {
|
|
348
|
+
return {
|
|
349
|
+
ruleId: this.ruleId,
|
|
350
|
+
config: this.config,
|
|
351
|
+
violations: this.violations,
|
|
352
|
+
stats: this.stats,
|
|
353
|
+
summary: {
|
|
354
|
+
filesAnalyzed: this.stats.filesAnalyzed,
|
|
355
|
+
violationsFound: this.stats.violationsFound,
|
|
356
|
+
analysisTime: this.stats.analysisTime,
|
|
357
|
+
averageTimePerFile: this.stats.filesAnalyzed > 0
|
|
358
|
+
? Math.round(this.stats.analysisTime / this.stats.filesAnalyzed)
|
|
359
|
+
: 0
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Cleanup resources
|
|
366
|
+
*/
|
|
367
|
+
cleanup() {
|
|
368
|
+
this.violations = [];
|
|
369
|
+
this.stats = {
|
|
370
|
+
filesAnalyzed: 0,
|
|
371
|
+
violationsFound: 0,
|
|
372
|
+
analysisTime: 0,
|
|
373
|
+
cacheHits: 0
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Validation helpers
|
|
379
|
+
*/
|
|
380
|
+
|
|
381
|
+
validateRequiredFields(violation) {
|
|
382
|
+
const required = ['filePath', 'line', 'message'];
|
|
383
|
+
const missing = required.filter(field => !violation[field]);
|
|
384
|
+
|
|
385
|
+
if (missing.length > 0) {
|
|
386
|
+
throw new Error(`${this.ruleId}: Missing required violation fields: ${missing.join(', ')}`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Code snippet extraction
|
|
392
|
+
*/
|
|
393
|
+
extractCodeSnippet(symbolTable, line, range = 2) {
|
|
394
|
+
// This would need implementation based on source file access
|
|
395
|
+
// For now, return a placeholder
|
|
396
|
+
return {
|
|
397
|
+
startLine: Math.max(1, line - range),
|
|
398
|
+
endLine: line + range,
|
|
399
|
+
code: `// Code snippet around line ${line}`
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Get current violations
|
|
405
|
+
*/
|
|
406
|
+
getViolations() {
|
|
407
|
+
return this.violations;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Clear violations (for reuse)
|
|
412
|
+
*/
|
|
413
|
+
clearViolations() {
|
|
414
|
+
this.violations = [];
|
|
415
|
+
this.stats.violationsFound = 0;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Suggestion generation
|
|
420
|
+
*/
|
|
421
|
+
generateSuggestion(violationType, context = {}) {
|
|
422
|
+
// Base suggestions - to be overridden by specific rules
|
|
423
|
+
const suggestions = {
|
|
424
|
+
'multi-layer-retry': 'Consider consolidating retry logic into a single mechanism to avoid conflicts',
|
|
425
|
+
'redundant-retry': 'Remove redundant retry configuration to simplify error handling',
|
|
426
|
+
'missing-retry': 'Add retry configuration for better resilience'
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
return suggestions[violationType] || 'Review this code for best practices';
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
module.exports = SemanticRuleBase;
|