@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,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
+ };
@@ -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