@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.
- package/CHANGELOG.md +115 -1
- package/CONTRIBUTING.md +249 -605
- package/README.md +3 -4
- package/config/ci-cd.json +54 -0
- package/config/development.json +56 -0
- package/config/large-project.json +143 -0
- package/config/presets/all.json +0 -1
- package/config/release.json +70 -0
- package/config/rule-analysis-strategies.js +38 -3
- package/config/rules/enhanced-rules-registry.json +474 -1179
- package/config/rules/rules-registry-generated.json +3 -3
- package/core/cli-action-handler.js +24 -30
- package/core/cli-program.js +11 -3
- package/core/config-merger.js +29 -2
- package/core/enhanced-rules-registry.js +3 -2
- package/core/semantic-engine.js +129 -19
- package/core/semantic-rule-base.js +4 -2
- package/core/unified-rule-registry.js +1 -1
- package/docs/COMMAND-EXAMPLES.md +134 -0
- package/docs/LARGE-PROJECT-GUIDE.md +324 -0
- package/engines/heuristic-engine.js +135 -16
- package/integrations/eslint/plugin/index.js +0 -2
- package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +59 -1
- package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +26 -1
- package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +54 -19
- package/origin-rules/common-en.md +19 -15
- package/package.json +1 -1
- package/rules/common/C002_no_duplicate_code/analyzer.js +334 -36
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +220 -35
- package/rules/common/C006_function_naming/analyzer.js +29 -3
- package/rules/common/C010_limit_block_nesting/analyzer.js +181 -337
- package/rules/common/C010_limit_block_nesting/config.json +64 -0
- package/rules/common/C010_limit_block_nesting/regex-based-analyzer.js +379 -0
- package/rules/common/C010_limit_block_nesting/symbol-based-analyzer.js +231 -0
- package/rules/common/C013_no_dead_code/analyzer.js +75 -177
- package/rules/common/C013_no_dead_code/config.json +61 -0
- package/rules/common/C013_no_dead_code/regex-based-analyzer.js +345 -0
- package/rules/common/C013_no_dead_code/symbol-based-analyzer.js +640 -0
- package/rules/common/C014_dependency_injection/analyzer.js +48 -313
- package/rules/common/C014_dependency_injection/config.json +26 -0
- package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +751 -0
- package/rules/common/C017_constructor_logic/analyzer.js +254 -17
- package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -0
- package/rules/common/C018_no_throw_generic_error/analyzer.js +232 -0
- package/rules/common/C018_no_throw_generic_error/config.json +50 -0
- package/rules/common/C018_no_throw_generic_error/regex-based-analyzer.js +387 -0
- package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +314 -0
- package/rules/common/C019_log_level_usage/analyzer.js +110 -317
- package/rules/common/C019_log_level_usage/pattern-analyzer.js +88 -0
- package/rules/common/C019_log_level_usage/system-log-analyzer.js +1267 -0
- package/rules/common/C023_no_duplicate_variable/analyzer.js +180 -0
- package/rules/common/C023_no_duplicate_variable/config.json +50 -0
- package/rules/common/C023_no_duplicate_variable/symbol-based-analyzer.js +158 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/analyzer.js +180 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/config.json +50 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +181 -0
- package/rules/common/C030_use_custom_error_classes/analyzer.js +200 -0
- package/rules/common/C033_separate_service_repository/README.md +78 -0
- package/rules/common/C033_separate_service_repository/analyzer.js +160 -0
- package/rules/common/C033_separate_service_repository/config.json +50 -0
- package/rules/common/C033_separate_service_repository/regex-based-analyzer.js +585 -0
- package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +368 -0
- package/rules/common/C035_error_logging_context/STRATEGY.md +99 -0
- package/rules/common/C035_error_logging_context/analyzer.js +232 -0
- package/rules/common/C035_error_logging_context/config.json +54 -0
- package/rules/common/C035_error_logging_context/regex-based-analyzer.js +299 -0
- package/rules/common/C035_error_logging_context/symbol-based-analyzer.js +454 -0
- package/rules/common/C040_centralized_validation/analyzer.js +165 -0
- package/rules/common/C040_centralized_validation/config.json +46 -0
- package/rules/common/C040_centralized_validation/regex-based-analyzer.js +243 -0
- package/rules/common/C040_centralized_validation/symbol-based-analyzer.js +416 -0
- package/rules/common/{C076_single_test_behavior → C072_single_test_behavior}/analyzer.js +6 -6
- package/rules/common/C076_explicit_function_types/README.md +30 -0
- package/rules/common/C076_explicit_function_types/analyzer.js +172 -0
- package/rules/common/C076_explicit_function_types/config.json +15 -0
- package/rules/common/C076_explicit_function_types/semantic-analyzer.js +341 -0
- package/rules/index.js +6 -1
- package/rules/parser/rule-parser.js +13 -2
- package/rules/security/S005_no_origin_auth/README.md +226 -0
- package/rules/security/S005_no_origin_auth/analyzer.js +184 -0
- package/rules/security/S005_no_origin_auth/ast-analyzer.js +406 -0
- package/rules/security/S005_no_origin_auth/config.json +85 -0
- package/rules/security/S006_no_plaintext_recovery_codes/README.md +139 -0
- package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +306 -0
- package/rules/security/S006_no_plaintext_recovery_codes/config.json +48 -0
- package/rules/security/S007_no_plaintext_otp/README.md +198 -0
- package/rules/security/S007_no_plaintext_otp/analyzer.js +406 -0
- package/rules/security/S007_no_plaintext_otp/config.json +79 -0
- package/rules/security/S007_no_plaintext_otp/semantic-analyzer.js +609 -0
- package/rules/security/S007_no_plaintext_otp/semantic-config.json +195 -0
- package/rules/security/S007_no_plaintext_otp/semantic-wrapper.js +280 -0
- package/rules/security/S009_no_insecure_encryption/README.md +158 -0
- package/rules/security/S009_no_insecure_encryption/analyzer.js +319 -0
- package/rules/security/S009_no_insecure_encryption/config.json +55 -0
- package/rules/security/S010_no_insecure_encryption/README.md +224 -0
- package/rules/security/S010_no_insecure_encryption/analyzer.js +493 -0
- package/rules/security/S010_no_insecure_encryption/config.json +48 -0
- package/rules/security/S016_no_sensitive_querystring/STRATEGY.md +149 -0
- package/rules/security/S016_no_sensitive_querystring/analyzer.js +276 -0
- package/rules/security/S016_no_sensitive_querystring/config.json +127 -0
- package/rules/security/S016_no_sensitive_querystring/regex-based-analyzer.js +258 -0
- package/rules/security/S016_no_sensitive_querystring/symbol-based-analyzer.js +495 -0
- package/rules/security/S027_no_hardcoded_secrets/analyzer.js +180 -366
- package/rules/security/S027_no_hardcoded_secrets/categories.json +153 -0
- package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +250 -0
- package/rules/security/S048_no_current_password_in_reset/README.md +222 -0
- package/rules/security/S048_no_current_password_in_reset/analyzer.js +366 -0
- package/rules/security/S048_no_current_password_in_reset/config.json +48 -0
- package/rules/security/S055_content_type_validation/README.md +176 -0
- package/rules/security/S055_content_type_validation/analyzer.js +312 -0
- package/rules/security/S055_content_type_validation/config.json +48 -0
- package/rules/utils/rule-helpers.js +140 -1
- package/scripts/consolidate-config.js +116 -0
- package/scripts/prepare-release.sh +1 -1
- package/config/rules/rules-registry.json +0 -765
- package/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
- package/docs/FUTURE_PACKAGES.md +0 -83
- package/docs/HEURISTIC_VS_AI.md +0 -113
- package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +0 -112
- package/docs/PRODUCTION_SIZE_IMPACT.md +0 -183
- package/docs/RELEASE_GUIDE.md +0 -230
- package/docs/STANDARDIZED-CATEGORY-FILTERING.md +0 -156
- package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +0 -254
- package/rules/common/C006_function_naming/smart-analyzer.js +0 -503
|
@@ -441,7 +441,7 @@
|
|
|
441
441
|
},
|
|
442
442
|
"C019": {
|
|
443
443
|
"name": "Do not use `error` log level for non-critical issues",
|
|
444
|
-
"description": "
|
|
444
|
+
"description": "Prevent noisy logs and false alarms; ensure consistent and meaningful log levels across the system.",
|
|
445
445
|
"category": "Common",
|
|
446
446
|
"severity": "major",
|
|
447
447
|
"languages": [
|
|
@@ -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": "
|
|
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": "
|
|
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
|
|
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:
|
|
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
|
/**
|
package/core/cli-program.js
CHANGED
|
@@ -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,
|
|
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,
|
|
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=
|
|
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
|
|
package/core/config-merger.js
CHANGED
|
@@ -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
|
-
|
|
213
|
-
|
|
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
|
-
'
|
|
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
|
};
|
package/core/semantic-engine.js
CHANGED
|
@@ -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
|
-
|
|
63
|
-
|
|
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
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
console.
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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(
|
|
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.
|
|
337
|
+
line: sourceFile.getLineAndColumnAtPos(arg.getStart()).line
|
|
236
338
|
})),
|
|
237
|
-
line: callExpr.
|
|
238
|
-
column: callExpr.
|
|
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(
|
|
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.
|
|
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
|
-
|
|
51
|
+
if (options?.verbose) {
|
|
52
|
+
console.log(`🔧 Rule ${this.ruleId} initialized with semantic analysis`);
|
|
53
|
+
}
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
/**
|
package/docs/COMMAND-EXAMPLES.md
CHANGED
|
@@ -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
|