@sun-asterisk/sunlint 1.1.7 → 1.2.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/.sunlint.json +1 -1
- package/CHANGELOG.md +83 -0
- package/README.md +66 -4
- package/config/presets/all.json +125 -0
- package/config/presets/beginner.json +16 -8
- package/config/presets/ci.json +12 -4
- package/config/presets/maintainability.json +38 -0
- package/config/presets/performance.json +32 -0
- package/config/presets/quality.json +103 -0
- package/config/presets/recommended.json +36 -12
- package/config/presets/security.json +88 -0
- package/config/presets/strict.json +15 -5
- package/config/rules/rules-registry-generated.json +6312 -0
- package/config/rules-summary.json +1941 -0
- package/core/adapters/sunlint-rule-adapter.js +452 -0
- package/core/analysis-orchestrator.js +4 -4
- package/core/config-manager.js +28 -5
- package/core/rule-selection-service.js +52 -55
- package/docs/CONFIGURATION.md +111 -3
- package/docs/LANGUAGE-SPECIFIC-RULES.md +308 -0
- package/docs/README.md +3 -0
- package/docs/STANDARDIZED-CATEGORY-FILTERING.md +156 -0
- package/engines/eslint-engine.js +92 -2
- package/engines/heuristic-engine.js +8 -31
- package/origin-rules/common-en.md +1320 -0
- package/origin-rules/dart-en.md +289 -0
- package/origin-rules/java-en.md +60 -0
- package/origin-rules/kotlin-mobile-en.md +453 -0
- package/origin-rules/reactjs-en.md +102 -0
- package/origin-rules/security-en.md +1055 -0
- package/origin-rules/swift-en.md +449 -0
- package/origin-rules/typescript-en.md +136 -0
- package/package.json +6 -5
- package/scripts/copy-rules.js +86 -0
- package/rules/README.md +0 -252
- package/rules/common/C002_no_duplicate_code/analyzer.js +0 -65
- package/rules/common/C002_no_duplicate_code/config.json +0 -23
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +0 -418
- package/rules/common/C003_no_vague_abbreviations/config.json +0 -35
- package/rules/common/C006_function_naming/analyzer.js +0 -349
- package/rules/common/C006_function_naming/config.json +0 -86
- package/rules/common/C010_limit_block_nesting/analyzer.js +0 -389
- package/rules/common/C013_no_dead_code/analyzer.js +0 -206
- package/rules/common/C014_dependency_injection/analyzer.js +0 -338
- package/rules/common/C017_constructor_logic/analyzer.js +0 -314
- package/rules/common/C019_log_level_usage/analyzer.js +0 -362
- package/rules/common/C019_log_level_usage/config.json +0 -121
- package/rules/common/C029_catch_block_logging/analyzer.js +0 -373
- package/rules/common/C029_catch_block_logging/config.json +0 -59
- package/rules/common/C031_validation_separation/analyzer.js +0 -186
- package/rules/common/C041_no_sensitive_hardcode/analyzer.js +0 -292
- package/rules/common/C042_boolean_name_prefix/analyzer.js +0 -300
- package/rules/common/C043_no_console_or_print/analyzer.js +0 -304
- package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +0 -351
- package/rules/common/C075_explicit_return_types/analyzer.js +0 -103
- package/rules/common/C076_single_test_behavior/analyzer.js +0 -121
- package/rules/docs/C002_no_duplicate_code.md +0 -57
- package/rules/docs/C031_validation_separation.md +0 -72
- package/rules/index.js +0 -149
- package/rules/migration/converter.js +0 -385
- package/rules/migration/mapping.json +0 -164
- package/rules/security/S026_json_schema_validation/analyzer.js +0 -251
- package/rules/security/S026_json_schema_validation/config.json +0 -27
- package/rules/security/S027_no_hardcoded_secrets/analyzer.js +0 -263
- package/rules/security/S027_no_hardcoded_secrets/config.json +0 -29
- package/rules/security/S029_csrf_protection/analyzer.js +0 -264
- package/rules/tests/C002_no_duplicate_code.test.js +0 -50
- package/rules/universal/C010/generic.js +0 -0
- package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
- package/rules/utils/ast-utils.js +0 -191
- package/rules/utils/base-analyzer.js +0 -98
- package/rules/utils/pattern-matchers.js +0 -239
- package/rules/utils/rule-helpers.js +0 -264
- package/rules/utils/severity-constants.js +0 -93
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
const { SimpleRuleParser } = require('../../rules/parser/rule-parser-simple');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* SunLint Rule Adapter - Unified interface for rule access
|
|
7
|
+
*
|
|
8
|
+
* Follows the same pattern as sunlint-vscode RuleAdapter for consistency
|
|
9
|
+
* Provides abstraction layer between business logic and rule storage
|
|
10
|
+
*
|
|
11
|
+
* Features:
|
|
12
|
+
* - Singleton pattern for global rule access
|
|
13
|
+
* - Caching for performance
|
|
14
|
+
* - Unified interface across SunLint CLI and VSCode extension
|
|
15
|
+
* - Support for multiple rule sources (origin-rules, registry)
|
|
16
|
+
*/
|
|
17
|
+
class SunlintRuleAdapter {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.parser = new SimpleRuleParser();
|
|
20
|
+
this.rulesCache = new Map();
|
|
21
|
+
this.registryCache = null;
|
|
22
|
+
this.isInitialized = false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get singleton instance (same pattern as VSCode)
|
|
27
|
+
*/
|
|
28
|
+
static getInstance() {
|
|
29
|
+
if (!SunlintRuleAdapter.instance) {
|
|
30
|
+
SunlintRuleAdapter.instance = new SunlintRuleAdapter();
|
|
31
|
+
}
|
|
32
|
+
return SunlintRuleAdapter.instance;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Initialize the adapter - loads rules from available sources
|
|
37
|
+
*/
|
|
38
|
+
async initialize(options = {}) {
|
|
39
|
+
if (this.isInitialized) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const { rulesDir, useRegistry = true } = options;
|
|
45
|
+
|
|
46
|
+
// Try to load from generated registry first (preferred)
|
|
47
|
+
if (useRegistry) {
|
|
48
|
+
await this.loadFromRegistry();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Fallback to origin-rules parsing
|
|
52
|
+
if (!this.registryCache) {
|
|
53
|
+
await this.loadFromOriginRules(rulesDir);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.isInitialized = true;
|
|
57
|
+
console.log(`✅ SunlintRuleAdapter initialized with ${this.getAllRuleIds().length} rules`);
|
|
58
|
+
return true;
|
|
59
|
+
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error('❌ Failed to initialize SunlintRuleAdapter:', error.message);
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Load rules from generated registry (preferred method)
|
|
68
|
+
*/
|
|
69
|
+
async loadFromRegistry() {
|
|
70
|
+
try {
|
|
71
|
+
const registryPath = path.join(__dirname, '../../config/rules/rules-registry-generated.json');
|
|
72
|
+
|
|
73
|
+
if (fs.existsSync(registryPath)) {
|
|
74
|
+
const registryData = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
75
|
+
this.registryCache = registryData.rules || {};
|
|
76
|
+
console.log(`📊 Loaded ${Object.keys(this.registryCache).length} rules from registry`);
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return false;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.warn('⚠️ Failed to load registry:', error.message);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Load rules from origin-rules directory (fallback method)
|
|
89
|
+
*/
|
|
90
|
+
async loadFromOriginRules(customRulesDir = null) {
|
|
91
|
+
try {
|
|
92
|
+
const rules = this.parser.parseAllRules(customRulesDir);
|
|
93
|
+
|
|
94
|
+
// Convert to cache format
|
|
95
|
+
this.rulesCache.clear();
|
|
96
|
+
rules.forEach(rule => {
|
|
97
|
+
if (rule.id) {
|
|
98
|
+
this.rulesCache.set(rule.id, this.normalizeRule(rule));
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
console.log(`📋 Loaded ${this.rulesCache.size} rules from origin-rules`);
|
|
103
|
+
return true;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error('❌ Failed to load from origin-rules:', error.message);
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Normalize rule format for consistent interface
|
|
112
|
+
*/
|
|
113
|
+
normalizeRule(rule) {
|
|
114
|
+
return {
|
|
115
|
+
id: rule.id,
|
|
116
|
+
name: rule.title || rule.name || `${rule.id} Rule`,
|
|
117
|
+
description: rule.description || 'No description available',
|
|
118
|
+
title: rule.title || rule.name || `${rule.id} Rule`,
|
|
119
|
+
details: Array.isArray(rule.details) ? rule.details : [rule.details || ''],
|
|
120
|
+
tools: rule.tools || [],
|
|
121
|
+
principles: rule.principles || [],
|
|
122
|
+
version: rule.version || '1.0.0',
|
|
123
|
+
status: rule.status || 'activated',
|
|
124
|
+
severity: rule.severity || 'warning',
|
|
125
|
+
category: rule.category || 'quality',
|
|
126
|
+
languages: rule.languages || ['typescript', 'javascript'],
|
|
127
|
+
framework: rule.framework,
|
|
128
|
+
// Metadata
|
|
129
|
+
source: this.registryCache ? 'registry' : 'origin-rules'
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get rule by ID
|
|
135
|
+
*/
|
|
136
|
+
getRuleById(ruleId) {
|
|
137
|
+
if (!this.isInitialized) {
|
|
138
|
+
console.warn('⚠️ RuleAdapter not initialized. Call initialize() first.');
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Try registry first
|
|
143
|
+
if (this.registryCache && this.registryCache[ruleId]) {
|
|
144
|
+
return this.normalizeRule({
|
|
145
|
+
id: ruleId,
|
|
146
|
+
...this.registryCache[ruleId]
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Try cache
|
|
151
|
+
if (this.rulesCache.has(ruleId)) {
|
|
152
|
+
return this.rulesCache.get(ruleId);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get all rules
|
|
160
|
+
*/
|
|
161
|
+
getAllRules() {
|
|
162
|
+
if (!this.isInitialized) {
|
|
163
|
+
console.warn('⚠️ RuleAdapter not initialized. Call initialize() first.');
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const rules = [];
|
|
168
|
+
|
|
169
|
+
if (this.registryCache) {
|
|
170
|
+
// From registry
|
|
171
|
+
Object.entries(this.registryCache).forEach(([ruleId, ruleData]) => {
|
|
172
|
+
rules.push(this.normalizeRule({ id: ruleId, ...ruleData }));
|
|
173
|
+
});
|
|
174
|
+
} else {
|
|
175
|
+
// From cache
|
|
176
|
+
rules.push(...this.rulesCache.values());
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return rules;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Get all rule IDs
|
|
184
|
+
*/
|
|
185
|
+
getAllRuleIds() {
|
|
186
|
+
if (this.registryCache) {
|
|
187
|
+
return Object.keys(this.registryCache);
|
|
188
|
+
}
|
|
189
|
+
return Array.from(this.rulesCache.keys());
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Validate rule ID
|
|
194
|
+
*/
|
|
195
|
+
isValidRuleId(ruleId) {
|
|
196
|
+
return this.getAllRuleIds().includes(ruleId);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Get rules by category
|
|
201
|
+
*/
|
|
202
|
+
getRulesByCategory(category) {
|
|
203
|
+
return this.getAllRules().filter(rule =>
|
|
204
|
+
rule.category && rule.category.toLowerCase() === category.toLowerCase()
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get rules by principles (enhanced for category filtering)
|
|
210
|
+
*/
|
|
211
|
+
getRulesByPrinciples(principles) {
|
|
212
|
+
const principlesArray = Array.isArray(principles) ? principles : [principles];
|
|
213
|
+
return this.getAllRules().filter(rule => {
|
|
214
|
+
if (!rule.principles || rule.principles.length === 0) return false;
|
|
215
|
+
return principlesArray.some(principle =>
|
|
216
|
+
rule.principles.some(rulePrinciple =>
|
|
217
|
+
rulePrinciple.toLowerCase().includes(principle.toLowerCase())
|
|
218
|
+
)
|
|
219
|
+
);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Get rules for specific category using core files approach
|
|
225
|
+
* Based on actual principles in the rule catalog
|
|
226
|
+
*/
|
|
227
|
+
getRulesByStandardCategory(category) {
|
|
228
|
+
const categoryPrincipleMap = {
|
|
229
|
+
'security': ['SECURITY'],
|
|
230
|
+
'quality': ['CODE_QUALITY'],
|
|
231
|
+
'performance': ['PERFORMANCE'],
|
|
232
|
+
'maintainability': ['MAINTAINABILITY'],
|
|
233
|
+
'testability': ['TESTABILITY'],
|
|
234
|
+
'reliability': ['RELIABILITY'],
|
|
235
|
+
'design': ['DESIGN_PATTERNS'],
|
|
236
|
+
'integration': ['INTEGRATION'],
|
|
237
|
+
'usability': ['USABILITY']
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const principles = categoryPrincipleMap[category.toLowerCase()];
|
|
241
|
+
if (!principles) {
|
|
242
|
+
console.warn(`⚠️ Unknown category: ${category}`);
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return this.getRulesByPrinciples(principles);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Get rules from core files only (common-en.md + security-en.md)
|
|
251
|
+
* This follows the standardization approach for category commands
|
|
252
|
+
*/
|
|
253
|
+
getCoreRules() {
|
|
254
|
+
// For now, we filter by source files if available in metadata
|
|
255
|
+
// In future, we can enhance this to track source files
|
|
256
|
+
return this.getAllRules().filter(rule => {
|
|
257
|
+
// If we have metadata about source files, use it
|
|
258
|
+
if (rule.sourceFile) {
|
|
259
|
+
return rule.sourceFile.includes('common-en') ||
|
|
260
|
+
rule.sourceFile.includes('security-en');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Fallback: include rules that don't seem language-specific
|
|
264
|
+
const languageSpecificPrefixes = ['T0', 'J0', 'D0', 'K0', 'SW0', 'P0', 'R0'];
|
|
265
|
+
const isLanguageSpecific = languageSpecificPrefixes.some(prefix =>
|
|
266
|
+
rule.id.startsWith(prefix)
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
return !isLanguageSpecific;
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Get standardized category rules (core files + principle filtering)
|
|
275
|
+
* This is the recommended method for --security, --quality, etc.
|
|
276
|
+
*/
|
|
277
|
+
getStandardCategoryRules(category) {
|
|
278
|
+
const coreRules = this.getCoreRules();
|
|
279
|
+
const categoryPrincipleMap = {
|
|
280
|
+
'security': ['SECURITY'],
|
|
281
|
+
'quality': ['CODE_QUALITY'],
|
|
282
|
+
'performance': ['PERFORMANCE'],
|
|
283
|
+
'maintainability': ['MAINTAINABILITY'],
|
|
284
|
+
'testability': ['TESTABILITY'],
|
|
285
|
+
'reliability': ['RELIABILITY'],
|
|
286
|
+
'design': ['DESIGN_PATTERNS'],
|
|
287
|
+
'integration': ['INTEGRATION'],
|
|
288
|
+
'usability': ['USABILITY']
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const principles = categoryPrincipleMap[category.toLowerCase()];
|
|
292
|
+
if (!principles) {
|
|
293
|
+
console.warn(`⚠️ Unknown standard category: ${category}`);
|
|
294
|
+
return [];
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Filter core rules by principles
|
|
298
|
+
return coreRules.filter(rule => {
|
|
299
|
+
if (!rule.principles || rule.principles.length === 0) return false;
|
|
300
|
+
return principles.some(principle =>
|
|
301
|
+
rule.principles.some(rulePrinciple =>
|
|
302
|
+
rulePrinciple.toLowerCase().includes(principle.toLowerCase())
|
|
303
|
+
)
|
|
304
|
+
);
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Search rules by text
|
|
310
|
+
*/
|
|
311
|
+
searchRules(query) {
|
|
312
|
+
const lowerQuery = query.toLowerCase();
|
|
313
|
+
return this.getAllRules().filter(rule =>
|
|
314
|
+
rule.id.toLowerCase().includes(lowerQuery) ||
|
|
315
|
+
rule.name.toLowerCase().includes(lowerQuery) ||
|
|
316
|
+
rule.description.toLowerCase().includes(lowerQuery)
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Get rules summary
|
|
322
|
+
*/
|
|
323
|
+
getRulesSummary() {
|
|
324
|
+
const rules = this.getAllRules();
|
|
325
|
+
const categories = {};
|
|
326
|
+
|
|
327
|
+
rules.forEach(rule => {
|
|
328
|
+
const category = rule.category || 'unknown';
|
|
329
|
+
categories[category] = (categories[category] || 0) + 1;
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
total: rules.length,
|
|
334
|
+
categories: categories,
|
|
335
|
+
source: this.registryCache ? 'registry' : 'origin-rules'
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Filter rules by criteria (unified interface)
|
|
341
|
+
*/
|
|
342
|
+
filterRules(criteria = {}) {
|
|
343
|
+
const {
|
|
344
|
+
ruleIds,
|
|
345
|
+
categories,
|
|
346
|
+
principles,
|
|
347
|
+
status = 'activated',
|
|
348
|
+
engines,
|
|
349
|
+
severity
|
|
350
|
+
} = criteria;
|
|
351
|
+
|
|
352
|
+
let rules = this.getAllRules();
|
|
353
|
+
|
|
354
|
+
// Filter by rule IDs
|
|
355
|
+
if (ruleIds && ruleIds.length > 0) {
|
|
356
|
+
rules = rules.filter(rule => ruleIds.includes(rule.id));
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Filter by categories
|
|
360
|
+
if (categories && categories.length > 0) {
|
|
361
|
+
rules = rules.filter(rule =>
|
|
362
|
+
categories.some(cat =>
|
|
363
|
+
rule.category && rule.category.toLowerCase() === cat.toLowerCase()
|
|
364
|
+
)
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Filter by principles
|
|
369
|
+
if (principles && principles.length > 0) {
|
|
370
|
+
rules = rules.filter(rule => {
|
|
371
|
+
if (!rule.principles) return false;
|
|
372
|
+
return principles.some(principle =>
|
|
373
|
+
rule.principles.some(rp =>
|
|
374
|
+
rp.toLowerCase().includes(principle.toLowerCase())
|
|
375
|
+
)
|
|
376
|
+
);
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Filter by status
|
|
381
|
+
if (status) {
|
|
382
|
+
rules = rules.filter(rule =>
|
|
383
|
+
rule.status && rule.status.toLowerCase() === status.toLowerCase()
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Filter by severity
|
|
388
|
+
if (severity) {
|
|
389
|
+
rules = rules.filter(rule =>
|
|
390
|
+
rule.severity && rule.severity.toLowerCase() === severity.toLowerCase()
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return rules;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Generate AI context for specific rules
|
|
399
|
+
*/
|
|
400
|
+
generateAIContext(ruleIds) {
|
|
401
|
+
const rules = ruleIds.map(id => this.getRuleById(id)).filter(Boolean);
|
|
402
|
+
|
|
403
|
+
return rules.map(rule => ({
|
|
404
|
+
id: rule.id,
|
|
405
|
+
title: rule.name,
|
|
406
|
+
description: rule.description,
|
|
407
|
+
details: Array.isArray(rule.details) ? rule.details.join('\n') : rule.details,
|
|
408
|
+
category: rule.category,
|
|
409
|
+
severity: rule.severity,
|
|
410
|
+
principles: rule.principles
|
|
411
|
+
}));
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Get configuration for specific engine
|
|
416
|
+
*/
|
|
417
|
+
getEngineRules(engine = 'heuristic') {
|
|
418
|
+
const allRules = this.getAllRules();
|
|
419
|
+
|
|
420
|
+
switch (engine.toLowerCase()) {
|
|
421
|
+
case 'heuristic':
|
|
422
|
+
// Heuristic engine supports most rules except ESLint-specific
|
|
423
|
+
return allRules.filter(rule => !rule.id.startsWith('T'));
|
|
424
|
+
|
|
425
|
+
case 'eslint':
|
|
426
|
+
// ESLint engine supports TypeScript rules and some C-series
|
|
427
|
+
return allRules.filter(rule =>
|
|
428
|
+
rule.id.startsWith('T') ||
|
|
429
|
+
['C006', 'C010', 'C019', 'C029', 'C031'].includes(rule.id)
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
case 'ai':
|
|
433
|
+
// AI engine supports all rules
|
|
434
|
+
return allRules;
|
|
435
|
+
|
|
436
|
+
default:
|
|
437
|
+
return allRules;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Clear cache and reinitialize
|
|
443
|
+
*/
|
|
444
|
+
async refresh(options = {}) {
|
|
445
|
+
this.rulesCache.clear();
|
|
446
|
+
this.registryCache = null;
|
|
447
|
+
this.isInitialized = false;
|
|
448
|
+
return await this.initialize(options);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
module.exports = SunlintRuleAdapter;
|
|
@@ -9,14 +9,14 @@ const chalk = require('chalk');
|
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const fs = require('fs');
|
|
11
11
|
const AnalysisEngineInterface = require('./interfaces/analysis-engine.interface');
|
|
12
|
-
const
|
|
12
|
+
const SunlintRuleAdapter = require('./adapters/sunlint-rule-adapter');
|
|
13
13
|
|
|
14
14
|
class AnalysisOrchestrator {
|
|
15
15
|
constructor() {
|
|
16
16
|
this.engines = new Map(); // Plugin registry
|
|
17
17
|
this.initialized = false;
|
|
18
18
|
this.defaultTimeout = 30000; // 30 seconds default timeout
|
|
19
|
-
this.
|
|
19
|
+
this.ruleAdapter = SunlintRuleAdapter.getInstance();
|
|
20
20
|
this.enginesConfigPath = path.join(__dirname, '..', 'config', 'engines', 'engines.json');
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -114,8 +114,8 @@ class AnalysisOrchestrator {
|
|
|
114
114
|
console.log(chalk.blue(`🔧 Initializing ${this.engines.size} analysis engines...`));
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
//
|
|
118
|
-
await this.
|
|
117
|
+
// Initialize rule adapter
|
|
118
|
+
await this.ruleAdapter.initialize();
|
|
119
119
|
|
|
120
120
|
// Initialize enabled engines
|
|
121
121
|
const enabledEngines = config.enabledEngines || Array.from(this.engines.keys());
|
package/core/config-manager.js
CHANGED
|
@@ -9,12 +9,14 @@ const ConfigPresetResolver = require('./config-preset-resolver');
|
|
|
9
9
|
const ConfigMerger = require('./config-merger');
|
|
10
10
|
const ConfigValidator = require('./config-validator');
|
|
11
11
|
const ConfigOverrideProcessor = require('./config-override-processor');
|
|
12
|
+
const SunlintRuleAdapter = require('./adapters/sunlint-rule-adapter');
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Main configuration manager - orchestrates config loading process
|
|
15
16
|
* Rule C005: Single responsibility - orchestrates other config services
|
|
16
17
|
* Rule C015: Domain language - ConfigManager as main coordinator
|
|
17
18
|
* Rule C014: Uses dependency injection for all services
|
|
19
|
+
* REFACTORED: Now uses SunlintRuleAdapter instead of direct registry access
|
|
18
20
|
*/
|
|
19
21
|
class ConfigManager {
|
|
20
22
|
constructor() {
|
|
@@ -24,6 +26,8 @@ class ConfigManager {
|
|
|
24
26
|
this.merger = new ConfigMerger();
|
|
25
27
|
this.validator = new ConfigValidator();
|
|
26
28
|
this.overrideProcessor = new ConfigOverrideProcessor();
|
|
29
|
+
this.ruleAdapter = SunlintRuleAdapter.getInstance();
|
|
30
|
+
this.initialized = false;
|
|
27
31
|
|
|
28
32
|
this.defaultConfig = {
|
|
29
33
|
rules: {},
|
|
@@ -150,8 +154,15 @@ class ConfigManager {
|
|
|
150
154
|
* Rule C006: loadConfiguration - verb-noun naming
|
|
151
155
|
* Rule C005: Single responsibility - orchestrates config loading
|
|
152
156
|
* Rule C012: Command method - loads and returns config
|
|
157
|
+
* REFACTORED: Now initializes rule adapter
|
|
153
158
|
*/
|
|
154
159
|
async loadConfiguration(configPath, cliOptions = {}) {
|
|
160
|
+
// Initialize rule adapter
|
|
161
|
+
if (!this.initialized) {
|
|
162
|
+
await this.ruleAdapter.initialize();
|
|
163
|
+
this.initialized = true;
|
|
164
|
+
}
|
|
165
|
+
|
|
155
166
|
// 1. Start with built-in defaults
|
|
156
167
|
let config = { ...this.defaultConfig };
|
|
157
168
|
|
|
@@ -332,13 +343,18 @@ class ConfigManager {
|
|
|
332
343
|
|
|
333
344
|
/**
|
|
334
345
|
* Rule C006: loadExtendedConfig - verb-noun naming
|
|
346
|
+
* REFACTORED: Now loads presets directly instead of through registry
|
|
335
347
|
*/
|
|
336
348
|
async loadExtendedConfig(extendPath) {
|
|
337
349
|
if (extendPath.startsWith('@sun/sunlint/')) {
|
|
338
|
-
// Load preset from
|
|
350
|
+
// Load preset directly from preset file
|
|
339
351
|
const presetName = extendPath.replace('@sun/sunlint/', '');
|
|
340
|
-
const
|
|
341
|
-
|
|
352
|
+
const presetPath = path.join(__dirname, '../config/presets', `${presetName}.json`);
|
|
353
|
+
if (fs.existsSync(presetPath)) {
|
|
354
|
+
return JSON.parse(fs.readFileSync(presetPath, 'utf8'));
|
|
355
|
+
} else {
|
|
356
|
+
throw new Error(`Preset not found: ${extendPath}`);
|
|
357
|
+
}
|
|
342
358
|
} else {
|
|
343
359
|
// Load from file path
|
|
344
360
|
const configPath = path.resolve(extendPath);
|
|
@@ -361,10 +377,17 @@ class ConfigManager {
|
|
|
361
377
|
/**
|
|
362
378
|
* Rule C006: getEffectiveRuleConfiguration - verb-noun naming
|
|
363
379
|
* Rule C014: Delegate to validator
|
|
380
|
+
* REFACTORED: Now uses rule adapter for rule validation
|
|
364
381
|
*/
|
|
365
382
|
getEffectiveRuleConfiguration(ruleId, config) {
|
|
366
|
-
|
|
367
|
-
|
|
383
|
+
// Validate rule exists via adapter
|
|
384
|
+
const rule = this.ruleAdapter.getRuleById(ruleId);
|
|
385
|
+
if (!rule) {
|
|
386
|
+
console.warn(`⚠️ Rule ${ruleId} not found in registry`);
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return this.validator.getEffectiveRuleConfiguration(ruleId, config, { rules: { [ruleId]: rule } });
|
|
368
391
|
}
|
|
369
392
|
|
|
370
393
|
/**
|