@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,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
|
-
//
|
|
39
|
-
|
|
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(`📋
|
|
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 (
|
|
50
|
-
|
|
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
|
|
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 (
|
|
58
|
-
|
|
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
|
|
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;
|