@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
@@ -441,7 +441,7 @@
441
441
  },
442
442
  "C019": {
443
443
  "name": "Do not use `error` log level for non-critical issues",
444
- "description": "Avoid noisy logs and false alarms; ensure meaningful log levels.",
444
+ "description": "Prevent noisy logs and false alarms; ensure consistent and meaningful log levels across the system.",
445
445
  "category": "Common",
446
446
  "severity": "major",
447
447
  "languages": [
@@ -914,7 +914,7 @@
914
914
  },
915
915
  "C039": {
916
916
  "name": "Do not store temporary data in global or static mutable fields",
917
- "description": "No description available",
917
+ "description": "Prevent issues related to shared state and race conditions in concurrent environments. Ensure thread-safety and testability. Using global or static mutable fields can introduce hard-to-detect and hard-to-fix bugs.",
918
918
  "category": "Common",
919
919
  "severity": "major",
920
920
  "languages": [
@@ -937,7 +937,7 @@
937
937
  },
938
938
  "C040": {
939
939
  "name": "Do not spread validation logic across multiple classes",
940
- "description": "No description available",
940
+ "description": "Centralize validation logic to simplify maintenance, increase reusability, and ensure consistency. Centralized validation helps reduce bugs and simplifies updating validation rules.",
941
941
  "category": "Common",
942
942
  "severity": "major",
943
943
  "languages": [
@@ -109,21 +109,29 @@ class CliActionHandler {
109
109
  }
110
110
 
111
111
  /**
112
- * Run analysis with modern orchestrator
113
- * Following Rule C006: Verb-noun naming
114
- * Following Rule C012: Command Query Separation - analysis is a command
112
+ * Run analysis using modern orchestrator
115
113
  */
116
114
  async runModernAnalysis(rulesToRun, files, config) {
117
115
  if (this.isModernMode) {
118
116
  console.log(chalk.blue('🚀 Using modern engine architecture'));
119
117
 
120
- // Initialize orchestrator with configuration
118
+ // Initialize orchestrator with configuration including targetFiles for optimization
121
119
  await this.orchestrator.initialize({
122
120
  enabledEngines: this.determineEnabledEngines(config),
123
121
  aiConfig: config.ai || {},
124
122
  eslintConfig: config.eslint || {},
125
- heuristicConfig: config.heuristic || {}
123
+ heuristicConfig: {
124
+ ...config.heuristic || {},
125
+ targetFiles: this.options.targetFiles, // Pass filtered files for semantic optimization
126
+ maxSemanticFiles: this.options.maxSemanticFiles !== undefined ? parseInt(this.options.maxSemanticFiles) : 1000, // Configurable semantic file limit
127
+ verbose: this.options.verbose // Pass verbose for debugging
128
+ }
126
129
  });
130
+
131
+ if (this.options.verbose) {
132
+ console.log(`🔧 Debug: maxSemanticFiles option = ${this.options.maxSemanticFiles}`);
133
+ console.log(`🔧 Debug: parsed maxSemanticFiles = ${this.options.maxSemanticFiles !== undefined ? parseInt(this.options.maxSemanticFiles) : 1000}`);
134
+ }
127
135
 
128
136
  // Run analysis with new orchestrator
129
137
  const results = await this.orchestrator.analyze(files, rulesToRun, {
@@ -136,21 +144,7 @@ class CliActionHandler {
136
144
  requestedEngine: this.options.engine
137
145
  }
138
146
  });
139
-
140
147
  return results;
141
- } else {
142
- console.log(chalk.yellow('🔄 Using legacy orchestrator'));
143
-
144
- // Ensure verbose/quiet flags are in config
145
- const analysisConfig = {
146
- ...config,
147
- verbose: this.options.verbose,
148
- quiet: this.options.quiet,
149
- // Pass requested engine to enable strict engine mode (no fallback)
150
- requestedEngine: this.options.engine
151
- };
152
-
153
- return await this.orchestrator.runAnalysis(rulesToRun, this.options, analysisConfig);
154
148
  }
155
149
  }
156
150
 
@@ -268,6 +262,17 @@ class CliActionHandler {
268
262
  * Following Rule C031: Separate validation logic
269
263
  */
270
264
  validateInput(config) {
265
+ // Validate engine option if specified (check this first, always)
266
+ if (this.options.engine) {
267
+ const validEngines = ['eslint', 'heuristic', 'openai'];
268
+ if (!validEngines.includes(this.options.engine)) {
269
+ throw new Error(
270
+ chalk.red(`❌ Invalid engine: ${this.options.engine}\n`) +
271
+ chalk.gray(`Valid engines: ${validEngines.join(', ')}`)
272
+ );
273
+ }
274
+ }
275
+
271
276
  // Priority 1: CLI --input parameter (highest priority)
272
277
  if (this.options.input) {
273
278
  // Validate CLI input path exists
@@ -302,17 +307,6 @@ class CliActionHandler {
302
307
  }
303
308
  return;
304
309
  }
305
-
306
- // Validate engine option if specified
307
- if (this.options.engine) {
308
- const validEngines = ['eslint', 'sunlint', 'heuristic'];
309
- if (!validEngines.includes(this.options.engine)) {
310
- throw new Error(
311
- chalk.red(`❌ Invalid engine: ${this.options.engine}\n`) +
312
- chalk.gray(`Valid engines: ${validEngines.join(', ')}`)
313
- );
314
- }
315
- }
316
310
  }
317
311
 
318
312
  /**
@@ -26,7 +26,7 @@ function createCliProgram() {
26
26
  // TypeScript specific options (Phase 1 focus)
27
27
  program
28
28
  .option('--typescript', 'Enable TypeScript-specific analysis')
29
- .option('--typescript-engine <engine>', 'TypeScript analysis engine (eslint,sunlint,hybrid)', 'sunlint')
29
+ .option('--typescript-engine <engine>', 'TypeScript analysis engine (eslint,heuristic,hybrid)', 'heuristic')
30
30
  .option('--ensure-deps', 'Ensure ESLint dependencies are installed');
31
31
 
32
32
  // Input/Output options (v1.x: explicit --input required)
@@ -58,7 +58,7 @@ function createCliProgram() {
58
58
 
59
59
  // Advanced options
60
60
  program
61
- .option('--engine <engine>', 'Force specific analysis engine (eslint,sunlint)', '')
61
+ .option('--engine <engine>', 'Force specific analysis engine (eslint,heuristic)', '')
62
62
  .option('--dry-run', 'Show what would be analyzed without running')
63
63
  .option('--verbose', 'Enable verbose logging')
64
64
  .option('--quiet', 'Suppress non-error output')
@@ -67,6 +67,7 @@ function createCliProgram() {
67
67
  .option('--no-ai', 'Force disable AI analysis (use heuristic only)')
68
68
  .option('--legacy', 'Use legacy analysis architecture')
69
69
  .option('--modern', 'Use modern plugin-based architecture (default)')
70
+ .option('--max-semantic-files <number>', 'Control semantic analysis scope: 0=disable, -1=unlimited, >0=limit (default: 1000)', '1000')
70
71
  .option('--list-engines', 'List available analysis engines');
71
72
 
72
73
  // ESLint Integration options
@@ -107,7 +108,7 @@ Version Strategy:
107
108
  Engine Configuration:
108
109
  $ sunlint --all --input=src # Use config engine setting
109
110
  $ sunlint --all --input=src --engine=eslint # Force ESLint engine
110
- $ sunlint --all --input=src --engine=sunlint # Force SunLint engine
111
+ $ sunlint --all --input=src --engine=heuristic # Force Heuristic engine
111
112
 
112
113
  CI/CD Integration:
113
114
  $ sunlint --all --changed-files --format=summary --no-ai
@@ -127,6 +128,13 @@ Advanced File Targeting:
127
128
  $ sunlint --languages=typescript,dart --include="src/**,packages/**" --input=.
128
129
  $ sunlint --all --only-source --exclude-tests --languages=typescript --input=.
129
130
 
131
+ Large Project Optimization:
132
+ $ sunlint --all --input=. --max-semantic-files=500 # Conservative analysis
133
+ $ sunlint --all --input=. --max-semantic-files=2000 # Comprehensive analysis
134
+ $ sunlint --all --input=. --max-semantic-files=-1 # Unlimited (all files)
135
+ $ sunlint --all --input=. --max-semantic-files=0 # Disable semantic analysis
136
+ $ sunlint --all --changed-files --max-semantic-files=300 # Fast CI analysis
137
+
130
138
  Sun* Engineering - Coding Standards Made Simple ☀️
131
139
  `);
132
140
 
@@ -209,8 +209,35 @@ class ConfigMerger {
209
209
  // Add flexible patterns for input paths
210
210
  const expandedInclude = [...currentInclude];
211
211
  for (const inputPath of inputPaths) {
212
- expandedInclude.push(inputPath + '/**');
213
- expandedInclude.push('**/' + inputPath + '/**');
212
+ // Check if inputPath is a file or directory
213
+ const fs = require('fs');
214
+ const path = require('path');
215
+
216
+ try {
217
+ const resolvedPath = path.resolve(inputPath);
218
+ if (fs.existsSync(resolvedPath)) {
219
+ const stat = fs.statSync(resolvedPath);
220
+ if (stat.isFile()) {
221
+ // For files, add the exact path
222
+ expandedInclude.push(inputPath);
223
+ expandedInclude.push('**/' + inputPath);
224
+ } else if (stat.isDirectory()) {
225
+ // For directories, add recursive patterns
226
+ expandedInclude.push(inputPath + '/**');
227
+ expandedInclude.push('**/' + inputPath + '/**');
228
+ }
229
+ } else {
230
+ // If path doesn't exist, assume it's a pattern and add both file and directory variants
231
+ expandedInclude.push(inputPath);
232
+ expandedInclude.push(inputPath + '/**');
233
+ expandedInclude.push('**/' + inputPath);
234
+ expandedInclude.push('**/' + inputPath + '/**');
235
+ }
236
+ } catch (error) {
237
+ // Fallback to original logic if file system check fails
238
+ expandedInclude.push(inputPath + '/**');
239
+ expandedInclude.push('**/' + inputPath + '/**');
240
+ }
214
241
  }
215
242
  result.include = expandedInclude;
216
243
 
@@ -100,7 +100,9 @@ class EnhancedRulesRegistry {
100
100
  'C006': ['eslint', 'heuristic', 'openai'],
101
101
  'C007': ['eslint', 'heuristic', 'openai'],
102
102
  'C014': ['eslint', 'heuristic', 'openai'],
103
- 'C033': ['eslint', 'heuristic'],
103
+ 'C018': ['heuristic', 'eslint'],
104
+ 'C033': ['heuristic', 'eslint'],
105
+ 'C035': ['heuristic', 'eslint'],
104
106
  'C040': ['eslint', 'heuristic'],
105
107
 
106
108
  // AI-enhanced rules (complex logic analysis)
@@ -109,7 +111,6 @@ class EnhancedRulesRegistry {
109
111
  'C015': ['openai', 'heuristic'],
110
112
  'C032': ['openai', 'heuristic'],
111
113
  'C034': ['openai', 'heuristic'],
112
- 'C035': ['openai', 'heuristic'],
113
114
  'C037': ['openai', 'heuristic', 'eslint'],
114
115
  'C038': ['openai', 'heuristic']
115
116
  };
@@ -8,6 +8,7 @@
8
8
 
9
9
  const path = require('path');
10
10
  const fs = require('fs').promises;
11
+ const { Project, SyntaxKind } = require('ts-morph');
11
12
 
12
13
  class SemanticEngine {
13
14
  constructor(options = {}) {
@@ -49,8 +50,9 @@ class SemanticEngine {
49
50
  /**
50
51
  * Initialize ts-morph project with optimized memory configuration
51
52
  * Designed for large projects (3000+ files, 800-1000 lines each)
53
+ * OPTIMIZED: Accept targetFiles parameter to avoid loading unnecessary files
52
54
  */
53
- async initialize(projectPath) {
55
+ async initialize(projectPath, targetFiles = null) {
54
56
  try {
55
57
  // Load ts-morph conditionally
56
58
  const { Project } = await import('ts-morph');
@@ -59,8 +61,8 @@ class SemanticEngine {
59
61
  const tsConfigPath = await this.findTsConfig(projectPath);
60
62
 
61
63
  // Initialize project with memory-optimized settings
62
- this.project = new Project({
63
- tsConfigFilePath: tsConfigPath,
64
+ // When using targetFiles, skip tsconfig to avoid auto-discovery
65
+ const projectOptions = {
64
66
  compilerOptions: {
65
67
  ...this.options.compilerOptions,
66
68
  // Memory optimization flags
@@ -78,22 +80,122 @@ class SemanticEngine {
78
80
  // Performance settings for large codebases
79
81
  resolutionHost: undefined, // Disable resolution host
80
82
  libFolderPath: undefined, // Don't load TypeScript libs
81
- });
83
+ };
84
+
85
+ // NEVER use project tsconfig.json to avoid file resolution issues
86
+ // Instead, load files explicitly to ensure they can be found
87
+ if (this.options.verbose) {
88
+ console.log(`🔧 SemanticEngine: Skipping project tsconfig.json to avoid file resolution issues`);
89
+ if (tsConfigPath) {
90
+ console.log(` 📋 Found tsconfig: ${tsConfigPath} (ignored for better compatibility)`);
91
+ }
92
+ }
93
+
94
+ this.project = new Project(projectOptions);
95
+
96
+ // Use provided targetFiles if available, otherwise discover
97
+ const sourceFiles = targetFiles || await this.discoverTargetFiles(projectPath);
98
+
99
+ // Filter to TypeScript/JavaScript files only for semantic analysis
100
+ const semanticFiles = sourceFiles.filter(filePath =>
101
+ /\.(ts|tsx|js|jsx)$/i.test(filePath)
102
+ );
103
+
104
+ if (targetFiles) {
105
+ console.log(`🎯 Targeted files received: ${targetFiles.length} total, ${semanticFiles.length} TS/JS files`);
106
+ if (semanticFiles.length < 10) {
107
+ console.log(` Files: ${semanticFiles.map(f => path.basename(f)).join(', ')}`);
108
+ }
109
+ }
110
+
111
+ // Adaptive loading strategy based on project size and user preference
112
+ const userMaxFiles = this.options.maxSemanticFiles;
113
+ let maxFiles;
114
+
115
+ if (userMaxFiles === -1) {
116
+ // Unlimited: Load all files
117
+ maxFiles = semanticFiles.length;
118
+ console.log(`🔧 Semantic Engine config: UNLIMITED analysis (all ${semanticFiles.length} files)`);
119
+ } else if (userMaxFiles === 0) {
120
+ // Disable semantic analysis
121
+ maxFiles = 0;
122
+ console.log(`🔧 Semantic Engine config: DISABLED semantic analysis (heuristic only)`);
123
+ } else if (userMaxFiles > 0) {
124
+ // User-specified limit
125
+ maxFiles = Math.min(userMaxFiles, semanticFiles.length);
126
+ console.log(`🔧 Semantic Engine config: USER limit ${maxFiles} files (requested: ${userMaxFiles})`);
127
+ } else {
128
+ // Auto-detect based on project size
129
+ maxFiles = semanticFiles.length > 1000 ? 1000 : semanticFiles.length;
130
+ console.log(`🔧 Semantic Engine config: AUTO limit ${maxFiles} files (project has ${semanticFiles.length} files)`);
131
+ }
132
+
133
+ if (this.options.verbose) {
134
+ console.log(`🔧 Semantic Engine detailed config:`);
135
+ console.log(` 📊 maxSemanticFiles option: ${this.options.maxSemanticFiles}`);
136
+ console.log(` 📈 Total semantic files: ${semanticFiles.length}`);
137
+ console.log(` 🎯 Files to load: ${maxFiles}`);
138
+ console.log(` 📉 Coverage: ${maxFiles > 0 ? Math.round(maxFiles/semanticFiles.length*100) : 0}%`);
139
+ }
82
140
 
83
- // Only add target files, not entire project
84
- const sourceFiles = await this.discoverTargetFiles(projectPath);
85
- if (sourceFiles.length > 100) {
86
- console.warn(`⚠️ Large project detected (${sourceFiles.length} files) - limited analysis mode`);
87
- // For large projects, only add first 50 files to avoid memory issues
88
- this.project.addSourceFilesAtPaths(sourceFiles.slice(0, 50));
141
+ // Skip semantic analysis if disabled
142
+ if (maxFiles === 0) {
143
+ console.log(`⚠️ Semantic analysis DISABLED - using heuristic rules only`);
144
+ console.log(`💡 To enable semantic analysis, use --max-semantic-files=1000 (or higher)`);
145
+ this.initialized = true;
146
+ return true;
147
+ }
148
+
149
+ if (semanticFiles.length > maxFiles && maxFiles !== semanticFiles.length) {
150
+ console.warn(`⚠️ Large semantic project detected (${semanticFiles.length} files)`);
151
+ console.warn(`⚠️ Loading ${maxFiles} files for memory optimization (${Math.round(maxFiles/semanticFiles.length*100)}% coverage)`);
152
+ if (userMaxFiles !== -1) {
153
+ console.warn(`⚠️ Use --max-semantic-files=-1 to analyze ALL files (unlimited)`);
154
+ console.warn(`⚠️ Use --max-semantic-files=${semanticFiles.length} to analyze exactly this project`);
155
+ }
156
+
157
+ const filesToLoad = semanticFiles.slice(0, maxFiles);
158
+
159
+ // Load files one by one to handle any parse errors gracefully
160
+ let successCount = 0;
161
+ let errorCount = 0;
162
+
163
+ for (const filePath of filesToLoad) {
164
+ try {
165
+ if (require('fs').existsSync(filePath)) {
166
+ this.project.addSourceFileAtPath(filePath);
167
+ successCount++;
168
+ } else {
169
+ errorCount++;
170
+ }
171
+ } catch (error) {
172
+ if (this.options.verbose) {
173
+ console.warn(`❌ Failed to load: ${path.basename(filePath)} - ${error.message}`);
174
+ }
175
+ errorCount++;
176
+ }
177
+ }
178
+
179
+ console.log(`📊 Semantic analysis: ${successCount} files loaded, ${errorCount} skipped`);
180
+
89
181
  } else {
90
- this.project.addSourceFilesAtPaths(sourceFiles);
182
+ console.log(`📊 Loading all ${semanticFiles.length} files for complete semantic analysis`);
183
+ // For projects within limits, load all files
184
+ this.project.addSourceFilesAtPaths(semanticFiles);
185
+ }
186
+
187
+ // Debug what ts-morph actually loaded
188
+ const actualFiles = this.project.getSourceFiles();
189
+ console.log(`📊 ts-morph loaded: ${actualFiles.length} files (expected: ${semanticFiles.length})`);
190
+ if (actualFiles.length > semanticFiles.length * 2) {
191
+ console.warn(`⚠️ ts-morph auto-discovered additional files (dependency resolution)`);
91
192
  }
92
193
 
93
194
  console.log(`🔧 Semantic Engine initialized (Memory Optimized):`);
94
195
  console.log(` 📁 Project: ${projectPath}`);
95
196
  console.log(` 📋 TS Config: ${tsConfigPath || 'default (minimal)'}`);
96
197
  console.log(` 📄 Files loaded: ${this.project.getSourceFiles().length}`);
198
+ console.log(` 🎯 Targeting mode: ${targetFiles ? 'Filtered files' : 'Auto-discovery'}`);
97
199
  console.log(` 💾 Memory mode: Optimized for large projects`);
98
200
 
99
201
  this.initialized = true;
@@ -199,7 +301,7 @@ class SemanticEngine {
199
301
  const namedImports = importDecl.getNamedImports().map(namedImport => ({
200
302
  name: namedImport.getName(),
201
303
  alias: namedImport.getAliasNode()?.getText(),
202
- line: namedImport.getStartLineNumber()
304
+ line: sourceFile.getLineAndColumnAtPos(namedImport.getStart()).line
203
305
  }));
204
306
 
205
307
  // Default import
@@ -209,7 +311,7 @@ class SemanticEngine {
209
311
  module: moduleSpecifier,
210
312
  defaultImport: defaultImport?.getText(),
211
313
  namedImports,
212
- line: importDecl.getStartLineNumber(),
314
+ line: sourceFile.getLineAndColumnAtPos(importDecl.getStart()).line,
213
315
  isTypeOnly: importDecl.isTypeOnly(),
214
316
  resolvedPath: this.resolveModule(moduleSpecifier, sourceFile)
215
317
  });
@@ -224,7 +326,7 @@ class SemanticEngine {
224
326
  extractFunctionCalls(sourceFile) {
225
327
  const calls = [];
226
328
 
227
- sourceFile.getDescendantsOfKind(sourceFile.getKindName().CallExpression || 210).forEach(callExpr => {
329
+ sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).forEach(callExpr => {
228
330
  const expression = callExpr.getExpression();
229
331
 
230
332
  calls.push({
@@ -232,10 +334,10 @@ class SemanticEngine {
232
334
  arguments: callExpr.getArguments().map(arg => ({
233
335
  text: arg.getText(),
234
336
  type: this.getExpressionType(arg),
235
- line: arg.getStartLineNumber()
337
+ line: sourceFile.getLineAndColumnAtPos(arg.getStart()).line
236
338
  })),
237
- line: callExpr.getStartLineNumber(),
238
- column: callExpr.getStartColumnNumber(),
339
+ line: sourceFile.getLineAndColumnAtPos(callExpr.getStart()).line,
340
+ column: sourceFile.getLineAndColumnAtPos(callExpr.getStart()).column,
239
341
 
240
342
  // Detailed analysis for retry patterns
241
343
  isRetryPattern: this.isRetryPattern(callExpr),
@@ -253,7 +355,7 @@ class SemanticEngine {
253
355
  extractHooks(sourceFile) {
254
356
  const hooks = [];
255
357
 
256
- sourceFile.getDescendantsOfKind(sourceFile.getKindName().CallExpression || 210).forEach(callExpr => {
358
+ sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).forEach(callExpr => {
257
359
  const expression = callExpr.getExpression();
258
360
  const functionName = expression.getText();
259
361
 
@@ -262,7 +364,7 @@ class SemanticEngine {
262
364
  hooks.push({
263
365
  hookName: functionName,
264
366
  arguments: callExpr.getArguments().map(arg => arg.getText()),
265
- line: callExpr.getStartLineNumber(),
367
+ line: sourceFile.getLineAndColumnAtPos(callExpr.getStart()).line,
266
368
 
267
369
  // Special analysis for useQuery, useMutation, etc.
268
370
  isQueryHook: this.isQueryHook(functionName),
@@ -555,6 +657,14 @@ class SemanticEngine {
555
657
  getParentContext(callExpr) { return null; }
556
658
  isKnownHook(functionName) { return false; }
557
659
  findSymbolUsages(sourceFile, namedImports) { return []; }
660
+
661
+ /**
662
+ * Check if symbol engine is ready for symbol-based analysis
663
+ * @returns {boolean} true if project is initialized and ready
664
+ */
665
+ isSymbolEngineReady() {
666
+ return this.initialized && this.project !== null;
667
+ }
558
668
  }
559
669
 
560
670
  module.exports = SemanticEngine;
@@ -41,14 +41,16 @@ class SemanticRuleBase {
41
41
  /**
42
42
  * Initialize rule with SemanticEngine instance
43
43
  */
44
- initialize(semanticEngine) {
44
+ initialize(semanticEngine, options = {}) {
45
45
  this.semanticEngine = semanticEngine;
46
46
 
47
47
  if (!this.semanticEngine || !this.semanticEngine.initialized) {
48
48
  throw new Error(`${this.ruleId}: SemanticEngine is required and must be initialized`);
49
49
  }
50
50
 
51
- console.log(`🔧 Rule ${this.ruleId} initialized with semantic analysis`);
51
+ if (options?.verbose) {
52
+ console.log(`🔧 Rule ${this.ruleId} initialized with semantic analysis`);
53
+ }
52
54
  }
53
55
 
54
56
  /**
@@ -481,4 +481,4 @@ module.exports = {
481
481
  }
482
482
  return instance;
483
483
  }
484
- };
484
+ };
@@ -113,6 +113,22 @@ node cli.js --all --input=. --timeout=60000 --format=summary --no-ai
113
113
  # Disable caching
114
114
  node cli.js --all --input=. --no-cache --format=summary --no-ai
115
115
 
116
+ # **Control semantic analysis for large projects**
117
+ # Default limit: 1000 files for performance balance
118
+ node cli.js --all --input=. --max-semantic-files=1000 --format=summary
119
+
120
+ # For small projects: Analyze all files
121
+ node cli.js --all --input=. --max-semantic-files=0 --format=summary
122
+
123
+ # For large projects: Conservative analysis
124
+ node cli.js --all --input=. --max-semantic-files=500 --format=summary
125
+
126
+ # For massive projects: Minimal semantic analysis
127
+ node cli.js --all --input=. --max-semantic-files=100 --format=summary
128
+
129
+ # Unlimited semantic analysis (use with caution!)
130
+ node cli.js --all --input=. --max-semantic-files=-1 --format=summary
131
+
116
132
  # Verbose logging
117
133
  node cli.js --all --input=. --verbose --format=summary --no-ai
118
134
 
@@ -203,6 +219,124 @@ node cli.js --all --changed-files --diff-base=origin/main --format=github --no-a
203
219
  node cli.js --all --changed-files --ai --format=detailed
204
220
  ```
205
221
 
222
+ ## 🏗️ **Large Project Strategies**
223
+
224
+ > **⚡ Performance Note**: SunLint uses semantic analysis for advanced rules (like C047). For projects with 1000+ files, you can control semantic analysis scope to balance accuracy vs performance.
225
+
226
+ ### **Strategy 1: Incremental Analysis** 📈
227
+ ```bash
228
+ # Start with changed files only (fastest)
229
+ node cli.js --all --changed-files --format=summary --no-ai
230
+
231
+ # Focus on specific directories
232
+ node cli.js --all --input=src/critical --max-semantic-files=2000 --format=summary
233
+
234
+ # Target important file patterns only
235
+ node cli.js --all --include="src/**/*.ts" --exclude="**/*.test.*,**/*.d.ts" --input=.
236
+
237
+ # Use directory-based analysis
238
+ node cli.js --all --input=src/auth --format=summary # Most critical module first
239
+ node cli.js --all --input=src/api --format=summary # Then API layer
240
+ node cli.js --all --input=src/utils --format=summary # Finally utilities
241
+ ```
242
+
243
+ ### **Strategy 2: Semantic Analysis Tuning** 🔧
244
+ ```bash
245
+ # Conservative: 500 files for faster analysis
246
+ node cli.js --all --input=. --max-semantic-files=500 --format=summary
247
+
248
+ # Balanced: 1000 files (default) for medium projects
249
+ node cli.js --all --input=. --max-semantic-files=1000 --format=summary
250
+
251
+ # Comprehensive: 2000+ files for complete analysis
252
+ node cli.js --all --input=. --max-semantic-files=2000 --format=summary
253
+
254
+ # Unlimited: All files (use for final validation)
255
+ node cli.js --all --input=. --max-semantic-files=-1 --format=summary
256
+
257
+ # Disable semantic analysis completely (heuristic only)
258
+ node cli.js --all --input=. --max-semantic-files=0 --format=summary
259
+ ```
260
+
261
+ ### **Strategy 3: Rule-Based Prioritization** 🎯
262
+ ```bash
263
+ # Phase 1: Critical security issues (fast heuristic rules)
264
+ node cli.js --security --input=. --max-semantic-files=0 --format=summary
265
+
266
+ # Phase 2: Code quality basics
267
+ node cli.js --rules=C006,C019,C029 --input=. --max-semantic-files=500 --format=summary
268
+
269
+ # Phase 3: Advanced semantic rules (targeted)
270
+ node cli.js --rules=C047 --input=src --max-semantic-files=1000 --format=summary
271
+
272
+ # Phase 4: Full comprehensive scan
273
+ node cli.js --all --input=. --max-semantic-files=-1 --format=detailed
274
+ ```
275
+
276
+ ### **Strategy 4: CI/CD Optimization** ⚡
277
+ ```bash
278
+ # PR checks: Fast semantic analysis
279
+ node cli.js --all --changed-files --max-semantic-files=300 --format=github --no-ai
280
+
281
+ # Nightly builds: Medium semantic analysis
282
+ node cli.js --all --input=. --max-semantic-files=1000 --format=json --output=nightly.json
283
+
284
+ # Weekly reports: Full semantic analysis
285
+ node cli.js --all --input=. --max-semantic-files=-1 --format=detailed --output=weekly.json
286
+
287
+ # Release validation: Comprehensive with baselines
288
+ node cli.js --all --input=. --max-semantic-files=2000 --baseline=last-release.json
289
+ ```
290
+
291
+ ### **Strategy 5: Memory & Performance Monitoring** 📊
292
+ ```bash
293
+ # Monitor file loading (debug mode)
294
+ node cli.js --all --input=. --max-semantic-files=1000 --verbose --debug
295
+
296
+ # Track performance with different limits
297
+ time node cli.js --all --input=. --max-semantic-files=500 --format=summary
298
+ time node cli.js --all --input=. --max-semantic-files=1000 --format=summary
299
+ time node cli.js --all --input=. --max-semantic-files=2000 --format=summary
300
+
301
+ # Memory-conscious analysis for CI
302
+ node cli.js --all --input=. --max-semantic-files=300 --max-concurrent=2 --format=summary
303
+ ```
304
+
305
+ ### **📋 Recommended Limits by Project Size**
306
+
307
+ | Project Size | Files Count | Recommended Limit | Use Case |
308
+ |-------------|-------------|-------------------|----------|
309
+ | Small | < 100 files | `--max-semantic-files=0` (all) | Complete analysis |
310
+ | Medium | 100-500 files | `--max-semantic-files=500` | Balanced |
311
+ | Large | 500-2000 files | `--max-semantic-files=1000` | Default recommended |
312
+ | Enterprise | 2000-5000 files | `--max-semantic-files=1500` | Conservative |
313
+ | Massive | 5000+ files | `--max-semantic-files=500` | Targeted analysis |
314
+
315
+ > **💡 Pro Tips for Large Projects:**
316
+ > 1. Use `--changed-files` for daily development
317
+ > 2. Use `--max-semantic-files=500` for CI/CD pipelines
318
+ > 3. Use `--max-semantic-files=-1` for release validation
319
+ > 4. Combine with `--include` patterns to focus on critical code
320
+ > 5. Monitor analysis time and adjust limits accordingly
321
+
322
+ ### **Example 1: Monorepo with 5000+ Files**
323
+ ```bash
324
+ # Daily development: Changed files only
325
+ node cli.js --all --changed-files --max-semantic-files=300 --format=summary
326
+
327
+ # Module-specific analysis
328
+ node cli.js --all --input=packages/core --max-semantic-files=1000 --format=summary
329
+ node cli.js --all --input=packages/api --max-semantic-files=1000 --format=summary
330
+
331
+ # CI pipeline: Conservative semantic analysis
332
+ node cli.js --all --changed-files --max-semantic-files=500 --format=github
333
+
334
+ # Release validation: Full analysis by modules
335
+ for dir in packages/*/; do
336
+ node cli.js --all --input="$dir" --max-semantic-files=2000 --format=json --output="${dir//\//-}-report.json"
337
+ done
338
+ ```
339
+
206
340
  ### **Example 2: Legacy Code Improvement**
207
341
  ```bash
208
342
  # Step 1: Baseline assessment