@sun-asterisk/sunlint 1.2.1 → 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 (109) 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/rule-analysis-strategies.js +18 -2
  8. package/config/rules/enhanced-rules-registry.json +2503 -0
  9. package/config/rules/rules-registry-generated.json +785 -837
  10. package/core/adapters/sunlint-rule-adapter.js +25 -30
  11. package/core/analysis-orchestrator.js +42 -2
  12. package/core/categories.js +52 -0
  13. package/core/category-constants.js +39 -0
  14. package/core/cli-action-handler.js +32 -5
  15. package/core/config-manager.js +111 -0
  16. package/core/config-merger.js +61 -0
  17. package/core/constants/categories.js +168 -0
  18. package/core/constants/defaults.js +165 -0
  19. package/core/constants/engines.js +185 -0
  20. package/core/constants/index.js +30 -0
  21. package/core/constants/rules.js +215 -0
  22. package/core/file-targeting-service.js +128 -7
  23. package/core/interfaces/rule-plugin.interface.js +207 -0
  24. package/core/plugin-manager.js +448 -0
  25. package/core/rule-selection-service.js +42 -15
  26. package/core/semantic-engine.js +560 -0
  27. package/core/semantic-rule-base.js +433 -0
  28. package/core/unified-rule-registry.js +484 -0
  29. package/docs/CONSTANTS-ARCHITECTURE.md +288 -0
  30. package/engines/core/base-engine.js +249 -0
  31. package/engines/engine-factory.js +275 -0
  32. package/engines/eslint-engine.js +180 -30
  33. package/engines/heuristic-engine.js +513 -56
  34. package/integrations/eslint/plugin/index.js +27 -27
  35. package/package.json +11 -6
  36. package/rules/README.md +252 -0
  37. package/rules/common/C002_no_duplicate_code/analyzer.js +65 -0
  38. package/rules/common/C002_no_duplicate_code/config.json +23 -0
  39. package/rules/common/C003_no_vague_abbreviations/analyzer.js +418 -0
  40. package/rules/common/C003_no_vague_abbreviations/config.json +35 -0
  41. package/rules/common/C006_function_naming/analyzer.js +504 -0
  42. package/rules/common/C006_function_naming/config.json +86 -0
  43. package/rules/common/C006_function_naming/smart-analyzer.js +503 -0
  44. package/rules/common/C010_limit_block_nesting/analyzer.js +389 -0
  45. package/rules/common/C012_command_query_separation/analyzer.js +481 -0
  46. package/rules/common/C012_command_query_separation/ast-analyzer.js +495 -0
  47. package/rules/common/C013_no_dead_code/analyzer.js +206 -0
  48. package/rules/common/C014_dependency_injection/analyzer.js +338 -0
  49. package/rules/common/C017_constructor_logic/analyzer.js +314 -0
  50. package/rules/common/C019_log_level_usage/analyzer.js +362 -0
  51. package/rules/common/C019_log_level_usage/config.json +121 -0
  52. package/rules/common/C029_catch_block_logging/analyzer-smart-pipeline.js +755 -0
  53. package/rules/common/C029_catch_block_logging/analyzer.js +141 -0
  54. package/rules/common/C029_catch_block_logging/config.json +59 -0
  55. package/rules/common/C031_validation_separation/analyzer.js +186 -0
  56. package/rules/common/C041_no_sensitive_hardcode/analyzer.js +292 -0
  57. package/rules/common/C041_no_sensitive_hardcode/ast-analyzer.js +296 -0
  58. package/rules/common/C042_boolean_name_prefix/analyzer.js +300 -0
  59. package/rules/common/C043_no_console_or_print/analyzer.js +431 -0
  60. package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +590 -0
  61. package/rules/common/C047_no_duplicate_retry_logic/c047-semantic-rule.js +278 -0
  62. package/rules/common/C047_no_duplicate_retry_logic/symbol-analyzer-enhanced.js +968 -0
  63. package/rules/common/C047_no_duplicate_retry_logic/symbol-config.json +71 -0
  64. package/rules/common/C075_explicit_return_types/analyzer.js +103 -0
  65. package/rules/common/C076_single_test_behavior/analyzer.js +121 -0
  66. package/rules/docs/C002_no_duplicate_code.md +57 -0
  67. package/rules/docs/C031_validation_separation.md +72 -0
  68. package/rules/index.js +162 -0
  69. package/rules/migration/converter.js +385 -0
  70. package/rules/migration/mapping.json +164 -0
  71. package/rules/parser/constants.js +31 -0
  72. package/rules/parser/file-config.js +80 -0
  73. package/rules/parser/rule-parser-simple.js +305 -0
  74. package/rules/parser/rule-parser.js +527 -0
  75. package/rules/security/S015_insecure_tls_certificate/analyzer.js +150 -0
  76. package/rules/security/S015_insecure_tls_certificate/ast-analyzer.js +237 -0
  77. package/rules/security/S023_no_json_injection/analyzer.js +278 -0
  78. package/rules/security/S023_no_json_injection/ast-analyzer.js +359 -0
  79. package/rules/security/S026_json_schema_validation/analyzer.js +251 -0
  80. package/rules/security/S026_json_schema_validation/config.json +27 -0
  81. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +436 -0
  82. package/rules/security/S027_no_hardcoded_secrets/config.json +29 -0
  83. package/rules/security/S029_csrf_protection/analyzer.js +330 -0
  84. package/rules/tests/C002_no_duplicate_code.test.js +50 -0
  85. package/rules/utils/ast-utils.js +191 -0
  86. package/rules/utils/base-analyzer.js +98 -0
  87. package/rules/utils/pattern-matchers.js +239 -0
  88. package/rules/utils/rule-helpers.js +264 -0
  89. package/rules/utils/severity-constants.js +93 -0
  90. package/scripts/category-manager.js +150 -0
  91. package/scripts/generate-rules-registry.js +88 -0
  92. package/scripts/generate_insights.js +188 -0
  93. package/scripts/migrate-rule-registry.js +157 -0
  94. package/scripts/validate-system.js +48 -0
  95. package/.sunlint.json +0 -35
  96. package/config/README.md +0 -88
  97. package/config/engines/eslint-rule-mapping.json +0 -74
  98. package/config/testing/test-s005-working.ts +0 -22
  99. package/engines/tree-sitter-parser.js +0 -0
  100. package/engines/universal-ast-engine.js +0 -0
  101. package/scripts/merge-reports.js +0 -424
  102. package/scripts/test-scripts/README.md +0 -22
  103. package/scripts/test-scripts/test-c041-comparison.js +0 -114
  104. package/scripts/test-scripts/test-c041-eslint.js +0 -67
  105. package/scripts/test-scripts/test-eslint-rules.js +0 -146
  106. package/scripts/test-scripts/test-real-world.js +0 -44
  107. package/scripts/test-scripts/test-rules-on-real-projects.js +0 -86
  108. /package/{config/schemas/sunlint-schema.json → rules/universal/C010/generic.js} +0 -0
  109. /package/{core/multi-rule-runner.js → rules/universal/C010/tree-sitter-analyzer.js} +0 -0
@@ -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,56 +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);
84
- const analyzerPath = path.join(categoryPath, ruleFolder, 'analyzer.js');
267
+ const ruleId = ruleFolder; // Use folder name directly as rule ID
268
+ const rulePath = path.join(categoryPath, ruleFolder);
85
269
 
86
- if (fs.existsSync(analyzerPath)) {
87
- try {
88
- // Load analyzer dynamically - handle both class and instance exports
89
- const analyzerModule = require(analyzerPath);
90
- const analyzer = analyzerModule.default || analyzerModule;
91
-
92
- // Check if it's a class constructor, instance, or factory function
93
- if (typeof analyzer === 'function') {
94
- // It's a class constructor
95
- this.ruleAnalyzers.set(ruleId, {
96
- path: analyzerPath,
97
- class: analyzer,
98
- folder: ruleFolder,
99
- category: categoryFolder,
100
- type: 'class'
101
- });
102
- this.supportedRulesList.push(ruleId);
103
- } else if (analyzer && typeof analyzer === 'object' && analyzer.analyze) {
104
- // It's an analyzer instance with analyze method
105
- this.ruleAnalyzers.set(ruleId, {
106
- path: analyzerPath,
107
- instance: analyzer,
108
- folder: ruleFolder,
109
- category: categoryFolder,
110
- type: 'instance'
111
- });
112
- this.supportedRulesList.push(ruleId);
113
- } else {
114
- console.warn(`⚠️ Analyzer for ${ruleId} has unsupported format:`, typeof analyzer);
115
- }
116
-
117
- } catch (error) {
118
- console.warn(`⚠️ Failed to load analyzer for ${ruleId}:`, error.message);
119
- }
120
- }
270
+ await this.loadRuleAnalyzer(ruleId, rulePath, categoryFolder);
121
271
  }
122
272
  }
273
+
274
+ } catch (error) {
275
+ console.warn('⚠️ Error scanning rule analyzers:', error.message);
276
+ }
277
+ }
123
278
 
124
- if (config.verbose) {
279
+ /**
280
+ * Manually load C047 semantic rule (special case)
281
+ */
282
+ async manuallyLoadC047() {
283
+ try {
125
284
  if (this.verbose) {
126
- console.log(`🔍 Found ${this.supportedRulesList.length} heuristic analyzers`);
127
- console.log(`🔍 Supported rules: ${this.supportedRulesList.join(', ')}`);
285
+ console.log(`[DEBUG] 🔬 Manually loading C047 semantic rule...`);
128
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}`);
129
307
  }
130
308
 
131
309
  } catch (error) {
132
- console.error('Failed to scan rule analyzers:', error.message);
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';
339
+
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
347
+ }
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);
379
+ }
380
+ }
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
+ }
394
+
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
+
425
+ if (this.verbose) {
426
+ console.log(`🧠 Registered semantic rule: ${ruleId}`);
427
+ }
428
+
429
+ } catch (error) {
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);
133
456
  }
134
457
  }
135
458
 
@@ -145,6 +468,60 @@ class HeuristicEngine extends AnalysisEngineInterface {
145
468
  return match ? match[1] : folderName;
146
469
  }
147
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
+
148
525
  /**
149
526
  * Analyze files using heuristic patterns
150
527
  * Following Rule C006: Verb-noun naming
@@ -178,6 +555,14 @@ class HeuristicEngine extends AnalysisEngineInterface {
178
555
  const filesByLanguage = this.groupFilesByLanguage(files);
179
556
 
180
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
+
181
566
  if (!this.isRuleSupported(rule.id)) {
182
567
  if (options.verbose) {
183
568
  console.warn(`⚠️ Rule ${rule.id} not supported by Heuristic engine, skipping...`);
@@ -186,7 +571,21 @@ class HeuristicEngine extends AnalysisEngineInterface {
186
571
  }
187
572
 
188
573
  try {
189
- 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
+ }
190
589
 
191
590
  if (ruleViolations.length > 0) {
192
591
  // Group violations by file
@@ -214,6 +613,59 @@ class HeuristicEngine extends AnalysisEngineInterface {
214
613
  return results;
215
614
  }
216
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
+
217
669
  /**
218
670
  * Analyze a specific rule across files
219
671
  * Following Rule C006: Verb-noun naming
@@ -223,21 +675,23 @@ class HeuristicEngine extends AnalysisEngineInterface {
223
675
  * @returns {Promise<Object[]>} Rule violations
224
676
  */
225
677
  async analyzeRule(rule, filesByLanguage, options) {
226
- 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
+
227
682
  if (!analyzerInfo) {
228
683
  return [];
229
684
  }
230
685
 
231
686
  try {
232
687
  // Get analyzer - handle both class and instance types
233
- const analyzerInfo = this.ruleAnalyzers.get(rule.id);
234
688
  let analyzer;
235
689
 
236
690
  if (analyzerInfo.type === 'class') {
237
691
  // Create analyzer instance from class
238
692
  const AnalyzerClass = analyzerInfo.class;
239
693
  try {
240
- analyzer = new AnalyzerClass();
694
+ analyzer = new AnalyzerClass({ verbose: options.verbose });
241
695
  } catch (constructorError) {
242
696
  throw new Error(`Failed to instantiate analyzer class: ${constructorError.message}`);
243
697
  }
@@ -264,7 +718,7 @@ class HeuristicEngine extends AnalysisEngineInterface {
264
718
 
265
719
  try {
266
720
  // Load rule config
267
- 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);
268
722
 
269
723
  // Run analysis with AST enhancement
270
724
  if (options.verbose) {
@@ -330,16 +784,19 @@ class HeuristicEngine extends AnalysisEngineInterface {
330
784
  * @param {string} ruleId - Rule ID
331
785
  * @param {string} ruleFolder - Rule folder name
332
786
  * @param {string} category - Rule category (common, security, etc)
787
+ * @param {boolean} verbose - Enable verbose logging
333
788
  * @returns {Promise<Object>} Rule configuration
334
789
  */
335
- async loadRuleConfig(ruleId, ruleFolder, category = 'common') {
790
+ async loadRuleConfig(ruleId, ruleFolder, category = 'common', verbose = false) {
336
791
  try {
337
792
  const configPath = path.resolve(__dirname, '../rules', category, ruleFolder, 'config.json');
338
793
  if (fs.existsSync(configPath)) {
339
794
  return require(configPath);
340
795
  }
341
796
  } catch (error) {
342
- 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
+ }
343
800
  }
344
801
 
345
802
  // Return minimal config