@sun-asterisk/sunlint 1.3.1 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/CONTRIBUTING.md +210 -1691
  3. package/README.md +5 -3
  4. package/config/rule-analysis-strategies.js +17 -1
  5. package/config/rules/enhanced-rules-registry.json +506 -1161
  6. package/config/rules/rules-registry-generated.json +1 -1
  7. package/core/analysis-orchestrator.js +167 -42
  8. package/core/auto-performance-manager.js +243 -0
  9. package/core/cli-action-handler.js +9 -1
  10. package/core/cli-program.js +19 -5
  11. package/core/constants/defaults.js +56 -0
  12. package/core/enhanced-rules-registry.js +2 -1
  13. package/core/performance-optimizer.js +271 -0
  14. package/core/semantic-engine.js +15 -3
  15. package/core/semantic-rule-base.js +4 -2
  16. package/docs/FILE_LIMITS_COMPLETION_REPORT.md +151 -0
  17. package/docs/FILE_LIMITS_EXPLANATION.md +190 -0
  18. package/docs/PERFORMANCE.md +311 -0
  19. package/docs/PERFORMANCE_MIGRATION_GUIDE.md +368 -0
  20. package/docs/PERFORMANCE_OPTIMIZATION_PLAN.md +255 -0
  21. package/docs/QUICK_FILE_LIMITS.md +64 -0
  22. package/docs/SIMPLIFIED_USAGE_GUIDE.md +208 -0
  23. package/engines/heuristic-engine.js +247 -9
  24. package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +59 -1
  25. package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +26 -1
  26. package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +54 -19
  27. package/origin-rules/common-en.md +11 -7
  28. package/package.json +2 -1
  29. package/rules/common/C002_no_duplicate_code/analyzer.js +334 -36
  30. package/rules/common/C003_no_vague_abbreviations/analyzer.js +220 -35
  31. package/rules/common/C006_function_naming/analyzer.js +29 -3
  32. package/rules/common/C010_limit_block_nesting/analyzer.js +181 -337
  33. package/rules/common/C010_limit_block_nesting/config.json +64 -0
  34. package/rules/common/C010_limit_block_nesting/regex-based-analyzer.js +379 -0
  35. package/rules/common/C010_limit_block_nesting/symbol-based-analyzer.js +231 -0
  36. package/rules/common/C013_no_dead_code/analyzer.js +75 -177
  37. package/rules/common/C013_no_dead_code/config.json +61 -0
  38. package/rules/common/C013_no_dead_code/regex-based-analyzer.js +345 -0
  39. package/rules/common/C013_no_dead_code/symbol-based-analyzer.js +640 -0
  40. package/rules/common/C014_dependency_injection/analyzer.js +48 -313
  41. package/rules/common/C014_dependency_injection/config.json +26 -0
  42. package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +751 -0
  43. package/rules/common/C018_no_throw_generic_error/analyzer.js +232 -0
  44. package/rules/common/C018_no_throw_generic_error/config.json +50 -0
  45. package/rules/common/C018_no_throw_generic_error/regex-based-analyzer.js +387 -0
  46. package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +314 -0
  47. package/rules/common/C019_log_level_usage/analyzer.js +110 -317
  48. package/rules/common/C019_log_level_usage/pattern-analyzer.js +88 -0
  49. package/rules/common/C019_log_level_usage/system-log-analyzer.js +1267 -0
  50. package/rules/common/C023_no_duplicate_variable/analyzer.js +180 -0
  51. package/rules/common/C023_no_duplicate_variable/config.json +50 -0
  52. package/rules/common/C023_no_duplicate_variable/symbol-based-analyzer.js +158 -0
  53. package/rules/common/C024_no_scatter_hardcoded_constants/analyzer.js +180 -0
  54. package/rules/common/C024_no_scatter_hardcoded_constants/config.json +50 -0
  55. package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +181 -0
  56. package/rules/common/C030_use_custom_error_classes/analyzer.js +200 -0
  57. package/rules/common/C035_error_logging_context/analyzer.js +3 -1
  58. package/rules/common/C048_no_bypass_architectural_layers/analyzer.js +180 -0
  59. package/rules/common/C048_no_bypass_architectural_layers/config.json +50 -0
  60. package/rules/common/C048_no_bypass_architectural_layers/symbol-based-analyzer.js +235 -0
  61. package/rules/common/C052_parsing_or_data_transformation/analyzer.js +180 -0
  62. package/rules/common/C052_parsing_or_data_transformation/config.json +50 -0
  63. package/rules/common/C052_parsing_or_data_transformation/symbol-based-analyzer.js +132 -0
  64. package/rules/index.js +7 -1
  65. package/rules/security/S009_no_insecure_encryption/README.md +158 -0
  66. package/rules/security/S009_no_insecure_encryption/analyzer.js +319 -0
  67. package/rules/security/S009_no_insecure_encryption/config.json +55 -0
  68. package/rules/security/S010_no_insecure_encryption/README.md +224 -0
  69. package/rules/security/S010_no_insecure_encryption/analyzer.js +493 -0
  70. package/rules/security/S010_no_insecure_encryption/config.json +48 -0
  71. package/rules/security/S016_no_sensitive_querystring/STRATEGY.md +149 -0
  72. package/rules/security/S016_no_sensitive_querystring/analyzer.js +276 -0
  73. package/rules/security/S016_no_sensitive_querystring/config.json +127 -0
  74. package/rules/security/S016_no_sensitive_querystring/regex-based-analyzer.js +258 -0
  75. package/rules/security/S016_no_sensitive_querystring/symbol-based-analyzer.js +495 -0
  76. package/rules/security/S017_use_parameterized_queries/README.md +128 -0
  77. package/rules/security/S017_use_parameterized_queries/analyzer.js +286 -0
  78. package/rules/security/S017_use_parameterized_queries/config.json +109 -0
  79. package/rules/security/S017_use_parameterized_queries/regex-based-analyzer.js +541 -0
  80. package/rules/security/S017_use_parameterized_queries/symbol-based-analyzer.js +777 -0
  81. package/rules/security/S031_secure_session_cookies/README.md +127 -0
  82. package/rules/security/S031_secure_session_cookies/analyzer.js +245 -0
  83. package/rules/security/S031_secure_session_cookies/config.json +86 -0
  84. package/rules/security/S031_secure_session_cookies/regex-based-analyzer.js +196 -0
  85. package/rules/security/S031_secure_session_cookies/symbol-based-analyzer.js +1084 -0
  86. package/rules/security/S032_httponly_session_cookies/FRAMEWORK_SUPPORT.md +209 -0
  87. package/rules/security/S032_httponly_session_cookies/README.md +184 -0
  88. package/rules/security/S032_httponly_session_cookies/analyzer.js +282 -0
  89. package/rules/security/S032_httponly_session_cookies/config.json +96 -0
  90. package/rules/security/S032_httponly_session_cookies/regex-based-analyzer.js +715 -0
  91. package/rules/security/S032_httponly_session_cookies/symbol-based-analyzer.js +1348 -0
  92. package/rules/security/S033_samesite_session_cookies/README.md +227 -0
  93. package/rules/security/S033_samesite_session_cookies/analyzer.js +242 -0
  94. package/rules/security/S033_samesite_session_cookies/config.json +87 -0
  95. package/rules/security/S033_samesite_session_cookies/regex-based-analyzer.js +703 -0
  96. package/rules/security/S033_samesite_session_cookies/symbol-based-analyzer.js +732 -0
  97. package/rules/security/S034_host_prefix_session_cookies/README.md +204 -0
  98. package/rules/security/S034_host_prefix_session_cookies/analyzer.js +290 -0
  99. package/rules/security/S034_host_prefix_session_cookies/config.json +62 -0
  100. package/rules/security/S034_host_prefix_session_cookies/regex-based-analyzer.js +478 -0
  101. package/rules/security/S034_host_prefix_session_cookies/symbol-based-analyzer.js +277 -0
  102. package/rules/security/S035_path_session_cookies/README.md +257 -0
  103. package/rules/security/S035_path_session_cookies/analyzer.js +316 -0
  104. package/rules/security/S035_path_session_cookies/config.json +99 -0
  105. package/rules/security/S035_path_session_cookies/regex-based-analyzer.js +724 -0
  106. package/rules/security/S035_path_session_cookies/symbol-based-analyzer.js +373 -0
  107. package/rules/security/S048_no_current_password_in_reset/README.md +222 -0
  108. package/rules/security/S048_no_current_password_in_reset/analyzer.js +366 -0
  109. package/rules/security/S048_no_current_password_in_reset/config.json +48 -0
  110. package/rules/security/S055_content_type_validation/README.md +176 -0
  111. package/rules/security/S055_content_type_validation/analyzer.js +312 -0
  112. package/rules/security/S055_content_type_validation/config.json +48 -0
  113. package/rules/utils/rule-helpers.js +140 -1
  114. package/scripts/batch-processing-demo.js +334 -0
  115. package/scripts/consolidate-config.js +116 -0
  116. package/scripts/performance-test.js +541 -0
  117. package/scripts/quick-performance-test.js +108 -0
  118. package/config/rules/S027-categories.json +0 -122
  119. package/config/rules/rules-registry.json +0 -777
  120. package/rules/common/C006_function_naming/smart-analyzer.js +0 -503
@@ -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', '3.0', ['typescript', 'javascript', 'dart', 'swift', 'kotlin', 'java', 'python', 'go', 'rust', 'all']);
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
- await this.loadRulesFromRegistry(config);
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 v3.0 initialized:`);
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 ${files.length} files with ${rules.length} rules`);
579
- console.log(`🔍 [HeuristicEngine] Files: ${files.map(f => path.basename(f)).join(', ')}`);
580
- console.log(`🔍 [HeuristicEngine] Rules: ${rules.map(r => r.id).join(', ')}`);
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
- return; // Good! Follows the pattern
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
- const errorClassName = getErrorClassName(node);
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
- // Check for generic Error usage
229
- if (errorClassName === 'Error') {
230
- context.report({
231
- node: node.argument,
232
- messageId: "useCustomError"
233
- });
234
- return;
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
- // In strict mode, only custom errors are allowed
238
- if (strictMode && errorClassName) {
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: "preferCustomError"
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
- // Check for other built-in errors that could be more specific
248
- if (allowedBuiltinErrors.has(errorClassName) && !strictMode) {
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: "useSpecificBuiltin"
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**: Avoid noisy logs and false alarms; ensure meaningful log levels.
273
- - **Details**:
274
- - Use `info` or `warn` for recoverable or normal issues
275
- - Reserve `error` for critical failures that require immediate attention
276
- - Use `warn` for potential issues that don't crash the system
277
- - Use `info` for normal business flow events
278
- - Use `debug` for detailed information when troubleshooting
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.1",
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",