@sun-asterisk/sunlint 1.3.1 → 1.3.3

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 (120) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/CONTRIBUTING.md +210 -1691
  3. package/README.md +5 -3
  4. package/config/rule-analysis-strategies.js +17 -1
  5. package/config/rules/enhanced-rules-registry.json +506 -1161
  6. package/config/rules/rules-registry-generated.json +1 -1
  7. package/core/analysis-orchestrator.js +167 -42
  8. package/core/auto-performance-manager.js +243 -0
  9. package/core/cli-action-handler.js +9 -1
  10. package/core/cli-program.js +19 -5
  11. package/core/constants/defaults.js +56 -0
  12. package/core/enhanced-rules-registry.js +2 -1
  13. package/core/performance-optimizer.js +271 -0
  14. package/core/semantic-engine.js +15 -3
  15. package/core/semantic-rule-base.js +4 -2
  16. package/docs/FILE_LIMITS_COMPLETION_REPORT.md +151 -0
  17. package/docs/FILE_LIMITS_EXPLANATION.md +190 -0
  18. package/docs/PERFORMANCE.md +311 -0
  19. package/docs/PERFORMANCE_MIGRATION_GUIDE.md +368 -0
  20. package/docs/PERFORMANCE_OPTIMIZATION_PLAN.md +255 -0
  21. package/docs/QUICK_FILE_LIMITS.md +64 -0
  22. package/docs/SIMPLIFIED_USAGE_GUIDE.md +208 -0
  23. package/engines/heuristic-engine.js +247 -9
  24. package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +59 -1
  25. package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +26 -1
  26. package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +54 -19
  27. package/origin-rules/common-en.md +11 -7
  28. package/package.json +2 -1
  29. package/rules/common/C002_no_duplicate_code/analyzer.js +334 -36
  30. package/rules/common/C003_no_vague_abbreviations/analyzer.js +220 -35
  31. package/rules/common/C006_function_naming/analyzer.js +29 -3
  32. package/rules/common/C010_limit_block_nesting/analyzer.js +181 -337
  33. package/rules/common/C010_limit_block_nesting/config.json +64 -0
  34. package/rules/common/C010_limit_block_nesting/regex-based-analyzer.js +379 -0
  35. package/rules/common/C010_limit_block_nesting/symbol-based-analyzer.js +231 -0
  36. package/rules/common/C013_no_dead_code/analyzer.js +75 -177
  37. package/rules/common/C013_no_dead_code/config.json +61 -0
  38. package/rules/common/C013_no_dead_code/regex-based-analyzer.js +345 -0
  39. package/rules/common/C013_no_dead_code/symbol-based-analyzer.js +640 -0
  40. package/rules/common/C014_dependency_injection/analyzer.js +48 -313
  41. package/rules/common/C014_dependency_injection/config.json +26 -0
  42. package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +751 -0
  43. package/rules/common/C018_no_throw_generic_error/analyzer.js +232 -0
  44. package/rules/common/C018_no_throw_generic_error/config.json +50 -0
  45. package/rules/common/C018_no_throw_generic_error/regex-based-analyzer.js +387 -0
  46. package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +314 -0
  47. package/rules/common/C019_log_level_usage/analyzer.js +110 -317
  48. package/rules/common/C019_log_level_usage/pattern-analyzer.js +88 -0
  49. package/rules/common/C019_log_level_usage/system-log-analyzer.js +1267 -0
  50. package/rules/common/C023_no_duplicate_variable/analyzer.js +180 -0
  51. package/rules/common/C023_no_duplicate_variable/config.json +50 -0
  52. package/rules/common/C023_no_duplicate_variable/symbol-based-analyzer.js +158 -0
  53. package/rules/common/C024_no_scatter_hardcoded_constants/analyzer.js +180 -0
  54. package/rules/common/C024_no_scatter_hardcoded_constants/config.json +50 -0
  55. package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +181 -0
  56. package/rules/common/C030_use_custom_error_classes/analyzer.js +200 -0
  57. package/rules/common/C035_error_logging_context/analyzer.js +3 -1
  58. package/rules/common/C048_no_bypass_architectural_layers/analyzer.js +180 -0
  59. package/rules/common/C048_no_bypass_architectural_layers/config.json +50 -0
  60. package/rules/common/C048_no_bypass_architectural_layers/symbol-based-analyzer.js +235 -0
  61. package/rules/common/C052_parsing_or_data_transformation/analyzer.js +180 -0
  62. package/rules/common/C052_parsing_or_data_transformation/config.json +50 -0
  63. package/rules/common/C052_parsing_or_data_transformation/symbol-based-analyzer.js +132 -0
  64. package/rules/index.js +7 -1
  65. package/rules/security/S009_no_insecure_encryption/README.md +158 -0
  66. package/rules/security/S009_no_insecure_encryption/analyzer.js +319 -0
  67. package/rules/security/S009_no_insecure_encryption/config.json +55 -0
  68. package/rules/security/S010_no_insecure_encryption/README.md +224 -0
  69. package/rules/security/S010_no_insecure_encryption/analyzer.js +493 -0
  70. package/rules/security/S010_no_insecure_encryption/config.json +48 -0
  71. package/rules/security/S016_no_sensitive_querystring/STRATEGY.md +149 -0
  72. package/rules/security/S016_no_sensitive_querystring/analyzer.js +276 -0
  73. package/rules/security/S016_no_sensitive_querystring/config.json +127 -0
  74. package/rules/security/S016_no_sensitive_querystring/regex-based-analyzer.js +258 -0
  75. package/rules/security/S016_no_sensitive_querystring/symbol-based-analyzer.js +495 -0
  76. package/rules/security/S017_use_parameterized_queries/README.md +128 -0
  77. package/rules/security/S017_use_parameterized_queries/analyzer.js +286 -0
  78. package/rules/security/S017_use_parameterized_queries/config.json +109 -0
  79. package/rules/security/S017_use_parameterized_queries/regex-based-analyzer.js +541 -0
  80. package/rules/security/S017_use_parameterized_queries/symbol-based-analyzer.js +777 -0
  81. package/rules/security/S031_secure_session_cookies/README.md +127 -0
  82. package/rules/security/S031_secure_session_cookies/analyzer.js +245 -0
  83. package/rules/security/S031_secure_session_cookies/config.json +86 -0
  84. package/rules/security/S031_secure_session_cookies/regex-based-analyzer.js +196 -0
  85. package/rules/security/S031_secure_session_cookies/symbol-based-analyzer.js +1084 -0
  86. package/rules/security/S032_httponly_session_cookies/FRAMEWORK_SUPPORT.md +209 -0
  87. package/rules/security/S032_httponly_session_cookies/README.md +184 -0
  88. package/rules/security/S032_httponly_session_cookies/analyzer.js +282 -0
  89. package/rules/security/S032_httponly_session_cookies/config.json +96 -0
  90. package/rules/security/S032_httponly_session_cookies/regex-based-analyzer.js +715 -0
  91. package/rules/security/S032_httponly_session_cookies/symbol-based-analyzer.js +1348 -0
  92. package/rules/security/S033_samesite_session_cookies/README.md +227 -0
  93. package/rules/security/S033_samesite_session_cookies/analyzer.js +242 -0
  94. package/rules/security/S033_samesite_session_cookies/config.json +87 -0
  95. package/rules/security/S033_samesite_session_cookies/regex-based-analyzer.js +703 -0
  96. package/rules/security/S033_samesite_session_cookies/symbol-based-analyzer.js +732 -0
  97. package/rules/security/S034_host_prefix_session_cookies/README.md +204 -0
  98. package/rules/security/S034_host_prefix_session_cookies/analyzer.js +290 -0
  99. package/rules/security/S034_host_prefix_session_cookies/config.json +62 -0
  100. package/rules/security/S034_host_prefix_session_cookies/regex-based-analyzer.js +478 -0
  101. package/rules/security/S034_host_prefix_session_cookies/symbol-based-analyzer.js +277 -0
  102. package/rules/security/S035_path_session_cookies/README.md +257 -0
  103. package/rules/security/S035_path_session_cookies/analyzer.js +316 -0
  104. package/rules/security/S035_path_session_cookies/config.json +99 -0
  105. package/rules/security/S035_path_session_cookies/regex-based-analyzer.js +724 -0
  106. package/rules/security/S035_path_session_cookies/symbol-based-analyzer.js +373 -0
  107. package/rules/security/S048_no_current_password_in_reset/README.md +222 -0
  108. package/rules/security/S048_no_current_password_in_reset/analyzer.js +366 -0
  109. package/rules/security/S048_no_current_password_in_reset/config.json +48 -0
  110. package/rules/security/S055_content_type_validation/README.md +176 -0
  111. package/rules/security/S055_content_type_validation/analyzer.js +312 -0
  112. package/rules/security/S055_content_type_validation/config.json +48 -0
  113. package/rules/utils/rule-helpers.js +140 -1
  114. package/scripts/batch-processing-demo.js +334 -0
  115. package/scripts/consolidate-config.js +116 -0
  116. package/scripts/performance-test.js +541 -0
  117. package/scripts/quick-performance-test.js +108 -0
  118. package/config/rules/S027-categories.json +0 -122
  119. package/config/rules/rules-registry.json +0 -777
  120. package/rules/common/C006_function_naming/smart-analyzer.js +0 -503
@@ -441,7 +441,7 @@
441
441
  },
442
442
  "C019": {
443
443
  "name": "Do not use `error` log level for non-critical issues",
444
- "description": "Avoid noisy logs and false alarms; ensure meaningful log levels.",
444
+ "description": "Prevent noisy logs and false alarms; ensure consistent and meaningful log levels across the system.",
445
445
  "category": "Common",
446
446
  "severity": "major",
447
447
  "languages": [
@@ -10,6 +10,7 @@ const path = require('path');
10
10
  const fs = require('fs');
11
11
  const AnalysisEngineInterface = require('./interfaces/analysis-engine.interface');
12
12
  const SunlintRuleAdapter = require('./adapters/sunlint-rule-adapter');
13
+ const PerformanceOptimizer = require('./performance-optimizer');
13
14
 
14
15
  class AnalysisOrchestrator {
15
16
  constructor() {
@@ -18,6 +19,7 @@ class AnalysisOrchestrator {
18
19
  this.defaultTimeout = 30000; // 30 seconds default timeout
19
20
  this.ruleAdapter = SunlintRuleAdapter.getInstance();
20
21
  this.enginesConfigPath = path.join(__dirname, '..', 'config', 'engines', 'engines.json');
22
+ this.performanceOptimizer = new PerformanceOptimizer();
21
23
  }
22
24
 
23
25
  /**
@@ -171,14 +173,35 @@ class AnalysisOrchestrator {
171
173
  throw new Error('No analysis engines registered');
172
174
  }
173
175
 
176
+ // Initialize performance optimizer
177
+ await this.performanceOptimizer.initialize(config);
178
+
179
+ // Apply performance optimizations to files and rules
180
+ const { optimizedFiles, optimizedRules, performanceMetrics } =
181
+ await this.performanceOptimizer.optimizeAnalysis(
182
+ options.files || [options.input],
183
+ rulesToRun,
184
+ config
185
+ );
186
+
187
+ if (!options.quiet) {
188
+ console.log(chalk.cyan(`🔍 Analyzing ${optimizedRules.length} rules on ${optimizedFiles.length} files...`));
189
+ if (performanceMetrics.filteredFiles > 0) {
190
+ console.log(chalk.gray(` 📦 Filtered ${performanceMetrics.filteredFiles} files for performance`));
191
+ }
192
+ if (performanceMetrics.ruleBatches > 1) {
193
+ console.log(chalk.gray(` 🔄 Using ${performanceMetrics.ruleBatches} rule batches`));
194
+ }
195
+ }
196
+
174
197
  // Group rules by their preferred engines
175
- const engineGroups = this.groupRulesByEngine(rulesToRun, config);
198
+ const engineGroups = this.groupRulesByEngine(optimizedRules, config);
176
199
 
177
200
  if (!options.quiet) {
178
- console.log(chalk.cyan(`🔍 Analyzing ${rulesToRun.length} rules across ${engineGroups.size} engines...`));
201
+ console.log(chalk.cyan(`🚀 Running analysis across ${engineGroups.size} engines...`));
179
202
  }
180
203
 
181
- // Run analysis on each engine
204
+ // Run analysis on each engine with batching
182
205
  const results = [];
183
206
  for (const [engineName, rules] of engineGroups) {
184
207
  const engine = this.engines.get(engineName);
@@ -187,35 +210,64 @@ class AnalysisOrchestrator {
187
210
  continue;
188
211
  }
189
212
 
190
- if (!options.quiet) {
191
- console.log(chalk.blue(`⚙️ Running ${rules.length} rules on ${engineName} engine...`));
192
- }
193
-
194
- try {
195
- const engineResult = await this.runEngineWithTimeout(
196
- engine,
197
- options.files || [options.input],
198
- rules,
199
- options
200
- );
201
-
202
- results.push({
203
- engine: engineName,
204
- rules: rules.map(r => r.id),
205
- ...engineResult
206
- });
213
+ // Process rules in batches for performance
214
+ const ruleBatches = this.performanceOptimizer.createRuleBatches(rules, config);
215
+
216
+ for (let i = 0; i < ruleBatches.length; i++) {
217
+ const batch = ruleBatches[i];
218
+ const batchNumber = i + 1;
207
219
 
208
- if (!options.quiet) {
209
- const violationCount = this.countViolations(engineResult);
210
- console.log(chalk.blue(`✅ ${engineName}: ${violationCount} violations found`));
220
+ if (!options.quiet && ruleBatches.length > 1) {
221
+ console.log(chalk.blue(`⚙️ ${engineName} - Batch ${batchNumber}/${ruleBatches.length}: ${batch.length} rules`));
222
+ } else if (!options.quiet) {
223
+ console.log(chalk.blue(`⚙️ Running ${batch.length} rules on ${engineName} engine...`));
224
+ }
225
+
226
+ try {
227
+ const engineResult = await this.runEngineWithOptimizations(
228
+ engine,
229
+ optimizedFiles,
230
+ batch,
231
+ options,
232
+ { batchNumber, totalBatches: ruleBatches.length }
233
+ );
234
+
235
+ results.push({
236
+ engine: engineName,
237
+ batch: batchNumber,
238
+ rules: batch.map(r => r.id),
239
+ ...engineResult
240
+ });
241
+
242
+ if (!options.quiet) {
243
+ const violationCount = this.countViolations(engineResult);
244
+ console.log(chalk.blue(`✅ ${engineName} batch ${batchNumber}: ${violationCount} violations found`));
245
+ }
246
+ } catch (error) {
247
+ // Enhanced error recovery with batch context
248
+ const errorInfo = this.performanceOptimizer.handleAnalysisError(error, {
249
+ engine: engineName,
250
+ batch: batchNumber,
251
+ rules: batch.map(r => r.id),
252
+ files: optimizedFiles.length
253
+ });
254
+
255
+ console.error(chalk.red(`❌ Engine ${engineName} batch ${batchNumber} failed:`), errorInfo.message);
256
+
257
+ if (errorInfo.shouldRetry && !options.noRetry) {
258
+ console.log(chalk.yellow(`🔄 Retrying with reduced batch size...`));
259
+ // Split batch and retry - implement recursive retry logic here
260
+ }
261
+ // Continue with other batches instead of failing completely
211
262
  }
212
- } catch (error) {
213
- console.error(chalk.red(`❌ Engine ${engineName} failed:`), error.message);
214
- // Continue with other engines instead of failing completely
215
263
  }
216
264
  }
217
265
 
218
- return this.mergeEngineResults(results, options);
266
+ // Merge results and add performance metrics
267
+ const mergedResults = this.mergeEngineResults(results, options);
268
+ mergedResults.performance = performanceMetrics;
269
+
270
+ return mergedResults;
219
271
 
220
272
  } catch (error) {
221
273
  console.error(chalk.red('❌ Analysis orchestration failed:'), error.message);
@@ -393,23 +445,68 @@ class AnalysisOrchestrator {
393
445
  }
394
446
 
395
447
  /**
396
- * Run engine analysis with timeout protection
448
+ * Run engine analysis with timeout protection and performance optimizations
397
449
  * Following Rule C006: Verb-noun naming
398
450
  * @param {AnalysisEngineInterface} engine - Engine to run
399
451
  * @param {string[]} files - Files to analyze
400
452
  * @param {Object[]} rules - Rules to apply
401
453
  * @param {Object} options - Analysis options
454
+ * @param {Object} batchInfo - Batch context information
402
455
  * @returns {Promise<Object>} Engine results
403
456
  */
404
- async runEngineWithTimeout(engine, files, rules, options) {
405
- const timeout = options.timeout || this.defaultTimeout;
457
+ async runEngineWithOptimizations(engine, files, rules, options, batchInfo = {}) {
458
+ // Dynamic timeout based on file count and rules
459
+ const adaptiveTimeout = this.performanceOptimizer.calculateAdaptiveTimeout(
460
+ files.length,
461
+ rules.length,
462
+ options.timeout || this.defaultTimeout
463
+ );
406
464
 
407
- return Promise.race([
408
- engine.analyze(files, rules, options),
409
- new Promise((_, reject) =>
410
- setTimeout(() => reject(new Error(`Engine ${engine.name} timed out after ${timeout}ms`)), timeout)
411
- )
412
- ]);
465
+ const enhancedOptions = {
466
+ ...options,
467
+ timeout: adaptiveTimeout,
468
+ batchInfo
469
+ };
470
+
471
+ try {
472
+ return await Promise.race([
473
+ engine.analyze(files, rules, enhancedOptions),
474
+ new Promise((_, reject) =>
475
+ setTimeout(() => reject(new Error(
476
+ `Engine ${engine.name} batch ${batchInfo.batchNumber || 1} timed out after ${adaptiveTimeout}ms`
477
+ )), adaptiveTimeout)
478
+ )
479
+ ]);
480
+ } catch (error) {
481
+ // Enhanced error context for debugging
482
+ const errorContext = {
483
+ engine: engine.name,
484
+ filesCount: files.length,
485
+ rulesCount: rules.length,
486
+ timeout: adaptiveTimeout,
487
+ batch: batchInfo
488
+ };
489
+
490
+ // Wrap error with context
491
+ const enhancedError = new Error(`${error.message} (Context: ${JSON.stringify(errorContext)})`);
492
+ enhancedError.originalError = error;
493
+ enhancedError.context = errorContext;
494
+
495
+ throw enhancedError;
496
+ }
497
+ }
498
+
499
+ /**
500
+ * Run engine analysis with timeout protection (legacy method for backward compatibility)
501
+ * Following Rule C006: Verb-noun naming
502
+ * @param {AnalysisEngineInterface} engine - Engine to run
503
+ * @param {string[]} files - Files to analyze
504
+ * @param {Object[]} rules - Rules to apply
505
+ * @param {Object} options - Analysis options
506
+ * @returns {Promise<Object>} Engine results
507
+ */
508
+ async runEngineWithTimeout(engine, files, rules, options) {
509
+ return this.runEngineWithOptimizations(engine, files, rules, options);
413
510
  }
414
511
 
415
512
  /**
@@ -425,6 +522,7 @@ class AnalysisOrchestrator {
425
522
  results: [],
426
523
  summary: {
427
524
  totalEngines: engineResults.length,
525
+ totalBatches: engineResults.length,
428
526
  totalViolations: 0,
429
527
  totalFiles: 0,
430
528
  engines: {}
@@ -436,8 +534,13 @@ class AnalysisOrchestrator {
436
534
  }
437
535
  };
438
536
 
537
+ // Track unique engines for summary
538
+ const uniqueEngines = new Set();
539
+
439
540
  // Combine results from all engines
440
541
  for (const engineResult of engineResults) {
542
+ uniqueEngines.add(engineResult.engine);
543
+
441
544
  // Add engine-specific results
442
545
  if (engineResult.results) {
443
546
  mergedResults.results.push(...engineResult.results);
@@ -445,16 +548,30 @@ class AnalysisOrchestrator {
445
548
 
446
549
  // Track engine statistics
447
550
  const violationCount = this.countViolations(engineResult);
448
- mergedResults.summary.engines[engineResult.engine] = {
449
- rules: engineResult.rules || [],
450
- violations: violationCount,
451
- files: engineResult.filesAnalyzed || 0
452
- };
551
+ const engineName = engineResult.engine;
552
+
553
+ if (!mergedResults.summary.engines[engineName]) {
554
+ mergedResults.summary.engines[engineName] = {
555
+ rules: [],
556
+ violations: 0,
557
+ files: 0,
558
+ batches: 0
559
+ };
560
+ }
561
+
562
+ // Accumulate engine statistics across batches
563
+ mergedResults.summary.engines[engineName].rules.push(...(engineResult.rules || []));
564
+ mergedResults.summary.engines[engineName].violations += violationCount;
565
+ mergedResults.summary.engines[engineName].files += engineResult.filesAnalyzed || 0;
566
+ mergedResults.summary.engines[engineName].batches += 1;
453
567
 
454
568
  mergedResults.summary.totalViolations += violationCount;
455
569
  mergedResults.summary.totalFiles += engineResult.filesAnalyzed || 0;
456
570
  }
457
571
 
572
+ // Update unique engine count
573
+ mergedResults.summary.totalEngines = uniqueEngines.size;
574
+
458
575
  return mergedResults;
459
576
  }
460
577
 
@@ -517,7 +634,7 @@ class AnalysisOrchestrator {
517
634
  }
518
635
 
519
636
  /**
520
- * Cleanup all engines
637
+ * Cleanup all engines and performance optimizer
521
638
  * Following Rule C006: Verb-noun naming
522
639
  * @returns {Promise<void>}
523
640
  */
@@ -529,6 +646,14 @@ class AnalysisOrchestrator {
529
646
  console.warn(chalk.yellow(`⚠️ Failed to cleanup engine ${engine.id}:`), error.message);
530
647
  }
531
648
  }
649
+
650
+ // Cleanup performance optimizer
651
+ try {
652
+ await this.performanceOptimizer.cleanup();
653
+ } catch (error) {
654
+ console.warn(chalk.yellow(`⚠️ Failed to cleanup performance optimizer:`), error.message);
655
+ }
656
+
532
657
  this.initialized = false;
533
658
  console.log(chalk.blue('🧹 Engine cleanup completed'));
534
659
  }
@@ -0,0 +1,243 @@
1
+ /**
2
+ * 🚀 Auto Performance Manager for SunLint
3
+ * Automatically detects optimal performance settings based on project characteristics
4
+ * GOAL: Simplify CLI by reducing user choices while maintaining performance
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ class AutoPerformanceManager {
11
+ constructor() {
12
+ // Smart defaults based on project analysis
13
+ this.performanceProfiles = {
14
+ auto: {
15
+ name: 'Auto-Detect',
16
+ detect: true,
17
+ description: 'Automatically choose best settings based on project size'
18
+ },
19
+ fast: {
20
+ name: 'Fast',
21
+ timeout: 30000, // 30s
22
+ batchSize: 20,
23
+ maxFiles: 500,
24
+ description: 'Quick analysis for small projects (<100 files)'
25
+ },
26
+ careful: {
27
+ name: 'Careful',
28
+ timeout: 120000, // 2 minutes
29
+ batchSize: 10,
30
+ maxFiles: 1500,
31
+ progressiveResults: true,
32
+ description: 'Thorough analysis for large projects (>500 files)'
33
+ }
34
+ };
35
+ }
36
+
37
+ /**
38
+ * 🎯 Get optimal performance settings with minimal user input
39
+ * Clarifies the difference between max-files and max-semantic-files:
40
+ * - max-files: Total files to analyze (performance limit)
41
+ * - max-semantic-files: Files to load into TypeScript symbol table (memory limit)
42
+ */
43
+ getOptimalSettings(options, targetFiles = []) {
44
+ const mode = options.performance || 'auto';
45
+
46
+ if (mode === 'auto') {
47
+ return this.autoDetectSettings(options, targetFiles);
48
+ }
49
+
50
+ return this.getProfileSettings(mode, options);
51
+ }
52
+
53
+ /**
54
+ * 🤖 Auto-detect optimal settings based on project characteristics
55
+ */
56
+ autoDetectSettings(options, targetFiles) {
57
+ const projectAnalysis = this.analyzeProject(options, targetFiles);
58
+ const profile = this.selectOptimalProfile(projectAnalysis);
59
+
60
+ if (options.verbose) {
61
+ console.log(`🤖 Auto-detected performance profile: ${profile.name}`);
62
+ console.log(` 📊 Project: ${projectAnalysis.fileCount} files, ${projectAnalysis.size} size`);
63
+ console.log(` ⚡ Settings: ${profile.timeout/1000}s timeout, ${profile.batchSize} rules/batch`);
64
+ }
65
+
66
+ return {
67
+ ...profile,
68
+ autoDetected: true,
69
+ projectAnalysis
70
+ };
71
+ }
72
+
73
+ /**
74
+ * 📊 Analyze project to determine optimal settings
75
+ */
76
+ analyzeProject(options, targetFiles) {
77
+ const fileCount = targetFiles.length;
78
+ const inputPath = options.input || process.cwd();
79
+
80
+ // Estimate project complexity
81
+ const hasNodeModules = fs.existsSync(path.join(inputPath, 'node_modules'));
82
+ const hasPackageJson = fs.existsSync(path.join(inputPath, 'package.json'));
83
+ const hasTsConfig = fs.existsSync(path.join(inputPath, 'tsconfig.json'));
84
+ const hasGitIgnore = fs.existsSync(path.join(inputPath, '.gitignore'));
85
+
86
+ // Simple heuristics for project size
87
+ let size = 'small';
88
+ let complexity = 'simple';
89
+
90
+ if (fileCount > 1000) {
91
+ size = 'enterprise';
92
+ complexity = 'complex';
93
+ } else if (fileCount > 500) {
94
+ size = 'large';
95
+ complexity = hasNodeModules && hasTsConfig ? 'complex' : 'medium';
96
+ } else if (fileCount > 100) {
97
+ size = 'medium';
98
+ complexity = hasTsConfig ? 'medium' : 'simple';
99
+ }
100
+
101
+ return {
102
+ fileCount,
103
+ size,
104
+ complexity,
105
+ hasNodeModules,
106
+ hasPackageJson,
107
+ hasTsConfig,
108
+ hasGitIgnore,
109
+ inputPath
110
+ };
111
+ }
112
+
113
+ /**
114
+ * 🎯 Select optimal profile based on project analysis
115
+ */
116
+ selectOptimalProfile(analysis) {
117
+ if (analysis.fileCount <= 100) {
118
+ return {
119
+ name: 'Auto-Fast',
120
+ timeout: 30000,
121
+ batchSize: 20,
122
+ maxFiles: 200, // Analysis limit
123
+ maxSemanticFiles: 100, // Symbol table limit (smaller for memory)
124
+ description: `Small project (${analysis.fileCount} files) - fast analysis`
125
+ };
126
+ }
127
+
128
+ if (analysis.fileCount <= 500) {
129
+ return {
130
+ name: 'Auto-Balanced',
131
+ timeout: 60000,
132
+ batchSize: 15,
133
+ maxFiles: 600, // Analysis limit
134
+ maxSemanticFiles: 300, // Symbol table limit
135
+ progressiveResults: true,
136
+ description: `Medium project (${analysis.fileCount} files) - balanced analysis`
137
+ };
138
+ }
139
+
140
+ if (analysis.fileCount <= 1000) {
141
+ return {
142
+ name: 'Auto-Careful',
143
+ timeout: 120000,
144
+ batchSize: 10,
145
+ maxFiles: 1200, // Analysis limit
146
+ maxSemanticFiles: 500, // Symbol table limit
147
+ progressiveResults: true,
148
+ streamingAnalysis: analysis.complexity === 'complex',
149
+ description: `Large project (${analysis.fileCount} files) - careful analysis`
150
+ };
151
+ }
152
+
153
+ // Enterprise projects
154
+ return {
155
+ name: 'Auto-Enterprise',
156
+ timeout: 300000,
157
+ batchSize: 5,
158
+ maxFiles: 1500, // Analysis limit
159
+ maxSemanticFiles: 300, // Conservative symbol table limit
160
+ progressiveResults: true,
161
+ streamingAnalysis: true,
162
+ smartSampling: true,
163
+ description: `Enterprise project (${analysis.fileCount} files) - conservative analysis`
164
+ };
165
+ }
166
+
167
+ /**
168
+ * ⚙️ Get predefined profile settings
169
+ */
170
+ getProfileSettings(mode, options) {
171
+ const profile = this.performanceProfiles[mode];
172
+
173
+ if (!profile) {
174
+ console.warn(`⚠️ Unknown performance mode: ${mode}, using auto-detect`);
175
+ return this.autoDetectSettings(options, []);
176
+ }
177
+
178
+ // Override with user-specified options
179
+ const settings = { ...profile };
180
+
181
+ if (options.timeout && options.timeout !== '0') {
182
+ settings.timeout = parseInt(options.timeout);
183
+ }
184
+
185
+ if (options.maxFiles && options.maxFiles !== '1000') {
186
+ settings.maxFiles = parseInt(options.maxFiles);
187
+ }
188
+
189
+ return settings;
190
+ }
191
+
192
+ /**
193
+ * 📋 Get user-friendly performance recommendations
194
+ */
195
+ getPerformanceRecommendations(options, targetFiles) {
196
+ const analysis = this.analyzeProject(options, targetFiles);
197
+ const profile = this.selectOptimalProfile(analysis);
198
+
199
+ const recommendations = [
200
+ `🎯 Recommended: sunlint --all --input=${options.input || 'src'} --performance=auto`
201
+ ];
202
+
203
+ if (analysis.fileCount > 500) {
204
+ recommendations.push(`💡 For faster results: sunlint --all --input=${options.input || 'src'} --performance=fast --max-files=300`);
205
+ }
206
+
207
+ if (analysis.complexity === 'complex') {
208
+ recommendations.push(`⚡ For thorough analysis: sunlint --all --input=${options.input || 'src'} --performance=careful --verbose`);
209
+ }
210
+
211
+ return {
212
+ analysis,
213
+ profile,
214
+ recommendations
215
+ };
216
+ }
217
+
218
+ /**
219
+ * 🎛️ Show simplified CLI usage for common scenarios
220
+ */
221
+ static getSimplifiedUsageExamples() {
222
+ return {
223
+ quickStart: [
224
+ 'sunlint --all --input=src', // Auto-detect everything
225
+ 'sunlint --rules=C019,C041,S027 --input=src', // Specific rules
226
+ 'sunlint --quality --input=src' // Quality rules only
227
+ ],
228
+ performance: [
229
+ 'sunlint --all --input=src --performance=auto', // Auto-detect (default)
230
+ 'sunlint --all --input=src --performance=fast', // Quick scan
231
+ 'sunlint --all --input=src --performance=careful', // Thorough analysis
232
+ 'sunlint --all --input=src --timeout=60000' // Custom timeout
233
+ ],
234
+ advanced: [
235
+ 'sunlint --all --input=src --verbose', // See detailed progress
236
+ 'sunlint --all --input=src --dry-run', // Preview analysis
237
+ 'sunlint --all --input=src --format=json' // JSON output
238
+ ]
239
+ };
240
+ }
241
+ }
242
+
243
+ module.exports = AutoPerformanceManager;
@@ -136,12 +136,20 @@ class CliActionHandler {
136
136
  // Run analysis with new orchestrator
137
137
  const results = await this.orchestrator.analyze(files, rulesToRun, {
138
138
  ...this.options,
139
+ timeout: parseInt(this.options.timeout) || 30000,
139
140
  config: {
140
141
  ...config,
141
142
  verbose: this.options.verbose,
142
143
  quiet: this.options.quiet,
143
144
  // Pass requested engine to enable strict engine mode (no fallback)
144
- requestedEngine: this.options.engine
145
+ requestedEngine: this.options.engine,
146
+ // Performance optimization settings
147
+ performanceMode: this.options.performanceMode,
148
+ ruleBatchSize: parseInt(this.options.ruleBatchSize) || 10,
149
+ fileBatchSize: parseInt(this.options.fileBatchSize) || 50,
150
+ maxFiles: parseInt(this.options.maxFiles) || 1000,
151
+ enableFileFiltering: !this.options.noFileFiltering,
152
+ enableBatching: !this.options.noBatching
145
153
  }
146
154
  });
147
155
  return results;
@@ -56,18 +56,22 @@ function createCliProgram() {
56
56
  .option('--save-baseline <file>', 'Save current results as baseline')
57
57
  .option('--fail-on-new-violations', 'Exit with error only on new violations (not existing)');
58
58
 
59
+ // Performance options (SIMPLIFIED)
60
+ program
61
+ .option('--timeout <milliseconds>', 'Analysis timeout in milliseconds (default: auto)', '0')
62
+ .option('--max-files <count>', 'Maximum files to analyze (default: auto-detect)', '0')
63
+ .option('--performance <mode>', 'Performance mode: auto, fast, careful (default: auto)', 'auto');
64
+
59
65
  // Advanced options
60
66
  program
61
- .option('--engine <engine>', 'Force specific analysis engine (eslint,heuristic)', '')
67
+ .option('--engine <engine>', 'Analysis engine (eslint,heuristic,auto)', 'auto')
62
68
  .option('--dry-run', 'Show what would be analyzed without running')
63
69
  .option('--verbose', 'Enable verbose logging')
64
70
  .option('--quiet', 'Suppress non-error output')
65
71
  .option('--debug', 'Enable debug mode')
66
72
  .option('--ai', 'Enable AI-powered analysis')
67
- .option('--no-ai', 'Force disable AI analysis (use heuristic only)')
68
- .option('--legacy', 'Use legacy analysis architecture')
69
- .option('--modern', 'Use modern plugin-based architecture (default)')
70
- .option('--max-semantic-files <number>', 'Control semantic analysis scope: 0=disable, -1=unlimited, >0=limit (default: 1000)', '1000')
73
+ .option('--no-ai', 'Force disable AI analysis')
74
+ .option('--max-semantic-files <number>', 'Symbol table file limit for TypeScript analysis (default: auto)', '0')
71
75
  .option('--list-engines', 'List available analysis engines');
72
76
 
73
77
  // ESLint Integration options
@@ -91,6 +95,16 @@ Examples:
91
95
 
92
96
  File Targeting:
93
97
  $ sunlint --all --include="src/**/*.ts" --exclude="**/*.test.*" --input=.
98
+
99
+ Performance (SIMPLIFIED):
100
+ $ sunlint --all --input=src --performance=auto # Auto-detect best settings
101
+ $ sunlint --all --input=src --performance=fast # Quick scan
102
+ $ sunlint --all --input=src --performance=careful # Thorough analysis
103
+ $ sunlint --all --input=src --timeout=60000 # Custom timeout (60s)
104
+
105
+ File Limits (when needed):
106
+ $ sunlint --all --input=src --max-files=500 # Limit total files analyzed
107
+ $ sunlint --all --input=src --max-semantic-files=200 # Limit TypeScript symbol table
94
108
  $ sunlint --all --languages=typescript,dart --input=src
95
109
  $ sunlint --typescript --exclude-tests --input=src
96
110
  $ sunlint --all --only-source --include="src/**,lib/**" --input=.
@@ -91,6 +91,61 @@ const DEFAULT_LIMITS = {
91
91
  MAX_OUTPUT_LINES: 1000 // Limit output to 1000 lines
92
92
  };
93
93
 
94
+ /**
95
+ * Performance optimization defaults
96
+ */
97
+ const DEFAULT_PERFORMANCE = {
98
+ // File filtering
99
+ ENABLE_FILE_FILTERING: true,
100
+ MAX_FILE_SIZE: 2 * 1024 * 1024, // 2MB per file
101
+ MAX_TOTAL_FILES: 1000, // Max 1000 files per analysis
102
+
103
+ // Batch processing
104
+ ENABLE_BATCHING: true,
105
+ RULE_BATCH_SIZE: 10, // Process 10 rules per batch
106
+ FILE_BATCH_SIZE: 50, // Process 50 files per batch
107
+
108
+ // Concurrency
109
+ MAX_CONCURRENT_BATCHES: 3, // Max 3 batches running simultaneously
110
+
111
+ // Memory management
112
+ ENABLE_MEMORY_MONITORING: true,
113
+ MAX_HEAP_SIZE_MB: 512, // 512MB heap limit
114
+ GC_THRESHOLD_MB: 256, // Trigger GC at 256MB
115
+
116
+ // Timeouts (adaptive)
117
+ BASE_TIMEOUT_MS: 30000, // 30s base timeout
118
+ TIMEOUT_PER_FILE_MS: 100, // +100ms per file
119
+ TIMEOUT_PER_RULE_MS: 1000, // +1s per rule
120
+ MAX_TIMEOUT_MS: 120000, // 2 minutes max timeout
121
+
122
+ // Error recovery
123
+ ENABLE_ERROR_RECOVERY: true,
124
+ MAX_RETRIES: 2, // Retry failed batches up to 2 times
125
+ RETRY_DELAY_MS: 1000, // 1s delay between retries
126
+
127
+ // Exclusion patterns for performance
128
+ HIGH_PERFORMANCE_EXCLUDES: [
129
+ '**/node_modules/**',
130
+ '**/.next/**',
131
+ '**/dist/**',
132
+ '**/build/**',
133
+ '**/coverage/**',
134
+ '**/.git/**',
135
+ '**/target/**',
136
+ '**/out/**',
137
+ '**/*.min.js',
138
+ '**/*.bundle.js',
139
+ '**/vendor/**',
140
+ '**/lib/**',
141
+ '**/libs/**',
142
+ '**/.vscode/**',
143
+ '**/.idea/**',
144
+ '**/tmp/**',
145
+ '**/temp/**'
146
+ ]
147
+ };
148
+
94
149
  /**
95
150
  * Default language extensions mapping
96
151
  */
@@ -155,6 +210,7 @@ module.exports = {
155
210
  DEFAULT_SEVERITIES,
156
211
  DEFAULT_TIMEOUTS,
157
212
  DEFAULT_LIMITS,
213
+ DEFAULT_PERFORMANCE,
158
214
  DEFAULT_LANGUAGE_EXTENSIONS,
159
215
 
160
216
  // Utility functions