@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.
- package/CHANGELOG.md +85 -0
- package/CONTRIBUTING.md +210 -1691
- package/README.md +5 -3
- package/config/rule-analysis-strategies.js +17 -1
- package/config/rules/enhanced-rules-registry.json +506 -1161
- package/config/rules/rules-registry-generated.json +1 -1
- package/core/analysis-orchestrator.js +167 -42
- package/core/auto-performance-manager.js +243 -0
- package/core/cli-action-handler.js +9 -1
- package/core/cli-program.js +19 -5
- package/core/constants/defaults.js +56 -0
- package/core/enhanced-rules-registry.js +2 -1
- package/core/performance-optimizer.js +271 -0
- package/core/semantic-engine.js +15 -3
- package/core/semantic-rule-base.js +4 -2
- package/docs/FILE_LIMITS_COMPLETION_REPORT.md +151 -0
- package/docs/FILE_LIMITS_EXPLANATION.md +190 -0
- package/docs/PERFORMANCE.md +311 -0
- package/docs/PERFORMANCE_MIGRATION_GUIDE.md +368 -0
- package/docs/PERFORMANCE_OPTIMIZATION_PLAN.md +255 -0
- package/docs/QUICK_FILE_LIMITS.md +64 -0
- package/docs/SIMPLIFIED_USAGE_GUIDE.md +208 -0
- package/engines/heuristic-engine.js +247 -9
- package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +59 -1
- package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +26 -1
- package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +54 -19
- package/origin-rules/common-en.md +11 -7
- package/package.json +2 -1
- package/rules/common/C002_no_duplicate_code/analyzer.js +334 -36
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +220 -35
- package/rules/common/C006_function_naming/analyzer.js +29 -3
- package/rules/common/C010_limit_block_nesting/analyzer.js +181 -337
- package/rules/common/C010_limit_block_nesting/config.json +64 -0
- package/rules/common/C010_limit_block_nesting/regex-based-analyzer.js +379 -0
- package/rules/common/C010_limit_block_nesting/symbol-based-analyzer.js +231 -0
- package/rules/common/C013_no_dead_code/analyzer.js +75 -177
- package/rules/common/C013_no_dead_code/config.json +61 -0
- package/rules/common/C013_no_dead_code/regex-based-analyzer.js +345 -0
- package/rules/common/C013_no_dead_code/symbol-based-analyzer.js +640 -0
- package/rules/common/C014_dependency_injection/analyzer.js +48 -313
- package/rules/common/C014_dependency_injection/config.json +26 -0
- package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +751 -0
- package/rules/common/C018_no_throw_generic_error/analyzer.js +232 -0
- package/rules/common/C018_no_throw_generic_error/config.json +50 -0
- package/rules/common/C018_no_throw_generic_error/regex-based-analyzer.js +387 -0
- package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +314 -0
- package/rules/common/C019_log_level_usage/analyzer.js +110 -317
- package/rules/common/C019_log_level_usage/pattern-analyzer.js +88 -0
- package/rules/common/C019_log_level_usage/system-log-analyzer.js +1267 -0
- package/rules/common/C023_no_duplicate_variable/analyzer.js +180 -0
- package/rules/common/C023_no_duplicate_variable/config.json +50 -0
- package/rules/common/C023_no_duplicate_variable/symbol-based-analyzer.js +158 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/analyzer.js +180 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/config.json +50 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +181 -0
- package/rules/common/C030_use_custom_error_classes/analyzer.js +200 -0
- package/rules/common/C035_error_logging_context/analyzer.js +3 -1
- package/rules/common/C048_no_bypass_architectural_layers/analyzer.js +180 -0
- package/rules/common/C048_no_bypass_architectural_layers/config.json +50 -0
- package/rules/common/C048_no_bypass_architectural_layers/symbol-based-analyzer.js +235 -0
- package/rules/common/C052_parsing_or_data_transformation/analyzer.js +180 -0
- package/rules/common/C052_parsing_or_data_transformation/config.json +50 -0
- package/rules/common/C052_parsing_or_data_transformation/symbol-based-analyzer.js +132 -0
- package/rules/index.js +7 -1
- package/rules/security/S009_no_insecure_encryption/README.md +158 -0
- package/rules/security/S009_no_insecure_encryption/analyzer.js +319 -0
- package/rules/security/S009_no_insecure_encryption/config.json +55 -0
- package/rules/security/S010_no_insecure_encryption/README.md +224 -0
- package/rules/security/S010_no_insecure_encryption/analyzer.js +493 -0
- package/rules/security/S010_no_insecure_encryption/config.json +48 -0
- package/rules/security/S016_no_sensitive_querystring/STRATEGY.md +149 -0
- package/rules/security/S016_no_sensitive_querystring/analyzer.js +276 -0
- package/rules/security/S016_no_sensitive_querystring/config.json +127 -0
- package/rules/security/S016_no_sensitive_querystring/regex-based-analyzer.js +258 -0
- package/rules/security/S016_no_sensitive_querystring/symbol-based-analyzer.js +495 -0
- package/rules/security/S017_use_parameterized_queries/README.md +128 -0
- package/rules/security/S017_use_parameterized_queries/analyzer.js +286 -0
- package/rules/security/S017_use_parameterized_queries/config.json +109 -0
- package/rules/security/S017_use_parameterized_queries/regex-based-analyzer.js +541 -0
- package/rules/security/S017_use_parameterized_queries/symbol-based-analyzer.js +777 -0
- package/rules/security/S031_secure_session_cookies/README.md +127 -0
- package/rules/security/S031_secure_session_cookies/analyzer.js +245 -0
- package/rules/security/S031_secure_session_cookies/config.json +86 -0
- package/rules/security/S031_secure_session_cookies/regex-based-analyzer.js +196 -0
- package/rules/security/S031_secure_session_cookies/symbol-based-analyzer.js +1084 -0
- package/rules/security/S032_httponly_session_cookies/FRAMEWORK_SUPPORT.md +209 -0
- package/rules/security/S032_httponly_session_cookies/README.md +184 -0
- package/rules/security/S032_httponly_session_cookies/analyzer.js +282 -0
- package/rules/security/S032_httponly_session_cookies/config.json +96 -0
- package/rules/security/S032_httponly_session_cookies/regex-based-analyzer.js +715 -0
- package/rules/security/S032_httponly_session_cookies/symbol-based-analyzer.js +1348 -0
- package/rules/security/S033_samesite_session_cookies/README.md +227 -0
- package/rules/security/S033_samesite_session_cookies/analyzer.js +242 -0
- package/rules/security/S033_samesite_session_cookies/config.json +87 -0
- package/rules/security/S033_samesite_session_cookies/regex-based-analyzer.js +703 -0
- package/rules/security/S033_samesite_session_cookies/symbol-based-analyzer.js +732 -0
- package/rules/security/S034_host_prefix_session_cookies/README.md +204 -0
- package/rules/security/S034_host_prefix_session_cookies/analyzer.js +290 -0
- package/rules/security/S034_host_prefix_session_cookies/config.json +62 -0
- package/rules/security/S034_host_prefix_session_cookies/regex-based-analyzer.js +478 -0
- package/rules/security/S034_host_prefix_session_cookies/symbol-based-analyzer.js +277 -0
- package/rules/security/S035_path_session_cookies/README.md +257 -0
- package/rules/security/S035_path_session_cookies/analyzer.js +316 -0
- package/rules/security/S035_path_session_cookies/config.json +99 -0
- package/rules/security/S035_path_session_cookies/regex-based-analyzer.js +724 -0
- package/rules/security/S035_path_session_cookies/symbol-based-analyzer.js +373 -0
- package/rules/security/S048_no_current_password_in_reset/README.md +222 -0
- package/rules/security/S048_no_current_password_in_reset/analyzer.js +366 -0
- package/rules/security/S048_no_current_password_in_reset/config.json +48 -0
- package/rules/security/S055_content_type_validation/README.md +176 -0
- package/rules/security/S055_content_type_validation/analyzer.js +312 -0
- package/rules/security/S055_content_type_validation/config.json +48 -0
- package/rules/utils/rule-helpers.js +140 -1
- package/scripts/batch-processing-demo.js +334 -0
- package/scripts/consolidate-config.js +116 -0
- package/scripts/performance-test.js +541 -0
- package/scripts/quick-performance-test.js +108 -0
- package/config/rules/S027-categories.json +0 -122
- package/config/rules/rules-registry.json +0 -777
- 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": "
|
|
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(
|
|
198
|
+
const engineGroups = this.groupRulesByEngine(optimizedRules, config);
|
|
176
199
|
|
|
177
200
|
if (!options.quiet) {
|
|
178
|
-
console.log(chalk.cyan(
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const
|
|
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
|
-
|
|
210
|
-
|
|
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
|
-
|
|
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
|
|
405
|
-
|
|
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
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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;
|
package/core/cli-program.js
CHANGED
|
@@ -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>', '
|
|
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
|
|
68
|
-
.option('--
|
|
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
|