@sun-asterisk/sunlint 1.2.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -1
- package/CONTRIBUTING.md +533 -70
- package/README.md +16 -2
- 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/rules/enhanced-rules-registry.json +2503 -0
- package/config/rules/rules-registry-generated.json +785 -837
- 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 +32 -5
- package/core/config-manager.js +111 -0
- package/core/config-merger.js +61 -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/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 +560 -0
- package/core/semantic-rule-base.js +433 -0
- package/core/unified-rule-registry.js +484 -0
- package/docs/CONSTANTS-ARCHITECTURE.md +288 -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 +511 -78
- package/integrations/eslint/plugin/index.js +27 -27
- package/package.json +10 -6
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +1 -1
- package/rules/common/C029_catch_block_logging/analyzer.js +17 -5
- 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/index.js +7 -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/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/engines/tree-sitter-parser.js +0 -0
- package/engines/universal-ast-engine.js +0 -0
- 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
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine Factory
|
|
3
|
+
* Creates and manages engine instances with plugin support
|
|
4
|
+
* Following Rule C005: Single responsibility - Engine creation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const PluginManager = require('../core/plugin-manager');
|
|
8
|
+
const BaseEngine = require('./core/base-engine');
|
|
9
|
+
|
|
10
|
+
// Engine imports
|
|
11
|
+
const HeuristicEngine = require('../engines/heuristic-engine');
|
|
12
|
+
const ESLintEngine = require('../engines/eslint-engine');
|
|
13
|
+
const OpenAIEngine = require('../engines/openai-engine');
|
|
14
|
+
|
|
15
|
+
class EngineFactory {
|
|
16
|
+
constructor() {
|
|
17
|
+
this.pluginManager = null;
|
|
18
|
+
this.engines = new Map();
|
|
19
|
+
this.verbose = false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Initialize factory with plugin manager
|
|
24
|
+
* @param {Object} config - Configuration options
|
|
25
|
+
*/
|
|
26
|
+
async initialize(config = {}) {
|
|
27
|
+
this.verbose = config.verbose || false;
|
|
28
|
+
|
|
29
|
+
// Initialize plugin manager
|
|
30
|
+
this.pluginManager = new PluginManager();
|
|
31
|
+
await this.pluginManager.initialize(config);
|
|
32
|
+
|
|
33
|
+
if (this.verbose) {
|
|
34
|
+
console.log('🏭 Engine Factory initialized');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Create an engine instance
|
|
40
|
+
* @param {string} engineType - Type of engine to create
|
|
41
|
+
* @param {Object} config - Engine configuration
|
|
42
|
+
* @returns {BaseEngine} Engine instance
|
|
43
|
+
*/
|
|
44
|
+
async createEngine(engineType, config = {}) {
|
|
45
|
+
const engineConfig = {
|
|
46
|
+
...config,
|
|
47
|
+
verbose: this.verbose,
|
|
48
|
+
pluginManager: this.pluginManager
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
let engine = null;
|
|
52
|
+
|
|
53
|
+
switch (engineType.toLowerCase()) {
|
|
54
|
+
case 'heuristic':
|
|
55
|
+
engine = new HeuristicEngine();
|
|
56
|
+
break;
|
|
57
|
+
case 'eslint':
|
|
58
|
+
engine = new ESLintEngine();
|
|
59
|
+
break;
|
|
60
|
+
case 'openai':
|
|
61
|
+
engine = new OpenAIEngine();
|
|
62
|
+
break;
|
|
63
|
+
default:
|
|
64
|
+
throw new Error(`Unknown engine type: ${engineType}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Set plugin manager and initialize
|
|
68
|
+
if (engine instanceof BaseEngine) {
|
|
69
|
+
engine.setPluginManager(this.pluginManager);
|
|
70
|
+
await engine.initialize(engineConfig);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Cache engine instance
|
|
74
|
+
this.engines.set(engineType, engine);
|
|
75
|
+
|
|
76
|
+
if (this.verbose) {
|
|
77
|
+
console.log(`🔧 Created ${engineType} engine with ${engine.ruleRegistry.size} rules`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return engine;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get an existing engine or create a new one
|
|
85
|
+
* @param {string} engineType - Type of engine
|
|
86
|
+
* @param {Object} config - Engine configuration
|
|
87
|
+
* @returns {BaseEngine} Engine instance
|
|
88
|
+
*/
|
|
89
|
+
async getEngine(engineType, config = {}) {
|
|
90
|
+
if (this.engines.has(engineType)) {
|
|
91
|
+
return this.engines.get(engineType);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return await this.createEngine(engineType, config);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get all available engines
|
|
99
|
+
* @returns {Map} Map of engine type to engine instance
|
|
100
|
+
*/
|
|
101
|
+
getAllEngines() {
|
|
102
|
+
return this.engines;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Check if engine type is supported
|
|
107
|
+
* @param {string} engineType - Engine type to check
|
|
108
|
+
* @returns {boolean} True if supported
|
|
109
|
+
*/
|
|
110
|
+
isEngineSupported(engineType) {
|
|
111
|
+
const supportedEngines = ['heuristic', 'eslint', 'openai'];
|
|
112
|
+
return supportedEngines.includes(engineType.toLowerCase());
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get engine metadata
|
|
117
|
+
* @param {string} engineType - Engine type
|
|
118
|
+
* @returns {Object|null} Engine metadata
|
|
119
|
+
*/
|
|
120
|
+
getEngineMetadata(engineType) {
|
|
121
|
+
const engine = this.engines.get(engineType);
|
|
122
|
+
return engine ? engine.getMetadata() : null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Reload custom rules in all engines
|
|
127
|
+
* @param {Object} config - Configuration options
|
|
128
|
+
*/
|
|
129
|
+
async reloadCustomRules(config = {}) {
|
|
130
|
+
if (this.pluginManager) {
|
|
131
|
+
await this.pluginManager.reloadCustomRules(config);
|
|
132
|
+
|
|
133
|
+
// Reinitialize all engines to pick up new rules
|
|
134
|
+
for (const [engineType, engine] of this.engines) {
|
|
135
|
+
if (engine instanceof BaseEngine) {
|
|
136
|
+
await engine.initialize({ ...config, verbose: this.verbose });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (this.verbose) {
|
|
141
|
+
console.log('🔄 Reloaded custom rules in all engines');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get compatible engines for a language
|
|
148
|
+
* @param {string} language - Programming language
|
|
149
|
+
* @returns {Array} Array of compatible engine types
|
|
150
|
+
*/
|
|
151
|
+
getCompatibleEngines(language) {
|
|
152
|
+
const compatible = [];
|
|
153
|
+
|
|
154
|
+
for (const [engineType, engine] of this.engines) {
|
|
155
|
+
if (engine.supportedLanguages.includes(language.toLowerCase())) {
|
|
156
|
+
compatible.push(engineType);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return compatible;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Create multiple engines from configuration
|
|
165
|
+
* @param {Object} engineConfig - Engine configuration object
|
|
166
|
+
* @returns {Map} Map of created engines
|
|
167
|
+
*/
|
|
168
|
+
async createEnginesFromConfig(engineConfig = {}) {
|
|
169
|
+
const engines = new Map();
|
|
170
|
+
|
|
171
|
+
for (const [engineType, config] of Object.entries(engineConfig)) {
|
|
172
|
+
if (this.isEngineSupported(engineType)) {
|
|
173
|
+
try {
|
|
174
|
+
const engine = await this.createEngine(engineType, config);
|
|
175
|
+
engines.set(engineType, engine);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
if (this.verbose) {
|
|
178
|
+
console.warn(`⚠️ Failed to create ${engineType} engine: ${error.message}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return engines;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Analyze files using best engine for language
|
|
189
|
+
* @param {Array} files - Files to analyze
|
|
190
|
+
* @param {string} language - Programming language
|
|
191
|
+
* @param {Array} rules - Rules to apply
|
|
192
|
+
* @param {Object} options - Analysis options
|
|
193
|
+
* @returns {Array} Array of violations
|
|
194
|
+
*/
|
|
195
|
+
async analyzeWithBestEngine(files, language, rules, options = {}) {
|
|
196
|
+
const compatibleEngines = this.getCompatibleEngines(language);
|
|
197
|
+
|
|
198
|
+
if (compatibleEngines.length === 0) {
|
|
199
|
+
throw new Error(`No compatible engines found for language: ${language}`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Priority: heuristic > eslint > openai
|
|
203
|
+
const enginePriority = ['heuristic', 'eslint', 'openai'];
|
|
204
|
+
const bestEngine = enginePriority.find(type => compatibleEngines.includes(type));
|
|
205
|
+
|
|
206
|
+
if (!bestEngine) {
|
|
207
|
+
throw new Error(`No suitable engine found for language: ${language}`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const engine = await this.getEngine(bestEngine, options);
|
|
211
|
+
return await engine.analyze(files, rules, { ...options, language });
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Cleanup all engines and plugin manager
|
|
216
|
+
*/
|
|
217
|
+
async cleanup() {
|
|
218
|
+
// Cleanup all engines
|
|
219
|
+
for (const [engineType, engine] of this.engines) {
|
|
220
|
+
try {
|
|
221
|
+
if (engine.cleanup) {
|
|
222
|
+
await engine.cleanup();
|
|
223
|
+
}
|
|
224
|
+
} catch (error) {
|
|
225
|
+
if (this.verbose) {
|
|
226
|
+
console.warn(`⚠️ Error cleaning up ${engineType} engine: ${error.message}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Cleanup plugin manager
|
|
232
|
+
if (this.pluginManager) {
|
|
233
|
+
await this.pluginManager.cleanup();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
this.engines.clear();
|
|
237
|
+
this.pluginManager = null;
|
|
238
|
+
|
|
239
|
+
if (this.verbose) {
|
|
240
|
+
console.log('🧹 Engine Factory cleanup completed');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Get factory statistics
|
|
246
|
+
* @returns {Object} Factory statistics
|
|
247
|
+
*/
|
|
248
|
+
getStatistics() {
|
|
249
|
+
const stats = {
|
|
250
|
+
engineCount: this.engines.size,
|
|
251
|
+
engines: {},
|
|
252
|
+
totalRules: 0
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
for (const [engineType, engine] of this.engines) {
|
|
256
|
+
const metadata = engine.getMetadata ? engine.getMetadata() : {};
|
|
257
|
+
stats.engines[engineType] = {
|
|
258
|
+
...metadata,
|
|
259
|
+
ruleCount: engine.ruleRegistry ? engine.ruleRegistry.size : 0
|
|
260
|
+
};
|
|
261
|
+
stats.totalRules += stats.engines[engineType].ruleCount;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (this.pluginManager) {
|
|
265
|
+
stats.pluginManager = {
|
|
266
|
+
totalPlugins: this.pluginManager.getAllPlugins().size,
|
|
267
|
+
customRules: this.pluginManager.customRules.size
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return stats;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
module.exports = EngineFactory;
|
package/engines/eslint-engine.js
CHANGED
|
@@ -9,6 +9,7 @@ const AnalysisEngineInterface = require('../core/interfaces/analysis-engine.inte
|
|
|
9
9
|
const dependencyChecker = require('../core/dependency-checker');
|
|
10
10
|
const fs = require('fs');
|
|
11
11
|
const path = require('path');
|
|
12
|
+
const { getInstance } = require('../core/unified-rule-registry');
|
|
12
13
|
|
|
13
14
|
class ESLintEngine extends AnalysisEngineInterface {
|
|
14
15
|
constructor() {
|
|
@@ -18,12 +19,16 @@ class ESLintEngine extends AnalysisEngineInterface {
|
|
|
18
19
|
this.configFiles = new Map();
|
|
19
20
|
this.ruleMapping = new Map();
|
|
20
21
|
|
|
22
|
+
// Unified rule registry
|
|
23
|
+
this.unifiedRegistry = getInstance();
|
|
24
|
+
|
|
21
25
|
// Load rule mapping immediately (synchronous)
|
|
22
26
|
try {
|
|
23
27
|
this.loadRuleMappingSync();
|
|
24
28
|
} catch (error) {
|
|
25
29
|
console.error('🚨 Constructor failed to load mapping:', error.message);
|
|
26
|
-
|
|
30
|
+
// Defer async mapping loading to when needed
|
|
31
|
+
this.mappingLoaded = false;
|
|
27
32
|
}
|
|
28
33
|
}
|
|
29
34
|
|
|
@@ -41,15 +46,27 @@ class ESLintEngine extends AnalysisEngineInterface {
|
|
|
41
46
|
for (const [sunlintRule, eslintRules] of Object.entries(mapping)) {
|
|
42
47
|
this.ruleMapping.set(sunlintRule, eslintRules);
|
|
43
48
|
}
|
|
49
|
+
this.mappingLoaded = true;
|
|
44
50
|
} else {
|
|
45
|
-
//
|
|
46
|
-
this.
|
|
47
|
-
console.warn('⚠️
|
|
51
|
+
// Mark as not loaded, will load from registry later
|
|
52
|
+
this.mappingLoaded = false;
|
|
53
|
+
console.warn('⚠️ Legacy ESLint mapping file not found, will load from unified registry');
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
} catch (error) {
|
|
51
57
|
console.warn('⚠️ [ESLintEngine] Failed to load ESLint rule mapping:', error.message);
|
|
52
|
-
this.
|
|
58
|
+
this.mappingLoaded = false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Ensure rule mapping is loaded (async)
|
|
64
|
+
*/
|
|
65
|
+
async ensureMappingLoaded() {
|
|
66
|
+
if (!this.mappingLoaded) {
|
|
67
|
+
console.log('📋 [ESLintEngine] Loading rule mapping from unified registry...');
|
|
68
|
+
await this.createDefaultRuleMapping();
|
|
69
|
+
this.mappingLoaded = true;
|
|
53
70
|
}
|
|
54
71
|
}
|
|
55
72
|
|
|
@@ -301,12 +318,14 @@ class ESLintEngine extends AnalysisEngineInterface {
|
|
|
301
318
|
const rules = this.extractRulesFromConfig(eslintConfig);
|
|
302
319
|
const needsReact = this.needsReactPlugins(rules);
|
|
303
320
|
const needsTypeScript = this.needsTypeScriptPlugins(rules);
|
|
321
|
+
const needsImport = this.needsImportPlugin(rules);
|
|
304
322
|
|
|
305
323
|
// Check plugin availability in target project
|
|
306
324
|
const hasReact = needsReact && this.isReactPluginAvailable(projectPath);
|
|
307
325
|
const hasReactHooks = needsReact && this.isReactHooksPluginAvailable(projectPath);
|
|
308
326
|
const hasTypeScript = needsTypeScript && this.isTypeScriptPluginAvailable(projectPath);
|
|
309
327
|
const hasTypeScriptParser = this.isTypeScriptParserAvailable(projectPath);
|
|
328
|
+
const hasImport = needsImport && this.isImportPluginAvailable(projectPath);
|
|
310
329
|
|
|
311
330
|
let pluginImports = '';
|
|
312
331
|
let pluginDefs = '{ "custom": customPlugin';
|
|
@@ -326,11 +345,16 @@ class ESLintEngine extends AnalysisEngineInterface {
|
|
|
326
345
|
pluginDefs += ', "@typescript-eslint": typescriptPlugin';
|
|
327
346
|
}
|
|
328
347
|
|
|
348
|
+
if (hasImport) {
|
|
349
|
+
pluginImports += `\nimport importPlugin from 'eslint-plugin-import';`;
|
|
350
|
+
pluginDefs += ', "import": importPlugin';
|
|
351
|
+
}
|
|
352
|
+
|
|
329
353
|
pluginDefs += ' }';
|
|
330
354
|
|
|
331
355
|
// Filter rules to only include those for available plugins
|
|
332
356
|
const filteredRules = {};
|
|
333
|
-
const skippedRules = { react: [], reactHooks: [], typescript: [] };
|
|
357
|
+
const skippedRules = { react: [], reactHooks: [], typescript: [], import: [] };
|
|
334
358
|
|
|
335
359
|
for (const [ruleKey, ruleConfig] of Object.entries(eslintConfig.rules || {})) {
|
|
336
360
|
if (ruleKey.startsWith('react/') && !hasReact) {
|
|
@@ -345,6 +369,10 @@ class ESLintEngine extends AnalysisEngineInterface {
|
|
|
345
369
|
skippedRules.typescript.push(ruleKey);
|
|
346
370
|
continue;
|
|
347
371
|
}
|
|
372
|
+
if (ruleKey.startsWith('import/') && !hasImport) {
|
|
373
|
+
skippedRules.import.push(ruleKey);
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
348
376
|
filteredRules[ruleKey] = ruleConfig;
|
|
349
377
|
}
|
|
350
378
|
|
|
@@ -358,6 +386,9 @@ class ESLintEngine extends AnalysisEngineInterface {
|
|
|
358
386
|
if (skippedRules.typescript.length > 0) {
|
|
359
387
|
console.warn(`⚠️ [ESLintEngine] Skipped ${skippedRules.typescript.length} TypeScript ESLint rules - plugin not available`);
|
|
360
388
|
}
|
|
389
|
+
if (skippedRules.import.length > 0) {
|
|
390
|
+
console.warn(`⚠️ [ESLintEngine] Skipped ${skippedRules.import.length} Import rules - plugin not available`);
|
|
391
|
+
}
|
|
361
392
|
|
|
362
393
|
// Use only SunLint analysis config (filteredRules) - do not merge with project rules
|
|
363
394
|
const mergedConfig = {
|
|
@@ -568,13 +599,18 @@ export default [
|
|
|
568
599
|
}
|
|
569
600
|
|
|
570
601
|
/**
|
|
571
|
-
* Create default rule mapping
|
|
602
|
+
* Create default rule mapping (DEPRECATED - use unified registry)
|
|
572
603
|
* Following Rule C006: Verb-noun naming
|
|
573
604
|
*/
|
|
574
|
-
createDefaultRuleMapping() {
|
|
575
|
-
console.log(
|
|
605
|
+
async createDefaultRuleMapping() {
|
|
606
|
+
console.log(`⚠️ [ESLintEngine] createDefaultRuleMapping() is DEPRECATED - using unified registry instead`);
|
|
607
|
+
|
|
608
|
+
// Use unified registry instead of hardcoded mappings
|
|
609
|
+
if (this.unifiedRegistry) {
|
|
610
|
+
return await this.loadMappingsFromRegistry();
|
|
611
|
+
}
|
|
576
612
|
|
|
577
|
-
//
|
|
613
|
+
// Legacy fallback mapping (will be removed)
|
|
578
614
|
const defaultMappings = {
|
|
579
615
|
'C005': ['max-statements-per-line', 'complexity'],
|
|
580
616
|
'C006': ['func-names', 'func-name-matching'],
|
|
@@ -599,6 +635,32 @@ export default [
|
|
|
599
635
|
console.warn('⚠️ Using default ESLint rule mapping');
|
|
600
636
|
}
|
|
601
637
|
|
|
638
|
+
/**
|
|
639
|
+
* Load rule mappings from unified registry
|
|
640
|
+
*/
|
|
641
|
+
async loadMappingsFromRegistry() {
|
|
642
|
+
if (!this.unifiedRegistry.initialized) {
|
|
643
|
+
await this.unifiedRegistry.initialize();
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const mappings = {};
|
|
647
|
+
for (const [ruleId, ruleDefinition] of this.unifiedRegistry.rules.entries()) {
|
|
648
|
+
if (ruleDefinition.engineMappings && ruleDefinition.engineMappings.eslint) {
|
|
649
|
+
mappings[ruleId] = ruleDefinition.engineMappings.eslint;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
console.log(`📋 [ESLintEngine] Loaded ${Object.keys(mappings).length} mappings from unified registry`);
|
|
654
|
+
|
|
655
|
+
// Clear existing mapping and set new ones
|
|
656
|
+
this.ruleMapping.clear();
|
|
657
|
+
for (const [sunlintRule, eslintRules] of Object.entries(mappings)) {
|
|
658
|
+
this.ruleMapping.set(sunlintRule, eslintRules);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return mappings;
|
|
662
|
+
}
|
|
663
|
+
|
|
602
664
|
/**
|
|
603
665
|
* Detect project type from package.json and file patterns
|
|
604
666
|
* @param {string} projectPath - Project path
|
|
@@ -908,6 +970,20 @@ export default [
|
|
|
908
970
|
}
|
|
909
971
|
}
|
|
910
972
|
|
|
973
|
+
/**
|
|
974
|
+
* Check if Import plugin is available in project
|
|
975
|
+
* @param {string} projectPath - Project path to check
|
|
976
|
+
* @returns {boolean} True if Import plugin is available
|
|
977
|
+
*/
|
|
978
|
+
isImportPluginAvailable(projectPath) {
|
|
979
|
+
try {
|
|
980
|
+
require.resolve('eslint-plugin-import', { paths: [projectPath] });
|
|
981
|
+
return true;
|
|
982
|
+
} catch (error) {
|
|
983
|
+
return false;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
911
987
|
/**
|
|
912
988
|
* Load React ESLint plugin
|
|
913
989
|
* Following Rule C006: Verb-noun naming
|
|
@@ -1026,6 +1102,9 @@ export default [
|
|
|
1026
1102
|
throw new Error('ESLint engine not initialized');
|
|
1027
1103
|
}
|
|
1028
1104
|
|
|
1105
|
+
// Ensure rule mapping is loaded from unified registry
|
|
1106
|
+
await this.ensureMappingLoaded();
|
|
1107
|
+
|
|
1029
1108
|
const results = {
|
|
1030
1109
|
results: [],
|
|
1031
1110
|
filesAnalyzed: 0,
|
|
@@ -1296,6 +1375,18 @@ export default [
|
|
|
1296
1375
|
});
|
|
1297
1376
|
}
|
|
1298
1377
|
|
|
1378
|
+
/**
|
|
1379
|
+
* Check if rules need Import plugin
|
|
1380
|
+
*/
|
|
1381
|
+
needsImportPlugin(rules) {
|
|
1382
|
+
// Check if any rules use import/ prefix or specific rules that need import plugin
|
|
1383
|
+
return rules.some(ruleId => {
|
|
1384
|
+
const id = typeof ruleId === 'string' ? ruleId : ruleId.id || ruleId.name;
|
|
1385
|
+
return id && (id.includes('import/') ||
|
|
1386
|
+
['C038', 'C040'].includes(id)); // Rules that map to import plugin
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1299
1390
|
/**
|
|
1300
1391
|
* Build dynamic plugins based on rules being analyzed
|
|
1301
1392
|
* @param {Array} rules - Rules to analyze
|
|
@@ -1370,8 +1461,41 @@ export default [
|
|
|
1370
1461
|
continue;
|
|
1371
1462
|
}
|
|
1372
1463
|
|
|
1373
|
-
// For Common rules (C series),
|
|
1464
|
+
// For Common rules (C series), check mapping file first
|
|
1374
1465
|
if (rule.id.startsWith('C')) {
|
|
1466
|
+
const eslintRules = this.ruleMapping.get(rule.id);
|
|
1467
|
+
|
|
1468
|
+
if (eslintRules && Array.isArray(eslintRules)) {
|
|
1469
|
+
// Check if mapping contains custom rules
|
|
1470
|
+
const customRules = eslintRules.filter(r => r.startsWith('custom/'));
|
|
1471
|
+
const builtinRules = eslintRules.filter(r => !r.startsWith('custom/'));
|
|
1472
|
+
|
|
1473
|
+
// If mapping has custom rules, use them
|
|
1474
|
+
if (customRules.length > 0) {
|
|
1475
|
+
for (const customRule of customRules) {
|
|
1476
|
+
const ruleConfig = this.mapSeverity(rule.severity || 'warning');
|
|
1477
|
+
|
|
1478
|
+
// Add rule configuration for specific rules
|
|
1479
|
+
if (rule.id === 'C010') {
|
|
1480
|
+
config.rules[customRule] = [ruleConfig, { maxDepth: 3 }];
|
|
1481
|
+
} else {
|
|
1482
|
+
config.rules[customRule] = ruleConfig;
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
continue;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
// If mapping has only builtin rules, use them
|
|
1489
|
+
if (builtinRules.length > 0) {
|
|
1490
|
+
for (const eslintRule of builtinRules) {
|
|
1491
|
+
const severity = this.mapSeverity(rule.severity || 'warning');
|
|
1492
|
+
config.rules[eslintRule] = severity;
|
|
1493
|
+
}
|
|
1494
|
+
continue;
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
// If no mapping found, fallback to auto-generated custom rule name
|
|
1375
1499
|
const customRuleName = `custom/${rule.id.toLowerCase()}`;
|
|
1376
1500
|
const ruleConfig = this.mapSeverity(rule.severity || 'warning');
|
|
1377
1501
|
|
|
@@ -1384,29 +1508,57 @@ export default [
|
|
|
1384
1508
|
continue;
|
|
1385
1509
|
}
|
|
1386
1510
|
|
|
1387
|
-
// For TypeScript rules (T series),
|
|
1511
|
+
// For TypeScript rules (T series), check mapping file first
|
|
1388
1512
|
if (rule.id.startsWith('T')) {
|
|
1513
|
+
const eslintRules = this.ruleMapping.get(rule.id);
|
|
1514
|
+
|
|
1515
|
+
if (eslintRules && Array.isArray(eslintRules)) {
|
|
1516
|
+
// Check if mapping contains custom rules
|
|
1517
|
+
const customRules = eslintRules.filter(r => r.startsWith('custom/'));
|
|
1518
|
+
const builtinRules = eslintRules.filter(r => !r.startsWith('custom/'));
|
|
1519
|
+
|
|
1520
|
+
// If mapping has custom rules, use them
|
|
1521
|
+
if (customRules.length > 0) {
|
|
1522
|
+
for (const customRule of customRules) {
|
|
1523
|
+
const ruleConfig = this.mapSeverity(rule.severity || 'warning');
|
|
1524
|
+
config.rules[customRule] = ruleConfig;
|
|
1525
|
+
}
|
|
1526
|
+
continue;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
// If mapping has only builtin rules, use them
|
|
1530
|
+
if (builtinRules.length > 0) {
|
|
1531
|
+
for (const eslintRule of builtinRules) {
|
|
1532
|
+
const severity = this.mapSeverity(rule.severity || 'warning');
|
|
1533
|
+
config.rules[eslintRule] = severity;
|
|
1534
|
+
}
|
|
1535
|
+
continue;
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
// If no mapping found, fallback to auto-generated custom rule name
|
|
1389
1540
|
const customRuleName = `custom/${rule.id.toLowerCase()}`;
|
|
1390
1541
|
const ruleConfig = this.mapSeverity(rule.severity || 'warning');
|
|
1391
1542
|
config.rules[customRuleName] = ruleConfig;
|
|
1392
1543
|
continue;
|
|
1393
1544
|
}
|
|
1394
1545
|
|
|
1395
|
-
// For other rules, check mapping file
|
|
1546
|
+
// For other rules, check mapping file first
|
|
1396
1547
|
const eslintRules = this.ruleMapping.get(rule.id);
|
|
1397
1548
|
|
|
1398
1549
|
if (eslintRules && Array.isArray(eslintRules)) {
|
|
1550
|
+
// Use mapping file (for builtin ESLint rules)
|
|
1399
1551
|
for (const eslintRule of eslintRules) {
|
|
1400
|
-
// Set rule severity based on SunLint rule
|
|
1401
1552
|
const severity = this.mapSeverity(rule.severity || 'warning');
|
|
1402
1553
|
config.rules[eslintRule] = severity;
|
|
1403
1554
|
}
|
|
1404
|
-
|
|
1405
|
-
// Fallback - try as custom rule
|
|
1406
|
-
const customRuleName = `custom/${rule.id.toLowerCase()}`;
|
|
1407
|
-
const ruleConfig = this.mapSeverity(rule.severity || 'warning');
|
|
1408
|
-
config.rules[customRuleName] = ruleConfig;
|
|
1555
|
+
continue;
|
|
1409
1556
|
}
|
|
1557
|
+
|
|
1558
|
+
// Fallback - try as custom rule for any remaining rules
|
|
1559
|
+
const customRuleName = `custom/${rule.id.toLowerCase()}`;
|
|
1560
|
+
const ruleConfig = this.mapSeverity(rule.severity || 'warning');
|
|
1561
|
+
config.rules[customRuleName] = ruleConfig;
|
|
1410
1562
|
}
|
|
1411
1563
|
|
|
1412
1564
|
return config;
|