@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.
Files changed (64) hide show
  1. package/CHANGELOG.md +40 -1
  2. package/CONTRIBUTING.md +533 -70
  3. package/README.md +16 -2
  4. package/config/engines/engines-enhanced.json +86 -0
  5. package/config/engines/semantic-config.json +114 -0
  6. package/config/eslint-rule-mapping.json +50 -38
  7. package/config/rules/enhanced-rules-registry.json +2503 -0
  8. package/config/rules/rules-registry-generated.json +785 -837
  9. package/core/adapters/sunlint-rule-adapter.js +25 -30
  10. package/core/analysis-orchestrator.js +42 -2
  11. package/core/categories.js +52 -0
  12. package/core/category-constants.js +39 -0
  13. package/core/cli-action-handler.js +32 -5
  14. package/core/config-manager.js +111 -0
  15. package/core/config-merger.js +61 -0
  16. package/core/constants/categories.js +168 -0
  17. package/core/constants/defaults.js +165 -0
  18. package/core/constants/engines.js +185 -0
  19. package/core/constants/index.js +30 -0
  20. package/core/constants/rules.js +215 -0
  21. package/core/file-targeting-service.js +128 -7
  22. package/core/interfaces/rule-plugin.interface.js +207 -0
  23. package/core/plugin-manager.js +448 -0
  24. package/core/rule-selection-service.js +42 -15
  25. package/core/semantic-engine.js +560 -0
  26. package/core/semantic-rule-base.js +433 -0
  27. package/core/unified-rule-registry.js +484 -0
  28. package/docs/CONSTANTS-ARCHITECTURE.md +288 -0
  29. package/engines/core/base-engine.js +249 -0
  30. package/engines/engine-factory.js +275 -0
  31. package/engines/eslint-engine.js +171 -19
  32. package/engines/heuristic-engine.js +511 -78
  33. package/integrations/eslint/plugin/index.js +27 -27
  34. package/package.json +10 -6
  35. package/rules/common/C003_no_vague_abbreviations/analyzer.js +1 -1
  36. package/rules/common/C029_catch_block_logging/analyzer.js +17 -5
  37. package/rules/common/C047_no_duplicate_retry_logic/c047-semantic-rule.js +278 -0
  38. package/rules/common/C047_no_duplicate_retry_logic/symbol-analyzer-enhanced.js +968 -0
  39. package/rules/common/C047_no_duplicate_retry_logic/symbol-config.json +71 -0
  40. package/rules/index.js +7 -0
  41. package/scripts/category-manager.js +150 -0
  42. package/scripts/generate-rules-registry.js +88 -0
  43. package/scripts/migrate-rule-registry.js +157 -0
  44. package/scripts/validate-system.js +48 -0
  45. package/.sunlint.json +0 -35
  46. package/config/README.md +0 -88
  47. package/config/engines/eslint-rule-mapping.json +0 -74
  48. package/config/schemas/sunlint-schema.json +0 -0
  49. package/config/testing/test-s005-working.ts +0 -22
  50. package/core/multi-rule-runner.js +0 -0
  51. package/engines/tree-sitter-parser.js +0 -0
  52. package/engines/universal-ast-engine.js +0 -0
  53. package/rules/common/C029_catch_block_logging/analyzer-backup.js +0 -426
  54. package/rules/common/C029_catch_block_logging/analyzer-fixed.js +0 -130
  55. package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +0 -487
  56. package/rules/common/C029_catch_block_logging/analyzer-simple.js +0 -110
  57. package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +0 -441
  58. package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +0 -127
  59. package/rules/common/C029_catch_block_logging/ast-analyzer.js +0 -133
  60. package/rules/common/C029_catch_block_logging/cfg-analyzer.js +0 -408
  61. package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +0 -454
  62. package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +0 -700
  63. package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +0 -568
  64. package/rules/common/C029_catch_block_logging/semantic-analyzer.js +0 -459
@@ -1,454 +0,0 @@
1
- /**
2
- * C029 Data Flow Analyzer - IDE-Level Detection
3
- *
4
- * Uses Control Flow Graph and Data Flow Analysis to detect unused error variables
5
- * Similar to how IDEs detect unused variables with gray highlighting
6
- */
7
-
8
- const astRegistry = require('../../../core/ast-modules');
9
-
10
- class C029DataFlowAnalyzer {
11
- constructor() {
12
- this.ruleId = 'C029';
13
- this.ruleName = 'Data Flow Enhanced Catch Block Analysis';
14
- this.description = 'IDE-level detection of unused error variables in catch blocks';
15
- this.astRegistry = astRegistry;
16
- }
17
-
18
- async analyze(files, language, options = {}) {
19
- const violations = [];
20
- console.log(`🧠 C029 Data Flow: Analyzing ${files.length} files with IDE-level detection...`);
21
-
22
- for (const filePath of files) {
23
- if (options.verbose) {
24
- console.log(`🧠 Data Flow analyzing ${path.basename(filePath)}`);
25
- }
26
-
27
- try {
28
- const content = require('fs').readFileSync(filePath, 'utf8');
29
- const fileLanguage = this.getLanguageFromPath(filePath);
30
-
31
- // Parse with full AST
32
- const ast = await this.parseAST(content, fileLanguage, filePath);
33
-
34
- if (ast) {
35
- const dataFlowViolations = this.analyzeDataFlow(ast, filePath, content);
36
- violations.push(...dataFlowViolations);
37
- }
38
-
39
- } catch (error) {
40
- console.warn(`C029 Data Flow skipping ${filePath}: ${error.message}`);
41
- }
42
- }
43
-
44
- return violations;
45
- }
46
-
47
- async parseAST(content, language, filePath) {
48
- try {
49
- if (!this.astRegistry || !this.astRegistry.parseCode) {
50
- return null;
51
- }
52
-
53
- return await this.astRegistry.parseCode(content, language, filePath);
54
- } catch (error) {
55
- return null;
56
- }
57
- }
58
-
59
- /**
60
- * Core Data Flow Analysis - IDE-level detection
61
- */
62
- analyzeDataFlow(ast, filePath, content) {
63
- const violations = [];
64
- const isTestFile = this.isTestFile(filePath);
65
-
66
- // Find all catch clauses in the AST
67
- const catchClauses = this.findCatchClauses(ast);
68
-
69
- for (const catchClause of catchClauses) {
70
- const analysis = this.analyzeCatchClauseDataFlow(catchClause, ast, filePath, isTestFile);
71
-
72
- if (analysis.isViolation) {
73
- violations.push({
74
- ruleId: this.ruleId,
75
- file: filePath,
76
- line: catchClause.loc ? catchClause.loc.start.line : 1,
77
- column: catchClause.loc ? catchClause.loc.start.column + 1 : 1,
78
- message: analysis.message,
79
- severity: analysis.severity,
80
- code: this.getCodeFromNode(catchClause, content),
81
- type: analysis.type,
82
- confidence: analysis.confidence,
83
- suggestion: analysis.suggestion
84
- });
85
- }
86
- }
87
-
88
- return violations;
89
- }
90
-
91
- /**
92
- * Find all catch clauses using AST traversal
93
- */
94
- findCatchClauses(ast) {
95
- const catchClauses = [];
96
-
97
- const visitor = {
98
- visitCatchClause: (node) => {
99
- catchClauses.push(node);
100
- return true; // Continue traversal
101
- }
102
- };
103
-
104
- this.traverseAST(ast, visitor);
105
- return catchClauses;
106
- }
107
-
108
- /**
109
- * Data Flow Analysis for single catch clause
110
- * Similar to IDE unused variable detection
111
- */
112
- analyzeCatchClauseDataFlow(catchClause, ast, filePath, isTestFile) {
113
- const errorParam = catchClause.param;
114
- const catchBody = catchClause.body;
115
-
116
- // 1. No error parameter - likely intentional ignore
117
- if (!errorParam) {
118
- return { isViolation: false, reason: 'no_param' };
119
- }
120
-
121
- // 2. Check for explicit ignore patterns
122
- if (this.isExplicitlyIgnored(errorParam)) {
123
- return { isViolation: false, reason: 'explicitly_ignored' };
124
- }
125
-
126
- // 3. Empty catch block
127
- if (!catchBody || !catchBody.body || catchBody.body.length === 0) {
128
- return {
129
- isViolation: true,
130
- type: 'empty_catch_block',
131
- message: 'Empty catch block - error is silently ignored',
132
- severity: 'error',
133
- confidence: 1.0,
134
- suggestion: 'Add error logging or explicit ignore comment'
135
- };
136
- }
137
-
138
- // 4. DATA FLOW ANALYSIS - Track error variable usage
139
- const errorUsage = this.analyzeErrorVariableUsage(errorParam, catchBody, isTestFile);
140
-
141
- if (!errorUsage.isUsed) {
142
- return {
143
- isViolation: true,
144
- type: 'unused_error_variable',
145
- message: `Error variable '${errorParam.name}' is declared but never used - silent error handling`,
146
- severity: 'error',
147
- confidence: 0.95,
148
- suggestion: 'Use the error variable for logging/handling or rename to indicate intentional ignore'
149
- };
150
- }
151
-
152
- if (errorUsage.isUsedButNotProcessed) {
153
- return {
154
- isViolation: true,
155
- type: 'error_not_processed',
156
- message: `Error variable '${errorParam.name}' is referenced but not processed - may hide bugs`,
157
- severity: 'warning',
158
- confidence: 0.8,
159
- suggestion: 'Ensure error is properly logged, handled, or rethrown'
160
- };
161
- }
162
-
163
- return { isViolation: false, reason: 'properly_handled' };
164
- }
165
-
166
- /**
167
- * IDE-Level Error Variable Usage Analysis
168
- * Tracks how error variable is used within catch block
169
- */
170
- analyzeErrorVariableUsage(errorParam, catchBody, isTestFile) {
171
- const errorName = errorParam.name;
172
- const usageAnalysis = {
173
- isUsed: false,
174
- isUsedButNotProcessed: false,
175
- usageTypes: [],
176
- references: []
177
- };
178
-
179
- // Find all references to error variable
180
- const references = this.findVariableReferences(errorName, catchBody);
181
- usageAnalysis.references = references;
182
- usageAnalysis.isUsed = references.length > 0;
183
-
184
- if (!usageAnalysis.isUsed) {
185
- return usageAnalysis;
186
- }
187
-
188
- // Analyze each reference to determine if error is processed
189
- for (const ref of references) {
190
- const usageType = this.categorizeErrorUsage(ref, isTestFile);
191
- usageAnalysis.usageTypes.push(usageType);
192
- }
193
-
194
- // Determine if error is properly processed
195
- const hasValidProcessing = usageAnalysis.usageTypes.some(type =>
196
- ['logging', 'rethrowing', 'test_assertion', 'error_handling', 'redux_thunk'].includes(type)
197
- );
198
-
199
- const hasOnlyTrivialUsage = usageAnalysis.usageTypes.every(type =>
200
- ['property_access', 'function_param', 'unused_assignment'].includes(type)
201
- );
202
-
203
- if (hasOnlyTrivialUsage) {
204
- usageAnalysis.isUsedButNotProcessed = true;
205
- }
206
-
207
- return usageAnalysis;
208
- }
209
-
210
- /**
211
- * Find all references to a variable within AST node
212
- * Similar to IDE "Find All References"
213
- */
214
- findVariableReferences(variableName, node) {
215
- const references = [];
216
-
217
- const visitor = {
218
- visitIdentifier: (identifierNode) => {
219
- if (identifierNode.name === variableName) {
220
- references.push(identifierNode);
221
- }
222
- return true;
223
- }
224
- };
225
-
226
- this.traverseAST(node, visitor);
227
- return references;
228
- }
229
-
230
- /**
231
- * Categorize how error variable is being used
232
- * Determines if usage is meaningful or trivial
233
- */
234
- categorizeErrorUsage(referenceNode, isTestFile) {
235
- const parent = referenceNode.parent;
236
-
237
- if (!parent) return 'unknown';
238
-
239
- // Check parent node type to understand usage context
240
- switch (parent.type) {
241
- case 'ThrowStatement':
242
- return 'rethrowing';
243
-
244
- case 'CallExpression':
245
- // Check if it's a logging call
246
- if (this.isLoggingCall(parent)) {
247
- return 'logging';
248
- }
249
- // Check if it's test assertion
250
- if (isTestFile && this.isTestAssertion(parent)) {
251
- return 'test_assertion';
252
- }
253
- // Check if it's error handler call
254
- if (this.isErrorHandlerCall(parent)) {
255
- return 'error_handling';
256
- }
257
- // Check if it's Redux thunk
258
- if (this.isReduxThunkCall(parent)) {
259
- return 'redux_thunk';
260
- }
261
- return 'function_param';
262
-
263
- case 'MemberExpression':
264
- // Accessing error properties (e.message, e.stack)
265
- if (parent.object === referenceNode) {
266
- return 'property_access';
267
- }
268
- return 'member_access';
269
-
270
- case 'AssignmentExpression':
271
- return 'assignment';
272
-
273
- case 'ReturnStatement':
274
- return 'return_value';
275
-
276
- default:
277
- return 'other_usage';
278
- }
279
- }
280
-
281
- /**
282
- * Check if call expression is logging
283
- */
284
- isLoggingCall(callNode) {
285
- if (callNode.type !== 'CallExpression') return false;
286
-
287
- const callee = callNode.callee;
288
-
289
- // console.error, console.log, etc.
290
- if (callee.type === 'MemberExpression' &&
291
- callee.object && callee.object.name === 'console') {
292
- return ['log', 'error', 'warn', 'debug'].includes(callee.property.name);
293
- }
294
-
295
- // logger.error, log.error, etc.
296
- if (callee.type === 'MemberExpression' &&
297
- callee.property && ['error', 'warn', 'log'].includes(callee.property.name)) {
298
- return true;
299
- }
300
-
301
- return false;
302
- }
303
-
304
- /**
305
- * Check if call expression is test assertion
306
- */
307
- isTestAssertion(callNode) {
308
- if (callNode.type !== 'CallExpression') return false;
309
-
310
- const callee = callNode.callee;
311
-
312
- // expect() calls
313
- if (callee.name === 'expect') return true;
314
-
315
- // assert() calls
316
- if (callee.name === 'assert') return true;
317
-
318
- // should() calls
319
- if (callee.type === 'MemberExpression' &&
320
- callee.property && callee.property.name === 'should') {
321
- return true;
322
- }
323
-
324
- return false;
325
- }
326
-
327
- /**
328
- * Check if call expression is error handler
329
- */
330
- isErrorHandlerCall(callNode) {
331
- if (callNode.type !== 'CallExpression') return false;
332
-
333
- const callee = callNode.callee;
334
- if (callee.type === 'Identifier') {
335
- const name = callee.name.toLowerCase();
336
- return name.includes('handle') || name.includes('process') ||
337
- name.includes('report') || name.includes('track');
338
- }
339
-
340
- return false;
341
- }
342
-
343
- /**
344
- * Check if call expression is Redux thunk pattern
345
- */
346
- isReduxThunkCall(callNode) {
347
- if (callNode.type !== 'CallExpression') return false;
348
-
349
- const callee = callNode.callee;
350
- if (callee.type === 'Identifier') {
351
- return ['rejectWithValue', 'dispatch', 'handleAxiosError'].includes(callee.name);
352
- }
353
-
354
- return false;
355
- }
356
-
357
- /**
358
- * Check for explicit ignore patterns
359
- */
360
- isExplicitlyIgnored(errorParam) {
361
- if (!errorParam || !errorParam.name) return false;
362
-
363
- const name = errorParam.name.toLowerCase();
364
-
365
- // Common ignore patterns
366
- const ignorePatterns = ['_', 'ignored', 'ignore', 'unused'];
367
- return ignorePatterns.includes(name);
368
- }
369
-
370
- /**
371
- * Test file detection
372
- */
373
- isTestFile(filePath) {
374
- const testPatterns = [
375
- '__tests__', '.test.', '.spec.', '/test/', '/tests/',
376
- 'test-', 'spec-', '.test/', '.spec/'
377
- ];
378
-
379
- return testPatterns.some(pattern => filePath.includes(pattern));
380
- }
381
-
382
- /**
383
- * Get source code from AST node
384
- */
385
- getCodeFromNode(node, content) {
386
- if (!node.loc) return '';
387
-
388
- const lines = content.split('\n');
389
- const startLine = node.loc.start.line - 1;
390
- const endLine = node.loc.end.line - 1;
391
-
392
- if (startLine === endLine) {
393
- return lines[startLine].slice(node.loc.start.column, node.loc.end.column);
394
- }
395
-
396
- const result = [lines[startLine].slice(node.loc.start.column)];
397
- for (let i = startLine + 1; i < endLine; i++) {
398
- result.push(lines[i]);
399
- }
400
- result.push(lines[endLine].slice(0, node.loc.end.column));
401
-
402
- return result.join('\n');
403
- }
404
-
405
- /**
406
- * AST Traversal utility
407
- */
408
- traverseAST(node, visitor) {
409
- if (!node || typeof node !== 'object') return;
410
-
411
- // Call appropriate visitor method
412
- const nodeType = node.type;
413
- if (visitor[`visit${nodeType}`]) {
414
- const shouldContinue = visitor[`visit${nodeType}`](node);
415
- if (!shouldContinue) return;
416
- }
417
-
418
- // Generic visit method
419
- if (visitor.visit) {
420
- const shouldContinue = visitor.visit(node);
421
- if (!shouldContinue) return;
422
- }
423
-
424
- // Traverse children
425
- for (const key in node) {
426
- if (key === 'parent') continue; // Avoid cycles
427
-
428
- const child = node[key];
429
- if (Array.isArray(child)) {
430
- for (const item of child) {
431
- if (item && typeof item === 'object') {
432
- item.parent = node; // Add parent reference
433
- this.traverseAST(item, visitor);
434
- }
435
- }
436
- } else if (child && typeof child === 'object') {
437
- child.parent = node; // Add parent reference
438
- this.traverseAST(child, visitor);
439
- }
440
- }
441
- }
442
-
443
- getLanguageFromPath(filePath) {
444
- const ext = filePath.split('.').pop().toLowerCase();
445
- const languageMap = {
446
- 'js': 'javascript', 'jsx': 'javascript',
447
- 'ts': 'typescript', 'tsx': 'typescript',
448
- 'mjs': 'javascript', 'cjs': 'javascript'
449
- };
450
- return languageMap[ext] || 'javascript';
451
- }
452
- }
453
-
454
- module.exports = new C029DataFlowAnalyzer();