@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.
- package/CHANGELOG.md +107 -1
- package/CONTRIBUTING.md +1654 -66
- package/README.md +19 -6
- package/config/ci-cd.json +54 -0
- package/config/development.json +56 -0
- 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/large-project.json +143 -0
- package/config/presets/all.json +0 -1
- package/config/release.json +70 -0
- package/config/rule-analysis-strategies.js +23 -4
- package/config/rules/S027-categories.json +122 -0
- package/config/rules/enhanced-rules-registry.json +2564 -0
- package/config/rules/rules-registry-generated.json +785 -837
- package/config/rules/rules-registry.json +13 -1
- 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 +53 -32
- package/core/cli-program.js +11 -3
- package/core/config-manager.js +111 -0
- package/core/config-merger.js +88 -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/enhanced-rules-registry.js +3 -3
- 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 +658 -0
- package/core/semantic-rule-base.js +433 -0
- package/core/unified-rule-registry.js +484 -0
- package/docs/COMMAND-EXAMPLES.md +134 -0
- package/docs/CONSTANTS-ARCHITECTURE.md +288 -0
- package/docs/LARGE-PROJECT-GUIDE.md +324 -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 +569 -78
- package/integrations/eslint/plugin/index.js +26 -28
- package/origin-rules/common-en.md +8 -8
- package/package.json +10 -6
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +1 -1
- package/rules/common/C017_constructor_logic/analyzer.js +254 -17
- package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -0
- package/rules/common/C029_catch_block_logging/analyzer.js +17 -5
- package/rules/common/C033_separate_service_repository/README.md +78 -0
- package/rules/common/C033_separate_service_repository/analyzer.js +160 -0
- package/rules/common/C033_separate_service_repository/config.json +50 -0
- package/rules/common/C033_separate_service_repository/regex-based-analyzer.js +585 -0
- package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +368 -0
- package/rules/common/C035_error_logging_context/STRATEGY.md +99 -0
- package/rules/common/C035_error_logging_context/analyzer.js +230 -0
- package/rules/common/C035_error_logging_context/config.json +54 -0
- package/rules/common/C035_error_logging_context/regex-based-analyzer.js +299 -0
- package/rules/common/C035_error_logging_context/symbol-based-analyzer.js +454 -0
- package/rules/common/C040_centralized_validation/analyzer.js +165 -0
- package/rules/common/C040_centralized_validation/config.json +46 -0
- package/rules/common/C040_centralized_validation/regex-based-analyzer.js +243 -0
- package/rules/common/C040_centralized_validation/symbol-based-analyzer.js +416 -0
- 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/common/{C076_single_test_behavior → C072_single_test_behavior}/analyzer.js +6 -6
- package/rules/common/C076_explicit_function_types/README.md +30 -0
- package/rules/common/C076_explicit_function_types/analyzer.js +172 -0
- package/rules/common/C076_explicit_function_types/config.json +15 -0
- package/rules/common/C076_explicit_function_types/semantic-analyzer.js +341 -0
- package/rules/index.js +8 -0
- package/rules/parser/rule-parser.js +13 -2
- package/rules/security/S005_no_origin_auth/README.md +226 -0
- package/rules/security/S005_no_origin_auth/analyzer.js +184 -0
- package/rules/security/S005_no_origin_auth/ast-analyzer.js +406 -0
- package/rules/security/S005_no_origin_auth/config.json +85 -0
- package/rules/security/S006_no_plaintext_recovery_codes/README.md +139 -0
- package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +306 -0
- package/rules/security/S006_no_plaintext_recovery_codes/config.json +48 -0
- package/rules/security/S007_no_plaintext_otp/README.md +198 -0
- package/rules/security/S007_no_plaintext_otp/analyzer.js +406 -0
- package/rules/security/S007_no_plaintext_otp/config.json +79 -0
- package/rules/security/S007_no_plaintext_otp/semantic-analyzer.js +609 -0
- package/rules/security/S007_no_plaintext_otp/semantic-config.json +195 -0
- package/rules/security/S007_no_plaintext_otp/semantic-wrapper.js +280 -0
- package/rules/security/S027_no_hardcoded_secrets/analyzer.js +180 -366
- package/rules/security/S027_no_hardcoded_secrets/categories.json +153 -0
- package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +250 -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/prepare-release.sh +1 -1
- 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/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
- package/docs/FUTURE_PACKAGES.md +0 -83
- package/docs/HEURISTIC_VS_AI.md +0 -113
- package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +0 -112
- package/docs/PRODUCTION_SIZE_IMPACT.md +0 -183
- package/docs/RELEASE_GUIDE.md +0 -230
- package/docs/STANDARDIZED-CATEGORY-FILTERING.md +0 -156
- package/engines/tree-sitter-parser.js +0 -0
- package/engines/universal-ast-engine.js +0 -0
- package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +0 -254
- 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,484 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified Rule Registry - Single Source of Truth
|
|
3
|
+
* Following Rule C005: Single responsibility - centralized rule management
|
|
4
|
+
* Following Rule C015: Use domain language - clear registry terms
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
class UnifiedRuleRegistry {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.rules = new Map();
|
|
13
|
+
this.engineCapabilities = new Map();
|
|
14
|
+
this.initialized = false;
|
|
15
|
+
this.verbose = false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Initialize registry with auto-discovery
|
|
20
|
+
* @param {Object} options - Configuration options
|
|
21
|
+
*/
|
|
22
|
+
async initialize(options = {}) {
|
|
23
|
+
if (this.initialized) return;
|
|
24
|
+
|
|
25
|
+
this.verbose = options.verbose || false;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
// 1. Load master rule definitions
|
|
29
|
+
await this.loadMasterRegistry();
|
|
30
|
+
|
|
31
|
+
// 2. Auto-discover analyzer files
|
|
32
|
+
await this.autoDiscoverAnalyzers();
|
|
33
|
+
|
|
34
|
+
// 3. Register engine capabilities
|
|
35
|
+
this.registerEngineCapabilities();
|
|
36
|
+
|
|
37
|
+
// 4. Validate consistency
|
|
38
|
+
await this.validateRegistry();
|
|
39
|
+
|
|
40
|
+
this.initialized = true;
|
|
41
|
+
|
|
42
|
+
if (this.verbose) {
|
|
43
|
+
console.log(`✅ Unified Registry initialized: ${this.rules.size} rules`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('❌ Failed to initialize Unified Rule Registry:', error.message);
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Load master rule definitions from primary source
|
|
54
|
+
*/
|
|
55
|
+
async loadMasterRegistry() {
|
|
56
|
+
// Try enhanced registry first, fall back to original
|
|
57
|
+
const registryPaths = [
|
|
58
|
+
path.resolve(__dirname, '../config/rules/enhanced-rules-registry.json'),
|
|
59
|
+
path.resolve(__dirname, '../config/rules/rules-registry.json')
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
let registryPath = null;
|
|
63
|
+
for (const tryPath of registryPaths) {
|
|
64
|
+
if (fs.existsSync(tryPath)) {
|
|
65
|
+
registryPath = tryPath;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!registryPath) {
|
|
71
|
+
throw new Error('No master registry found in config/rules/');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (this.verbose) {
|
|
75
|
+
console.log(`📋 Loading enhanced registry from: ${path.basename(registryPath)}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const registryData = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
80
|
+
const rules = registryData.rules || registryData;
|
|
81
|
+
|
|
82
|
+
for (const [ruleId, ruleConfig] of Object.entries(rules)) {
|
|
83
|
+
const ruleDefinition = {
|
|
84
|
+
id: ruleId,
|
|
85
|
+
name: ruleConfig.name,
|
|
86
|
+
description: ruleConfig.description,
|
|
87
|
+
category: ruleConfig.category,
|
|
88
|
+
severity: ruleConfig.severity || 'warning',
|
|
89
|
+
languages: ruleConfig.languages || ['javascript', 'typescript'],
|
|
90
|
+
|
|
91
|
+
// Use existing analyzer paths or initialize empty
|
|
92
|
+
analyzers: ruleConfig.analyzers || {},
|
|
93
|
+
|
|
94
|
+
// Use existing engine mappings or initialize empty
|
|
95
|
+
engineMappings: ruleConfig.engineMappings || {},
|
|
96
|
+
|
|
97
|
+
// Use existing strategy or initialize default
|
|
98
|
+
strategy: ruleConfig.strategy || {
|
|
99
|
+
preferred: 'regex',
|
|
100
|
+
fallbacks: ['ast'],
|
|
101
|
+
accuracy: {}
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
// Metadata
|
|
105
|
+
version: ruleConfig.version || '1.0.0',
|
|
106
|
+
status: ruleConfig.status || 'stable',
|
|
107
|
+
tags: ruleConfig.tags || []
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
this.rules.set(ruleId, ruleDefinition);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (this.verbose) {
|
|
114
|
+
console.log(`📋 Loaded ${this.rules.size} rules from master registry`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
} catch (error) {
|
|
118
|
+
throw new Error(`Failed to parse master registry: ${error.message}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Auto-discover analyzer files for all rules
|
|
124
|
+
*/
|
|
125
|
+
async autoDiscoverAnalyzers() {
|
|
126
|
+
const rulesBaseDir = path.resolve(__dirname, '../rules');
|
|
127
|
+
|
|
128
|
+
for (const [ruleId, ruleDefinition] of this.rules.entries()) {
|
|
129
|
+
const analyzers = await this.discoverAnalyzersForRule(ruleId, rulesBaseDir);
|
|
130
|
+
ruleDefinition.analyzers = analyzers;
|
|
131
|
+
|
|
132
|
+
// Infer preferred analysis strategy based on available analyzers
|
|
133
|
+
if (analyzers.semantic) {
|
|
134
|
+
ruleDefinition.strategy.preferred = 'semantic';
|
|
135
|
+
ruleDefinition.strategy.fallbacks = ['ast', 'regex'];
|
|
136
|
+
} else if (analyzers.ast) {
|
|
137
|
+
ruleDefinition.strategy.preferred = 'ast';
|
|
138
|
+
ruleDefinition.strategy.fallbacks = ['regex'];
|
|
139
|
+
} else if (analyzers.regex || analyzers.legacy) {
|
|
140
|
+
ruleDefinition.strategy.preferred = 'regex';
|
|
141
|
+
ruleDefinition.strategy.fallbacks = [];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (this.verbose) {
|
|
146
|
+
const rulesWithAnalyzers = Array.from(this.rules.values()).filter(rule =>
|
|
147
|
+
Object.keys(rule.analyzers).length > 0
|
|
148
|
+
).length;
|
|
149
|
+
console.log(`🔍 Auto-discovered analyzers for ${rulesWithAnalyzers}/${this.rules.size} rules`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Discover analyzer files for a specific rule
|
|
155
|
+
* @param {string} ruleId - Rule ID
|
|
156
|
+
* @param {string} rulesBaseDir - Base rules directory
|
|
157
|
+
* @returns {Object} Analyzer file paths
|
|
158
|
+
*/
|
|
159
|
+
async discoverAnalyzersForRule(ruleId, rulesBaseDir) {
|
|
160
|
+
const analyzers = {};
|
|
161
|
+
|
|
162
|
+
// Direct search in common directory using exact folder names
|
|
163
|
+
const commonRulesDir = path.join(rulesBaseDir, 'common');
|
|
164
|
+
|
|
165
|
+
if (fs.existsSync(commonRulesDir)) {
|
|
166
|
+
const ruleFolders = fs.readdirSync(commonRulesDir);
|
|
167
|
+
|
|
168
|
+
// Look for folder that starts with rule ID
|
|
169
|
+
const matchingFolder = ruleFolders.find(folder =>
|
|
170
|
+
folder.startsWith(ruleId + '_') || folder === ruleId
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
if (matchingFolder) {
|
|
174
|
+
const rulePath = path.join(commonRulesDir, matchingFolder);
|
|
175
|
+
|
|
176
|
+
// Check for different analyzer files
|
|
177
|
+
const analyzerFiles = {
|
|
178
|
+
semantic: path.join(rulePath, 'semantic-analyzer.js'),
|
|
179
|
+
ast: path.join(rulePath, 'ast-analyzer.js'),
|
|
180
|
+
regex: path.join(rulePath, 'regex-analyzer.js'),
|
|
181
|
+
legacy: path.join(rulePath, 'analyzer.js')
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
for (const [type, filePath] of Object.entries(analyzerFiles)) {
|
|
185
|
+
if (fs.existsSync(filePath)) {
|
|
186
|
+
analyzers[type] = filePath;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Also check other category directories (security, typescript, etc.)
|
|
193
|
+
const otherDirs = ['security', 'typescript', 'react'];
|
|
194
|
+
for (const categoryDir of otherDirs) {
|
|
195
|
+
const categoryPath = path.join(rulesBaseDir, categoryDir);
|
|
196
|
+
|
|
197
|
+
if (fs.existsSync(categoryPath)) {
|
|
198
|
+
const ruleFolders = fs.readdirSync(categoryPath);
|
|
199
|
+
const matchingFolder = ruleFolders.find(folder =>
|
|
200
|
+
folder.startsWith(ruleId + '_') || folder === ruleId
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
if (matchingFolder) {
|
|
204
|
+
const rulePath = path.join(categoryPath, matchingFolder);
|
|
205
|
+
|
|
206
|
+
const analyzerFiles = {
|
|
207
|
+
semantic: path.join(rulePath, 'semantic-analyzer.js'),
|
|
208
|
+
ast: path.join(rulePath, 'ast-analyzer.js'),
|
|
209
|
+
regex: path.join(rulePath, 'regex-analyzer.js'),
|
|
210
|
+
legacy: path.join(rulePath, 'analyzer.js')
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
for (const [type, filePath] of Object.entries(analyzerFiles)) {
|
|
214
|
+
if (fs.existsSync(filePath)) {
|
|
215
|
+
analyzers[type] = filePath;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// If we found analyzers, stop searching
|
|
220
|
+
if (Object.keys(analyzers).length > 0) {
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return analyzers;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Expand glob-like patterns to actual paths
|
|
232
|
+
* @param {string} baseDir - Base directory
|
|
233
|
+
* @param {string} pattern - Pattern with * wildcards
|
|
234
|
+
* @returns {string[]} Expanded paths
|
|
235
|
+
*/
|
|
236
|
+
expandPattern(baseDir, pattern) {
|
|
237
|
+
if (!pattern.includes('*')) {
|
|
238
|
+
return [path.join(baseDir, pattern)];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const parts = pattern.split('/');
|
|
242
|
+
let currentPaths = [baseDir];
|
|
243
|
+
|
|
244
|
+
for (const part of parts) {
|
|
245
|
+
if (part === '') continue;
|
|
246
|
+
|
|
247
|
+
const newPaths = [];
|
|
248
|
+
for (const currentPath of currentPaths) {
|
|
249
|
+
if (part.includes('*')) {
|
|
250
|
+
// Wildcard part - expand
|
|
251
|
+
if (fs.existsSync(currentPath)) {
|
|
252
|
+
const entries = fs.readdirSync(currentPath);
|
|
253
|
+
const regex = new RegExp('^' + part.replace(/\*/g, '.*') + '$');
|
|
254
|
+
|
|
255
|
+
for (const entry of entries) {
|
|
256
|
+
if (regex.test(entry)) {
|
|
257
|
+
newPaths.push(path.join(currentPath, entry));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
} else {
|
|
262
|
+
// Literal part
|
|
263
|
+
newPaths.push(path.join(currentPath, part));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
currentPaths = newPaths;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return currentPaths;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Register engine capabilities
|
|
274
|
+
*/
|
|
275
|
+
registerEngineCapabilities() {
|
|
276
|
+
// Define what each engine can handle
|
|
277
|
+
this.engineCapabilities.set('heuristic', ['semantic', 'ast', 'regex']);
|
|
278
|
+
this.engineCapabilities.set('eslint', ['ast', 'regex']);
|
|
279
|
+
this.engineCapabilities.set('openai', ['semantic']);
|
|
280
|
+
|
|
281
|
+
// Load ESLint mappings
|
|
282
|
+
this.loadESLintMappings();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Load ESLint rule mappings
|
|
287
|
+
*/
|
|
288
|
+
loadESLintMappings() {
|
|
289
|
+
const eslintMappingPath = path.resolve(__dirname, '../config/eslint-rule-mapping.json');
|
|
290
|
+
|
|
291
|
+
if (fs.existsSync(eslintMappingPath)) {
|
|
292
|
+
try {
|
|
293
|
+
const mappingData = JSON.parse(fs.readFileSync(eslintMappingPath, 'utf8'));
|
|
294
|
+
const mappings = mappingData.mappings || mappingData;
|
|
295
|
+
|
|
296
|
+
for (const [ruleId, eslintRules] of Object.entries(mappings)) {
|
|
297
|
+
if (this.rules.has(ruleId)) {
|
|
298
|
+
this.rules.get(ruleId).engineMappings.eslint = eslintRules;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (this.verbose) {
|
|
303
|
+
console.log(`🔗 Loaded ESLint mappings for ${Object.keys(mappings).length} rules`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
} catch (error) {
|
|
307
|
+
console.warn(`⚠️ Failed to load ESLint mappings: ${error.message}`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Validate registry consistency
|
|
314
|
+
*/
|
|
315
|
+
async validateRegistry() {
|
|
316
|
+
const issues = [];
|
|
317
|
+
|
|
318
|
+
for (const [ruleId, ruleDefinition] of this.rules.entries()) {
|
|
319
|
+
// Check if rule has at least one analyzer
|
|
320
|
+
if (Object.keys(ruleDefinition.analyzers).length === 0) {
|
|
321
|
+
issues.push(`${ruleId}: No analyzers found`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Check if analyzer files actually exist
|
|
325
|
+
for (const [type, filePath] of Object.entries(ruleDefinition.analyzers)) {
|
|
326
|
+
if (!fs.existsSync(filePath)) {
|
|
327
|
+
issues.push(`${ruleId}: ${type} analyzer not found at ${filePath}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (issues.length > 0 && this.verbose) {
|
|
333
|
+
console.warn(`⚠️ Registry validation found ${issues.length} issues:`);
|
|
334
|
+
issues.slice(0, 5).forEach(issue => console.warn(` - ${issue}`));
|
|
335
|
+
if (issues.length > 5) {
|
|
336
|
+
console.warn(` ... and ${issues.length - 5} more`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// === PUBLIC API ===
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Get rule definition by ID
|
|
345
|
+
* @param {string} ruleId - Rule ID
|
|
346
|
+
* @returns {Object|null} Rule definition
|
|
347
|
+
*/
|
|
348
|
+
getRuleDefinition(ruleId) {
|
|
349
|
+
return this.rules.get(ruleId) || null;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Get all rules supported by an engine
|
|
354
|
+
* @param {string} engine - Engine name
|
|
355
|
+
* @returns {Object[]} Array of rule definitions
|
|
356
|
+
*/
|
|
357
|
+
getRulesForEngine(engine) {
|
|
358
|
+
const capabilities = this.engineCapabilities.get(engine) || [];
|
|
359
|
+
const supportedRules = [];
|
|
360
|
+
|
|
361
|
+
for (const [ruleId, ruleDefinition] of this.rules.entries()) {
|
|
362
|
+
// Check if engine can handle this rule's preferred strategy
|
|
363
|
+
if (capabilities.includes(ruleDefinition.strategy.preferred)) {
|
|
364
|
+
supportedRules.push(ruleDefinition);
|
|
365
|
+
}
|
|
366
|
+
// Or if engine can handle any fallback strategy
|
|
367
|
+
else if (ruleDefinition.strategy.fallbacks.some(fallback => capabilities.includes(fallback))) {
|
|
368
|
+
supportedRules.push(ruleDefinition);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return supportedRules;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Get all supported rule IDs
|
|
377
|
+
* @returns {string[]} Array of rule IDs
|
|
378
|
+
*/
|
|
379
|
+
getSupportedRules() {
|
|
380
|
+
return Array.from(this.rules.keys());
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Resolve analyzer path for a rule and engine
|
|
385
|
+
* @param {string} ruleId - Rule ID
|
|
386
|
+
* @param {string} engine - Engine name
|
|
387
|
+
* @returns {string|null} Analyzer file path
|
|
388
|
+
*/
|
|
389
|
+
resolveAnalyzerPath(ruleId, engine) {
|
|
390
|
+
const ruleDefinition = this.rules.get(ruleId);
|
|
391
|
+
if (!ruleDefinition) return null;
|
|
392
|
+
|
|
393
|
+
const capabilities = this.engineCapabilities.get(engine) || [];
|
|
394
|
+
const analyzers = ruleDefinition.analyzers;
|
|
395
|
+
|
|
396
|
+
// Try preferred strategy first
|
|
397
|
+
const preferred = ruleDefinition.strategy.preferred;
|
|
398
|
+
if (capabilities.includes(preferred) && analyzers[preferred]) {
|
|
399
|
+
return analyzers[preferred];
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Try fallback strategies
|
|
403
|
+
for (const fallback of ruleDefinition.strategy.fallbacks) {
|
|
404
|
+
if (capabilities.includes(fallback) && analyzers[fallback]) {
|
|
405
|
+
return analyzers[fallback];
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Fall back to legacy analyzer if available and engine supports regex/ast
|
|
410
|
+
if (analyzers.legacy && (capabilities.includes('regex') || capabilities.includes('ast'))) {
|
|
411
|
+
return analyzers.legacy;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Get engine mapping for a rule (ESLint specific)
|
|
419
|
+
* @param {string} ruleId - Rule ID
|
|
420
|
+
* @param {string} engine - Engine name
|
|
421
|
+
* @returns {string[]} Array of engine-specific rule names
|
|
422
|
+
*/
|
|
423
|
+
getEngineMapping(ruleId, engine) {
|
|
424
|
+
const ruleDefinition = this.rules.get(ruleId);
|
|
425
|
+
if (!ruleDefinition) return [];
|
|
426
|
+
|
|
427
|
+
return ruleDefinition.engineMappings[engine] || [];
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Check if rule is supported by engine
|
|
432
|
+
* @param {string} ruleId - Rule ID
|
|
433
|
+
* @param {string} engine - Engine name
|
|
434
|
+
* @returns {boolean} True if supported
|
|
435
|
+
*/
|
|
436
|
+
isRuleSupported(ruleId, engine) {
|
|
437
|
+
const analyzerPath = this.resolveAnalyzerPath(ruleId, engine);
|
|
438
|
+
return analyzerPath !== null;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Get registry statistics
|
|
443
|
+
* @returns {Object} Registry stats
|
|
444
|
+
*/
|
|
445
|
+
getStats() {
|
|
446
|
+
const stats = {
|
|
447
|
+
totalRules: this.rules.size,
|
|
448
|
+
rulesByCategory: {},
|
|
449
|
+
rulesByEngine: {},
|
|
450
|
+
rulesWithAnalyzers: 0
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
for (const ruleDefinition of this.rules.values()) {
|
|
454
|
+
// Count by category
|
|
455
|
+
const category = ruleDefinition.category;
|
|
456
|
+
stats.rulesByCategory[category] = (stats.rulesByCategory[category] || 0) + 1;
|
|
457
|
+
|
|
458
|
+
// Count rules with analyzers
|
|
459
|
+
if (Object.keys(ruleDefinition.analyzers).length > 0) {
|
|
460
|
+
stats.rulesWithAnalyzers++;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Count by engine
|
|
465
|
+
for (const engine of this.engineCapabilities.keys()) {
|
|
466
|
+
stats.rulesByEngine[engine] = this.getRulesForEngine(engine).length;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return stats;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Singleton instance
|
|
474
|
+
let instance = null;
|
|
475
|
+
|
|
476
|
+
module.exports = {
|
|
477
|
+
UnifiedRuleRegistry,
|
|
478
|
+
getInstance: () => {
|
|
479
|
+
if (!instance) {
|
|
480
|
+
instance = new UnifiedRuleRegistry();
|
|
481
|
+
}
|
|
482
|
+
return instance;
|
|
483
|
+
}
|
|
484
|
+
};
|
package/docs/COMMAND-EXAMPLES.md
CHANGED
|
@@ -113,6 +113,22 @@ node cli.js --all --input=. --timeout=60000 --format=summary --no-ai
|
|
|
113
113
|
# Disable caching
|
|
114
114
|
node cli.js --all --input=. --no-cache --format=summary --no-ai
|
|
115
115
|
|
|
116
|
+
# **Control semantic analysis for large projects**
|
|
117
|
+
# Default limit: 1000 files for performance balance
|
|
118
|
+
node cli.js --all --input=. --max-semantic-files=1000 --format=summary
|
|
119
|
+
|
|
120
|
+
# For small projects: Analyze all files
|
|
121
|
+
node cli.js --all --input=. --max-semantic-files=0 --format=summary
|
|
122
|
+
|
|
123
|
+
# For large projects: Conservative analysis
|
|
124
|
+
node cli.js --all --input=. --max-semantic-files=500 --format=summary
|
|
125
|
+
|
|
126
|
+
# For massive projects: Minimal semantic analysis
|
|
127
|
+
node cli.js --all --input=. --max-semantic-files=100 --format=summary
|
|
128
|
+
|
|
129
|
+
# Unlimited semantic analysis (use with caution!)
|
|
130
|
+
node cli.js --all --input=. --max-semantic-files=-1 --format=summary
|
|
131
|
+
|
|
116
132
|
# Verbose logging
|
|
117
133
|
node cli.js --all --input=. --verbose --format=summary --no-ai
|
|
118
134
|
|
|
@@ -203,6 +219,124 @@ node cli.js --all --changed-files --diff-base=origin/main --format=github --no-a
|
|
|
203
219
|
node cli.js --all --changed-files --ai --format=detailed
|
|
204
220
|
```
|
|
205
221
|
|
|
222
|
+
## 🏗️ **Large Project Strategies**
|
|
223
|
+
|
|
224
|
+
> **⚡ Performance Note**: SunLint uses semantic analysis for advanced rules (like C047). For projects with 1000+ files, you can control semantic analysis scope to balance accuracy vs performance.
|
|
225
|
+
|
|
226
|
+
### **Strategy 1: Incremental Analysis** 📈
|
|
227
|
+
```bash
|
|
228
|
+
# Start with changed files only (fastest)
|
|
229
|
+
node cli.js --all --changed-files --format=summary --no-ai
|
|
230
|
+
|
|
231
|
+
# Focus on specific directories
|
|
232
|
+
node cli.js --all --input=src/critical --max-semantic-files=2000 --format=summary
|
|
233
|
+
|
|
234
|
+
# Target important file patterns only
|
|
235
|
+
node cli.js --all --include="src/**/*.ts" --exclude="**/*.test.*,**/*.d.ts" --input=.
|
|
236
|
+
|
|
237
|
+
# Use directory-based analysis
|
|
238
|
+
node cli.js --all --input=src/auth --format=summary # Most critical module first
|
|
239
|
+
node cli.js --all --input=src/api --format=summary # Then API layer
|
|
240
|
+
node cli.js --all --input=src/utils --format=summary # Finally utilities
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### **Strategy 2: Semantic Analysis Tuning** 🔧
|
|
244
|
+
```bash
|
|
245
|
+
# Conservative: 500 files for faster analysis
|
|
246
|
+
node cli.js --all --input=. --max-semantic-files=500 --format=summary
|
|
247
|
+
|
|
248
|
+
# Balanced: 1000 files (default) for medium projects
|
|
249
|
+
node cli.js --all --input=. --max-semantic-files=1000 --format=summary
|
|
250
|
+
|
|
251
|
+
# Comprehensive: 2000+ files for complete analysis
|
|
252
|
+
node cli.js --all --input=. --max-semantic-files=2000 --format=summary
|
|
253
|
+
|
|
254
|
+
# Unlimited: All files (use for final validation)
|
|
255
|
+
node cli.js --all --input=. --max-semantic-files=-1 --format=summary
|
|
256
|
+
|
|
257
|
+
# Disable semantic analysis completely (heuristic only)
|
|
258
|
+
node cli.js --all --input=. --max-semantic-files=0 --format=summary
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### **Strategy 3: Rule-Based Prioritization** 🎯
|
|
262
|
+
```bash
|
|
263
|
+
# Phase 1: Critical security issues (fast heuristic rules)
|
|
264
|
+
node cli.js --security --input=. --max-semantic-files=0 --format=summary
|
|
265
|
+
|
|
266
|
+
# Phase 2: Code quality basics
|
|
267
|
+
node cli.js --rules=C006,C019,C029 --input=. --max-semantic-files=500 --format=summary
|
|
268
|
+
|
|
269
|
+
# Phase 3: Advanced semantic rules (targeted)
|
|
270
|
+
node cli.js --rules=C047 --input=src --max-semantic-files=1000 --format=summary
|
|
271
|
+
|
|
272
|
+
# Phase 4: Full comprehensive scan
|
|
273
|
+
node cli.js --all --input=. --max-semantic-files=-1 --format=detailed
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### **Strategy 4: CI/CD Optimization** ⚡
|
|
277
|
+
```bash
|
|
278
|
+
# PR checks: Fast semantic analysis
|
|
279
|
+
node cli.js --all --changed-files --max-semantic-files=300 --format=github --no-ai
|
|
280
|
+
|
|
281
|
+
# Nightly builds: Medium semantic analysis
|
|
282
|
+
node cli.js --all --input=. --max-semantic-files=1000 --format=json --output=nightly.json
|
|
283
|
+
|
|
284
|
+
# Weekly reports: Full semantic analysis
|
|
285
|
+
node cli.js --all --input=. --max-semantic-files=-1 --format=detailed --output=weekly.json
|
|
286
|
+
|
|
287
|
+
# Release validation: Comprehensive with baselines
|
|
288
|
+
node cli.js --all --input=. --max-semantic-files=2000 --baseline=last-release.json
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### **Strategy 5: Memory & Performance Monitoring** 📊
|
|
292
|
+
```bash
|
|
293
|
+
# Monitor file loading (debug mode)
|
|
294
|
+
node cli.js --all --input=. --max-semantic-files=1000 --verbose --debug
|
|
295
|
+
|
|
296
|
+
# Track performance with different limits
|
|
297
|
+
time node cli.js --all --input=. --max-semantic-files=500 --format=summary
|
|
298
|
+
time node cli.js --all --input=. --max-semantic-files=1000 --format=summary
|
|
299
|
+
time node cli.js --all --input=. --max-semantic-files=2000 --format=summary
|
|
300
|
+
|
|
301
|
+
# Memory-conscious analysis for CI
|
|
302
|
+
node cli.js --all --input=. --max-semantic-files=300 --max-concurrent=2 --format=summary
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### **📋 Recommended Limits by Project Size**
|
|
306
|
+
|
|
307
|
+
| Project Size | Files Count | Recommended Limit | Use Case |
|
|
308
|
+
|-------------|-------------|-------------------|----------|
|
|
309
|
+
| Small | < 100 files | `--max-semantic-files=0` (all) | Complete analysis |
|
|
310
|
+
| Medium | 100-500 files | `--max-semantic-files=500` | Balanced |
|
|
311
|
+
| Large | 500-2000 files | `--max-semantic-files=1000` | Default recommended |
|
|
312
|
+
| Enterprise | 2000-5000 files | `--max-semantic-files=1500` | Conservative |
|
|
313
|
+
| Massive | 5000+ files | `--max-semantic-files=500` | Targeted analysis |
|
|
314
|
+
|
|
315
|
+
> **💡 Pro Tips for Large Projects:**
|
|
316
|
+
> 1. Use `--changed-files` for daily development
|
|
317
|
+
> 2. Use `--max-semantic-files=500` for CI/CD pipelines
|
|
318
|
+
> 3. Use `--max-semantic-files=-1` for release validation
|
|
319
|
+
> 4. Combine with `--include` patterns to focus on critical code
|
|
320
|
+
> 5. Monitor analysis time and adjust limits accordingly
|
|
321
|
+
|
|
322
|
+
### **Example 1: Monorepo with 5000+ Files**
|
|
323
|
+
```bash
|
|
324
|
+
# Daily development: Changed files only
|
|
325
|
+
node cli.js --all --changed-files --max-semantic-files=300 --format=summary
|
|
326
|
+
|
|
327
|
+
# Module-specific analysis
|
|
328
|
+
node cli.js --all --input=packages/core --max-semantic-files=1000 --format=summary
|
|
329
|
+
node cli.js --all --input=packages/api --max-semantic-files=1000 --format=summary
|
|
330
|
+
|
|
331
|
+
# CI pipeline: Conservative semantic analysis
|
|
332
|
+
node cli.js --all --changed-files --max-semantic-files=500 --format=github
|
|
333
|
+
|
|
334
|
+
# Release validation: Full analysis by modules
|
|
335
|
+
for dir in packages/*/; do
|
|
336
|
+
node cli.js --all --input="$dir" --max-semantic-files=2000 --format=json --output="${dir//\//-}-report.json"
|
|
337
|
+
done
|
|
338
|
+
```
|
|
339
|
+
|
|
206
340
|
### **Example 2: Legacy Code Improvement**
|
|
207
341
|
```bash
|
|
208
342
|
# Step 1: Baseline assessment
|