@sun-asterisk/sunlint 1.2.2 → 1.3.1

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 (124) hide show
  1. package/CHANGELOG.md +107 -1
  2. package/CONTRIBUTING.md +1654 -66
  3. package/README.md +19 -6
  4. package/config/ci-cd.json +54 -0
  5. package/config/development.json +56 -0
  6. package/config/engines/engines-enhanced.json +86 -0
  7. package/config/engines/semantic-config.json +114 -0
  8. package/config/eslint-rule-mapping.json +50 -38
  9. package/config/large-project.json +143 -0
  10. package/config/presets/all.json +0 -1
  11. package/config/release.json +70 -0
  12. package/config/rule-analysis-strategies.js +23 -4
  13. package/config/rules/S027-categories.json +122 -0
  14. package/config/rules/enhanced-rules-registry.json +2564 -0
  15. package/config/rules/rules-registry-generated.json +785 -837
  16. package/config/rules/rules-registry.json +13 -1
  17. package/core/adapters/sunlint-rule-adapter.js +25 -30
  18. package/core/analysis-orchestrator.js +42 -2
  19. package/core/categories.js +52 -0
  20. package/core/category-constants.js +39 -0
  21. package/core/cli-action-handler.js +53 -32
  22. package/core/cli-program.js +11 -3
  23. package/core/config-manager.js +111 -0
  24. package/core/config-merger.js +88 -0
  25. package/core/constants/categories.js +168 -0
  26. package/core/constants/defaults.js +165 -0
  27. package/core/constants/engines.js +185 -0
  28. package/core/constants/index.js +30 -0
  29. package/core/constants/rules.js +215 -0
  30. package/core/enhanced-rules-registry.js +3 -3
  31. package/core/file-targeting-service.js +128 -7
  32. package/core/interfaces/rule-plugin.interface.js +207 -0
  33. package/core/plugin-manager.js +448 -0
  34. package/core/rule-selection-service.js +42 -15
  35. package/core/semantic-engine.js +658 -0
  36. package/core/semantic-rule-base.js +433 -0
  37. package/core/unified-rule-registry.js +484 -0
  38. package/docs/COMMAND-EXAMPLES.md +134 -0
  39. package/docs/CONSTANTS-ARCHITECTURE.md +288 -0
  40. package/docs/LARGE-PROJECT-GUIDE.md +324 -0
  41. package/engines/core/base-engine.js +249 -0
  42. package/engines/engine-factory.js +275 -0
  43. package/engines/eslint-engine.js +171 -19
  44. package/engines/heuristic-engine.js +569 -78
  45. package/integrations/eslint/plugin/index.js +26 -28
  46. package/origin-rules/common-en.md +8 -8
  47. package/package.json +10 -6
  48. package/rules/common/C003_no_vague_abbreviations/analyzer.js +1 -1
  49. package/rules/common/C017_constructor_logic/analyzer.js +254 -17
  50. package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -0
  51. package/rules/common/C029_catch_block_logging/analyzer.js +17 -5
  52. package/rules/common/C033_separate_service_repository/README.md +78 -0
  53. package/rules/common/C033_separate_service_repository/analyzer.js +160 -0
  54. package/rules/common/C033_separate_service_repository/config.json +50 -0
  55. package/rules/common/C033_separate_service_repository/regex-based-analyzer.js +585 -0
  56. package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +368 -0
  57. package/rules/common/C035_error_logging_context/STRATEGY.md +99 -0
  58. package/rules/common/C035_error_logging_context/analyzer.js +230 -0
  59. package/rules/common/C035_error_logging_context/config.json +54 -0
  60. package/rules/common/C035_error_logging_context/regex-based-analyzer.js +299 -0
  61. package/rules/common/C035_error_logging_context/symbol-based-analyzer.js +454 -0
  62. package/rules/common/C040_centralized_validation/analyzer.js +165 -0
  63. package/rules/common/C040_centralized_validation/config.json +46 -0
  64. package/rules/common/C040_centralized_validation/regex-based-analyzer.js +243 -0
  65. package/rules/common/C040_centralized_validation/symbol-based-analyzer.js +416 -0
  66. package/rules/common/C047_no_duplicate_retry_logic/c047-semantic-rule.js +278 -0
  67. package/rules/common/C047_no_duplicate_retry_logic/symbol-analyzer-enhanced.js +968 -0
  68. package/rules/common/C047_no_duplicate_retry_logic/symbol-config.json +71 -0
  69. package/rules/common/{C076_single_test_behavior → C072_single_test_behavior}/analyzer.js +6 -6
  70. package/rules/common/C076_explicit_function_types/README.md +30 -0
  71. package/rules/common/C076_explicit_function_types/analyzer.js +172 -0
  72. package/rules/common/C076_explicit_function_types/config.json +15 -0
  73. package/rules/common/C076_explicit_function_types/semantic-analyzer.js +341 -0
  74. package/rules/index.js +8 -0
  75. package/rules/parser/rule-parser.js +13 -2
  76. package/rules/security/S005_no_origin_auth/README.md +226 -0
  77. package/rules/security/S005_no_origin_auth/analyzer.js +184 -0
  78. package/rules/security/S005_no_origin_auth/ast-analyzer.js +406 -0
  79. package/rules/security/S005_no_origin_auth/config.json +85 -0
  80. package/rules/security/S006_no_plaintext_recovery_codes/README.md +139 -0
  81. package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +306 -0
  82. package/rules/security/S006_no_plaintext_recovery_codes/config.json +48 -0
  83. package/rules/security/S007_no_plaintext_otp/README.md +198 -0
  84. package/rules/security/S007_no_plaintext_otp/analyzer.js +406 -0
  85. package/rules/security/S007_no_plaintext_otp/config.json +79 -0
  86. package/rules/security/S007_no_plaintext_otp/semantic-analyzer.js +609 -0
  87. package/rules/security/S007_no_plaintext_otp/semantic-config.json +195 -0
  88. package/rules/security/S007_no_plaintext_otp/semantic-wrapper.js +280 -0
  89. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +180 -366
  90. package/rules/security/S027_no_hardcoded_secrets/categories.json +153 -0
  91. package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +250 -0
  92. package/scripts/category-manager.js +150 -0
  93. package/scripts/generate-rules-registry.js +88 -0
  94. package/scripts/migrate-rule-registry.js +157 -0
  95. package/scripts/prepare-release.sh +1 -1
  96. package/scripts/validate-system.js +48 -0
  97. package/.sunlint.json +0 -35
  98. package/config/README.md +0 -88
  99. package/config/engines/eslint-rule-mapping.json +0 -74
  100. package/config/schemas/sunlint-schema.json +0 -0
  101. package/config/testing/test-s005-working.ts +0 -22
  102. package/core/multi-rule-runner.js +0 -0
  103. package/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
  104. package/docs/FUTURE_PACKAGES.md +0 -83
  105. package/docs/HEURISTIC_VS_AI.md +0 -113
  106. package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +0 -112
  107. package/docs/PRODUCTION_SIZE_IMPACT.md +0 -183
  108. package/docs/RELEASE_GUIDE.md +0 -230
  109. package/docs/STANDARDIZED-CATEGORY-FILTERING.md +0 -156
  110. package/engines/tree-sitter-parser.js +0 -0
  111. package/engines/universal-ast-engine.js +0 -0
  112. package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +0 -254
  113. package/rules/common/C029_catch_block_logging/analyzer-backup.js +0 -426
  114. package/rules/common/C029_catch_block_logging/analyzer-fixed.js +0 -130
  115. package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +0 -487
  116. package/rules/common/C029_catch_block_logging/analyzer-simple.js +0 -110
  117. package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +0 -441
  118. package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +0 -127
  119. package/rules/common/C029_catch_block_logging/ast-analyzer.js +0 -133
  120. package/rules/common/C029_catch_block_logging/cfg-analyzer.js +0 -408
  121. package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +0 -454
  122. package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +0 -700
  123. package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +0 -568
  124. 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,33 @@ 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
+ // Note: semantic engine will be initialized in initialize() with proper config
29
+ this.semanticEngine = null;
30
+ this.semanticRules = new Map();
31
+ this.symbolTableEnabled = false;
32
+
33
+ // Unified rule registry
34
+ this.unifiedRegistry = getUnifiedRegistry();
23
35
  }
24
36
 
25
37
  /**
26
- * Initialize Heuristic engine with configuration
38
+ * Initialize Heuristic engine with ts-morph core and configuration
27
39
  * Following Rule C006: Verb-noun naming
28
40
  * @param {Object} config - Engine configuration
29
41
  */
@@ -32,18 +44,27 @@ class HeuristicEngine extends AnalysisEngineInterface {
32
44
  // Store verbosity setting
33
45
  this.verbose = config?.verbose || false;
34
46
 
47
+ // Initialize unified rule registry
48
+ await this.unifiedRegistry.initialize({ verbose: this.verbose });
49
+
35
50
  // Check for optional AST dependencies
36
51
  dependencyChecker.checkAndNotify('ast');
37
52
 
53
+ // Initialize ts-morph Symbol Table (core requirement)
54
+ await this.initializeSymbolTable(config);
55
+
38
56
  // Initialize rule adapter
39
57
  await this.ruleAdapter.initialize();
40
58
 
41
- // Scan for available rule analyzers
42
- await this.scanRuleAnalyzers(config);
59
+ // Load available rules from unified registry
60
+ await this.loadRulesFromRegistry(config);
43
61
 
44
62
  this.initialized = true;
45
63
  if (this.verbose) {
46
- console.log(`🔍 Heuristic engine v2.0 initialized with ${this.supportedRulesList.length} rules (AST-enhanced)`);
64
+ console.log(`🔍 Heuristic engine v3.0 initialized:`);
65
+ console.log(` 📊 Total rules: ${this.supportedRulesList.length}`);
66
+ console.log(` 🧠 Symbol Table: ${this.symbolTableInitialized ? 'enabled' : 'disabled'}`);
67
+ console.log(` 🔧 Semantic rules: ${this.semanticRules.size}`);
47
68
  }
48
69
 
49
70
  } catch (error) {
@@ -53,7 +74,181 @@ class HeuristicEngine extends AnalysisEngineInterface {
53
74
  }
54
75
 
55
76
  /**
56
- * Scan for available rule analyzers
77
+ * Initialize ts-morph Symbol Table as core requirement
78
+ * OPTIMIZED: Use targeted files instead of entire project for better performance
79
+ */
80
+ async initializeSymbolTable(config) {
81
+ const projectPath = config?.projectPath || process.cwd();
82
+
83
+ try {
84
+ // Initialize semantic engine with config options including maxSemanticFiles
85
+ const semanticOptions = {
86
+ maxSemanticFiles: config?.maxSemanticFiles,
87
+ verbose: this.verbose,
88
+ ...config?.semanticOptions
89
+ };
90
+
91
+ this.semanticEngine = new SemanticEngine(semanticOptions);
92
+
93
+ // ts-morph is now a core dependency - but optimized for targeted files
94
+ const success = await this.semanticEngine.initialize(projectPath, config?.targetFiles);
95
+
96
+ if (success) {
97
+ this.semanticEnabled = true;
98
+ this.symbolTableInitialized = true;
99
+ if (this.verbose) {
100
+ console.log(`🧠 Symbol Table initialized for: ${projectPath}`);
101
+ }
102
+ } else {
103
+ if (this.verbose) {
104
+ console.warn('⚠️ Symbol Table initialization failed, using fallback mode');
105
+ }
106
+ }
107
+
108
+ } catch (error) {
109
+ if (this.verbose) {
110
+ console.warn('⚠️ ts-morph Symbol Table unavailable:', error.message);
111
+ console.warn('⚠️ Falling back to traditional AST/regex analysis only');
112
+ }
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Load rules from unified registry instead of scanning directories
118
+ * Following Rule C006: Verb-noun naming
119
+ * @param {Object} config - Engine configuration
120
+ */
121
+ async loadRulesFromRegistry(config = {}) {
122
+ try {
123
+ // Get rules supported by heuristic engine from unified registry
124
+ const supportedRules = this.unifiedRegistry.getRulesForEngine('heuristic');
125
+
126
+ if (this.verbose) {
127
+ console.log(`🔍 [HeuristicEngine] Found ${supportedRules.length} rules from unified registry`);
128
+ }
129
+
130
+ // Load each rule
131
+ for (const ruleDefinition of supportedRules) {
132
+ await this.loadRuleFromDefinition(ruleDefinition);
133
+ }
134
+
135
+ // Manually load C047 if needed (DEPRECATED - C047 now in enhanced registry)
136
+ // if (!this.semanticRules.has('C047') && !this.ruleAnalyzers.has('C047')) {
137
+ // await this.manuallyLoadC047();
138
+ // }
139
+
140
+ if (this.verbose) {
141
+ console.log(`✅ [HeuristicEngine] Loaded ${this.supportedRulesList.length} rules from unified registry`);
142
+ }
143
+
144
+ } catch (error) {
145
+ console.error('Failed to load rules from registry:', error.message);
146
+ // Fallback to old scanning method
147
+ await this.scanRuleAnalyzers(config);
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Load a single rule from its definition
153
+ * @param {Object} ruleDefinition - Rule definition from unified registry
154
+ */
155
+ async loadRuleFromDefinition(ruleDefinition) {
156
+ const ruleId = ruleDefinition.id;
157
+
158
+ try {
159
+ // Resolve best analyzer path for this engine
160
+ const analyzerPath = this.unifiedRegistry.resolveAnalyzerPath(ruleId, 'heuristic');
161
+
162
+ if (!analyzerPath) {
163
+ if (this.verbose) {
164
+ console.warn(`⚠️ [HeuristicEngine] No compatible analyzer found for ${ruleId}`);
165
+ }
166
+ return;
167
+ }
168
+
169
+ // Determine analyzer type from path and strategy
170
+ const strategy = ruleDefinition.strategy.preferred;
171
+ const category = ruleDefinition.category;
172
+
173
+ if (strategy === 'semantic' && this.symbolTableInitialized) {
174
+ // Load as semantic rule
175
+ await this.loadSemanticRule(ruleId, analyzerPath, { category });
176
+ } else {
177
+ // Load as traditional rule (ast/regex)
178
+ await this.loadTraditionalRule(ruleId, analyzerPath, { category, type: strategy });
179
+ }
180
+
181
+ } catch (error) {
182
+ if (this.verbose) {
183
+ console.warn(`⚠️ [HeuristicEngine] Failed to load rule ${ruleId}:`, error.message);
184
+ }
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Load semantic rule from analyzer path
190
+ * @param {string} ruleId - Rule ID
191
+ * @param {string} analyzerPath - Path to analyzer file
192
+ * @param {Object} metadata - Rule metadata
193
+ */
194
+ async loadSemanticRule(ruleId, analyzerPath, metadata) {
195
+ try {
196
+ const SemanticRuleClass = require(analyzerPath);
197
+
198
+ // Verify it extends SemanticRuleBase
199
+ if (this.isSemanticRule(SemanticRuleClass)) {
200
+ await this.registerSemanticRule(ruleId, SemanticRuleClass, {
201
+ path: analyzerPath,
202
+ category: metadata.category
203
+ });
204
+
205
+ if (this.verbose) {
206
+ console.log(`🧠 [HeuristicEngine] Loaded semantic rule: ${ruleId}`);
207
+ }
208
+ } else {
209
+ // Not a semantic rule, fallback to traditional
210
+ await this.loadTraditionalRule(ruleId, analyzerPath, metadata);
211
+ }
212
+
213
+ } catch (error) {
214
+ if (this.verbose) {
215
+ console.warn(`⚠️ [HeuristicEngine] Failed to load semantic rule ${ruleId}:`, error.message);
216
+ }
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Load traditional rule (ast/regex) from analyzer path
222
+ * @param {string} ruleId - Rule ID
223
+ * @param {string} analyzerPath - Path to analyzer file
224
+ * @param {Object} metadata - Rule metadata
225
+ */
226
+ async loadTraditionalRule(ruleId, analyzerPath, metadata) {
227
+ try {
228
+ const analyzerModule = require(analyzerPath);
229
+ const AnalyzerClass = analyzerModule.default || analyzerModule;
230
+
231
+ this.registerTraditionalRule(ruleId, AnalyzerClass, {
232
+ path: analyzerPath,
233
+ category: metadata.category,
234
+ folder: ruleId, // Add folder name for config loading
235
+ type: metadata.type || 'regex'
236
+ });
237
+
238
+ if (this.verbose) {
239
+ console.log(`🔧 [HeuristicEngine] Loaded ${metadata.type} rule: ${ruleId}`);
240
+ }
241
+
242
+ } catch (error) {
243
+ if (this.verbose) {
244
+ console.warn(`⚠️ [HeuristicEngine] Failed to load traditional rule ${ruleId}:`, error.message);
245
+ }
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Scan for available rule analyzers with semantic support
251
+ * Priority: semantic > ast > regex
57
252
  * Following Rule C006: Verb-noun naming
58
253
  */
59
254
  async scanRuleAnalyzers(config = {}) {
@@ -80,80 +275,223 @@ class HeuristicEngine extends AnalysisEngineInterface {
80
275
  .map(dirent => dirent.name);
81
276
 
82
277
  for (const ruleFolder of ruleFolders) {
83
- const ruleId = this.extractRuleIdFromFolder(ruleFolder);
278
+ const ruleId = ruleFolder; // Use folder name directly as rule ID
279
+ const rulePath = path.join(categoryPath, ruleFolder);
84
280
 
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
- }
281
+ await this.loadRuleAnalyzer(ruleId, rulePath, categoryFolder);
282
+ }
283
+ }
284
+
285
+ } catch (error) {
286
+ console.warn('⚠️ Error scanning rule analyzers:', error.message);
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Manually load C047 semantic rule (special case)
292
+ */
293
+ async manuallyLoadC047() {
294
+ try {
295
+ if (this.verbose) {
296
+ console.log(`[DEBUG] 🔬 Manually loading C047 semantic rule...`);
297
+ }
298
+
299
+ const c047Path = path.resolve(__dirname, '../rules/common/C047_no_duplicate_retry_logic/c047-semantic-rule.js');
300
+
301
+ if (fs.existsSync(c047Path)) {
302
+ const C047SemanticRule = require(c047Path);
303
+ const instance = new C047SemanticRule();
304
+
305
+ // Register as semantic rule
306
+ await this.registerSemanticRule('C047', C047SemanticRule, {
307
+ path: c047Path,
308
+ category: 'common',
309
+ type: 'semantic',
310
+ description: 'C047 - No Duplicate Retry Logic (Semantic Analysis)'
311
+ });
312
+
313
+ if (this.verbose) {
314
+ console.log(`[DEBUG] ✅ C047 semantic rule loaded successfully`);
315
+ }
316
+ } else {
317
+ console.warn(`⚠️ C047 semantic rule not found at: ${c047Path}`);
318
+ }
319
+
320
+ } catch (error) {
321
+ console.warn(`⚠️ Failed to manually load C047:`, error.message);
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Load rule analyzer with semantic priority
327
+ */
328
+ async loadRuleAnalyzer(ruleId, rulePath, categoryFolder) {
329
+ // Analyzer priority: semantic > ast > regex
330
+ const analyzerCandidates = [
331
+ { path: path.join(rulePath, 'semantic-analyzer.js'), type: 'semantic' },
332
+ { path: path.join(rulePath, 'ast-analyzer.js'), type: 'ast' },
333
+ { path: path.join(rulePath, 'regex-analyzer.js'), type: 'regex' },
334
+ { path: path.join(rulePath, 'analyzer.js'), type: 'regex' } // legacy fallback
335
+ ];
336
+
337
+ let selectedAnalyzer = null;
338
+ let analyzerPath = null;
339
+ let analyzerType = null;
340
+
341
+ // Try semantic analyzer first if Symbol Table available
342
+ if (this.symbolTableInitialized) {
343
+ const semanticCandidate = analyzerCandidates[0];
344
+ if (fs.existsSync(semanticCandidate.path)) {
345
+ try {
346
+ const analyzerModule = require(semanticCandidate.path);
347
+ selectedAnalyzer = analyzerModule.default || analyzerModule;
348
+ analyzerPath = semanticCandidate.path;
349
+ analyzerType = 'semantic';
104
350
 
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
- }
351
+ // Verify it extends SemanticRuleBase
352
+ if (this.isSemanticRule(selectedAnalyzer)) {
353
+ await this.registerSemanticRule(ruleId, selectedAnalyzer, {
354
+ path: analyzerPath,
355
+ category: categoryFolder
356
+ });
357
+ return; // Successfully registered semantic rule
115
358
  }
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
- }
359
+ } catch (error) {
360
+ console.debug(`Semantic analyzer for ${ruleId} failed to load:`, error.message);
361
+ }
362
+ }
363
+ }
364
+
365
+ // Fall back to AST analyzer
366
+ const astCandidate = analyzerCandidates[1];
367
+ if (fs.existsSync(astCandidate.path)) {
368
+ try {
369
+ const analyzerModule = require(astCandidate.path);
370
+ selectedAnalyzer = analyzerModule.default || analyzerModule;
371
+ analyzerPath = astCandidate.path;
372
+ analyzerType = 'ast';
373
+ } catch (error) {
374
+ console.debug(`AST analyzer for ${ruleId} failed to load:`, error.message);
375
+ }
376
+ }
377
+
378
+ // Fall back to regex analyzer
379
+ if (!selectedAnalyzer) {
380
+ for (const regexCandidate of analyzerCandidates.slice(2)) {
381
+ if (fs.existsSync(regexCandidate.path)) {
382
+ try {
383
+ const analyzerModule = require(regexCandidate.path);
384
+ selectedAnalyzer = analyzerModule.default || analyzerModule;
385
+ analyzerPath = regexCandidate.path;
386
+ analyzerType = 'regex';
387
+ break;
388
+ } catch (error) {
389
+ console.debug(`Regex analyzer for ${ruleId} failed to load:`, error.message);
144
390
  }
145
391
  }
146
392
  }
393
+ }
394
+
395
+ // Register traditional (non-semantic) analyzer
396
+ if (selectedAnalyzer) {
397
+ this.registerTraditionalRule(ruleId, selectedAnalyzer, {
398
+ path: analyzerPath,
399
+ category: categoryFolder,
400
+ folder: fullRuleId, // Add folder name for config loading
401
+ type: analyzerType
402
+ });
403
+ }
404
+ }
405
+
406
+ /**
407
+ * Check if analyzer is a semantic rule
408
+ */
409
+ isSemanticRule(analyzerClass) {
410
+ if (typeof analyzerClass !== 'function') return false;
411
+
412
+ try {
413
+ const instance = new analyzerClass(analyzerClass.name);
414
+ return instance instanceof SemanticRuleBase;
415
+ } catch (error) {
416
+ return false;
417
+ }
418
+ }
147
419
 
148
- if (config.verbose) {
420
+ /**
421
+ * Register semantic rule (lazy initialization)
422
+ */
423
+ async registerSemanticRule(ruleId, analyzerClass, metadata) {
424
+ try {
425
+ // Store rule class and metadata for lazy initialization
426
+ this.semanticRules.set(ruleId, {
427
+ analyzerClass,
428
+ metadata,
429
+ type: 'semantic',
430
+ initialized: false,
431
+ instance: null
432
+ });
433
+
434
+ this.supportedRulesList.push(ruleId);
435
+
149
436
  if (this.verbose) {
150
- console.log(`🔍 Found ${this.supportedRulesList.length} heuristic analyzers`);
151
- console.log(`🔍 Supported rules: ${this.supportedRulesList.join(', ')}`);
437
+ console.log(`🧠 Registered semantic rule: ${ruleId} (lazy initialization)`);
152
438
  }
439
+
440
+ } catch (error) {
441
+ console.warn(`⚠️ Failed to register semantic rule ${ruleId}:`, error.message);
442
+ }
443
+ }
444
+
445
+ /**
446
+ * Initialize semantic rule on-demand
447
+ */
448
+ async initializeSemanticRule(ruleId) {
449
+ const ruleEntry = this.semanticRules.get(ruleId);
450
+ if (!ruleEntry || ruleEntry.initialized) {
451
+ return ruleEntry?.instance;
452
+ }
453
+
454
+ try {
455
+ const instance = new ruleEntry.analyzerClass(ruleId);
456
+ instance.initialize(this.semanticEngine);
457
+
458
+ // Update entry with initialized instance
459
+ ruleEntry.instance = instance;
460
+ ruleEntry.initialized = true;
461
+
462
+ if (this.verbose) {
463
+ console.log(`🔧 Rule ${ruleId} initialized with semantic analysis`);
153
464
  }
154
465
 
466
+ return instance;
155
467
  } catch (error) {
156
- console.error('Failed to scan rule analyzers:', error.message);
468
+ console.warn(`⚠️ Failed to initialize semantic rule ${ruleId}:`, error.message);
469
+ return null;
470
+ }
471
+ }
472
+
473
+ /**
474
+ * Register traditional (heuristic/AST/regex) rule
475
+ */
476
+ registerTraditionalRule(ruleId, analyzer, metadata) {
477
+ if (typeof analyzer === 'function') {
478
+ // Class constructor
479
+ this.ruleAnalyzers.set(ruleId, {
480
+ ...metadata,
481
+ class: analyzer,
482
+ type: 'class'
483
+ });
484
+ this.supportedRulesList.push(ruleId);
485
+ } else if (analyzer && typeof analyzer === 'object' && analyzer.analyze) {
486
+ // Instance with analyze method
487
+ this.ruleAnalyzers.set(ruleId, {
488
+ ...metadata,
489
+ instance: analyzer,
490
+ type: 'instance'
491
+ });
492
+ this.supportedRulesList.push(ruleId);
493
+ } else {
494
+ console.warn(`⚠️ Analyzer for ${ruleId} has unsupported format:`, typeof analyzer);
157
495
  }
158
496
  }
159
497
 
@@ -169,6 +507,60 @@ class HeuristicEngine extends AnalysisEngineInterface {
169
507
  return match ? match[1] : folderName;
170
508
  }
171
509
 
510
+ /**
511
+ * Get full rule ID from short rule ID (C029 -> C029_catch_block_logging)
512
+ * @param {string} ruleId - Short rule ID
513
+ * @returns {string} Full rule ID or original if not found
514
+ */
515
+ getFullRuleId(ruleId) {
516
+ // Check exact match first
517
+ if (this.ruleAnalyzers.has(ruleId)) {
518
+ return ruleId;
519
+ }
520
+
521
+ // Find full rule ID that starts with short rule ID
522
+ const shortRulePattern = new RegExp(`^${ruleId}_`);
523
+ const fullRuleId = Array.from(this.ruleAnalyzers.keys()).find(fullId => shortRulePattern.test(fullId));
524
+
525
+ return fullRuleId || ruleId; // Return original if not found
526
+ }
527
+
528
+ /**
529
+ * Check if a rule is supported by this engine
530
+ * Following Rule C006: Verb-noun naming
531
+ * @param {string} ruleId - Rule ID to check
532
+ * @returns {boolean} True if rule is supported
533
+ */
534
+ isRuleSupported(ruleId) {
535
+ // Special case: C047 is always supported (loaded on-demand)
536
+ if (ruleId === 'C047') {
537
+ return true;
538
+ }
539
+
540
+ // Use unified registry for primary lookup
541
+ if (this.unifiedRegistry && this.unifiedRegistry.initialized) {
542
+ return this.unifiedRegistry.isRuleSupported(ruleId, 'heuristic');
543
+ }
544
+
545
+ // Fallback to original logic for backward compatibility
546
+ return this.supportedRulesList.includes(ruleId) ||
547
+ this.semanticRules.has(ruleId) ||
548
+ this.ruleAnalyzers.has(ruleId) ||
549
+ this.checkShortRuleIdMatch(ruleId);
550
+ }
551
+
552
+ /**
553
+ * Check short rule ID matches (backward compatibility)
554
+ * @param {string} ruleId - Short rule ID (e.g., C029)
555
+ * @returns {boolean} True if matches any full rule ID
556
+ */
557
+ checkShortRuleIdMatch(ruleId) {
558
+ const shortRulePattern = new RegExp(`^${ruleId}_`);
559
+ return this.supportedRulesList.some(fullRuleId => shortRulePattern.test(fullRuleId)) ||
560
+ Array.from(this.semanticRules.keys()).some(fullRuleId => shortRulePattern.test(fullRuleId)) ||
561
+ Array.from(this.ruleAnalyzers.keys()).some(fullRuleId => shortRulePattern.test(fullRuleId));
562
+ }
563
+
172
564
  /**
173
565
  * Analyze files using heuristic patterns
174
566
  * Following Rule C006: Verb-noun naming
@@ -202,6 +594,14 @@ class HeuristicEngine extends AnalysisEngineInterface {
202
594
  const filesByLanguage = this.groupFilesByLanguage(files);
203
595
 
204
596
  for (const rule of rules) {
597
+ // Special case: Load C047 semantic rule on-demand
598
+ if (rule.id === 'C047' && !this.semanticRules.has('C047')) {
599
+ if (options.verbose) {
600
+ console.log(`🔬 [HeuristicEngine] Loading C047 semantic rule on-demand...`);
601
+ }
602
+ await this.manuallyLoadC047();
603
+ }
604
+
205
605
  if (!this.isRuleSupported(rule.id)) {
206
606
  if (options.verbose) {
207
607
  console.warn(`⚠️ Rule ${rule.id} not supported by Heuristic engine, skipping...`);
@@ -210,7 +610,21 @@ class HeuristicEngine extends AnalysisEngineInterface {
210
610
  }
211
611
 
212
612
  try {
213
- const ruleViolations = await this.analyzeRule(rule, filesByLanguage, options);
613
+ let ruleViolations = [];
614
+
615
+ // Check if this is a semantic rule first (higher priority)
616
+ if (this.semanticRules.has(rule.id)) {
617
+ if (options.verbose) {
618
+ console.log(`🧠 [HeuristicEngine] Running semantic analysis for rule ${rule.id}`);
619
+ }
620
+ ruleViolations = await this.analyzeSemanticRule(rule, files, options);
621
+ } else {
622
+ // Fallback to traditional analysis
623
+ if (options.verbose) {
624
+ console.log(`🔧 [HeuristicEngine] Running traditional analysis for rule ${rule.id}`);
625
+ }
626
+ ruleViolations = await this.analyzeRule(rule, filesByLanguage, options);
627
+ }
214
628
 
215
629
  if (ruleViolations.length > 0) {
216
630
  // Group violations by file
@@ -238,6 +652,65 @@ class HeuristicEngine extends AnalysisEngineInterface {
238
652
  return results;
239
653
  }
240
654
 
655
+ /**
656
+ * Analyze semantic rule across files
657
+ * Following Rule C006: Verb-noun naming
658
+ * @param {Object} rule - Rule to analyze
659
+ * @param {string[]} files - Files to analyze
660
+ * @param {Object} options - Analysis options
661
+ * @returns {Promise<Object[]>} Rule violations
662
+ */
663
+ async analyzeSemanticRule(rule, files, options) {
664
+ const semanticRuleInfo = this.semanticRules.get(rule.id);
665
+ if (!semanticRuleInfo) {
666
+ console.warn(`⚠️ Semantic rule ${rule.id} not found`);
667
+ return [];
668
+ }
669
+
670
+ try {
671
+ // Initialize rule on-demand (lazy initialization)
672
+ const ruleInstance = await this.initializeSemanticRule(rule.id);
673
+ if (!ruleInstance) {
674
+ console.warn(`⚠️ Failed to initialize semantic rule ${rule.id}`);
675
+ return [];
676
+ }
677
+
678
+ const allViolations = [];
679
+
680
+ // Run semantic analysis for each file
681
+ for (const filePath of files) {
682
+ try {
683
+ if (options.verbose) {
684
+ console.log(`🧠 [SemanticRule] Analyzing ${path.basename(filePath)} with ${rule.id}`);
685
+ }
686
+
687
+ // Call semantic rule's analyzeFile method
688
+ await ruleInstance.analyzeFile(filePath, options);
689
+
690
+ // Get violations from the rule instance
691
+ const fileViolations = ruleInstance.getViolations();
692
+ allViolations.push(...fileViolations);
693
+
694
+ // Clear violations for next file
695
+ ruleInstance.clearViolations();
696
+
697
+ } catch (fileError) {
698
+ console.warn(`⚠️ Semantic rule ${rule.id} failed for ${filePath}:`, fileError.message);
699
+ }
700
+ }
701
+
702
+ if (options.verbose && allViolations.length > 0) {
703
+ console.log(`🧠 [SemanticRule] Found ${allViolations.length} violations for ${rule.id}`);
704
+ }
705
+
706
+ return allViolations;
707
+
708
+ } catch (error) {
709
+ console.error(`❌ Failed to run semantic rule ${rule.id}:`, error.message);
710
+ return [];
711
+ }
712
+ }
713
+
241
714
  /**
242
715
  * Analyze a specific rule across files
243
716
  * Following Rule C006: Verb-noun naming
@@ -247,27 +720,42 @@ class HeuristicEngine extends AnalysisEngineInterface {
247
720
  * @returns {Promise<Object[]>} Rule violations
248
721
  */
249
722
  async analyzeRule(rule, filesByLanguage, options) {
250
- const analyzerInfo = this.ruleAnalyzers.get(rule.id);
723
+ // Get full rule ID (C029 -> C029_catch_block_logging)
724
+ const fullRuleId = this.getFullRuleId(rule.id);
725
+ const analyzerInfo = this.ruleAnalyzers.get(fullRuleId);
726
+
251
727
  if (!analyzerInfo) {
252
728
  return [];
253
729
  }
254
730
 
255
731
  try {
256
732
  // Get analyzer - handle both class and instance types
257
- const analyzerInfo = this.ruleAnalyzers.get(rule.id);
258
733
  let analyzer;
259
734
 
260
735
  if (analyzerInfo.type === 'class') {
261
736
  // Create analyzer instance from class
262
737
  const AnalyzerClass = analyzerInfo.class;
263
738
  try {
264
- analyzer = new AnalyzerClass();
739
+ analyzer = new AnalyzerClass({
740
+ verbose: options.verbose,
741
+ semanticEngine: this.semanticEngine
742
+ });
743
+
744
+ // Initialize with semantic engine if method exists
745
+ if (analyzer.initialize && typeof analyzer.initialize === 'function') {
746
+ await analyzer.initialize(this.semanticEngine);
747
+ }
265
748
  } catch (constructorError) {
266
749
  throw new Error(`Failed to instantiate analyzer class: ${constructorError.message}`);
267
750
  }
268
751
  } else if (analyzerInfo.type === 'instance') {
269
752
  // Use existing analyzer instance
270
753
  analyzer = analyzerInfo.instance;
754
+
755
+ // Initialize existing instance with semantic engine if method exists
756
+ if (analyzer.initialize && typeof analyzer.initialize === 'function') {
757
+ await analyzer.initialize(this.semanticEngine);
758
+ }
271
759
  } else {
272
760
  throw new Error(`Unknown analyzer type: ${analyzerInfo.type}`);
273
761
  }
@@ -288,7 +776,7 @@ class HeuristicEngine extends AnalysisEngineInterface {
288
776
 
289
777
  try {
290
778
  // Load rule config
291
- const ruleConfig = await this.loadRuleConfig(rule.id, analyzerInfo.folder, analyzerInfo.category);
779
+ const ruleConfig = await this.loadRuleConfig(rule.id, analyzerInfo.folder, analyzerInfo.category, options.verbose);
292
780
 
293
781
  // Run analysis with AST enhancement
294
782
  if (options.verbose) {
@@ -354,16 +842,19 @@ class HeuristicEngine extends AnalysisEngineInterface {
354
842
  * @param {string} ruleId - Rule ID
355
843
  * @param {string} ruleFolder - Rule folder name
356
844
  * @param {string} category - Rule category (common, security, etc)
845
+ * @param {boolean} verbose - Enable verbose logging
357
846
  * @returns {Promise<Object>} Rule configuration
358
847
  */
359
- async loadRuleConfig(ruleId, ruleFolder, category = 'common') {
848
+ async loadRuleConfig(ruleId, ruleFolder, category = 'common', verbose = false) {
360
849
  try {
361
850
  const configPath = path.resolve(__dirname, '../rules', category, ruleFolder, 'config.json');
362
851
  if (fs.existsSync(configPath)) {
363
852
  return require(configPath);
364
853
  }
365
854
  } catch (error) {
366
- console.warn(`⚠️ Failed to load config for ${ruleId}:`, error.message);
855
+ if (verbose) {
856
+ console.warn(`[DEBUG] ⚠️ Failed to load config for ${ruleId}:`, error.message);
857
+ }
367
858
  }
368
859
 
369
860
  // Return minimal config
@@ -870,4 +1361,4 @@ class HeuristicEngine extends AnalysisEngineInterface {
870
1361
  }
871
1362
  }
872
1363
 
873
- module.exports = HeuristicEngine;
1364
+ module.exports = HeuristicEngine;