@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
|
@@ -12,12 +12,13 @@ const SunlintRuleAdapter = require('../core/adapters/sunlint-rule-adapter');
|
|
|
12
12
|
const SemanticEngine = require('../core/semantic-engine');
|
|
13
13
|
const SemanticRuleBase = require('../core/semantic-rule-base');
|
|
14
14
|
const { getInstance: getUnifiedRegistry } = require('../core/unified-rule-registry');
|
|
15
|
+
const AutoPerformanceManager = require('../core/auto-performance-manager');
|
|
15
16
|
const fs = require('fs');
|
|
16
17
|
const path = require('path');
|
|
17
18
|
|
|
18
19
|
class HeuristicEngine extends AnalysisEngineInterface {
|
|
19
20
|
constructor() {
|
|
20
|
-
super('heuristic', '
|
|
21
|
+
super('heuristic', '4.0', ['typescript', 'javascript', 'dart', 'swift', 'kotlin', 'java', 'python', 'go', 'rust', 'all']);
|
|
21
22
|
|
|
22
23
|
this.ruleAnalyzers = new Map();
|
|
23
24
|
this.supportedRulesList = [];
|
|
@@ -32,18 +33,38 @@ class HeuristicEngine extends AnalysisEngineInterface {
|
|
|
32
33
|
|
|
33
34
|
// Unified rule registry
|
|
34
35
|
this.unifiedRegistry = getUnifiedRegistry();
|
|
36
|
+
|
|
37
|
+
// ✅ PERFORMANCE OPTIMIZATIONS (Integrated)
|
|
38
|
+
this.performanceManager = new AutoPerformanceManager();
|
|
39
|
+
this.performanceConfig = null;
|
|
40
|
+
this.metrics = {
|
|
41
|
+
startTime: null,
|
|
42
|
+
filesProcessed: 0,
|
|
43
|
+
rulesProcessed: 0,
|
|
44
|
+
violationsFound: 0,
|
|
45
|
+
memoryUsage: 0
|
|
46
|
+
};
|
|
35
47
|
}
|
|
36
48
|
|
|
37
49
|
/**
|
|
38
50
|
* Initialize Heuristic engine with ts-morph core and configuration
|
|
51
|
+
* ✅ ENHANCED: Now includes performance optimization
|
|
39
52
|
* Following Rule C006: Verb-noun naming
|
|
40
53
|
* @param {Object} config - Engine configuration
|
|
41
54
|
*/
|
|
42
55
|
async initialize(config) {
|
|
43
56
|
try {
|
|
57
|
+
// ✅ PERFORMANCE: Get optimal settings based on project
|
|
58
|
+
this.performanceConfig = this.performanceManager.getOptimalSettings(config, config?.targetFiles || []);
|
|
59
|
+
|
|
44
60
|
// Store verbosity setting
|
|
45
61
|
this.verbose = config?.verbose || false;
|
|
46
62
|
|
|
63
|
+
if (this.verbose && this.performanceConfig.autoDetected) {
|
|
64
|
+
console.log(`🤖 [HeuristicEngine] Auto-detected performance profile: ${this.performanceConfig.name}`);
|
|
65
|
+
console.log(` ⚡ Settings: ${this.performanceConfig.timeout/1000}s timeout, ${this.performanceConfig.batchSize || 'auto'} batch size`);
|
|
66
|
+
}
|
|
67
|
+
|
|
47
68
|
// Initialize unified rule registry
|
|
48
69
|
await this.unifiedRegistry.initialize({ verbose: this.verbose });
|
|
49
70
|
|
|
@@ -56,15 +77,21 @@ class HeuristicEngine extends AnalysisEngineInterface {
|
|
|
56
77
|
// Initialize rule adapter
|
|
57
78
|
await this.ruleAdapter.initialize();
|
|
58
79
|
|
|
59
|
-
// Load available rules from unified registry
|
|
60
|
-
|
|
80
|
+
// Load available rules from unified registry (OPTIMIZED: skip for performance)
|
|
81
|
+
// Rules will be loaded on-demand in analyze() method
|
|
82
|
+
if (config.loadAllRules) {
|
|
83
|
+
await this.loadRulesFromRegistry(config);
|
|
84
|
+
} else if (this.verbose) {
|
|
85
|
+
console.log(`⚡ [HeuristicEngine] Skipping bulk rule loading for performance - will load on-demand`);
|
|
86
|
+
}
|
|
61
87
|
|
|
62
88
|
this.initialized = true;
|
|
63
89
|
if (this.verbose) {
|
|
64
|
-
console.log(`🔍 Heuristic engine
|
|
90
|
+
console.log(`🔍 Heuristic engine v4.0 initialized:`);
|
|
65
91
|
console.log(` 📊 Total rules: ${this.supportedRulesList.length}`);
|
|
66
92
|
console.log(` 🧠 Symbol Table: ${this.symbolTableInitialized ? 'enabled' : 'disabled'}`);
|
|
67
93
|
console.log(` 🔧 Semantic rules: ${this.semanticRules.size}`);
|
|
94
|
+
console.log(` ⚡ Performance: ${this.performanceConfig.name || 'standard'}`);
|
|
68
95
|
}
|
|
69
96
|
|
|
70
97
|
} catch (error) {
|
|
@@ -89,6 +116,8 @@ class HeuristicEngine extends AnalysisEngineInterface {
|
|
|
89
116
|
};
|
|
90
117
|
|
|
91
118
|
this.semanticEngine = new SemanticEngine(semanticOptions);
|
|
119
|
+
// Pass verbose option to semantic engine
|
|
120
|
+
this.semanticEngine.verbose = this.verbose;
|
|
92
121
|
|
|
93
122
|
// ts-morph is now a core dependency - but optimized for targeted files
|
|
94
123
|
const success = await this.semanticEngine.initialize(projectPath, config?.targetFiles);
|
|
@@ -287,6 +316,43 @@ class HeuristicEngine extends AnalysisEngineInterface {
|
|
|
287
316
|
}
|
|
288
317
|
}
|
|
289
318
|
|
|
319
|
+
/**
|
|
320
|
+
* Lazy load a single rule on-demand
|
|
321
|
+
* @param {string} ruleId - Rule ID to load
|
|
322
|
+
* @param {Object} options - Loading options
|
|
323
|
+
*/
|
|
324
|
+
async lazyLoadRule(ruleId, options = {}) {
|
|
325
|
+
try {
|
|
326
|
+
const ruleDefinition = this.unifiedRegistry.getRuleDefinition(ruleId);
|
|
327
|
+
|
|
328
|
+
if (!ruleDefinition) {
|
|
329
|
+
if (options.verbose) {
|
|
330
|
+
console.warn(`⚠️ [HeuristicEngine] Rule definition not found for ${ruleId}`);
|
|
331
|
+
}
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Check if rule supports heuristic engine
|
|
336
|
+
if (!this.unifiedRegistry.isRuleSupported(ruleId, 'heuristic')) {
|
|
337
|
+
if (options.verbose) {
|
|
338
|
+
console.warn(`⚠️ [HeuristicEngine] Rule ${ruleId} not supported by heuristic engine`);
|
|
339
|
+
}
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (options.verbose) {
|
|
344
|
+
console.log(`🔄 [HeuristicEngine] Lazy loading rule ${ruleId}...`);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
await this.loadRuleFromDefinition(ruleDefinition);
|
|
348
|
+
|
|
349
|
+
} catch (error) {
|
|
350
|
+
if (options.verbose) {
|
|
351
|
+
console.warn(`⚠️ [HeuristicEngine] Failed to lazy load rule ${ruleId}:`, error.message);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
290
356
|
/**
|
|
291
357
|
* Manually load C047 semantic rule (special case)
|
|
292
358
|
*/
|
|
@@ -453,7 +519,7 @@ class HeuristicEngine extends AnalysisEngineInterface {
|
|
|
453
519
|
|
|
454
520
|
try {
|
|
455
521
|
const instance = new ruleEntry.analyzerClass(ruleId);
|
|
456
|
-
instance.initialize(this.semanticEngine);
|
|
522
|
+
instance.initialize(this.semanticEngine, { verbose: this.verbose });
|
|
457
523
|
|
|
458
524
|
// Update entry with initialized instance
|
|
459
525
|
ruleEntry.instance = instance;
|
|
@@ -563,6 +629,7 @@ class HeuristicEngine extends AnalysisEngineInterface {
|
|
|
563
629
|
|
|
564
630
|
/**
|
|
565
631
|
* Analyze files using heuristic patterns
|
|
632
|
+
* ✅ ENHANCED: Now includes performance optimizations and batch processing
|
|
566
633
|
* Following Rule C006: Verb-noun naming
|
|
567
634
|
* @param {string[]} files - Files to analyze
|
|
568
635
|
* @param {Object[]} rules - Rules to apply
|
|
@@ -574,12 +641,166 @@ class HeuristicEngine extends AnalysisEngineInterface {
|
|
|
574
641
|
throw new Error('Heuristic engine not initialized');
|
|
575
642
|
}
|
|
576
643
|
|
|
644
|
+
// ✅ PERFORMANCE: Apply file limits and timeout protection
|
|
645
|
+
const startTime = Date.now();
|
|
646
|
+
this.metrics.startTime = startTime;
|
|
647
|
+
|
|
648
|
+
// Apply analysis file limits (different from semantic file limits)
|
|
649
|
+
const maxFiles = this.getAnalysisFileLimit(options);
|
|
650
|
+
const limitedFiles = files.slice(0, maxFiles);
|
|
651
|
+
|
|
652
|
+
if (files.length > maxFiles && this.verbose) {
|
|
653
|
+
console.warn(`⚠️ [HeuristicEngine] Analysis file limit: ${limitedFiles.length}/${files.length} files`);
|
|
654
|
+
console.log(` 💡 Note: Symbol table uses separate limit (--max-semantic-files)`);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Set up timeout if configured
|
|
658
|
+
const timeout = this.performanceConfig?.timeout || parseInt(options.timeout) || 0;
|
|
659
|
+
let timeoutId = null;
|
|
660
|
+
|
|
661
|
+
if (timeout > 0) {
|
|
662
|
+
timeoutId = setTimeout(() => {
|
|
663
|
+
throw new Error(`Analysis timeout after ${timeout}ms`);
|
|
664
|
+
}, timeout);
|
|
665
|
+
}
|
|
666
|
+
|
|
577
667
|
if (options.verbose) {
|
|
578
|
-
console.log(`🔍 [HeuristicEngine] Analyzing ${
|
|
579
|
-
|
|
580
|
-
|
|
668
|
+
console.log(`🔍 [HeuristicEngine] Analyzing ${limitedFiles.length} files with ${rules.length} rules`);
|
|
669
|
+
if (this.performanceConfig?.name) {
|
|
670
|
+
console.log(`⚡ [Performance] Using ${this.performanceConfig.name} profile`);
|
|
671
|
+
}
|
|
672
|
+
if (timeout > 0) {
|
|
673
|
+
console.log(`⏰ [Timeout] Analysis will timeout after ${timeout/1000}s`);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
try {
|
|
678
|
+
// Check if we should use batch processing
|
|
679
|
+
if (this.shouldUseBatchProcessing(limitedFiles, rules)) {
|
|
680
|
+
return await this.analyzeBatched(limitedFiles, rules, options);
|
|
681
|
+
} else {
|
|
682
|
+
return await this.analyzeStandard(limitedFiles, rules, options);
|
|
683
|
+
}
|
|
684
|
+
} finally {
|
|
685
|
+
// Clear timeout
|
|
686
|
+
if (timeoutId) {
|
|
687
|
+
clearTimeout(timeoutId);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// Log performance metrics
|
|
691
|
+
const duration = Date.now() - startTime;
|
|
692
|
+
this.metrics.filesProcessed = limitedFiles.length;
|
|
693
|
+
this.metrics.rulesProcessed = rules.length;
|
|
694
|
+
|
|
695
|
+
if (options.verbose) {
|
|
696
|
+
console.log(`✅ [HeuristicEngine] Analysis completed in ${duration}ms`);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* ✅ NEW: Get analysis file limit (separate from semantic file limit)
|
|
703
|
+
*/
|
|
704
|
+
getAnalysisFileLimit(options) {
|
|
705
|
+
// User-specified limit
|
|
706
|
+
if (options.maxFiles && parseInt(options.maxFiles) > 0) {
|
|
707
|
+
return parseInt(options.maxFiles);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Performance config limit
|
|
711
|
+
if (this.performanceConfig?.maxFiles) {
|
|
712
|
+
return this.performanceConfig.maxFiles;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// Default based on performance mode
|
|
716
|
+
const mode = options.performance || 'auto';
|
|
717
|
+
const defaults = {
|
|
718
|
+
fast: 500,
|
|
719
|
+
auto: 1000,
|
|
720
|
+
careful: 1500
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
return defaults[mode] || 1000;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* ✅ NEW: Determine if batch processing should be used
|
|
728
|
+
*/
|
|
729
|
+
shouldUseBatchProcessing(files, rules) {
|
|
730
|
+
const batchThreshold = this.performanceConfig?.batchThreshold || 100;
|
|
731
|
+
const totalWorkload = files.length * rules.length;
|
|
732
|
+
|
|
733
|
+
return totalWorkload > batchThreshold ||
|
|
734
|
+
files.length > 200 ||
|
|
735
|
+
rules.length > 30;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* ✅ NEW: Batch processing for large workloads
|
|
740
|
+
*/
|
|
741
|
+
async analyzeBatched(files, rules, options) {
|
|
742
|
+
if (options.verbose) {
|
|
743
|
+
console.log(`� [HeuristicEngine] Using batch processing for large workload`);
|
|
581
744
|
}
|
|
582
745
|
|
|
746
|
+
const results = {
|
|
747
|
+
results: [],
|
|
748
|
+
filesAnalyzed: files.length,
|
|
749
|
+
engine: 'heuristic',
|
|
750
|
+
metadata: {
|
|
751
|
+
rulesAnalyzed: rules.map(r => r.id),
|
|
752
|
+
analyzersUsed: [],
|
|
753
|
+
batchProcessing: true
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
// Create rule batches
|
|
758
|
+
const batchSize = this.performanceConfig?.batchSize || 10;
|
|
759
|
+
const ruleBatches = [];
|
|
760
|
+
|
|
761
|
+
for (let i = 0; i < rules.length; i += batchSize) {
|
|
762
|
+
ruleBatches.push(rules.slice(i, i + batchSize));
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
if (options.verbose) {
|
|
766
|
+
console.log(`📦 [Batch] Processing ${ruleBatches.length} rule batches (${batchSize} rules each)`);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// Process each batch
|
|
770
|
+
for (let batchIndex = 0; batchIndex < ruleBatches.length; batchIndex++) {
|
|
771
|
+
const ruleBatch = ruleBatches[batchIndex];
|
|
772
|
+
|
|
773
|
+
if (options.verbose) {
|
|
774
|
+
console.log(`⚡ [Batch ${batchIndex + 1}/${ruleBatches.length}] Processing ${ruleBatch.length} rules...`);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
const batchResults = await this.analyzeStandard(files, ruleBatch, options);
|
|
778
|
+
|
|
779
|
+
// Merge batch results
|
|
780
|
+
for (const fileResult of batchResults.results) {
|
|
781
|
+
let existingFile = results.results.find(r => r.file === fileResult.file);
|
|
782
|
+
if (!existingFile) {
|
|
783
|
+
existingFile = { file: fileResult.file, violations: [] };
|
|
784
|
+
results.results.push(existingFile);
|
|
785
|
+
}
|
|
786
|
+
existingFile.violations.push(...fileResult.violations);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
results.metadata.analyzersUsed.push(...batchResults.metadata.analyzersUsed);
|
|
790
|
+
|
|
791
|
+
// Memory management
|
|
792
|
+
if (batchIndex % 3 === 0 && global.gc) {
|
|
793
|
+
global.gc(); // Trigger garbage collection every 3 batches
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
return results;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* ✅ REFACTORED: Standard analysis method (extracted from original analyze)
|
|
802
|
+
*/
|
|
803
|
+
async analyzeStandard(files, rules, options) {
|
|
583
804
|
const results = {
|
|
584
805
|
results: [],
|
|
585
806
|
filesAnalyzed: files.length,
|
|
@@ -602,6 +823,14 @@ class HeuristicEngine extends AnalysisEngineInterface {
|
|
|
602
823
|
await this.manuallyLoadC047();
|
|
603
824
|
}
|
|
604
825
|
|
|
826
|
+
// Lazy load rule if not already loaded
|
|
827
|
+
if (!this.isRuleSupported(rule.id)) {
|
|
828
|
+
if (options.verbose) {
|
|
829
|
+
console.log(`🔄 [HeuristicEngine] Lazy loading rule ${rule.id}...`);
|
|
830
|
+
}
|
|
831
|
+
await this.lazyLoadRule(rule.id, options);
|
|
832
|
+
}
|
|
833
|
+
|
|
605
834
|
if (!this.isRuleSupported(rule.id)) {
|
|
606
835
|
if (options.verbose) {
|
|
607
836
|
console.warn(`⚠️ Rule ${rule.id} not supported by Heuristic engine, skipping...`);
|
|
@@ -722,6 +951,15 @@ class HeuristicEngine extends AnalysisEngineInterface {
|
|
|
722
951
|
async analyzeRule(rule, filesByLanguage, options) {
|
|
723
952
|
// Get full rule ID (C029 -> C029_catch_block_logging)
|
|
724
953
|
const fullRuleId = this.getFullRuleId(rule.id);
|
|
954
|
+
|
|
955
|
+
// Lazy load rule if not already loaded
|
|
956
|
+
if (!this.ruleAnalyzers.has(fullRuleId)) {
|
|
957
|
+
if (options.verbose) {
|
|
958
|
+
console.log(`🔄 [HeuristicEngine] Lazy loading rule ${rule.id} for analysis...`);
|
|
959
|
+
}
|
|
960
|
+
await this.lazyLoadRule(rule.id, options);
|
|
961
|
+
}
|
|
962
|
+
|
|
725
963
|
const analyzerInfo = this.ruleAnalyzers.get(fullRuleId);
|
|
726
964
|
|
|
727
965
|
if (!analyzerInfo) {
|
|
@@ -788,7 +1026,7 @@ class HeuristicEngine extends AnalysisEngineInterface {
|
|
|
788
1026
|
rule.id,
|
|
789
1027
|
languageFiles,
|
|
790
1028
|
language,
|
|
791
|
-
{ ...ruleConfig, ...options }
|
|
1029
|
+
{ ...ruleConfig, ...options, semanticEngine: this.semanticEngine }
|
|
792
1030
|
);
|
|
793
1031
|
|
|
794
1032
|
allViolations.push(...languageViolations);
|
|
@@ -92,6 +92,59 @@ const c003Rule = {
|
|
|
92
92
|
return false;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
function isMathContext(node, name) {
|
|
96
|
+
// Check for math variable patterns
|
|
97
|
+
const mathPatterns = [
|
|
98
|
+
// Coordinate pairs: x1, y1, x2, y2
|
|
99
|
+
/^[xyz][12]$/i,
|
|
100
|
+
// Delta notation: dx, dy, dt, dr
|
|
101
|
+
/^d[xyztr]$/i,
|
|
102
|
+
// Math constants: a, b, c in equations
|
|
103
|
+
/^[abc]$/i,
|
|
104
|
+
// Vector components: vx, vy, vz
|
|
105
|
+
/^v[xyz]$/i,
|
|
106
|
+
// Position/point notation: p1, p2
|
|
107
|
+
/^p\d+$/i
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
if (mathPatterns.some(pattern => pattern.test(name))) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Check if we're in a math function context
|
|
115
|
+
let parent = node.parent;
|
|
116
|
+
while (parent) {
|
|
117
|
+
if (parent.type === 'FunctionDeclaration' || parent.type === 'FunctionExpression' || parent.type === 'ArrowFunctionExpression') {
|
|
118
|
+
const functionName = parent.id && parent.id.name;
|
|
119
|
+
if (functionName && /^(distance|calculate|compute|solve|formula|algorithm|equation|math)/i.test(functionName)) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
break; // Don't check beyond the immediate function
|
|
123
|
+
}
|
|
124
|
+
parent = parent.parent;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Check if we're in a context with Math operations
|
|
128
|
+
let currentNode = node.parent;
|
|
129
|
+
while (currentNode) {
|
|
130
|
+
if (currentNode.type === 'CallExpression') {
|
|
131
|
+
const callee = currentNode.callee;
|
|
132
|
+
if (callee && callee.object && callee.object.name === 'Math') {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
if (callee && callee.name && /^(sqrt|pow|abs|sin|cos|tan|distance|calculate)$/i.test(callee.name)) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (currentNode.type === 'BinaryExpression' && ['+', '-', '*', '/'].includes(currentNode.operator)) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
currentNode = currentNode.parent;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
95
148
|
function checkVariableName(node, name) {
|
|
96
149
|
// Safety checks
|
|
97
150
|
if (!node || !name || typeof name !== 'string') {
|
|
@@ -112,7 +165,7 @@ const c003Rule = {
|
|
|
112
165
|
|
|
113
166
|
// Single character check
|
|
114
167
|
if (name.length === 1) {
|
|
115
|
-
if (!allowedSingleChar.has(name.toLowerCase()) && !isCounterContext(node)) {
|
|
168
|
+
if (!allowedSingleChar.has(name.toLowerCase()) && !isCounterContext(node) && !isMathContext(node, name)) {
|
|
116
169
|
context.report({
|
|
117
170
|
node,
|
|
118
171
|
messageId: "singleChar",
|
|
@@ -137,6 +190,11 @@ const c003Rule = {
|
|
|
137
190
|
return;
|
|
138
191
|
}
|
|
139
192
|
|
|
193
|
+
// Check for math context before flagging as unclear
|
|
194
|
+
if (isMathContext(node, name)) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
140
198
|
// Check for unclear/generic names
|
|
141
199
|
if (unclearNames.has(name.toLowerCase())) {
|
|
142
200
|
context.report({
|
|
@@ -79,6 +79,22 @@ const c006Rule = {
|
|
|
79
79
|
...commonVerbPrefixes,
|
|
80
80
|
...(options.allowedVerbs || [])
|
|
81
81
|
]);
|
|
82
|
+
|
|
83
|
+
// Generic/vague verbs that should be flagged even if they are technically verbs
|
|
84
|
+
const genericVerbs = new Set([
|
|
85
|
+
'do', 'handle', 'process', 'manage', 'execute', 'work', 'stuff', 'thing', 'data'
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
function isGenericVerbUsage(name) {
|
|
89
|
+
// Check if the function name is exactly a generic verb or starts with generic verb + something generic
|
|
90
|
+
const genericPatterns = [
|
|
91
|
+
/^(do|handle|process|manage|execute)(Something|Stuff|Data|Info|Work|Thing|Items|Objects?)$/i,
|
|
92
|
+
/^(do|handle|process|manage|execute)$/i,
|
|
93
|
+
/^(do|handle|process|manage|execute)[A-Z].*$/i // Any pattern starting with generic verb + capital letter
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
return genericPatterns.some(pattern => pattern.test(name));
|
|
97
|
+
}
|
|
82
98
|
|
|
83
99
|
const allowConstructors = options.allowConstructors !== false;
|
|
84
100
|
|
|
@@ -142,7 +158,16 @@ const c006Rule = {
|
|
|
142
158
|
|
|
143
159
|
// Check if it follows verb-noun pattern
|
|
144
160
|
if (isVerbNounPattern(name)) {
|
|
145
|
-
|
|
161
|
+
// But still check if it's using generic verbs that should be flagged
|
|
162
|
+
if (isGenericVerbUsage(name)) {
|
|
163
|
+
context.report({
|
|
164
|
+
node,
|
|
165
|
+
messageId: "notVerbNoun",
|
|
166
|
+
data: { name }
|
|
167
|
+
});
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
return; // Good! Follows the pattern and not generic
|
|
146
171
|
}
|
|
147
172
|
|
|
148
173
|
// Check if it's likely a noun-only name
|
|
@@ -52,6 +52,10 @@ const c030Rule = {
|
|
|
52
52
|
messages: {
|
|
53
53
|
useCustomError: "Use custom error class instead of generic 'Error'. Consider using specific error types like ValidationError, NotFoundError, BusinessRuleError, etc. Vietnamese: 'Dùng custom error class thay vì Error generic'",
|
|
54
54
|
useSpecificBuiltin: "Consider using a more specific built-in error type like TypeError, RangeError, or a custom error class. Vietnamese: 'Cân nhắc dùng built-in error cụ thể hơn hoặc custom error class'",
|
|
55
|
+
throwStringLiteral: "Use custom error classes instead of throwing string literals",
|
|
56
|
+
throwTemplateLiteral: "Use custom error classes instead of throwing template literals",
|
|
57
|
+
throwNumber: "Use custom error classes instead of throwing numbers",
|
|
58
|
+
throwVariable: "Use custom error classes instead of throwing variables",
|
|
55
59
|
missingErrorCode: "Custom error class should include an error code property. Vietnamese: 'Custom error class nên có thuộc tính error code'",
|
|
56
60
|
missingStatusCode: "HTTP-related error class should include a status code property. Vietnamese: 'Error class liên quan HTTP nên có thuộc tính status code'",
|
|
57
61
|
preferCustomError: "Prefer custom error classes for better error categorization and handling. Vietnamese: 'Ưu tiên custom error classes để phân loại và xử lý lỗi tốt hơn'"
|
|
@@ -223,35 +227,66 @@ const c030Rule = {
|
|
|
223
227
|
// Skip rethrow statements if allowed
|
|
224
228
|
if (isRethrowStatement(node)) return;
|
|
225
229
|
|
|
226
|
-
|
|
230
|
+
// Handle different throw argument types
|
|
231
|
+
if (node.argument) {
|
|
232
|
+
// Check for new Error(...) constructors
|
|
233
|
+
if (node.argument.type === 'NewExpression' &&
|
|
234
|
+
node.argument.callee &&
|
|
235
|
+
node.argument.callee.name === 'Error') {
|
|
236
|
+
context.report({
|
|
237
|
+
node: node.argument,
|
|
238
|
+
messageId: "useCustomError"
|
|
239
|
+
});
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
227
242
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
243
|
+
// Check for other built-in error constructors
|
|
244
|
+
if (node.argument.type === 'NewExpression' &&
|
|
245
|
+
node.argument.callee &&
|
|
246
|
+
allowedBuiltinErrors.has(node.argument.callee.name)) {
|
|
247
|
+
if (['TypeError', 'RangeError'].includes(node.argument.callee.name)) {
|
|
248
|
+
context.report({
|
|
249
|
+
node: node.argument,
|
|
250
|
+
messageId: "useSpecificBuiltin"
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
236
255
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if (!isCustomErrorClass(errorClassName) && !allowedBuiltinErrors.has(errorClassName)) {
|
|
256
|
+
// Check for throwing string literals
|
|
257
|
+
if (node.argument.type === 'Literal' && typeof node.argument.value === 'string') {
|
|
240
258
|
context.report({
|
|
241
259
|
node: node.argument,
|
|
242
|
-
messageId: "
|
|
260
|
+
messageId: "throwStringLiteral"
|
|
243
261
|
});
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Check for throwing template literals
|
|
266
|
+
if (node.argument.type === 'TemplateLiteral') {
|
|
267
|
+
context.report({
|
|
268
|
+
node: node.argument,
|
|
269
|
+
messageId: "throwTemplateLiteral"
|
|
270
|
+
});
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Check for throwing numbers
|
|
275
|
+
if (node.argument.type === 'Literal' && typeof node.argument.value === 'number') {
|
|
276
|
+
context.report({
|
|
277
|
+
node: node.argument,
|
|
278
|
+
messageId: "throwNumber"
|
|
279
|
+
});
|
|
280
|
+
return;
|
|
244
281
|
}
|
|
245
|
-
}
|
|
246
282
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
// Only suggest if it's a generic built-in error
|
|
250
|
-
if (['TypeError', 'RangeError'].includes(errorClassName)) {
|
|
283
|
+
// Check for throwing variables (identifiers)
|
|
284
|
+
if (node.argument.type === 'Identifier') {
|
|
251
285
|
context.report({
|
|
252
286
|
node: node.argument,
|
|
253
|
-
messageId: "
|
|
287
|
+
messageId: "throwVariable"
|
|
254
288
|
});
|
|
289
|
+
return;
|
|
255
290
|
}
|
|
256
291
|
}
|
|
257
292
|
},
|
|
@@ -269,13 +269,17 @@
|
|
|
269
269
|
|
|
270
270
|
### 📘 Rule C019 – Do not use `error` log level for non-critical issues
|
|
271
271
|
|
|
272
|
-
- **Objective**:
|
|
273
|
-
- **Details**:
|
|
274
|
-
-
|
|
275
|
-
-
|
|
276
|
-
- Use `
|
|
277
|
-
- Use `
|
|
278
|
-
-
|
|
272
|
+
- **Objective**: Prevent noisy logs and false alarms; ensure consistent and meaningful log levels across the system.
|
|
273
|
+
- **Details**:
|
|
274
|
+
- Reserve `error` for critical failures that require immediate attention or system intervention.
|
|
275
|
+
- Use `warn` for potential issues that may affect functionality but don’t crash the system (e.g., retryable errors).
|
|
276
|
+
- Use `info` for normal business events (e.g., login, order success, expected validation failures).
|
|
277
|
+
- Use `debug` for detailed troubleshooting information; avoid excessive debug logs in production.
|
|
278
|
+
- Avoid using `error` for:
|
|
279
|
+
- Expected business cases (e.g., wrong password, expired card).
|
|
280
|
+
- Normal validation failures.
|
|
281
|
+
- Temporary, recoverable conditions (e.g., network retry).
|
|
282
|
+
- Additional goal: Ensure **logs exist at the right places with the right severity level**, avoiding both over-logging and missing critical logs.
|
|
279
283
|
- **Applies to**: All languages
|
|
280
284
|
- **Tools**: Log linter / Custom rule
|
|
281
285
|
- **Principles**: CODE_QUALITY
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sun-asterisk/sunlint",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
4
4
|
"description": "☀️ SunLint - Multi-language static analysis tool for code quality and security | Sun* Engineering Standards",
|
|
5
5
|
"main": "cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"test:integration": "echo 'Temporarily disabled - config extension issue'",
|
|
23
23
|
"test:realworld": "node examples/integration-tests/realworld-integration-test.js",
|
|
24
24
|
"test:cli": "node examples/integration-tests/direct-cli-test.js",
|
|
25
|
+
"test:performance": "node test/performance-test.js",
|
|
25
26
|
"test:c019": "node cli.js --rule=C019 --input=examples/test-fixtures --format=eslint",
|
|
26
27
|
"test:c006": "node cli.js --rule=C006 --input=examples/test-fixtures --format=eslint",
|
|
27
28
|
"test:c029": "node cli.js --rule=C029 --input=examples/test-fixtures --format=eslint",
|