@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.
- package/CHANGELOG.md +107 -1
- package/CONTRIBUTING.md +1654 -66
- package/README.md +19 -6
- package/config/ci-cd.json +54 -0
- package/config/development.json +56 -0
- package/config/engines/engines-enhanced.json +86 -0
- package/config/engines/semantic-config.json +114 -0
- package/config/eslint-rule-mapping.json +50 -38
- package/config/large-project.json +143 -0
- package/config/presets/all.json +0 -1
- package/config/release.json +70 -0
- package/config/rule-analysis-strategies.js +23 -4
- package/config/rules/S027-categories.json +122 -0
- package/config/rules/enhanced-rules-registry.json +2564 -0
- package/config/rules/rules-registry-generated.json +785 -837
- package/config/rules/rules-registry.json +13 -1
- package/core/adapters/sunlint-rule-adapter.js +25 -30
- package/core/analysis-orchestrator.js +42 -2
- package/core/categories.js +52 -0
- package/core/category-constants.js +39 -0
- package/core/cli-action-handler.js +53 -32
- package/core/cli-program.js +11 -3
- package/core/config-manager.js +111 -0
- package/core/config-merger.js +88 -0
- package/core/constants/categories.js +168 -0
- package/core/constants/defaults.js +165 -0
- package/core/constants/engines.js +185 -0
- package/core/constants/index.js +30 -0
- package/core/constants/rules.js +215 -0
- package/core/enhanced-rules-registry.js +3 -3
- package/core/file-targeting-service.js +128 -7
- package/core/interfaces/rule-plugin.interface.js +207 -0
- package/core/plugin-manager.js +448 -0
- package/core/rule-selection-service.js +42 -15
- package/core/semantic-engine.js +658 -0
- package/core/semantic-rule-base.js +433 -0
- package/core/unified-rule-registry.js +484 -0
- package/docs/COMMAND-EXAMPLES.md +134 -0
- package/docs/CONSTANTS-ARCHITECTURE.md +288 -0
- package/docs/LARGE-PROJECT-GUIDE.md +324 -0
- package/engines/core/base-engine.js +249 -0
- package/engines/engine-factory.js +275 -0
- package/engines/eslint-engine.js +171 -19
- package/engines/heuristic-engine.js +569 -78
- package/integrations/eslint/plugin/index.js +26 -28
- package/origin-rules/common-en.md +8 -8
- package/package.json +10 -6
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +1 -1
- package/rules/common/C017_constructor_logic/analyzer.js +254 -17
- package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -0
- package/rules/common/C029_catch_block_logging/analyzer.js +17 -5
- package/rules/common/C033_separate_service_repository/README.md +78 -0
- package/rules/common/C033_separate_service_repository/analyzer.js +160 -0
- package/rules/common/C033_separate_service_repository/config.json +50 -0
- package/rules/common/C033_separate_service_repository/regex-based-analyzer.js +585 -0
- package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +368 -0
- package/rules/common/C035_error_logging_context/STRATEGY.md +99 -0
- package/rules/common/C035_error_logging_context/analyzer.js +230 -0
- package/rules/common/C035_error_logging_context/config.json +54 -0
- package/rules/common/C035_error_logging_context/regex-based-analyzer.js +299 -0
- package/rules/common/C035_error_logging_context/symbol-based-analyzer.js +454 -0
- package/rules/common/C040_centralized_validation/analyzer.js +165 -0
- package/rules/common/C040_centralized_validation/config.json +46 -0
- package/rules/common/C040_centralized_validation/regex-based-analyzer.js +243 -0
- package/rules/common/C040_centralized_validation/symbol-based-analyzer.js +416 -0
- package/rules/common/C047_no_duplicate_retry_logic/c047-semantic-rule.js +278 -0
- package/rules/common/C047_no_duplicate_retry_logic/symbol-analyzer-enhanced.js +968 -0
- package/rules/common/C047_no_duplicate_retry_logic/symbol-config.json +71 -0
- package/rules/common/{C076_single_test_behavior → C072_single_test_behavior}/analyzer.js +6 -6
- package/rules/common/C076_explicit_function_types/README.md +30 -0
- package/rules/common/C076_explicit_function_types/analyzer.js +172 -0
- package/rules/common/C076_explicit_function_types/config.json +15 -0
- package/rules/common/C076_explicit_function_types/semantic-analyzer.js +341 -0
- package/rules/index.js +8 -0
- package/rules/parser/rule-parser.js +13 -2
- package/rules/security/S005_no_origin_auth/README.md +226 -0
- package/rules/security/S005_no_origin_auth/analyzer.js +184 -0
- package/rules/security/S005_no_origin_auth/ast-analyzer.js +406 -0
- package/rules/security/S005_no_origin_auth/config.json +85 -0
- package/rules/security/S006_no_plaintext_recovery_codes/README.md +139 -0
- package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +306 -0
- package/rules/security/S006_no_plaintext_recovery_codes/config.json +48 -0
- package/rules/security/S007_no_plaintext_otp/README.md +198 -0
- package/rules/security/S007_no_plaintext_otp/analyzer.js +406 -0
- package/rules/security/S007_no_plaintext_otp/config.json +79 -0
- package/rules/security/S007_no_plaintext_otp/semantic-analyzer.js +609 -0
- package/rules/security/S007_no_plaintext_otp/semantic-config.json +195 -0
- package/rules/security/S007_no_plaintext_otp/semantic-wrapper.js +280 -0
- package/rules/security/S027_no_hardcoded_secrets/analyzer.js +180 -366
- package/rules/security/S027_no_hardcoded_secrets/categories.json +153 -0
- package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +250 -0
- package/scripts/category-manager.js +150 -0
- package/scripts/generate-rules-registry.js +88 -0
- package/scripts/migrate-rule-registry.js +157 -0
- package/scripts/prepare-release.sh +1 -1
- package/scripts/validate-system.js +48 -0
- package/.sunlint.json +0 -35
- package/config/README.md +0 -88
- package/config/engines/eslint-rule-mapping.json +0 -74
- package/config/schemas/sunlint-schema.json +0 -0
- package/config/testing/test-s005-working.ts +0 -22
- package/core/multi-rule-runner.js +0 -0
- package/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
- package/docs/FUTURE_PACKAGES.md +0 -83
- package/docs/HEURISTIC_VS_AI.md +0 -113
- package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +0 -112
- package/docs/PRODUCTION_SIZE_IMPACT.md +0 -183
- package/docs/RELEASE_GUIDE.md +0 -230
- package/docs/STANDARDIZED-CATEGORY-FILTERING.md +0 -156
- package/engines/tree-sitter-parser.js +0 -0
- package/engines/universal-ast-engine.js +0 -0
- package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +0 -254
- package/rules/common/C029_catch_block_logging/analyzer-backup.js +0 -426
- package/rules/common/C029_catch_block_logging/analyzer-fixed.js +0 -130
- package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +0 -487
- package/rules/common/C029_catch_block_logging/analyzer-simple.js +0 -110
- package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +0 -441
- package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +0 -127
- package/rules/common/C029_catch_block_logging/ast-analyzer.js +0 -133
- package/rules/common/C029_catch_block_logging/cfg-analyzer.js +0 -408
- package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +0 -454
- package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +0 -700
- package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +0 -568
- package/rules/common/C029_catch_block_logging/semantic-analyzer.js +0 -459
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Heuristic Analysis Engine Plugin
|
|
3
|
-
* Following Rule C005: Single responsibility - Pattern-based analysis with
|
|
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', '
|
|
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
|
-
//
|
|
42
|
-
await this.
|
|
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
|
|
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
|
-
*
|
|
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 =
|
|
278
|
+
const ruleId = ruleFolder; // Use folder name directly as rule ID
|
|
279
|
+
const rulePath = path.join(categoryPath, ruleFolder);
|
|
84
280
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
//
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|