@sun-asterisk/sunlint 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +40 -1
  2. package/CONTRIBUTING.md +533 -70
  3. package/README.md +16 -2
  4. package/config/engines/engines-enhanced.json +86 -0
  5. package/config/engines/semantic-config.json +114 -0
  6. package/config/eslint-rule-mapping.json +50 -38
  7. package/config/rules/enhanced-rules-registry.json +2503 -0
  8. package/config/rules/rules-registry-generated.json +785 -837
  9. package/core/adapters/sunlint-rule-adapter.js +25 -30
  10. package/core/analysis-orchestrator.js +42 -2
  11. package/core/categories.js +52 -0
  12. package/core/category-constants.js +39 -0
  13. package/core/cli-action-handler.js +32 -5
  14. package/core/config-manager.js +111 -0
  15. package/core/config-merger.js +61 -0
  16. package/core/constants/categories.js +168 -0
  17. package/core/constants/defaults.js +165 -0
  18. package/core/constants/engines.js +185 -0
  19. package/core/constants/index.js +30 -0
  20. package/core/constants/rules.js +215 -0
  21. package/core/file-targeting-service.js +128 -7
  22. package/core/interfaces/rule-plugin.interface.js +207 -0
  23. package/core/plugin-manager.js +448 -0
  24. package/core/rule-selection-service.js +42 -15
  25. package/core/semantic-engine.js +560 -0
  26. package/core/semantic-rule-base.js +433 -0
  27. package/core/unified-rule-registry.js +484 -0
  28. package/docs/CONSTANTS-ARCHITECTURE.md +288 -0
  29. package/engines/core/base-engine.js +249 -0
  30. package/engines/engine-factory.js +275 -0
  31. package/engines/eslint-engine.js +171 -19
  32. package/engines/heuristic-engine.js +511 -78
  33. package/integrations/eslint/plugin/index.js +27 -27
  34. package/package.json +10 -6
  35. package/rules/common/C003_no_vague_abbreviations/analyzer.js +1 -1
  36. package/rules/common/C029_catch_block_logging/analyzer.js +17 -5
  37. package/rules/common/C047_no_duplicate_retry_logic/c047-semantic-rule.js +278 -0
  38. package/rules/common/C047_no_duplicate_retry_logic/symbol-analyzer-enhanced.js +968 -0
  39. package/rules/common/C047_no_duplicate_retry_logic/symbol-config.json +71 -0
  40. package/rules/index.js +7 -0
  41. package/scripts/category-manager.js +150 -0
  42. package/scripts/generate-rules-registry.js +88 -0
  43. package/scripts/migrate-rule-registry.js +157 -0
  44. package/scripts/validate-system.js +48 -0
  45. package/.sunlint.json +0 -35
  46. package/config/README.md +0 -88
  47. package/config/engines/eslint-rule-mapping.json +0 -74
  48. package/config/schemas/sunlint-schema.json +0 -0
  49. package/config/testing/test-s005-working.ts +0 -22
  50. package/core/multi-rule-runner.js +0 -0
  51. package/engines/tree-sitter-parser.js +0 -0
  52. package/engines/universal-ast-engine.js +0 -0
  53. package/rules/common/C029_catch_block_logging/analyzer-backup.js +0 -426
  54. package/rules/common/C029_catch_block_logging/analyzer-fixed.js +0 -130
  55. package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +0 -487
  56. package/rules/common/C029_catch_block_logging/analyzer-simple.js +0 -110
  57. package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +0 -441
  58. package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +0 -127
  59. package/rules/common/C029_catch_block_logging/ast-analyzer.js +0 -133
  60. package/rules/common/C029_catch_block_logging/cfg-analyzer.js +0 -408
  61. package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +0 -454
  62. package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +0 -700
  63. package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +0 -568
  64. package/rules/common/C029_catch_block_logging/semantic-analyzer.js +0 -459
@@ -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;
@@ -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
- this.createDefaultRuleMapping();
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
- // Create default mapping for common rules
46
- this.createDefaultRuleMapping();
47
- console.warn('⚠️ Using default ESLint rule mapping');
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.createDefaultRuleMapping();
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(`🚨 [ESLintEngine] createDefaultRuleMapping() called - MAPPING WILL BE OVERWRITTEN!`);
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
- // Map common SunLint rules to ESLint equivalents
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), use rule ID directly in custom plugin
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), use rule ID directly in custom plugin
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
- } else {
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;