@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,6 +1,6 @@
1
1
  /**
2
- * Heuristic Analysis Engine Plugin
3
- * Following Rule C005: Single responsibility - Pattern-based analysis with AST enhancement
2
+ * Heuristic Analysis Engine Plugin with ts-morph Core Integration
3
+ * Following Rule C005: Single responsibility - Pattern-based analysis with ts-morph core
4
4
  * Following Rule C014: Dependency injection - implements interface
5
5
  * Following Rule C015: Use domain language - clear heuristic analysis terms
6
6
  */
@@ -9,21 +9,32 @@ const AnalysisEngineInterface = require('../core/interfaces/analysis-engine.inte
9
9
  const ASTModuleRegistry = require('../core/ast-modules/index');
10
10
  const dependencyChecker = require('../core/dependency-checker');
11
11
  const SunlintRuleAdapter = require('../core/adapters/sunlint-rule-adapter');
12
+ const SemanticEngine = require('../core/semantic-engine');
13
+ const SemanticRuleBase = require('../core/semantic-rule-base');
14
+ const { getInstance: getUnifiedRegistry } = require('../core/unified-rule-registry');
12
15
  const fs = require('fs');
13
16
  const path = require('path');
14
17
 
15
18
  class HeuristicEngine extends AnalysisEngineInterface {
16
19
  constructor() {
17
- super('heuristic', '2.0', ['typescript', 'javascript', 'dart', 'swift', 'kotlin', 'java', 'python', 'go', 'rust', 'all']);
20
+ super('heuristic', '3.0', ['typescript', 'javascript', 'dart', 'swift', 'kotlin', 'java', 'python', 'go', 'rust', 'all']);
18
21
 
19
22
  this.ruleAnalyzers = new Map();
20
23
  this.supportedRulesList = [];
21
24
  this.ruleAdapter = SunlintRuleAdapter.getInstance();
22
25
  this.astRegistry = ASTModuleRegistry;
26
+
27
+ // ts-morph as core technology for heuristic engine
28
+ this.semanticEngine = new SemanticEngine();
29
+ this.semanticRules = new Map();
30
+ this.symbolTableEnabled = false;
31
+
32
+ // Unified rule registry
33
+ this.unifiedRegistry = getUnifiedRegistry();
23
34
  }
24
35
 
25
36
  /**
26
- * Initialize Heuristic engine with configuration
37
+ * Initialize Heuristic engine with ts-morph core and configuration
27
38
  * Following Rule C006: Verb-noun naming
28
39
  * @param {Object} config - Engine configuration
29
40
  */
@@ -32,18 +43,27 @@ class HeuristicEngine extends AnalysisEngineInterface {
32
43
  // Store verbosity setting
33
44
  this.verbose = config?.verbose || false;
34
45
 
46
+ // Initialize unified rule registry
47
+ await this.unifiedRegistry.initialize({ verbose: this.verbose });
48
+
35
49
  // Check for optional AST dependencies
36
50
  dependencyChecker.checkAndNotify('ast');
37
51
 
52
+ // Initialize ts-morph Symbol Table (core requirement)
53
+ await this.initializeSymbolTable(config);
54
+
38
55
  // Initialize rule adapter
39
56
  await this.ruleAdapter.initialize();
40
57
 
41
- // Scan for available rule analyzers
42
- await this.scanRuleAnalyzers(config);
58
+ // Load available rules from unified registry
59
+ await this.loadRulesFromRegistry(config);
43
60
 
44
61
  this.initialized = true;
45
62
  if (this.verbose) {
46
- console.log(`🔍 Heuristic engine v2.0 initialized with ${this.supportedRulesList.length} rules (AST-enhanced)`);
63
+ console.log(`🔍 Heuristic engine v3.0 initialized:`);
64
+ console.log(` 📊 Total rules: ${this.supportedRulesList.length}`);
65
+ console.log(` 🧠 Symbol Table: ${this.symbolTableInitialized ? 'enabled' : 'disabled'}`);
66
+ console.log(` 🔧 Semantic rules: ${this.semanticRules.size}`);
47
67
  }
48
68
 
49
69
  } catch (error) {
@@ -53,7 +73,171 @@ class HeuristicEngine extends AnalysisEngineInterface {
53
73
  }
54
74
 
55
75
  /**
56
- * Scan for available rule analyzers
76
+ * Initialize ts-morph Symbol Table as core requirement
77
+ */
78
+ async initializeSymbolTable(config) {
79
+ const projectPath = config?.projectPath || process.cwd();
80
+
81
+ try {
82
+ // ts-morph is now a core dependency
83
+ const success = await this.semanticEngine.initialize(projectPath);
84
+
85
+ if (success) {
86
+ this.semanticEnabled = true;
87
+ this.symbolTableInitialized = true;
88
+ if (this.verbose) {
89
+ console.log(`🧠 Symbol Table initialized for: ${projectPath}`);
90
+ }
91
+ } else {
92
+ if (this.verbose) {
93
+ console.warn('⚠️ Symbol Table initialization failed, using fallback mode');
94
+ }
95
+ }
96
+
97
+ } catch (error) {
98
+ if (this.verbose) {
99
+ console.warn('⚠️ ts-morph Symbol Table unavailable:', error.message);
100
+ console.warn('⚠️ Falling back to traditional AST/regex analysis only');
101
+ }
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Load rules from unified registry instead of scanning directories
107
+ * Following Rule C006: Verb-noun naming
108
+ * @param {Object} config - Engine configuration
109
+ */
110
+ async loadRulesFromRegistry(config = {}) {
111
+ try {
112
+ // Get rules supported by heuristic engine from unified registry
113
+ const supportedRules = this.unifiedRegistry.getRulesForEngine('heuristic');
114
+
115
+ if (this.verbose) {
116
+ console.log(`🔍 [HeuristicEngine] Found ${supportedRules.length} rules from unified registry`);
117
+ }
118
+
119
+ // Load each rule
120
+ for (const ruleDefinition of supportedRules) {
121
+ await this.loadRuleFromDefinition(ruleDefinition);
122
+ }
123
+
124
+ // Manually load C047 if needed (DEPRECATED - C047 now in enhanced registry)
125
+ // if (!this.semanticRules.has('C047') && !this.ruleAnalyzers.has('C047')) {
126
+ // await this.manuallyLoadC047();
127
+ // }
128
+
129
+ if (this.verbose) {
130
+ console.log(`✅ [HeuristicEngine] Loaded ${this.supportedRulesList.length} rules from unified registry`);
131
+ }
132
+
133
+ } catch (error) {
134
+ console.error('Failed to load rules from registry:', error.message);
135
+ // Fallback to old scanning method
136
+ await this.scanRuleAnalyzers(config);
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Load a single rule from its definition
142
+ * @param {Object} ruleDefinition - Rule definition from unified registry
143
+ */
144
+ async loadRuleFromDefinition(ruleDefinition) {
145
+ const ruleId = ruleDefinition.id;
146
+
147
+ try {
148
+ // Resolve best analyzer path for this engine
149
+ const analyzerPath = this.unifiedRegistry.resolveAnalyzerPath(ruleId, 'heuristic');
150
+
151
+ if (!analyzerPath) {
152
+ if (this.verbose) {
153
+ console.warn(`⚠️ [HeuristicEngine] No compatible analyzer found for ${ruleId}`);
154
+ }
155
+ return;
156
+ }
157
+
158
+ // Determine analyzer type from path and strategy
159
+ const strategy = ruleDefinition.strategy.preferred;
160
+ const category = ruleDefinition.category;
161
+
162
+ if (strategy === 'semantic' && this.symbolTableInitialized) {
163
+ // Load as semantic rule
164
+ await this.loadSemanticRule(ruleId, analyzerPath, { category });
165
+ } else {
166
+ // Load as traditional rule (ast/regex)
167
+ await this.loadTraditionalRule(ruleId, analyzerPath, { category, type: strategy });
168
+ }
169
+
170
+ } catch (error) {
171
+ if (this.verbose) {
172
+ console.warn(`⚠️ [HeuristicEngine] Failed to load rule ${ruleId}:`, error.message);
173
+ }
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Load semantic rule from analyzer path
179
+ * @param {string} ruleId - Rule ID
180
+ * @param {string} analyzerPath - Path to analyzer file
181
+ * @param {Object} metadata - Rule metadata
182
+ */
183
+ async loadSemanticRule(ruleId, analyzerPath, metadata) {
184
+ try {
185
+ const SemanticRuleClass = require(analyzerPath);
186
+
187
+ // Verify it extends SemanticRuleBase
188
+ if (this.isSemanticRule(SemanticRuleClass)) {
189
+ await this.registerSemanticRule(ruleId, SemanticRuleClass, {
190
+ path: analyzerPath,
191
+ category: metadata.category
192
+ });
193
+
194
+ if (this.verbose) {
195
+ console.log(`🧠 [HeuristicEngine] Loaded semantic rule: ${ruleId}`);
196
+ }
197
+ } else {
198
+ // Not a semantic rule, fallback to traditional
199
+ await this.loadTraditionalRule(ruleId, analyzerPath, metadata);
200
+ }
201
+
202
+ } catch (error) {
203
+ if (this.verbose) {
204
+ console.warn(`⚠️ [HeuristicEngine] Failed to load semantic rule ${ruleId}:`, error.message);
205
+ }
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Load traditional rule (ast/regex) from analyzer path
211
+ * @param {string} ruleId - Rule ID
212
+ * @param {string} analyzerPath - Path to analyzer file
213
+ * @param {Object} metadata - Rule metadata
214
+ */
215
+ async loadTraditionalRule(ruleId, analyzerPath, metadata) {
216
+ try {
217
+ const analyzerModule = require(analyzerPath);
218
+ const AnalyzerClass = analyzerModule.default || analyzerModule;
219
+
220
+ this.registerTraditionalRule(ruleId, AnalyzerClass, {
221
+ path: analyzerPath,
222
+ category: metadata.category,
223
+ folder: ruleId, // Add folder name for config loading
224
+ type: metadata.type || 'regex'
225
+ });
226
+
227
+ if (this.verbose) {
228
+ console.log(`🔧 [HeuristicEngine] Loaded ${metadata.type} rule: ${ruleId}`);
229
+ }
230
+
231
+ } catch (error) {
232
+ if (this.verbose) {
233
+ console.warn(`⚠️ [HeuristicEngine] Failed to load traditional rule ${ruleId}:`, error.message);
234
+ }
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Scan for available rule analyzers with semantic support
240
+ * Priority: semantic > ast > regex
57
241
  * Following Rule C006: Verb-noun naming
58
242
  */
59
243
  async scanRuleAnalyzers(config = {}) {
@@ -80,80 +264,195 @@ class HeuristicEngine extends AnalysisEngineInterface {
80
264
  .map(dirent => dirent.name);
81
265
 
82
266
  for (const ruleFolder of ruleFolders) {
83
- const ruleId = this.extractRuleIdFromFolder(ruleFolder);
267
+ const ruleId = ruleFolder; // Use folder name directly as rule ID
268
+ const rulePath = path.join(categoryPath, ruleFolder);
84
269
 
85
- // Priority 1: Check for AST analyzer (enhanced accuracy)
86
- const astAnalyzerPath = path.join(categoryPath, ruleFolder, 'ast-analyzer.js');
87
- const regexAnalyzerPath = path.join(categoryPath, ruleFolder, 'analyzer.js');
88
-
89
- let selectedAnalyzer = null;
90
- let analyzerPath = null;
91
- let analyzerType = 'regex';
92
-
93
- // Try AST analyzer first
94
- if (fs.existsSync(astAnalyzerPath)) {
95
- try {
96
- const analyzerModule = require(astAnalyzerPath);
97
- selectedAnalyzer = analyzerModule.default || analyzerModule;
98
- analyzerPath = astAnalyzerPath;
99
- analyzerType = 'ast';
100
- } catch (error) {
101
- console.debug(`AST analyzer for ${ruleId} failed to load, falling back to regex:`, error.message);
102
- }
103
- }
270
+ await this.loadRuleAnalyzer(ruleId, rulePath, categoryFolder);
271
+ }
272
+ }
273
+
274
+ } catch (error) {
275
+ console.warn('⚠️ Error scanning rule analyzers:', error.message);
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Manually load C047 semantic rule (special case)
281
+ */
282
+ async manuallyLoadC047() {
283
+ try {
284
+ if (this.verbose) {
285
+ console.log(`[DEBUG] 🔬 Manually loading C047 semantic rule...`);
286
+ }
287
+
288
+ const c047Path = path.resolve(__dirname, '../rules/common/C047_no_duplicate_retry_logic/c047-semantic-rule.js');
289
+
290
+ if (fs.existsSync(c047Path)) {
291
+ const C047SemanticRule = require(c047Path);
292
+ const instance = new C047SemanticRule();
293
+
294
+ // Register as semantic rule
295
+ await this.registerSemanticRule('C047', C047SemanticRule, {
296
+ path: c047Path,
297
+ category: 'common',
298
+ type: 'semantic',
299
+ description: 'C047 - No Duplicate Retry Logic (Semantic Analysis)'
300
+ });
301
+
302
+ if (this.verbose) {
303
+ console.log(`[DEBUG] ✅ C047 semantic rule loaded successfully`);
304
+ }
305
+ } else {
306
+ console.warn(`⚠️ C047 semantic rule not found at: ${c047Path}`);
307
+ }
308
+
309
+ } catch (error) {
310
+ console.warn(`⚠️ Failed to manually load C047:`, error.message);
311
+ }
312
+ }
313
+
314
+ /**
315
+ * Load rule analyzer with semantic priority
316
+ */
317
+ async loadRuleAnalyzer(ruleId, rulePath, categoryFolder) {
318
+ // Analyzer priority: semantic > ast > regex
319
+ const analyzerCandidates = [
320
+ { path: path.join(rulePath, 'semantic-analyzer.js'), type: 'semantic' },
321
+ { path: path.join(rulePath, 'ast-analyzer.js'), type: 'ast' },
322
+ { path: path.join(rulePath, 'regex-analyzer.js'), type: 'regex' },
323
+ { path: path.join(rulePath, 'analyzer.js'), type: 'regex' } // legacy fallback
324
+ ];
325
+
326
+ let selectedAnalyzer = null;
327
+ let analyzerPath = null;
328
+ let analyzerType = null;
329
+
330
+ // Try semantic analyzer first if Symbol Table available
331
+ if (this.symbolTableInitialized) {
332
+ const semanticCandidate = analyzerCandidates[0];
333
+ if (fs.existsSync(semanticCandidate.path)) {
334
+ try {
335
+ const analyzerModule = require(semanticCandidate.path);
336
+ selectedAnalyzer = analyzerModule.default || analyzerModule;
337
+ analyzerPath = semanticCandidate.path;
338
+ analyzerType = 'semantic';
104
339
 
105
- // Fallback to regex analyzer
106
- if (!selectedAnalyzer && fs.existsSync(regexAnalyzerPath)) {
107
- try {
108
- const analyzerModule = require(regexAnalyzerPath);
109
- selectedAnalyzer = analyzerModule.default || analyzerModule;
110
- analyzerPath = regexAnalyzerPath;
111
- analyzerType = 'regex';
112
- } catch (error) {
113
- console.warn(`⚠️ Failed to load analyzer for ${ruleId}:`, error.message);
114
- }
340
+ // Verify it extends SemanticRuleBase
341
+ if (this.isSemanticRule(selectedAnalyzer)) {
342
+ await this.registerSemanticRule(ruleId, selectedAnalyzer, {
343
+ path: analyzerPath,
344
+ category: categoryFolder
345
+ });
346
+ return; // Successfully registered semantic rule
115
347
  }
116
-
117
- if (selectedAnalyzer) {
118
- // Check if it's a class constructor, instance, or factory function
119
- if (typeof selectedAnalyzer === 'function') {
120
- // It's a class constructor
121
- this.ruleAnalyzers.set(ruleId, {
122
- path: analyzerPath,
123
- class: selectedAnalyzer,
124
- folder: ruleFolder,
125
- category: categoryFolder,
126
- type: 'class',
127
- analyzerType: analyzerType // 'ast' or 'regex'
128
- });
129
- this.supportedRulesList.push(ruleId);
130
- } else if (selectedAnalyzer && typeof selectedAnalyzer === 'object' && selectedAnalyzer.analyze) {
131
- // It's an analyzer instance with analyze method
132
- this.ruleAnalyzers.set(ruleId, {
133
- path: analyzerPath,
134
- instance: selectedAnalyzer,
135
- folder: ruleFolder,
136
- category: categoryFolder,
137
- type: 'instance',
138
- analyzerType: analyzerType // 'ast' or 'regex'
139
- });
140
- this.supportedRulesList.push(ruleId);
141
- } else {
142
- console.warn(`⚠️ Analyzer for ${ruleId} has unsupported format:`, typeof selectedAnalyzer);
143
- }
348
+ } catch (error) {
349
+ console.debug(`Semantic analyzer for ${ruleId} failed to load:`, error.message);
350
+ }
351
+ }
352
+ }
353
+
354
+ // Fall back to AST analyzer
355
+ const astCandidate = analyzerCandidates[1];
356
+ if (fs.existsSync(astCandidate.path)) {
357
+ try {
358
+ const analyzerModule = require(astCandidate.path);
359
+ selectedAnalyzer = analyzerModule.default || analyzerModule;
360
+ analyzerPath = astCandidate.path;
361
+ analyzerType = 'ast';
362
+ } catch (error) {
363
+ console.debug(`AST analyzer for ${ruleId} failed to load:`, error.message);
364
+ }
365
+ }
366
+
367
+ // Fall back to regex analyzer
368
+ if (!selectedAnalyzer) {
369
+ for (const regexCandidate of analyzerCandidates.slice(2)) {
370
+ if (fs.existsSync(regexCandidate.path)) {
371
+ try {
372
+ const analyzerModule = require(regexCandidate.path);
373
+ selectedAnalyzer = analyzerModule.default || analyzerModule;
374
+ analyzerPath = regexCandidate.path;
375
+ analyzerType = 'regex';
376
+ break;
377
+ } catch (error) {
378
+ console.debug(`Regex analyzer for ${ruleId} failed to load:`, error.message);
144
379
  }
145
380
  }
146
381
  }
382
+ }
383
+
384
+ // Register traditional (non-semantic) analyzer
385
+ if (selectedAnalyzer) {
386
+ this.registerTraditionalRule(ruleId, selectedAnalyzer, {
387
+ path: analyzerPath,
388
+ category: categoryFolder,
389
+ folder: fullRuleId, // Add folder name for config loading
390
+ type: analyzerType
391
+ });
392
+ }
393
+ }
147
394
 
148
- if (config.verbose) {
395
+ /**
396
+ * Check if analyzer is a semantic rule
397
+ */
398
+ isSemanticRule(analyzerClass) {
399
+ if (typeof analyzerClass !== 'function') return false;
400
+
401
+ try {
402
+ const instance = new analyzerClass(analyzerClass.name);
403
+ return instance instanceof SemanticRuleBase;
404
+ } catch (error) {
405
+ return false;
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Register semantic rule
411
+ */
412
+ async registerSemanticRule(ruleId, analyzerClass, metadata) {
413
+ try {
414
+ const instance = new analyzerClass(ruleId);
415
+ instance.initialize(this.semanticEngine);
416
+
417
+ this.semanticRules.set(ruleId, {
418
+ instance,
419
+ metadata,
420
+ type: 'semantic'
421
+ });
422
+
423
+ this.supportedRulesList.push(ruleId);
424
+
149
425
  if (this.verbose) {
150
- console.log(`🔍 Found ${this.supportedRulesList.length} heuristic analyzers`);
151
- console.log(`🔍 Supported rules: ${this.supportedRulesList.join(', ')}`);
152
- }
426
+ console.log(`🧠 Registered semantic rule: ${ruleId}`);
153
427
  }
154
428
 
155
429
  } catch (error) {
156
- console.error('Failed to scan rule analyzers:', error.message);
430
+ console.warn(`⚠️ Failed to register semantic rule ${ruleId}:`, error.message);
431
+ }
432
+ }
433
+
434
+ /**
435
+ * Register traditional (heuristic/AST/regex) rule
436
+ */
437
+ registerTraditionalRule(ruleId, analyzer, metadata) {
438
+ if (typeof analyzer === 'function') {
439
+ // Class constructor
440
+ this.ruleAnalyzers.set(ruleId, {
441
+ ...metadata,
442
+ class: analyzer,
443
+ type: 'class'
444
+ });
445
+ this.supportedRulesList.push(ruleId);
446
+ } else if (analyzer && typeof analyzer === 'object' && analyzer.analyze) {
447
+ // Instance with analyze method
448
+ this.ruleAnalyzers.set(ruleId, {
449
+ ...metadata,
450
+ instance: analyzer,
451
+ type: 'instance'
452
+ });
453
+ this.supportedRulesList.push(ruleId);
454
+ } else {
455
+ console.warn(`⚠️ Analyzer for ${ruleId} has unsupported format:`, typeof analyzer);
157
456
  }
158
457
  }
159
458
 
@@ -169,6 +468,60 @@ class HeuristicEngine extends AnalysisEngineInterface {
169
468
  return match ? match[1] : folderName;
170
469
  }
171
470
 
471
+ /**
472
+ * Get full rule ID from short rule ID (C029 -> C029_catch_block_logging)
473
+ * @param {string} ruleId - Short rule ID
474
+ * @returns {string} Full rule ID or original if not found
475
+ */
476
+ getFullRuleId(ruleId) {
477
+ // Check exact match first
478
+ if (this.ruleAnalyzers.has(ruleId)) {
479
+ return ruleId;
480
+ }
481
+
482
+ // Find full rule ID that starts with short rule ID
483
+ const shortRulePattern = new RegExp(`^${ruleId}_`);
484
+ const fullRuleId = Array.from(this.ruleAnalyzers.keys()).find(fullId => shortRulePattern.test(fullId));
485
+
486
+ return fullRuleId || ruleId; // Return original if not found
487
+ }
488
+
489
+ /**
490
+ * Check if a rule is supported by this engine
491
+ * Following Rule C006: Verb-noun naming
492
+ * @param {string} ruleId - Rule ID to check
493
+ * @returns {boolean} True if rule is supported
494
+ */
495
+ isRuleSupported(ruleId) {
496
+ // Special case: C047 is always supported (loaded on-demand)
497
+ if (ruleId === 'C047') {
498
+ return true;
499
+ }
500
+
501
+ // Use unified registry for primary lookup
502
+ if (this.unifiedRegistry && this.unifiedRegistry.initialized) {
503
+ return this.unifiedRegistry.isRuleSupported(ruleId, 'heuristic');
504
+ }
505
+
506
+ // Fallback to original logic for backward compatibility
507
+ return this.supportedRulesList.includes(ruleId) ||
508
+ this.semanticRules.has(ruleId) ||
509
+ this.ruleAnalyzers.has(ruleId) ||
510
+ this.checkShortRuleIdMatch(ruleId);
511
+ }
512
+
513
+ /**
514
+ * Check short rule ID matches (backward compatibility)
515
+ * @param {string} ruleId - Short rule ID (e.g., C029)
516
+ * @returns {boolean} True if matches any full rule ID
517
+ */
518
+ checkShortRuleIdMatch(ruleId) {
519
+ const shortRulePattern = new RegExp(`^${ruleId}_`);
520
+ return this.supportedRulesList.some(fullRuleId => shortRulePattern.test(fullRuleId)) ||
521
+ Array.from(this.semanticRules.keys()).some(fullRuleId => shortRulePattern.test(fullRuleId)) ||
522
+ Array.from(this.ruleAnalyzers.keys()).some(fullRuleId => shortRulePattern.test(fullRuleId));
523
+ }
524
+
172
525
  /**
173
526
  * Analyze files using heuristic patterns
174
527
  * Following Rule C006: Verb-noun naming
@@ -202,6 +555,14 @@ class HeuristicEngine extends AnalysisEngineInterface {
202
555
  const filesByLanguage = this.groupFilesByLanguage(files);
203
556
 
204
557
  for (const rule of rules) {
558
+ // Special case: Load C047 semantic rule on-demand
559
+ if (rule.id === 'C047' && !this.semanticRules.has('C047')) {
560
+ if (options.verbose) {
561
+ console.log(`🔬 [HeuristicEngine] Loading C047 semantic rule on-demand...`);
562
+ }
563
+ await this.manuallyLoadC047();
564
+ }
565
+
205
566
  if (!this.isRuleSupported(rule.id)) {
206
567
  if (options.verbose) {
207
568
  console.warn(`⚠️ Rule ${rule.id} not supported by Heuristic engine, skipping...`);
@@ -210,7 +571,21 @@ class HeuristicEngine extends AnalysisEngineInterface {
210
571
  }
211
572
 
212
573
  try {
213
- const ruleViolations = await this.analyzeRule(rule, filesByLanguage, options);
574
+ let ruleViolations = [];
575
+
576
+ // Check if this is a semantic rule first (higher priority)
577
+ if (this.semanticRules.has(rule.id)) {
578
+ if (options.verbose) {
579
+ console.log(`🧠 [HeuristicEngine] Running semantic analysis for rule ${rule.id}`);
580
+ }
581
+ ruleViolations = await this.analyzeSemanticRule(rule, files, options);
582
+ } else {
583
+ // Fallback to traditional analysis
584
+ if (options.verbose) {
585
+ console.log(`🔧 [HeuristicEngine] Running traditional analysis for rule ${rule.id}`);
586
+ }
587
+ ruleViolations = await this.analyzeRule(rule, filesByLanguage, options);
588
+ }
214
589
 
215
590
  if (ruleViolations.length > 0) {
216
591
  // Group violations by file
@@ -238,6 +613,59 @@ class HeuristicEngine extends AnalysisEngineInterface {
238
613
  return results;
239
614
  }
240
615
 
616
+ /**
617
+ * Analyze semantic rule across files
618
+ * Following Rule C006: Verb-noun naming
619
+ * @param {Object} rule - Rule to analyze
620
+ * @param {string[]} files - Files to analyze
621
+ * @param {Object} options - Analysis options
622
+ * @returns {Promise<Object[]>} Rule violations
623
+ */
624
+ async analyzeSemanticRule(rule, files, options) {
625
+ const semanticRuleInfo = this.semanticRules.get(rule.id);
626
+ if (!semanticRuleInfo) {
627
+ console.warn(`⚠️ Semantic rule ${rule.id} not found`);
628
+ return [];
629
+ }
630
+
631
+ try {
632
+ const ruleInstance = semanticRuleInfo.instance;
633
+ const allViolations = [];
634
+
635
+ // Run semantic analysis for each file
636
+ for (const filePath of files) {
637
+ try {
638
+ if (options.verbose) {
639
+ console.log(`🧠 [SemanticRule] Analyzing ${path.basename(filePath)} with ${rule.id}`);
640
+ }
641
+
642
+ // Call semantic rule's analyzeFile method
643
+ await ruleInstance.analyzeFile(filePath, options);
644
+
645
+ // Get violations from the rule instance
646
+ const fileViolations = ruleInstance.getViolations();
647
+ allViolations.push(...fileViolations);
648
+
649
+ // Clear violations for next file
650
+ ruleInstance.clearViolations();
651
+
652
+ } catch (fileError) {
653
+ console.warn(`⚠️ Semantic rule ${rule.id} failed for ${filePath}:`, fileError.message);
654
+ }
655
+ }
656
+
657
+ if (options.verbose && allViolations.length > 0) {
658
+ console.log(`🧠 [SemanticRule] Found ${allViolations.length} violations for ${rule.id}`);
659
+ }
660
+
661
+ return allViolations;
662
+
663
+ } catch (error) {
664
+ console.error(`❌ Failed to run semantic rule ${rule.id}:`, error.message);
665
+ return [];
666
+ }
667
+ }
668
+
241
669
  /**
242
670
  * Analyze a specific rule across files
243
671
  * Following Rule C006: Verb-noun naming
@@ -247,21 +675,23 @@ class HeuristicEngine extends AnalysisEngineInterface {
247
675
  * @returns {Promise<Object[]>} Rule violations
248
676
  */
249
677
  async analyzeRule(rule, filesByLanguage, options) {
250
- const analyzerInfo = this.ruleAnalyzers.get(rule.id);
678
+ // Get full rule ID (C029 -> C029_catch_block_logging)
679
+ const fullRuleId = this.getFullRuleId(rule.id);
680
+ const analyzerInfo = this.ruleAnalyzers.get(fullRuleId);
681
+
251
682
  if (!analyzerInfo) {
252
683
  return [];
253
684
  }
254
685
 
255
686
  try {
256
687
  // Get analyzer - handle both class and instance types
257
- const analyzerInfo = this.ruleAnalyzers.get(rule.id);
258
688
  let analyzer;
259
689
 
260
690
  if (analyzerInfo.type === 'class') {
261
691
  // Create analyzer instance from class
262
692
  const AnalyzerClass = analyzerInfo.class;
263
693
  try {
264
- analyzer = new AnalyzerClass();
694
+ analyzer = new AnalyzerClass({ verbose: options.verbose });
265
695
  } catch (constructorError) {
266
696
  throw new Error(`Failed to instantiate analyzer class: ${constructorError.message}`);
267
697
  }
@@ -288,7 +718,7 @@ class HeuristicEngine extends AnalysisEngineInterface {
288
718
 
289
719
  try {
290
720
  // Load rule config
291
- const ruleConfig = await this.loadRuleConfig(rule.id, analyzerInfo.folder, analyzerInfo.category);
721
+ const ruleConfig = await this.loadRuleConfig(rule.id, analyzerInfo.folder, analyzerInfo.category, options.verbose);
292
722
 
293
723
  // Run analysis with AST enhancement
294
724
  if (options.verbose) {
@@ -354,16 +784,19 @@ class HeuristicEngine extends AnalysisEngineInterface {
354
784
  * @param {string} ruleId - Rule ID
355
785
  * @param {string} ruleFolder - Rule folder name
356
786
  * @param {string} category - Rule category (common, security, etc)
787
+ * @param {boolean} verbose - Enable verbose logging
357
788
  * @returns {Promise<Object>} Rule configuration
358
789
  */
359
- async loadRuleConfig(ruleId, ruleFolder, category = 'common') {
790
+ async loadRuleConfig(ruleId, ruleFolder, category = 'common', verbose = false) {
360
791
  try {
361
792
  const configPath = path.resolve(__dirname, '../rules', category, ruleFolder, 'config.json');
362
793
  if (fs.existsSync(configPath)) {
363
794
  return require(configPath);
364
795
  }
365
796
  } catch (error) {
366
- console.warn(`⚠️ Failed to load config for ${ruleId}:`, error.message);
797
+ if (verbose) {
798
+ console.warn(`[DEBUG] ⚠️ Failed to load config for ${ruleId}:`, error.message);
799
+ }
367
800
  }
368
801
 
369
802
  // Return minimal config