@sun-asterisk/sunlint 1.3.0 → 1.3.2

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 (124) hide show
  1. package/CHANGELOG.md +115 -1
  2. package/CONTRIBUTING.md +249 -605
  3. package/README.md +3 -4
  4. package/config/ci-cd.json +54 -0
  5. package/config/development.json +56 -0
  6. package/config/large-project.json +143 -0
  7. package/config/presets/all.json +0 -1
  8. package/config/release.json +70 -0
  9. package/config/rule-analysis-strategies.js +38 -3
  10. package/config/rules/enhanced-rules-registry.json +474 -1179
  11. package/config/rules/rules-registry-generated.json +3 -3
  12. package/core/cli-action-handler.js +24 -30
  13. package/core/cli-program.js +11 -3
  14. package/core/config-merger.js +29 -2
  15. package/core/enhanced-rules-registry.js +3 -2
  16. package/core/semantic-engine.js +129 -19
  17. package/core/semantic-rule-base.js +4 -2
  18. package/core/unified-rule-registry.js +1 -1
  19. package/docs/COMMAND-EXAMPLES.md +134 -0
  20. package/docs/LARGE-PROJECT-GUIDE.md +324 -0
  21. package/engines/heuristic-engine.js +135 -16
  22. package/integrations/eslint/plugin/index.js +0 -2
  23. package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +59 -1
  24. package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +26 -1
  25. package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +54 -19
  26. package/origin-rules/common-en.md +19 -15
  27. package/package.json +1 -1
  28. package/rules/common/C002_no_duplicate_code/analyzer.js +334 -36
  29. package/rules/common/C003_no_vague_abbreviations/analyzer.js +220 -35
  30. package/rules/common/C006_function_naming/analyzer.js +29 -3
  31. package/rules/common/C010_limit_block_nesting/analyzer.js +181 -337
  32. package/rules/common/C010_limit_block_nesting/config.json +64 -0
  33. package/rules/common/C010_limit_block_nesting/regex-based-analyzer.js +379 -0
  34. package/rules/common/C010_limit_block_nesting/symbol-based-analyzer.js +231 -0
  35. package/rules/common/C013_no_dead_code/analyzer.js +75 -177
  36. package/rules/common/C013_no_dead_code/config.json +61 -0
  37. package/rules/common/C013_no_dead_code/regex-based-analyzer.js +345 -0
  38. package/rules/common/C013_no_dead_code/symbol-based-analyzer.js +640 -0
  39. package/rules/common/C014_dependency_injection/analyzer.js +48 -313
  40. package/rules/common/C014_dependency_injection/config.json +26 -0
  41. package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +751 -0
  42. package/rules/common/C017_constructor_logic/analyzer.js +254 -17
  43. package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -0
  44. package/rules/common/C018_no_throw_generic_error/analyzer.js +232 -0
  45. package/rules/common/C018_no_throw_generic_error/config.json +50 -0
  46. package/rules/common/C018_no_throw_generic_error/regex-based-analyzer.js +387 -0
  47. package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +314 -0
  48. package/rules/common/C019_log_level_usage/analyzer.js +110 -317
  49. package/rules/common/C019_log_level_usage/pattern-analyzer.js +88 -0
  50. package/rules/common/C019_log_level_usage/system-log-analyzer.js +1267 -0
  51. package/rules/common/C023_no_duplicate_variable/analyzer.js +180 -0
  52. package/rules/common/C023_no_duplicate_variable/config.json +50 -0
  53. package/rules/common/C023_no_duplicate_variable/symbol-based-analyzer.js +158 -0
  54. package/rules/common/C024_no_scatter_hardcoded_constants/analyzer.js +180 -0
  55. package/rules/common/C024_no_scatter_hardcoded_constants/config.json +50 -0
  56. package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +181 -0
  57. package/rules/common/C030_use_custom_error_classes/analyzer.js +200 -0
  58. package/rules/common/C033_separate_service_repository/README.md +78 -0
  59. package/rules/common/C033_separate_service_repository/analyzer.js +160 -0
  60. package/rules/common/C033_separate_service_repository/config.json +50 -0
  61. package/rules/common/C033_separate_service_repository/regex-based-analyzer.js +585 -0
  62. package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +368 -0
  63. package/rules/common/C035_error_logging_context/STRATEGY.md +99 -0
  64. package/rules/common/C035_error_logging_context/analyzer.js +232 -0
  65. package/rules/common/C035_error_logging_context/config.json +54 -0
  66. package/rules/common/C035_error_logging_context/regex-based-analyzer.js +299 -0
  67. package/rules/common/C035_error_logging_context/symbol-based-analyzer.js +454 -0
  68. package/rules/common/C040_centralized_validation/analyzer.js +165 -0
  69. package/rules/common/C040_centralized_validation/config.json +46 -0
  70. package/rules/common/C040_centralized_validation/regex-based-analyzer.js +243 -0
  71. package/rules/common/C040_centralized_validation/symbol-based-analyzer.js +416 -0
  72. package/rules/common/{C076_single_test_behavior → C072_single_test_behavior}/analyzer.js +6 -6
  73. package/rules/common/C076_explicit_function_types/README.md +30 -0
  74. package/rules/common/C076_explicit_function_types/analyzer.js +172 -0
  75. package/rules/common/C076_explicit_function_types/config.json +15 -0
  76. package/rules/common/C076_explicit_function_types/semantic-analyzer.js +341 -0
  77. package/rules/index.js +6 -1
  78. package/rules/parser/rule-parser.js +13 -2
  79. package/rules/security/S005_no_origin_auth/README.md +226 -0
  80. package/rules/security/S005_no_origin_auth/analyzer.js +184 -0
  81. package/rules/security/S005_no_origin_auth/ast-analyzer.js +406 -0
  82. package/rules/security/S005_no_origin_auth/config.json +85 -0
  83. package/rules/security/S006_no_plaintext_recovery_codes/README.md +139 -0
  84. package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +306 -0
  85. package/rules/security/S006_no_plaintext_recovery_codes/config.json +48 -0
  86. package/rules/security/S007_no_plaintext_otp/README.md +198 -0
  87. package/rules/security/S007_no_plaintext_otp/analyzer.js +406 -0
  88. package/rules/security/S007_no_plaintext_otp/config.json +79 -0
  89. package/rules/security/S007_no_plaintext_otp/semantic-analyzer.js +609 -0
  90. package/rules/security/S007_no_plaintext_otp/semantic-config.json +195 -0
  91. package/rules/security/S007_no_plaintext_otp/semantic-wrapper.js +280 -0
  92. package/rules/security/S009_no_insecure_encryption/README.md +158 -0
  93. package/rules/security/S009_no_insecure_encryption/analyzer.js +319 -0
  94. package/rules/security/S009_no_insecure_encryption/config.json +55 -0
  95. package/rules/security/S010_no_insecure_encryption/README.md +224 -0
  96. package/rules/security/S010_no_insecure_encryption/analyzer.js +493 -0
  97. package/rules/security/S010_no_insecure_encryption/config.json +48 -0
  98. package/rules/security/S016_no_sensitive_querystring/STRATEGY.md +149 -0
  99. package/rules/security/S016_no_sensitive_querystring/analyzer.js +276 -0
  100. package/rules/security/S016_no_sensitive_querystring/config.json +127 -0
  101. package/rules/security/S016_no_sensitive_querystring/regex-based-analyzer.js +258 -0
  102. package/rules/security/S016_no_sensitive_querystring/symbol-based-analyzer.js +495 -0
  103. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +180 -366
  104. package/rules/security/S027_no_hardcoded_secrets/categories.json +153 -0
  105. package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +250 -0
  106. package/rules/security/S048_no_current_password_in_reset/README.md +222 -0
  107. package/rules/security/S048_no_current_password_in_reset/analyzer.js +366 -0
  108. package/rules/security/S048_no_current_password_in_reset/config.json +48 -0
  109. package/rules/security/S055_content_type_validation/README.md +176 -0
  110. package/rules/security/S055_content_type_validation/analyzer.js +312 -0
  111. package/rules/security/S055_content_type_validation/config.json +48 -0
  112. package/rules/utils/rule-helpers.js +140 -1
  113. package/scripts/consolidate-config.js +116 -0
  114. package/scripts/prepare-release.sh +1 -1
  115. package/config/rules/rules-registry.json +0 -765
  116. package/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
  117. package/docs/FUTURE_PACKAGES.md +0 -83
  118. package/docs/HEURISTIC_VS_AI.md +0 -113
  119. package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +0 -112
  120. package/docs/PRODUCTION_SIZE_IMPACT.md +0 -183
  121. package/docs/RELEASE_GUIDE.md +0 -230
  122. package/docs/STANDARDIZED-CATEGORY-FILTERING.md +0 -156
  123. package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +0 -254
  124. package/rules/common/C006_function_naming/smart-analyzer.js +0 -503
@@ -0,0 +1,324 @@
1
+ # 🏗️ Large Project Analysis Guide
2
+
3
+ > **For projects with 1000+ files**: Complete strategies to optimize SunLint performance while maintaining comprehensive analysis coverage.
4
+
5
+ ## 📊 Overview
6
+
7
+ SunLint uses **semantic analysis** for advanced rules like `C047` (retry pattern detection). For large projects, you can control the scope of semantic analysis to balance accuracy vs performance.
8
+
9
+ ### 🎯 Key Benefits
10
+
11
+ - **Configurable file limits**: Control memory usage and analysis time
12
+ - **Smart defaults**: Automatic optimization for different project sizes
13
+ - **Multiple strategies**: Choose the best approach for your workflow
14
+ - **Full coverage options**: Ensure no violations are missed
15
+
16
+ ## ⚙️ Configuration Options
17
+
18
+ ### CLI Option: `--max-semantic-files`
19
+
20
+ Controls how many files are loaded for semantic analysis:
21
+
22
+ ```bash
23
+ # Default behavior (auto-detect)
24
+ node cli.js --all --input=.
25
+
26
+ # Conservative analysis (500 files)
27
+ node cli.js --all --input=. --max-semantic-files=500
28
+
29
+ # Balanced analysis (1000 files - default)
30
+ node cli.js --all --input=. --max-semantic-files=1000
31
+
32
+ # Comprehensive analysis (2000 files)
33
+ node cli.js --all --input=. --max-semantic-files=2000
34
+
35
+ # Unlimited analysis (all files)
36
+ node cli.js --all --input=. --max-semantic-files=-1
37
+
38
+ # Disable semantic analysis (heuristic only)
39
+ node cli.js --all --input=. --max-semantic-files=0
40
+ ```
41
+
42
+ ### 📋 Recommended Limits by Project Size
43
+
44
+ | Project Size | Files Count | Recommended Limit | Memory Usage | Analysis Time |
45
+ |-------------|-------------|-------------------|--------------|---------------|
46
+ | **Small** | < 100 files | `0` (all files) | Low | Fast |
47
+ | **Medium** | 100-500 files | `500` | Medium | Medium |
48
+ | **Large** | 500-2000 files | `1000` ⭐ | Medium-High | Medium |
49
+ | **Enterprise** | 2000-5000 files | `1500` | High | Slow |
50
+ | **Massive** | 5000+ files | `500-1000` | Controlled | Reasonable |
51
+
52
+ ⭐ **Default recommended setting**
53
+
54
+ ## 🚀 Analysis Strategies
55
+
56
+ ### Strategy 1: Incremental Development
57
+
58
+ Perfect for daily development work:
59
+
60
+ ```bash
61
+ # Focus on changed files only (fastest)
62
+ node cli.js --all --changed-files --max-semantic-files=300 --format=summary
63
+
64
+ # Target specific modules
65
+ node cli.js --all --input=src/auth --max-semantic-files=1000 --format=summary
66
+ node cli.js --all --input=src/api --max-semantic-files=1000 --format=summary
67
+
68
+ # Use file patterns to focus on critical code
69
+ node cli.js --all --include="src/**/*.ts" --exclude="**/*.test.*" --max-semantic-files=1500
70
+ ```
71
+
72
+ ### Strategy 2: CI/CD Pipeline Optimization
73
+
74
+ Optimize for different CI stages:
75
+
76
+ ```bash
77
+ # PR checks: Fast semantic analysis
78
+ node cli.js --all --changed-files --max-semantic-files=300 --format=github --no-ai
79
+
80
+ # Nightly builds: Medium coverage
81
+ node cli.js --all --input=. --max-semantic-files=1000 --format=json --output=nightly.json
82
+
83
+ # Weekly reports: Comprehensive analysis
84
+ node cli.js --all --input=. --max-semantic-files=-1 --format=detailed --output=weekly.json
85
+
86
+ # Release validation: Full coverage with baseline
87
+ node cli.js --all --input=. --max-semantic-files=2000 --baseline=last-release.json
88
+ ```
89
+
90
+ ### Strategy 3: Rule-Based Prioritization
91
+
92
+ Different limits for different rule types:
93
+
94
+ ```bash
95
+ # Phase 1: Critical security (fast heuristic rules)
96
+ node cli.js --security --input=. --max-semantic-files=0 --format=summary
97
+
98
+ # Phase 2: Code quality basics
99
+ node cli.js --rules=C006,C019,C029 --input=. --max-semantic-files=500 --format=summary
100
+
101
+ # Phase 3: Advanced semantic rules (targeted)
102
+ node cli.js --rules=C047 --input=src --max-semantic-files=1000 --format=summary
103
+
104
+ # Phase 4: Full comprehensive scan
105
+ node cli.js --all --input=. --max-semantic-files=-1 --format=detailed
106
+ ```
107
+
108
+ ### Strategy 4: Monorepo Management
109
+
110
+ For large monorepos with multiple packages:
111
+
112
+ ```bash
113
+ # Analyze each package separately
114
+ for package in packages/*/; do
115
+ node cli.js --all --input="$package" --max-semantic-files=1000 \
116
+ --format=json --output="${package//\//-}-report.json"
117
+ done
118
+
119
+ # Focus on core packages first
120
+ node cli.js --all --input=packages/core --max-semantic-files=2000 --format=summary
121
+ node cli.js --all --input=packages/api --max-semantic-files=1500 --format=summary
122
+ node cli.js --all --input=packages/ui --max-semantic-files=1000 --format=summary
123
+
124
+ # Changed files across the entire monorepo
125
+ node cli.js --all --changed-files --max-semantic-files=500 --format=summary
126
+ ```
127
+
128
+ ## 📈 Performance Monitoring
129
+
130
+ ### Memory & Time Tracking
131
+
132
+ ```bash
133
+ # Monitor performance with different limits
134
+ time node cli.js --all --input=. --max-semantic-files=500 --format=summary
135
+ time node cli.js --all --input=. --max-semantic-files=1000 --format=summary
136
+ time node cli.js --all --input=. --max-semantic-files=2000 --format=summary
137
+
138
+ # Memory-conscious analysis for CI
139
+ node cli.js --all --input=. --max-semantic-files=300 --max-concurrent=2 --format=summary
140
+
141
+ # Debug file loading behavior
142
+ node cli.js --all --input=. --max-semantic-files=1000 --verbose --debug
143
+ ```
144
+
145
+ ### Coverage Analysis
146
+
147
+ Check what percentage of your project is being analyzed:
148
+
149
+ ```bash
150
+ # Show file loading statistics
151
+ node cli.js --all --input=. --max-semantic-files=1000 --verbose --format=summary
152
+
153
+ # Compare different limits
154
+ node cli.js --all --input=. --max-semantic-files=500 --verbose --dry-run
155
+ node cli.js --all --input=. --max-semantic-files=1000 --verbose --dry-run
156
+ node cli.js --all --input=. --max-semantic-files=-1 --verbose --dry-run
157
+ ```
158
+
159
+ ## 🎛️ Configuration Files
160
+
161
+ ### sunlint.config.json
162
+
163
+ Create a configuration file for consistent settings:
164
+
165
+ ```json
166
+ {
167
+ "performance": {
168
+ "maxSemanticFiles": 1000,
169
+ "maxConcurrentRules": 5,
170
+ "timeoutMs": 30000
171
+ },
172
+ "input": ["src", "lib"],
173
+ "exclude": [
174
+ "**/*.test.*",
175
+ "**/*.d.ts",
176
+ "**/generated/**"
177
+ ],
178
+ "output": {
179
+ "format": "summary"
180
+ },
181
+ "engines": {
182
+ "semantic": {
183
+ "enabled": true,
184
+ "fileLimit": 1000
185
+ }
186
+ }
187
+ }
188
+ ```
189
+
190
+ ### Environment-Specific Configs
191
+
192
+ Different configs for different environments:
193
+
194
+ ```bash
195
+ # Development (fast feedback)
196
+ cp config/sunlint.dev.json sunlint.config.json
197
+ node cli.js --all --input=.
198
+
199
+ # CI (balanced coverage)
200
+ cp config/sunlint.ci.json sunlint.config.json
201
+ node cli.js --all --changed-files
202
+
203
+ # Release (comprehensive)
204
+ cp config/sunlint.release.json sunlint.config.json
205
+ node cli.js --all --input=.
206
+ ```
207
+
208
+ ## 💡 Best Practices
209
+
210
+ ### 1. Start Conservative, Scale Up
211
+
212
+ ```bash
213
+ # Begin with conservative limits
214
+ node cli.js --all --input=. --max-semantic-files=500 --format=summary
215
+
216
+ # Gradually increase if performance allows
217
+ node cli.js --all --input=. --max-semantic-files=1000 --format=summary
218
+ node cli.js --all --input=. --max-semantic-files=1500 --format=summary
219
+ ```
220
+
221
+ ### 2. Use Different Limits for Different Contexts
222
+
223
+ ```bash
224
+ # Daily development: Focus on changed files
225
+ alias sunlint-dev="node cli.js --all --changed-files --max-semantic-files=300"
226
+
227
+ # Code review: Medium coverage
228
+ alias sunlint-review="node cli.js --all --changed-files --max-semantic-files=500"
229
+
230
+ # Release preparation: Full coverage
231
+ alias sunlint-release="node cli.js --all --input=. --max-semantic-files=-1"
232
+ ```
233
+
234
+ ### 3. Monitor and Adjust
235
+
236
+ Track your analysis performance over time:
237
+
238
+ ```bash
239
+ # Create performance baseline
240
+ echo "Project size: $(find . -name '*.ts' -o -name '*.js' | wc -l) files"
241
+ time node cli.js --all --input=. --max-semantic-files=1000 --format=summary
242
+
243
+ # Adjust based on CI constraints
244
+ if [[ $CI_MEMORY_LIMIT -lt 4096 ]]; then
245
+ SEMANTIC_LIMIT=500
246
+ else
247
+ SEMANTIC_LIMIT=1000
248
+ fi
249
+
250
+ node cli.js --all --input=. --max-semantic-files=$SEMANTIC_LIMIT --format=summary
251
+ ```
252
+
253
+ ### 4. Combine with File Targeting
254
+
255
+ Use semantic limits together with file patterns:
256
+
257
+ ```bash
258
+ # Focus semantic analysis on source files only
259
+ node cli.js --all --include="src/**/*.ts" --exclude="**/*.test.*" --max-semantic-files=1500
260
+
261
+ # Analyze tests separately with lower limits
262
+ node cli.js --all --include="**/*.test.*" --max-semantic-files=500
263
+
264
+ # Target critical modules with higher limits
265
+ node cli.js --all --input=src/security --max-semantic-files=2000
266
+ node cli.js --all --input=src/api --max-semantic-files=1500
267
+ ```
268
+
269
+ ## 🔍 Troubleshooting
270
+
271
+ ### Memory Issues
272
+
273
+ If you encounter out-of-memory errors:
274
+
275
+ ```bash
276
+ # Reduce semantic file limit
277
+ node cli.js --all --input=. --max-semantic-files=500
278
+
279
+ # Disable semantic analysis completely
280
+ node cli.js --all --input=. --max-semantic-files=0
281
+
282
+ # Reduce concurrent rules
283
+ node cli.js --all --input=. --max-semantic-files=1000 --max-concurrent=2
284
+ ```
285
+
286
+ ### Slow Analysis
287
+
288
+ If analysis takes too long:
289
+
290
+ ```bash
291
+ # Use incremental analysis
292
+ node cli.js --all --changed-files --max-semantic-files=300
293
+
294
+ # Focus on specific directories
295
+ node cli.js --all --input=src/critical --max-semantic-files=1000
296
+
297
+ # Use timeout limits
298
+ node cli.js --all --input=. --max-semantic-files=1000 --timeout=60000
299
+ ```
300
+
301
+ ### Missed Violations
302
+
303
+ If you suspect violations are being missed:
304
+
305
+ ```bash
306
+ # Run comprehensive analysis periodically
307
+ node cli.js --all --input=. --max-semantic-files=-1 --format=detailed
308
+
309
+ # Compare different limits
310
+ node cli.js --all --input=. --max-semantic-files=1000 --output=report-1k.json
311
+ node cli.js --all --input=. --max-semantic-files=-1 --output=report-full.json
312
+ diff report-1k.json report-full.json
313
+ ```
314
+
315
+ ## 📚 Related Documentation
316
+
317
+ - [Command Examples](./COMMAND-EXAMPLES.md) - Complete CLI usage examples
318
+ - [Configuration Guide](./CONFIGURATION.md) - Detailed configuration options
319
+ - [CI/CD Guide](./CI-CD-GUIDE.md) - Integration with CI/CD pipelines
320
+ - [Architecture](./ARCHITECTURE.md) - Technical implementation details
321
+
322
+ ---
323
+
324
+ **💡 Pro Tip**: For projects with 2000+ files, consider breaking analysis into modules and running them in parallel, rather than analyzing everything at once.
@@ -25,7 +25,8 @@ class HeuristicEngine extends AnalysisEngineInterface {
25
25
  this.astRegistry = ASTModuleRegistry;
26
26
 
27
27
  // ts-morph as core technology for heuristic engine
28
- this.semanticEngine = new SemanticEngine();
28
+ // Note: semantic engine will be initialized in initialize() with proper config
29
+ this.semanticEngine = null;
29
30
  this.semanticRules = new Map();
30
31
  this.symbolTableEnabled = false;
31
32
 
@@ -55,8 +56,13 @@ class HeuristicEngine extends AnalysisEngineInterface {
55
56
  // Initialize rule adapter
56
57
  await this.ruleAdapter.initialize();
57
58
 
58
- // Load available rules from unified registry
59
- await this.loadRulesFromRegistry(config);
59
+ // Load available rules from unified registry (OPTIMIZED: skip for performance)
60
+ // Rules will be loaded on-demand in analyze() method
61
+ if (config.loadAllRules) {
62
+ await this.loadRulesFromRegistry(config);
63
+ } else if (this.verbose) {
64
+ console.log(`⚡ [HeuristicEngine] Skipping bulk rule loading for performance - will load on-demand`);
65
+ }
60
66
 
61
67
  this.initialized = true;
62
68
  if (this.verbose) {
@@ -74,13 +80,25 @@ class HeuristicEngine extends AnalysisEngineInterface {
74
80
 
75
81
  /**
76
82
  * Initialize ts-morph Symbol Table as core requirement
83
+ * OPTIMIZED: Use targeted files instead of entire project for better performance
77
84
  */
78
85
  async initializeSymbolTable(config) {
79
86
  const projectPath = config?.projectPath || process.cwd();
80
87
 
81
88
  try {
82
- // ts-morph is now a core dependency
83
- const success = await this.semanticEngine.initialize(projectPath);
89
+ // Initialize semantic engine with config options including maxSemanticFiles
90
+ const semanticOptions = {
91
+ maxSemanticFiles: config?.maxSemanticFiles,
92
+ verbose: this.verbose,
93
+ ...config?.semanticOptions
94
+ };
95
+
96
+ this.semanticEngine = new SemanticEngine(semanticOptions);
97
+ // Pass verbose option to semantic engine
98
+ this.semanticEngine.verbose = this.verbose;
99
+
100
+ // ts-morph is now a core dependency - but optimized for targeted files
101
+ const success = await this.semanticEngine.initialize(projectPath, config?.targetFiles);
84
102
 
85
103
  if (success) {
86
104
  this.semanticEnabled = true;
@@ -276,6 +294,43 @@ class HeuristicEngine extends AnalysisEngineInterface {
276
294
  }
277
295
  }
278
296
 
297
+ /**
298
+ * Lazy load a single rule on-demand
299
+ * @param {string} ruleId - Rule ID to load
300
+ * @param {Object} options - Loading options
301
+ */
302
+ async lazyLoadRule(ruleId, options = {}) {
303
+ try {
304
+ const ruleDefinition = this.unifiedRegistry.getRuleDefinition(ruleId);
305
+
306
+ if (!ruleDefinition) {
307
+ if (options.verbose) {
308
+ console.warn(`⚠️ [HeuristicEngine] Rule definition not found for ${ruleId}`);
309
+ }
310
+ return;
311
+ }
312
+
313
+ // Check if rule supports heuristic engine
314
+ if (!this.unifiedRegistry.isRuleSupported(ruleId, 'heuristic')) {
315
+ if (options.verbose) {
316
+ console.warn(`⚠️ [HeuristicEngine] Rule ${ruleId} not supported by heuristic engine`);
317
+ }
318
+ return;
319
+ }
320
+
321
+ if (options.verbose) {
322
+ console.log(`🔄 [HeuristicEngine] Lazy loading rule ${ruleId}...`);
323
+ }
324
+
325
+ await this.loadRuleFromDefinition(ruleDefinition);
326
+
327
+ } catch (error) {
328
+ if (options.verbose) {
329
+ console.warn(`⚠️ [HeuristicEngine] Failed to lazy load rule ${ruleId}:`, error.message);
330
+ }
331
+ }
332
+ }
333
+
279
334
  /**
280
335
  * Manually load C047 semantic rule (special case)
281
336
  */
@@ -407,23 +462,23 @@ class HeuristicEngine extends AnalysisEngineInterface {
407
462
  }
408
463
 
409
464
  /**
410
- * Register semantic rule
465
+ * Register semantic rule (lazy initialization)
411
466
  */
412
467
  async registerSemanticRule(ruleId, analyzerClass, metadata) {
413
468
  try {
414
- const instance = new analyzerClass(ruleId);
415
- instance.initialize(this.semanticEngine);
416
-
469
+ // Store rule class and metadata for lazy initialization
417
470
  this.semanticRules.set(ruleId, {
418
- instance,
471
+ analyzerClass,
419
472
  metadata,
420
- type: 'semantic'
473
+ type: 'semantic',
474
+ initialized: false,
475
+ instance: null
421
476
  });
422
477
 
423
478
  this.supportedRulesList.push(ruleId);
424
479
 
425
480
  if (this.verbose) {
426
- console.log(`🧠 Registered semantic rule: ${ruleId}`);
481
+ console.log(`🧠 Registered semantic rule: ${ruleId} (lazy initialization)`);
427
482
  }
428
483
 
429
484
  } catch (error) {
@@ -431,6 +486,34 @@ class HeuristicEngine extends AnalysisEngineInterface {
431
486
  }
432
487
  }
433
488
 
489
+ /**
490
+ * Initialize semantic rule on-demand
491
+ */
492
+ async initializeSemanticRule(ruleId) {
493
+ const ruleEntry = this.semanticRules.get(ruleId);
494
+ if (!ruleEntry || ruleEntry.initialized) {
495
+ return ruleEntry?.instance;
496
+ }
497
+
498
+ try {
499
+ const instance = new ruleEntry.analyzerClass(ruleId);
500
+ instance.initialize(this.semanticEngine, { verbose: this.verbose });
501
+
502
+ // Update entry with initialized instance
503
+ ruleEntry.instance = instance;
504
+ ruleEntry.initialized = true;
505
+
506
+ if (this.verbose) {
507
+ console.log(`🔧 Rule ${ruleId} initialized with semantic analysis`);
508
+ }
509
+
510
+ return instance;
511
+ } catch (error) {
512
+ console.warn(`⚠️ Failed to initialize semantic rule ${ruleId}:`, error.message);
513
+ return null;
514
+ }
515
+ }
516
+
434
517
  /**
435
518
  * Register traditional (heuristic/AST/regex) rule
436
519
  */
@@ -563,6 +646,14 @@ class HeuristicEngine extends AnalysisEngineInterface {
563
646
  await this.manuallyLoadC047();
564
647
  }
565
648
 
649
+ // Lazy load rule if not already loaded
650
+ if (!this.isRuleSupported(rule.id)) {
651
+ if (options.verbose) {
652
+ console.log(`🔄 [HeuristicEngine] Lazy loading rule ${rule.id}...`);
653
+ }
654
+ await this.lazyLoadRule(rule.id, options);
655
+ }
656
+
566
657
  if (!this.isRuleSupported(rule.id)) {
567
658
  if (options.verbose) {
568
659
  console.warn(`⚠️ Rule ${rule.id} not supported by Heuristic engine, skipping...`);
@@ -629,7 +720,13 @@ class HeuristicEngine extends AnalysisEngineInterface {
629
720
  }
630
721
 
631
722
  try {
632
- const ruleInstance = semanticRuleInfo.instance;
723
+ // Initialize rule on-demand (lazy initialization)
724
+ const ruleInstance = await this.initializeSemanticRule(rule.id);
725
+ if (!ruleInstance) {
726
+ console.warn(`⚠️ Failed to initialize semantic rule ${rule.id}`);
727
+ return [];
728
+ }
729
+
633
730
  const allViolations = [];
634
731
 
635
732
  // Run semantic analysis for each file
@@ -677,6 +774,15 @@ class HeuristicEngine extends AnalysisEngineInterface {
677
774
  async analyzeRule(rule, filesByLanguage, options) {
678
775
  // Get full rule ID (C029 -> C029_catch_block_logging)
679
776
  const fullRuleId = this.getFullRuleId(rule.id);
777
+
778
+ // Lazy load rule if not already loaded
779
+ if (!this.ruleAnalyzers.has(fullRuleId)) {
780
+ if (options.verbose) {
781
+ console.log(`🔄 [HeuristicEngine] Lazy loading rule ${rule.id} for analysis...`);
782
+ }
783
+ await this.lazyLoadRule(rule.id, options);
784
+ }
785
+
680
786
  const analyzerInfo = this.ruleAnalyzers.get(fullRuleId);
681
787
 
682
788
  if (!analyzerInfo) {
@@ -691,13 +797,26 @@ class HeuristicEngine extends AnalysisEngineInterface {
691
797
  // Create analyzer instance from class
692
798
  const AnalyzerClass = analyzerInfo.class;
693
799
  try {
694
- analyzer = new AnalyzerClass({ verbose: options.verbose });
800
+ analyzer = new AnalyzerClass({
801
+ verbose: options.verbose,
802
+ semanticEngine: this.semanticEngine
803
+ });
804
+
805
+ // Initialize with semantic engine if method exists
806
+ if (analyzer.initialize && typeof analyzer.initialize === 'function') {
807
+ await analyzer.initialize(this.semanticEngine);
808
+ }
695
809
  } catch (constructorError) {
696
810
  throw new Error(`Failed to instantiate analyzer class: ${constructorError.message}`);
697
811
  }
698
812
  } else if (analyzerInfo.type === 'instance') {
699
813
  // Use existing analyzer instance
700
814
  analyzer = analyzerInfo.instance;
815
+
816
+ // Initialize existing instance with semantic engine if method exists
817
+ if (analyzer.initialize && typeof analyzer.initialize === 'function') {
818
+ await analyzer.initialize(this.semanticEngine);
819
+ }
701
820
  } else {
702
821
  throw new Error(`Unknown analyzer type: ${analyzerInfo.type}`);
703
822
  }
@@ -730,7 +849,7 @@ class HeuristicEngine extends AnalysisEngineInterface {
730
849
  rule.id,
731
850
  languageFiles,
732
851
  language,
733
- { ...ruleConfig, ...options }
852
+ { ...ruleConfig, ...options, semanticEngine: this.semanticEngine }
734
853
  );
735
854
 
736
855
  allViolations.push(...languageViolations);
@@ -1303,4 +1422,4 @@ class HeuristicEngine extends AnalysisEngineInterface {
1303
1422
  }
1304
1423
  }
1305
1424
 
1306
- module.exports = HeuristicEngine;
1425
+ module.exports = HeuristicEngine;
@@ -22,7 +22,6 @@ const c043 = require("./rules/common/c043-no-console-or-print.js");
22
22
  const c047 = require("./rules/common/c047-no-duplicate-retry-logic.js");
23
23
  const c072 = require("./rules/common/c072-one-assert-per-test.js");
24
24
  const c075 = require("./rules/common/c075-explicit-function-return-types.js");
25
- const c076 = require("./rules/common/c076-single-behavior-per-test.js");
26
25
 
27
26
  // 📘 TypeScript Rules (T-series)
28
27
  const t002 = require("./rules/typescript/t002-interface-prefix-i.js");
@@ -103,7 +102,6 @@ module.exports = {
103
102
  "c047-no-duplicate-retry-logic": c047,
104
103
  "c072-one-assert-per-test": c072,
105
104
  "c075-explicit-function-return-types": c075,
106
- "c076-single-behavior-per-test": c076,
107
105
  "t002-interface-prefix-i": t002,
108
106
  "t003-ts-ignore-reason": t003,
109
107
  "t004-no-empty-type": t004,
@@ -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({