@sun-asterisk/sunlint 1.3.2 → 1.3.4

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 (60) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +5 -3
  3. package/config/rules/enhanced-rules-registry.json +144 -33
  4. package/core/analysis-orchestrator.js +173 -42
  5. package/core/auto-performance-manager.js +243 -0
  6. package/core/cli-action-handler.js +24 -2
  7. package/core/cli-program.js +19 -5
  8. package/core/constants/defaults.js +56 -0
  9. package/core/performance-optimizer.js +271 -0
  10. package/docs/FILE_LIMITS_COMPLETION_REPORT.md +151 -0
  11. package/docs/FILE_LIMITS_EXPLANATION.md +190 -0
  12. package/docs/PERFORMANCE.md +311 -0
  13. package/docs/PERFORMANCE_MIGRATION_GUIDE.md +368 -0
  14. package/docs/PERFORMANCE_OPTIMIZATION_PLAN.md +255 -0
  15. package/docs/QUICK_FILE_LIMITS.md +64 -0
  16. package/docs/SIMPLIFIED_USAGE_GUIDE.md +208 -0
  17. package/engines/engine-factory.js +7 -0
  18. package/engines/heuristic-engine.js +182 -5
  19. package/package.json +2 -1
  20. package/rules/common/C048_no_bypass_architectural_layers/analyzer.js +180 -0
  21. package/rules/common/C048_no_bypass_architectural_layers/config.json +50 -0
  22. package/rules/common/C048_no_bypass_architectural_layers/symbol-based-analyzer.js +235 -0
  23. package/rules/common/C052_parsing_or_data_transformation/analyzer.js +180 -0
  24. package/rules/common/C052_parsing_or_data_transformation/config.json +50 -0
  25. package/rules/common/C052_parsing_or_data_transformation/symbol-based-analyzer.js +132 -0
  26. package/rules/index.js +2 -0
  27. package/rules/security/S017_use_parameterized_queries/README.md +128 -0
  28. package/rules/security/S017_use_parameterized_queries/analyzer.js +286 -0
  29. package/rules/security/S017_use_parameterized_queries/config.json +109 -0
  30. package/rules/security/S017_use_parameterized_queries/regex-based-analyzer.js +541 -0
  31. package/rules/security/S017_use_parameterized_queries/symbol-based-analyzer.js +777 -0
  32. package/rules/security/S031_secure_session_cookies/README.md +127 -0
  33. package/rules/security/S031_secure_session_cookies/analyzer.js +245 -0
  34. package/rules/security/S031_secure_session_cookies/config.json +86 -0
  35. package/rules/security/S031_secure_session_cookies/regex-based-analyzer.js +196 -0
  36. package/rules/security/S031_secure_session_cookies/symbol-based-analyzer.js +1084 -0
  37. package/rules/security/S032_httponly_session_cookies/FRAMEWORK_SUPPORT.md +209 -0
  38. package/rules/security/S032_httponly_session_cookies/README.md +184 -0
  39. package/rules/security/S032_httponly_session_cookies/analyzer.js +282 -0
  40. package/rules/security/S032_httponly_session_cookies/config.json +96 -0
  41. package/rules/security/S032_httponly_session_cookies/regex-based-analyzer.js +715 -0
  42. package/rules/security/S032_httponly_session_cookies/symbol-based-analyzer.js +1348 -0
  43. package/rules/security/S033_samesite_session_cookies/README.md +227 -0
  44. package/rules/security/S033_samesite_session_cookies/analyzer.js +242 -0
  45. package/rules/security/S033_samesite_session_cookies/config.json +87 -0
  46. package/rules/security/S033_samesite_session_cookies/regex-based-analyzer.js +703 -0
  47. package/rules/security/S033_samesite_session_cookies/symbol-based-analyzer.js +732 -0
  48. package/rules/security/S034_host_prefix_session_cookies/README.md +204 -0
  49. package/rules/security/S034_host_prefix_session_cookies/analyzer.js +290 -0
  50. package/rules/security/S034_host_prefix_session_cookies/config.json +62 -0
  51. package/rules/security/S034_host_prefix_session_cookies/regex-based-analyzer.js +478 -0
  52. package/rules/security/S034_host_prefix_session_cookies/symbol-based-analyzer.js +277 -0
  53. package/rules/security/S035_path_session_cookies/README.md +257 -0
  54. package/rules/security/S035_path_session_cookies/analyzer.js +316 -0
  55. package/rules/security/S035_path_session_cookies/config.json +99 -0
  56. package/rules/security/S035_path_session_cookies/regex-based-analyzer.js +724 -0
  57. package/rules/security/S035_path_session_cookies/symbol-based-analyzer.js +373 -0
  58. package/scripts/batch-processing-demo.js +334 -0
  59. package/scripts/performance-test.js +541 -0
  60. package/scripts/quick-performance-test.js +108 -0
@@ -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;
@@ -156,6 +164,20 @@ class CliActionHandler {
156
164
  determineEnabledEngines(config) {
157
165
  // If specific engine is requested via --engine option, use only that engine
158
166
  if (this.options.engine) {
167
+ // Handle "auto" engine selection
168
+ if (this.options.engine === 'auto') {
169
+ // Auto-select best engines: default to heuristic for compatibility
170
+ const autoEngines = ['heuristic'];
171
+
172
+ // Add ESLint for JS/TS files if available
173
+ if (this.hasJavaScriptTypeScriptFiles() || config.eslint?.enabled !== false) {
174
+ autoEngines.push('eslint');
175
+ }
176
+
177
+ return autoEngines;
178
+ }
179
+
180
+ // Return specific engine as requested
159
181
  return [this.options.engine];
160
182
  }
161
183
 
@@ -264,7 +286,7 @@ class CliActionHandler {
264
286
  validateInput(config) {
265
287
  // Validate engine option if specified (check this first, always)
266
288
  if (this.options.engine) {
267
- const validEngines = ['eslint', 'heuristic', 'openai'];
289
+ const validEngines = ['auto', 'eslint', 'heuristic', 'openai'];
268
290
  if (!validEngines.includes(this.options.engine)) {
269
291
  throw new Error(
270
292
  chalk.red(`❌ Invalid engine: ${this.options.engine}\n`) +
@@ -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
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Performance Optimization Module for SunLint v1.3.2
3
+ * Comprehensive optimizations to handle large projects efficiently
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const { DEFAULT_PERFORMANCE } = require('./constants/defaults');
9
+
10
+ class PerformanceOptimizer {
11
+ constructor() {
12
+ this.excludePatterns = DEFAULT_PERFORMANCE.HIGH_PERFORMANCE_EXCLUDES;
13
+ this.fileSizeLimits = {
14
+ maxFileSize: DEFAULT_PERFORMANCE.MAX_FILE_SIZE,
15
+ maxTotalFiles: DEFAULT_PERFORMANCE.MAX_TOTAL_FILES
16
+ };
17
+ this.config = {};
18
+ this.initialized = false;
19
+ }
20
+
21
+ /**
22
+ * Initialize performance optimizer with configuration
23
+ */
24
+ async initialize(config = {}) {
25
+ this.config = {
26
+ ...DEFAULT_PERFORMANCE,
27
+ ...config
28
+ };
29
+ this.initialized = true;
30
+ }
31
+
32
+ /**
33
+ * Main optimization method called by analysis orchestrator
34
+ */
35
+ async optimizeAnalysis(files, rules, config) {
36
+ const startTime = Date.now();
37
+
38
+ // Filter files for performance
39
+ const optimizedFiles = this.config.enableFileFiltering !== false
40
+ ? await this.smartFileFilter(files)
41
+ : files;
42
+
43
+ // Apply rule batching if enabled
44
+ const optimizedRules = this.config.enableBatching !== false
45
+ ? rules
46
+ : rules;
47
+
48
+ const performanceMetrics = {
49
+ originalFiles: files.length,
50
+ optimizedFiles: optimizedFiles.length,
51
+ filteredFiles: files.length - optimizedFiles.length,
52
+ originalRules: rules.length,
53
+ optimizedRules: optimizedRules.length,
54
+ ruleBatches: this.calculateBatchCount(rules, config),
55
+ optimizationTime: Date.now() - startTime
56
+ };
57
+
58
+ if (config.verbose) {
59
+ console.log(`⚡ Performance optimization: ${performanceMetrics.filteredFiles} files filtered, ${performanceMetrics.ruleBatches} batches`);
60
+ }
61
+
62
+ return {
63
+ optimizedFiles,
64
+ optimizedRules,
65
+ performanceMetrics
66
+ };
67
+ }
68
+
69
+ /**
70
+ * Calculate number of batches for rules
71
+ */
72
+ calculateBatchCount(rules, config) {
73
+ const batchSize = config.ruleBatchSize || this.config.RULE_BATCH_SIZE;
74
+ return Math.ceil(rules.length / batchSize);
75
+ }
76
+
77
+ /**
78
+ * Smart file filtering to exclude performance-heavy directories
79
+ */
80
+ async smartFileFilter(files) {
81
+ const filtered = [];
82
+ let totalSize = 0;
83
+
84
+ for (const file of files) {
85
+ // Skip if matches exclude patterns
86
+ if (this.shouldExcludeFile(file)) {
87
+ continue;
88
+ }
89
+
90
+ try {
91
+ const stats = await fs.promises.stat(file);
92
+
93
+ // Skip large files
94
+ if (stats.size > this.fileSizeLimits.maxFileSize) {
95
+ if (this.config.verbose) {
96
+ console.log(`⚠️ Skipping large file: ${path.basename(file)} (${(stats.size / 1024 / 1024).toFixed(1)}MB)`);
97
+ }
98
+ continue;
99
+ }
100
+
101
+ // Check total size limit
102
+ if (totalSize + stats.size > this.fileSizeLimits.maxTotalSize) {
103
+ if (this.config.verbose) {
104
+ console.log(`⚠️ Reached total size limit, stopping at ${filtered.length} files`);
105
+ }
106
+ break;
107
+ }
108
+
109
+ // Check file count limit
110
+ if (filtered.length >= this.fileSizeLimits.maxTotalFiles) {
111
+ if (this.config.verbose) {
112
+ console.log(`⚠️ Reached file count limit: ${this.fileSizeLimits.maxTotalFiles} files`);
113
+ }
114
+ break;
115
+ }
116
+
117
+ filtered.push(file);
118
+ totalSize += stats.size;
119
+
120
+ } catch (error) {
121
+ // Skip files we can't read
122
+ if (this.config.verbose) {
123
+ console.warn(`⚠️ Cannot read file ${file}: ${error.message}`);
124
+ }
125
+ continue;
126
+ }
127
+ }
128
+
129
+ if (this.config.verbose) {
130
+ console.log(`📊 Performance filter: ${filtered.length}/${files.length} files (${(totalSize / 1024 / 1024).toFixed(1)}MB)`);
131
+ }
132
+ return filtered;
133
+ }
134
+
135
+ shouldExcludeFile(filePath) {
136
+ const normalizedPath = filePath.replace(/\\/g, '/');
137
+
138
+ return this.excludePatterns.some(pattern => {
139
+ const regex = this.globToRegex(pattern);
140
+ const match = regex.test(normalizedPath);
141
+ return match;
142
+ });
143
+ }
144
+
145
+ globToRegex(glob) {
146
+ // Simple but effective glob to regex conversion
147
+ let regex = glob
148
+ .replace(/\./g, '\\.') // Escape dots
149
+ .replace(/\*\*/g, '___DOUBLE_STAR___') // Temp placeholder
150
+ .replace(/\*/g, '[^/]*') // Single * matches within path segment
151
+ .replace(/___DOUBLE_STAR___/g, '.*') // ** matches across path segments
152
+ .replace(/\?/g, '[^/]'); // ? matches single character
153
+
154
+ // Ensure pattern matches anywhere in the path
155
+ if (!regex.startsWith('.*')) {
156
+ regex = '.*' + regex;
157
+ }
158
+ if (!regex.endsWith('.*')) {
159
+ regex = regex + '.*';
160
+ }
161
+
162
+ return new RegExp(regex, 'i');
163
+ }
164
+
165
+ /**
166
+ * Adaptive timeout based on file count and rules
167
+ */
168
+ calculateAdaptiveTimeout(fileCount, ruleCount, baseTimeout = 30000) {
169
+ const perFileMs = this.config.TIMEOUT_PER_FILE_MS || 100;
170
+ const perRuleMs = this.config.TIMEOUT_PER_RULE_MS || 1000;
171
+ const maxTimeout = this.config.MAX_TIMEOUT_MS || 120000;
172
+
173
+ const adaptiveTimeout = Math.min(
174
+ baseTimeout + (fileCount * perFileMs) + (ruleCount * perRuleMs),
175
+ maxTimeout
176
+ );
177
+
178
+ if (this.config.verbose) {
179
+ console.log(`⏱️ Adaptive timeout: ${(adaptiveTimeout / 1000).toFixed(1)}s for ${fileCount} files, ${ruleCount} rules`);
180
+ }
181
+ return adaptiveTimeout;
182
+ }
183
+
184
+ /**
185
+ * Memory-aware rule batching
186
+ */
187
+ createRuleBatches(rules, config = {}) {
188
+ const fileCount = config.fileCount || 100;
189
+ const batchSize = config.ruleBatchSize || (fileCount > 100 ? 5 : 10);
190
+ const batches = [];
191
+
192
+ for (let i = 0; i < rules.length; i += batchSize) {
193
+ batches.push(rules.slice(i, i + batchSize));
194
+ }
195
+
196
+ if (this.config.verbose) {
197
+ console.log(`📦 Created ${batches.length} rule batches (${batchSize} rules each)`);
198
+ }
199
+ return batches;
200
+ }
201
+
202
+ /**
203
+ * Enhanced error recovery with context
204
+ */
205
+ handleAnalysisError(error, context = {}) {
206
+ const errorInfo = {
207
+ message: error.message,
208
+ shouldRetry: false,
209
+ retryWithReducedBatch: false,
210
+ context
211
+ };
212
+
213
+ // Determine if error is recoverable
214
+ if (error.message.includes('timeout') ||
215
+ error.message.includes('timed out') ||
216
+ error.message.includes('Maximum call stack size exceeded')) {
217
+ errorInfo.shouldRetry = true;
218
+ errorInfo.retryWithReducedBatch = true;
219
+ }
220
+
221
+ if (error.message.includes('ENOMEM') ||
222
+ error.message.includes('memory')) {
223
+ errorInfo.shouldRetry = true;
224
+ errorInfo.retryWithReducedBatch = true;
225
+ }
226
+
227
+ return errorInfo;
228
+ }
229
+
230
+ /**
231
+ * Execute operation with error recovery
232
+ */
233
+ async executeWithRecovery(operation, context = {}) {
234
+ const maxRetries = this.config.MAX_RETRIES || 2;
235
+ const retryDelay = this.config.RETRY_DELAY_MS || 1000;
236
+
237
+ for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
238
+ try {
239
+ return await operation();
240
+ } catch (error) {
241
+ if (attempt > maxRetries) {
242
+ throw error; // Final attempt failed
243
+ }
244
+
245
+ const errorInfo = this.handleAnalysisError(error, context);
246
+
247
+ if (!errorInfo.shouldRetry) {
248
+ throw error; // Not recoverable
249
+ }
250
+
251
+ if (this.config.verbose) {
252
+ console.warn(`⚠️ Attempt ${attempt} failed, retrying in ${retryDelay}ms...`);
253
+ }
254
+
255
+ // Wait before retry
256
+ await new Promise(resolve => setTimeout(resolve, retryDelay));
257
+ }
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Cleanup resources
263
+ */
264
+ async cleanup() {
265
+ // Perform any necessary cleanup
266
+ this.initialized = false;
267
+ this.config = {};
268
+ }
269
+ }
270
+
271
+ module.exports = PerformanceOptimizer;