@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
@@ -0,0 +1,448 @@
1
+ /**
2
+ * Plugin Manager
3
+ * Manages rule plugins lifecycle and loading
4
+ * Following Rule C005: Single responsibility - Plugin management
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const { RulePluginInterface, SemanticRuleInterface, CustomRuleInterface } = require('./interfaces/rule-plugin.interface');
10
+ const { isValidCategory, getValidCategories, getDefaultCategory, normalizeCategory } = require('./constants/categories');
11
+
12
+ class PluginManager {
13
+ constructor() {
14
+ this.plugins = new Map();
15
+ this.customRules = new Map();
16
+ this.loadedEngines = new Set();
17
+ this.verbose = false;
18
+ }
19
+
20
+ /**
21
+ * Initialize plugin manager
22
+ * @param {Object} config - Configuration options
23
+ */
24
+ async initialize(config = {}) {
25
+ this.verbose = config.verbose || false;
26
+
27
+ // Load core rules first (always loaded)
28
+ await this.loadCoreRules(config);
29
+
30
+ // Load custom rules from .sunlint.json (additional support)
31
+ await this.loadCustomRules(config);
32
+
33
+ if (this.verbose) {
34
+ console.log(`🔌 Plugin Manager initialized: ${this.plugins.size} rules loaded`);
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Load core rules from rules directory
40
+ * @param {Object} config - Configuration options
41
+ */
42
+ async loadCoreRules(config = {}) {
43
+ const rulesDir = path.resolve(__dirname, '../../rules');
44
+
45
+ if (!fs.existsSync(rulesDir)) {
46
+ console.warn('⚠️ Rules directory not found');
47
+ return;
48
+ }
49
+
50
+ const categories = fs.readdirSync(rulesDir, { withFileTypes: true })
51
+ .filter(dirent => dirent.isDirectory())
52
+ .filter(dirent => !['tests', 'docs', 'utils', 'migration'].includes(dirent.name))
53
+ .map(dirent => dirent.name);
54
+
55
+ for (const category of categories) {
56
+ await this.loadCategoryRules(category, rulesDir, config);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Load rules from a category directory
62
+ * @param {string} category - Category name
63
+ * @param {string} rulesDir - Rules directory path
64
+ * @param {Object} config - Configuration options
65
+ */
66
+ async loadCategoryRules(category, rulesDir, config) {
67
+ const categoryPath = path.join(rulesDir, category);
68
+
69
+ const ruleFolders = fs.readdirSync(categoryPath, { withFileTypes: true })
70
+ .filter(dirent => dirent.isDirectory())
71
+ .map(dirent => dirent.name);
72
+
73
+ for (const ruleFolder of ruleFolders) {
74
+ const rulePath = path.join(categoryPath, ruleFolder);
75
+ await this.loadRulePlugin(ruleFolder, rulePath, category, config);
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Load a single rule plugin
81
+ * @param {string} ruleId - Rule identifier
82
+ * @param {string} rulePath - Path to rule directory
83
+ * @param {string} category - Rule category
84
+ * @param {Object} config - Configuration options
85
+ */
86
+ async loadRulePlugin(ruleId, rulePath, category, config) {
87
+ try {
88
+ // Try to load semantic rule first
89
+ const semanticPath = path.join(rulePath, 'semantic-analyzer.js');
90
+ if (fs.existsSync(semanticPath)) {
91
+ await this.loadSemanticRule(ruleId, semanticPath, category, config);
92
+ return;
93
+ }
94
+
95
+ // Try AST analyzer
96
+ const astPath = path.join(rulePath, 'ast-analyzer.js');
97
+ if (fs.existsSync(astPath)) {
98
+ await this.loadPluginRule(ruleId, astPath, category, 'ast', config);
99
+ return;
100
+ }
101
+
102
+ // Try regex analyzer
103
+ const regexPath = path.join(rulePath, 'analyzer.js');
104
+ if (fs.existsSync(regexPath)) {
105
+ await this.loadPluginRule(ruleId, regexPath, category, 'regex', config);
106
+ return;
107
+ }
108
+
109
+ if (this.verbose) {
110
+ console.warn(`⚠️ No analyzer found for rule ${ruleId}`);
111
+ }
112
+
113
+ } catch (error) {
114
+ if (this.verbose) {
115
+ console.warn(`⚠️ Failed to load rule ${ruleId}:`, error.message);
116
+ }
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Load semantic rule plugin
122
+ * @param {string} ruleId - Rule identifier
123
+ * @param {string} analyzerPath - Path to analyzer
124
+ * @param {string} category - Rule category
125
+ * @param {Object} config - Configuration options
126
+ */
127
+ async loadSemanticRule(ruleId, analyzerPath, category, config) {
128
+ try {
129
+ const AnalyzerClass = require(analyzerPath);
130
+ const metadata = await this.loadRuleMetadata(ruleId, path.dirname(analyzerPath));
131
+
132
+ const plugin = new AnalyzerClass(ruleId, { ...metadata, category, type: 'semantic' });
133
+
134
+ if (plugin instanceof SemanticRuleInterface) {
135
+ this.registerPlugin(ruleId, plugin, 'semantic');
136
+
137
+ if (this.verbose) {
138
+ console.log(`🧠 Loaded semantic rule: ${ruleId}`);
139
+ }
140
+ } else {
141
+ throw new Error(`${ruleId} does not implement SemanticRuleInterface`);
142
+ }
143
+
144
+ } catch (error) {
145
+ if (this.verbose) {
146
+ console.warn(`⚠️ Failed to load semantic rule ${ruleId}:`, error.message);
147
+ }
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Load standard plugin rule
153
+ * @param {string} ruleId - Rule identifier
154
+ * @param {string} analyzerPath - Path to analyzer
155
+ * @param {string} category - Rule category
156
+ * @param {string} type - Analyzer type
157
+ * @param {Object} config - Configuration options
158
+ */
159
+ async loadPluginRule(ruleId, analyzerPath, category, type, config) {
160
+ try {
161
+ const analyzerModule = require(analyzerPath);
162
+ const AnalyzerClass = analyzerModule.default || analyzerModule;
163
+ const metadata = await this.loadRuleMetadata(ruleId, path.dirname(analyzerPath));
164
+
165
+ // Create plugin wrapper for legacy analyzers
166
+ const plugin = this.createLegacyPluginWrapper(ruleId, AnalyzerClass, {
167
+ ...metadata,
168
+ category,
169
+ type
170
+ });
171
+
172
+ this.registerPlugin(ruleId, plugin, type);
173
+
174
+ if (this.verbose) {
175
+ console.log(`🔧 Loaded ${type} rule: ${ruleId}`);
176
+ }
177
+
178
+ } catch (error) {
179
+ if (this.verbose) {
180
+ console.warn(`⚠️ Failed to load ${type} rule ${ruleId}:`, error.message);
181
+ }
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Create plugin wrapper for legacy analyzers
187
+ * @param {string} ruleId - Rule identifier
188
+ * @param {Function|Object} analyzer - Analyzer class or instance
189
+ * @param {Object} metadata - Rule metadata
190
+ * @returns {RulePluginInterface} Plugin wrapper
191
+ */
192
+ createLegacyPluginWrapper(ruleId, analyzer, metadata) {
193
+ return new class extends RulePluginInterface {
194
+ constructor() {
195
+ super(ruleId, metadata);
196
+ this.analyzer = typeof analyzer === 'function' ? new analyzer() : analyzer;
197
+ }
198
+
199
+ async initialize(config = {}) {
200
+ if (this.analyzer.initialize) {
201
+ await this.analyzer.initialize(config);
202
+ }
203
+ }
204
+
205
+ async analyze(files, language, options = {}) {
206
+ if (!this.analyzer.analyze) {
207
+ throw new Error(`Analyzer for ${ruleId} missing analyze method`);
208
+ }
209
+
210
+ return await this.analyzer.analyze(files, language, options);
211
+ }
212
+ }();
213
+ }
214
+
215
+ /**
216
+ * Load rule metadata from config.json
217
+ * @param {string} ruleId - Rule identifier
218
+ * @param {string} rulePath - Rule directory path
219
+ * @returns {Object} Rule metadata
220
+ */
221
+ async loadRuleMetadata(ruleId, rulePath) {
222
+ const configPath = path.join(rulePath, 'config.json');
223
+
224
+ if (fs.existsSync(configPath)) {
225
+ try {
226
+ return require(configPath);
227
+ } catch (error) {
228
+ if (this.verbose) {
229
+ console.warn(`⚠️ Failed to load config for ${ruleId}:`, error.message);
230
+ }
231
+ }
232
+ }
233
+
234
+ return {
235
+ name: ruleId,
236
+ description: `Analysis for rule ${ruleId}`,
237
+ severity: 'warning'
238
+ };
239
+ }
240
+
241
+ /**
242
+ * Load custom rules from .sunlint.json
243
+ * @param {Object} config - Configuration options
244
+ */
245
+ async loadCustomRules(config = {}) {
246
+ const configPath = path.resolve(process.cwd(), '.sunlint.json');
247
+
248
+ if (!fs.existsSync(configPath)) {
249
+ return;
250
+ }
251
+
252
+ try {
253
+ const sunlintConfig = require(configPath);
254
+
255
+ // Support both new and legacy config formats
256
+ const customRules = sunlintConfig.customRules ||
257
+ sunlintConfig.custom ||
258
+ {}; // Default to empty if no custom rules
259
+
260
+ for (const [ruleId, ruleConfig] of Object.entries(customRules)) {
261
+ await this.loadCustomRule(ruleId, ruleConfig, config);
262
+ }
263
+
264
+ if (this.verbose && Object.keys(customRules).length > 0) {
265
+ console.log(`📋 Loaded ${Object.keys(customRules).length} custom rules from config`);
266
+ }
267
+
268
+ } catch (error) {
269
+ if (this.verbose) {
270
+ console.warn(`⚠️ Failed to load custom rules config:`, error.message);
271
+ }
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Load a custom rule
277
+ * @param {string} ruleId - Rule identifier
278
+ * @param {Object} ruleConfig - Rule configuration
279
+ * @param {Object} config - Global configuration
280
+ */
281
+ async loadCustomRule(ruleId, ruleConfig, config) {
282
+ try {
283
+ if (!ruleConfig.path) {
284
+ throw new Error(`Custom rule ${ruleId} missing path`);
285
+ }
286
+
287
+ // Validate and normalize category
288
+ const originalCategory = ruleConfig.category;
289
+ ruleConfig.category = normalizeCategory(ruleConfig.category);
290
+
291
+ if (originalCategory && originalCategory !== ruleConfig.category) {
292
+ console.warn(`⚠️ Invalid category '${originalCategory}' for rule ${ruleId}. Valid categories: ${getValidCategories().join(', ')}`);
293
+ console.warn(` Auto-corrected to: '${ruleConfig.category}'`);
294
+ }
295
+
296
+ const rulePath = path.resolve(process.cwd(), ruleConfig.path);
297
+
298
+ if (!fs.existsSync(rulePath)) {
299
+ throw new Error(`Custom rule file not found: ${rulePath}`);
300
+ }
301
+
302
+ const CustomRuleClass = require(rulePath);
303
+ const plugin = new CustomRuleClass(ruleId, ruleConfig);
304
+
305
+ if (!(plugin instanceof CustomRuleInterface)) {
306
+ throw new Error(`Custom rule ${ruleId} must extend CustomRuleInterface`);
307
+ }
308
+
309
+ this.registerPlugin(ruleId, plugin, 'custom');
310
+ this.customRules.set(ruleId, { path: rulePath, config: ruleConfig });
311
+
312
+ if (this.verbose) {
313
+ console.log(`🎨 Loaded custom rule: ${ruleId} (category: ${ruleConfig.category || 'common'})`);
314
+ }
315
+
316
+ } catch (error) {
317
+ if (this.verbose) {
318
+ console.warn(`⚠️ Failed to load custom rule ${ruleId}:`, error.message);
319
+ }
320
+ }
321
+ }
322
+
323
+ /**
324
+ * Register a plugin
325
+ * @param {string} ruleId - Rule identifier
326
+ * @param {RulePluginInterface} plugin - Plugin instance
327
+ * @param {string} type - Plugin type
328
+ */
329
+ registerPlugin(ruleId, plugin, type) {
330
+ this.plugins.set(ruleId, {
331
+ plugin,
332
+ type,
333
+ engines: [], // Will be populated when engines request rules
334
+ metadata: plugin.getMetadata()
335
+ });
336
+ }
337
+
338
+ /**
339
+ * Get rules for a specific engine
340
+ * @param {string} engineName - Engine name
341
+ * @param {Object} config - Configuration options
342
+ * @returns {Map} Map of rule ID to plugin info
343
+ */
344
+ async loadRulesForEngine(engineName, config = {}) {
345
+ const engineRules = new Map();
346
+
347
+ for (const [ruleId, pluginInfo] of this.plugins) {
348
+ // Check if rule is compatible with engine
349
+ if (this.isRuleCompatibleWithEngine(ruleId, engineName, pluginInfo)) {
350
+ engineRules.set(ruleId, {
351
+ plugin: pluginInfo.plugin,
352
+ type: pluginInfo.type,
353
+ metadata: pluginInfo.metadata
354
+ });
355
+
356
+ // Track which engines use this rule
357
+ if (!pluginInfo.engines.includes(engineName)) {
358
+ pluginInfo.engines.push(engineName);
359
+ }
360
+ }
361
+ }
362
+
363
+ this.loadedEngines.add(engineName);
364
+ return engineRules;
365
+ }
366
+
367
+ /**
368
+ * Check if rule is compatible with engine
369
+ * @param {string} ruleId - Rule identifier
370
+ * @param {string} engineName - Engine name
371
+ * @param {Object} pluginInfo - Plugin information
372
+ * @returns {boolean} True if compatible
373
+ */
374
+ isRuleCompatibleWithEngine(ruleId, engineName, pluginInfo) {
375
+ const { type, plugin } = pluginInfo;
376
+
377
+ // Engine compatibility rules
378
+ const compatibility = {
379
+ heuristic: ['semantic', 'ast', 'regex', 'custom'],
380
+ eslint: ['eslint', 'custom'],
381
+ openai: ['semantic', 'custom']
382
+ };
383
+
384
+ return compatibility[engineName]?.includes(type) || false;
385
+ }
386
+
387
+ /**
388
+ * Get plugin information
389
+ * @param {string} ruleId - Rule identifier
390
+ * @returns {Object|null} Plugin information
391
+ */
392
+ getPlugin(ruleId) {
393
+ return this.plugins.get(ruleId) || null;
394
+ }
395
+
396
+ /**
397
+ * Get all loaded plugins
398
+ * @returns {Map} All plugins
399
+ */
400
+ getAllPlugins() {
401
+ return this.plugins;
402
+ }
403
+
404
+ /**
405
+ * Reload custom rules (for hot-reload during development)
406
+ * @param {Object} config - Configuration options
407
+ */
408
+ async reloadCustomRules(config = {}) {
409
+ // Clear existing custom rules
410
+ for (const [ruleId, pluginInfo] of this.plugins) {
411
+ if (pluginInfo.type === 'custom') {
412
+ this.plugins.delete(ruleId);
413
+ }
414
+ }
415
+ this.customRules.clear();
416
+
417
+ // Reload custom rules
418
+ await this.loadCustomRules(config);
419
+
420
+ if (this.verbose) {
421
+ const customCount = Array.from(this.plugins.values()).filter(p => p.type === 'custom').length;
422
+ console.log(`🔄 Reloaded ${customCount} custom rules`);
423
+ }
424
+ }
425
+
426
+ /**
427
+ * Cleanup plugin manager
428
+ */
429
+ async cleanup() {
430
+ for (const [ruleId, pluginInfo] of this.plugins) {
431
+ try {
432
+ await pluginInfo.plugin.cleanup();
433
+ } catch (error) {
434
+ // Ignore cleanup errors
435
+ }
436
+ }
437
+
438
+ this.plugins.clear();
439
+ this.customRules.clear();
440
+ this.loadedEngines.clear();
441
+
442
+ if (this.verbose) {
443
+ console.log('🔌 Plugin Manager cleanup completed');
444
+ }
445
+ }
446
+ }
447
+
448
+ module.exports = PluginManager;
@@ -5,6 +5,8 @@
5
5
  */
6
6
 
7
7
  const chalk = require('chalk');
8
+ const fs = require('fs');
9
+ const path = require('path');
8
10
  const RuleMappingService = require('./rule-mapping-service');
9
11
  const SunlintRuleAdapter = require('./adapters/sunlint-rule-adapter');
10
12
 
@@ -35,31 +37,25 @@ class RuleSelectionService {
35
37
  } else if (options.rules) {
36
38
  selectedRules = options.rules.split(',').map(r => r.trim());
37
39
  } else if (options.all) {
38
- // For --all, use all supported rules from adapter
39
- const adapterRules = this.ruleAdapter.getAllRuleIds();
40
- const eslintSupportedRules = this.ruleMappingService.getSupportedSunLintRules();
41
-
42
- // Combine and deduplicate
43
- selectedRules = [...new Set([...adapterRules, ...eslintSupportedRules])];
40
+ // Handle --all shortcut (load from preset file)
41
+ selectedRules = this.loadPresetRules('all');
44
42
 
45
43
  if (options.verbose) {
46
- console.log(chalk.blue(`📋 Found ${selectedRules.length} total rules (${adapterRules.length} adapter + ${eslintSupportedRules.length} ESLint)`));
44
+ console.log(chalk.blue(`📋 Selected ${selectedRules.length} rules from all preset file`));
47
45
  }
48
46
  } else if (options.quality) {
49
- // Handle --quality shortcut (standardized approach)
50
- const categoryRules = this.ruleAdapter.getStandardCategoryRules('quality');
51
- selectedRules = categoryRules.map(rule => rule.id);
47
+ // Handle --quality shortcut (load from preset file)
48
+ selectedRules = this.loadPresetRules('quality');
52
49
 
53
50
  if (options.verbose) {
54
- console.log(chalk.blue(`📋 Selected ${selectedRules.length} quality rules from core files`));
51
+ console.log(chalk.blue(`📋 Selected ${selectedRules.length} quality rules from preset file`));
55
52
  }
56
53
  } else if (options.security) {
57
- // Handle --security shortcut (standardized approach)
58
- const categoryRules = this.ruleAdapter.getStandardCategoryRules('security');
59
- selectedRules = categoryRules.map(rule => rule.id);
54
+ // Handle --security shortcut (load from preset file)
55
+ selectedRules = this.loadPresetRules('security');
60
56
 
61
57
  if (options.verbose) {
62
- console.log(chalk.blue(`📋 Selected ${selectedRules.length} security rules from core files`));
58
+ console.log(chalk.blue(`📋 Selected ${selectedRules.length} security rules from preset file`));
63
59
  }
64
60
  } else if (options.category) {
65
61
  // Handle --category shortcut (standardized approach)
@@ -121,6 +117,37 @@ class RuleSelectionService {
121
117
  const rule = this.ruleAdapter.getRuleById(ruleId);
122
118
  return rule ? rule.name : `Rule ${ruleId}`;
123
119
  }
120
+
121
+ /**
122
+ * Load rules from preset configuration files
123
+ * @param {string} presetName - Name of preset (quality, security, all)
124
+ * @returns {Array} Array of rule IDs
125
+ */
126
+ loadPresetRules(presetName) {
127
+ try {
128
+ const presetPath = path.join(__dirname, '../config/presets', `${presetName}.json`);
129
+
130
+ if (!fs.existsSync(presetPath)) {
131
+ console.warn(chalk.yellow(`⚠️ Preset file not found: ${presetPath}`));
132
+ return [];
133
+ }
134
+
135
+ const presetConfig = JSON.parse(fs.readFileSync(presetPath, 'utf8'));
136
+ const ruleIds = Object.keys(presetConfig.rules || {});
137
+
138
+ if (ruleIds.length === 0) {
139
+ console.warn(chalk.yellow(`⚠️ No rules found in preset: ${presetName}`));
140
+ return [];
141
+ }
142
+
143
+ console.log(chalk.green(`✅ Loaded ${ruleIds.length} rules from ${presetName} preset`));
144
+ return ruleIds;
145
+
146
+ } catch (error) {
147
+ console.error(chalk.red(`❌ Failed to load preset ${presetName}:`, error.message));
148
+ return [];
149
+ }
150
+ }
124
151
  }
125
152
 
126
153
  module.exports = RuleSelectionService;