@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
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C023 Main Analyzer - Do not declare duplicate variable
|
|
3
|
+
* Primary: Do not declare duplicate variable names in the same scope.
|
|
4
|
+
* Fallback: Regex-based for all other cases
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const C023SymbolBasedAnalyzer = require('./symbol-based-analyzer');
|
|
8
|
+
|
|
9
|
+
class C023Analyzer {
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
12
|
+
console.log(`🔧 [C023] Constructor called with options:`, !!options);
|
|
13
|
+
console.log(`🔧 [C023] Options type:`, typeof options, Object.keys(options || {}));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
this.ruleId = 'C023';
|
|
17
|
+
this.ruleName = 'Do not declare duplicate variable';
|
|
18
|
+
this.description = 'Do not declare duplicate variable names in the same scope';
|
|
19
|
+
this.semanticEngine = options.semanticEngine || null;
|
|
20
|
+
this.verbose = options.verbose || false;
|
|
21
|
+
|
|
22
|
+
// Configuration
|
|
23
|
+
this.config = {
|
|
24
|
+
useSymbolBased: true, // Primary approach
|
|
25
|
+
fallbackToRegex: false, // Only when symbol fails completely
|
|
26
|
+
symbolBasedOnly: false // Can be set to true for pure mode
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Initialize both analyzers
|
|
30
|
+
try {
|
|
31
|
+
this.symbolAnalyzer = new C023SymbolBasedAnalyzer(this.semanticEngine);
|
|
32
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
33
|
+
console.log(`🔧 [C023] Symbol analyzer created successfully`);
|
|
34
|
+
}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error(`🔧 [C023] Error creating symbol analyzer:`, error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Initialize with semantic engine
|
|
42
|
+
*/
|
|
43
|
+
async initialize(semanticEngine = null) {
|
|
44
|
+
if (semanticEngine) {
|
|
45
|
+
this.semanticEngine = semanticEngine;
|
|
46
|
+
}
|
|
47
|
+
this.verbose = semanticEngine?.verbose || false;
|
|
48
|
+
|
|
49
|
+
// Initialize both analyzers
|
|
50
|
+
await this.symbolAnalyzer.initialize(semanticEngine);
|
|
51
|
+
|
|
52
|
+
// Ensure verbose flag is propagated
|
|
53
|
+
this.symbolAnalyzer.verbose = this.verbose;
|
|
54
|
+
|
|
55
|
+
if (this.verbose) {
|
|
56
|
+
console.log(`🔧 [C023 Hybrid] Analyzer initialized - verbose: ${this.verbose}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async analyze(files, language, options = {}) {
|
|
61
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
62
|
+
console.log(`🔧 [C023] analyze() method called with ${files.length} files, language: ${language}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const violations = [];
|
|
66
|
+
|
|
67
|
+
for (const filePath of files) {
|
|
68
|
+
try {
|
|
69
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
70
|
+
console.log(`🔧 [C023] Processing file: ${filePath}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const fileViolations = await this.analyzeFile(filePath, options);
|
|
74
|
+
violations.push(...fileViolations);
|
|
75
|
+
|
|
76
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
77
|
+
console.log(`🔧 [C023] File ${filePath}: Found ${fileViolations.length} violations`);
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.warn(`❌ [C023] Analysis failed for ${filePath}:`, error.message);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
85
|
+
console.log(`🔧 [C023] Total violations found: ${violations.length}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return violations;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async analyzeFile(filePath, options = {}) {
|
|
92
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
93
|
+
console.log(`🔧 [C023] analyzeFile() called for: ${filePath}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 1. Try Symbol-based analysis first (primary)
|
|
97
|
+
if (this.config.useSymbolBased &&
|
|
98
|
+
this.semanticEngine?.project &&
|
|
99
|
+
this.semanticEngine?.initialized) {
|
|
100
|
+
try {
|
|
101
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
102
|
+
console.log(`🔧 [C023] Trying symbol-based analysis...`);
|
|
103
|
+
}
|
|
104
|
+
const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
|
|
105
|
+
if (sourceFile) {
|
|
106
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
107
|
+
console.log(`🔧 [C023] Source file found, analyzing with symbol-based...`);
|
|
108
|
+
}
|
|
109
|
+
const violations = await this.symbolAnalyzer.analyzeFileWithSymbols(filePath, { ...options, verbose: options.verbose });
|
|
110
|
+
|
|
111
|
+
// Mark violations with analysis strategy
|
|
112
|
+
violations.forEach(v => v.analysisStrategy = 'symbol-based');
|
|
113
|
+
|
|
114
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
115
|
+
console.log(`✅ [C023] Symbol-based analysis: ${violations.length} violations`);
|
|
116
|
+
}
|
|
117
|
+
return violations; // Return even if 0 violations - symbol analysis completed successfully
|
|
118
|
+
} else {
|
|
119
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
120
|
+
console.log(`⚠️ [C023] Source file not found in project`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.warn(`⚠️ [C023] Symbol analysis failed: ${error.message}`);
|
|
125
|
+
// Continue to fallback
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
129
|
+
console.log(`🔄 [C023] Symbol analysis conditions check:`);
|
|
130
|
+
console.log(` - useSymbolBased: ${this.config.useSymbolBased}`);
|
|
131
|
+
console.log(` - semanticEngine: ${!!this.semanticEngine}`);
|
|
132
|
+
console.log(` - semanticEngine.project: ${!!this.semanticEngine?.project}`);
|
|
133
|
+
console.log(` - semanticEngine.initialized: ${this.semanticEngine?.initialized}`);
|
|
134
|
+
console.log(`🔄 [C023] Symbol analysis unavailable, using regex fallback`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (options?.verbose) {
|
|
139
|
+
console.log(`🔧 [C023] No analysis methods succeeded, returning empty`);
|
|
140
|
+
}
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async analyzeFileBasic(filePath, options = {}) {
|
|
145
|
+
console.log(`🔧 [C023] analyzeFileBasic() called for: ${filePath}`);
|
|
146
|
+
console.log(`🔧 [C023] semanticEngine exists: ${!!this.semanticEngine}`);
|
|
147
|
+
console.log(`🔧 [C023] symbolAnalyzer exists: ${!!this.symbolAnalyzer}`);
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
// Try symbol-based analysis first
|
|
151
|
+
if (this.semanticEngine?.isSymbolEngineReady?.() &&
|
|
152
|
+
this.semanticEngine.project) {
|
|
153
|
+
|
|
154
|
+
if (this.verbose) {
|
|
155
|
+
console.log(`🔍 [C023] Using symbol-based analysis for ${filePath}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const violations = await this.symbolAnalyzer.analyzeFileBasic(filePath, options);
|
|
159
|
+
return violations;
|
|
160
|
+
}
|
|
161
|
+
} catch (error) {
|
|
162
|
+
if (this.verbose) {
|
|
163
|
+
console.warn(`⚠️ [C023] Symbol analysis failed: ${error.message}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Methods for compatibility with different engine invocation patterns
|
|
170
|
+
*/
|
|
171
|
+
async analyzeFileWithSymbols(filePath, options = {}) {
|
|
172
|
+
return this.analyzeFile(filePath, options);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async analyzeWithSemantics(filePath, options = {}) {
|
|
176
|
+
return this.analyzeFile(filePath, options);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
module.exports = C023Analyzer;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "C023",
|
|
3
|
+
"name": "C023_do_not_declare_duplicate_variable",
|
|
4
|
+
"category": "architecture",
|
|
5
|
+
"description": "C023 - Do not declare duplicate variable names in the same scope.",
|
|
6
|
+
"severity": "warning",
|
|
7
|
+
"enabled": true,
|
|
8
|
+
"semantic": {
|
|
9
|
+
"enabled": true,
|
|
10
|
+
"priority": "high",
|
|
11
|
+
"fallback": "heuristic"
|
|
12
|
+
},
|
|
13
|
+
"patterns": {
|
|
14
|
+
"include": [
|
|
15
|
+
"**/*.js",
|
|
16
|
+
"**/*.ts",
|
|
17
|
+
"**/*.jsx",
|
|
18
|
+
"**/*.tsx"
|
|
19
|
+
],
|
|
20
|
+
"exclude": [
|
|
21
|
+
"**/*.test.*",
|
|
22
|
+
"**/*.spec.*",
|
|
23
|
+
"**/*.mock.*",
|
|
24
|
+
"**/test/**",
|
|
25
|
+
"**/tests/**",
|
|
26
|
+
"**/spec/**"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"options": {
|
|
30
|
+
"strictMode": false,
|
|
31
|
+
"allowedDbMethods": [],
|
|
32
|
+
"repositoryPatterns": [
|
|
33
|
+
"*Repository*",
|
|
34
|
+
"*Repo*",
|
|
35
|
+
"*DAO*",
|
|
36
|
+
"*Store*"
|
|
37
|
+
],
|
|
38
|
+
"servicePatterns": [
|
|
39
|
+
"*Service*",
|
|
40
|
+
"*UseCase*",
|
|
41
|
+
"*Handler*",
|
|
42
|
+
"*Manager*"
|
|
43
|
+
],
|
|
44
|
+
"complexityThreshold": {
|
|
45
|
+
"methodLength": 200,
|
|
46
|
+
"cyclomaticComplexity": 5,
|
|
47
|
+
"nestedDepth": 3
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C023 Symbol-based Analyzer - Advanced Do not declare duplicate variable
|
|
3
|
+
* Purpose: Use AST + Symbol Resolution to analyze log content quality in catch blocks
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { SyntaxKind } = require('ts-morph');
|
|
7
|
+
|
|
8
|
+
class C023SymbolBasedAnalyzer {
|
|
9
|
+
constructor(semanticEngine = null) {
|
|
10
|
+
this.ruleId = 'C023';
|
|
11
|
+
this.ruleName = 'Error declare duplicate variable names in the same scope. (Symbol-Based)';
|
|
12
|
+
this.semanticEngine = semanticEngine;
|
|
13
|
+
this.verbose = false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async initialize(semanticEngine = null) {
|
|
17
|
+
if (semanticEngine) {
|
|
18
|
+
this.semanticEngine = semanticEngine;
|
|
19
|
+
}
|
|
20
|
+
this.verbose = semanticEngine?.verbose || false;
|
|
21
|
+
|
|
22
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
23
|
+
console.log(`🔧 [C023 Symbol-Based] Analyzer initialized, verbose: ${this.verbose}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async analyzeFileBasic(filePath, options = {}) {
|
|
28
|
+
// This is the main entry point called by the hybrid analyzer
|
|
29
|
+
return await this.analyzeFileWithSymbols(filePath, options);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async analyzeFileWithSymbols(filePath, options = {}) {
|
|
33
|
+
const violations = [];
|
|
34
|
+
|
|
35
|
+
// Enable verbose mode if requested
|
|
36
|
+
const verbose = options.verbose || this.verbose;
|
|
37
|
+
|
|
38
|
+
if (!this.semanticEngine?.project) {
|
|
39
|
+
if (verbose) {
|
|
40
|
+
console.warn('[C023 Symbol-Based] No semantic engine available, skipping analysis');
|
|
41
|
+
}
|
|
42
|
+
return violations;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (verbose) {
|
|
46
|
+
console.log(`🔍 [C023 Symbol-Based] Starting analysis for ${filePath}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
|
|
51
|
+
if (!sourceFile) {
|
|
52
|
+
return violations;
|
|
53
|
+
}
|
|
54
|
+
this.checkScope(sourceFile, violations);
|
|
55
|
+
if (verbose) {
|
|
56
|
+
console.log(`🔍 [C023 Symbol-Based] Total violations found: ${violations.length}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return violations;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (verbose) {
|
|
62
|
+
console.warn(`[C023 Symbol-Based] Analysis failed for ${filePath}:`, error.message);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return violations;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
checkScope(node, violations) {
|
|
70
|
+
const seen = new Map();
|
|
71
|
+
|
|
72
|
+
node.forEachChild((child) => {
|
|
73
|
+
switch (child.getKind()) {
|
|
74
|
+
case SyntaxKind.VariableStatement: {
|
|
75
|
+
for (const decl of child.getDeclarations()) {
|
|
76
|
+
this.checkDuplicate(decl, seen, violations);
|
|
77
|
+
}
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
case SyntaxKind.FunctionDeclaration:
|
|
82
|
+
case SyntaxKind.FunctionExpression:
|
|
83
|
+
case SyntaxKind.ArrowFunction:
|
|
84
|
+
case SyntaxKind.MethodDeclaration:
|
|
85
|
+
case SyntaxKind.Constructor: { // ✅ also cover constructors
|
|
86
|
+
const func = child;
|
|
87
|
+
// Params
|
|
88
|
+
func.getParameters().forEach((p) =>
|
|
89
|
+
this.checkDuplicate(p, seen, violations)
|
|
90
|
+
);
|
|
91
|
+
// Body as new scope
|
|
92
|
+
const body = func.getBody?.();
|
|
93
|
+
if (body) this.checkScope(body, violations);
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
case SyntaxKind.Block: {
|
|
98
|
+
this.checkScope(child, violations);
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
case SyntaxKind.CatchClause: {
|
|
103
|
+
const catchVar = child.getVariableDeclaration();
|
|
104
|
+
if (catchVar) {
|
|
105
|
+
// ✅ register catch param in scope
|
|
106
|
+
this.checkDuplicate(catchVar, seen, violations);
|
|
107
|
+
}
|
|
108
|
+
// Traverse catch block (same scope as catch param)
|
|
109
|
+
this.checkScope(child.getBlock(), violations);
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
case SyntaxKind.ForStatement:
|
|
114
|
+
case SyntaxKind.ForOfStatement:
|
|
115
|
+
case SyntaxKind.ForInStatement: {
|
|
116
|
+
const initializer = child.getInitializer?.();
|
|
117
|
+
if (initializer) {
|
|
118
|
+
initializer.getDeclarations?.().forEach((decl) =>
|
|
119
|
+
this.checkDuplicate(decl, seen, violations)
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
const statement = child.getStatement?.();
|
|
123
|
+
if (statement) this.checkScope(statement, violations);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
default: {
|
|
128
|
+
this.checkScope(child, violations);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
checkDuplicate(node, seen, violations) {
|
|
135
|
+
const name = node.getName?.();
|
|
136
|
+
if (!name) return;
|
|
137
|
+
const filePath = node.getSourceFile().getFilePath();
|
|
138
|
+
|
|
139
|
+
if (seen.has(name)) {
|
|
140
|
+
violations.push({
|
|
141
|
+
ruleId: this.ruleId,
|
|
142
|
+
severity: 'warning',
|
|
143
|
+
message: `Duplicate variable name found: "${name}"`,
|
|
144
|
+
source: this.ruleId,
|
|
145
|
+
file: filePath,
|
|
146
|
+
line: node.getStartLineNumber(),
|
|
147
|
+
column: node.getStart() - node.getStartLinePos(),
|
|
148
|
+
description: `[SYMBOL-BASED] Duplicate variable names can lead to confusion and bugs. Use unique names.`,
|
|
149
|
+
suggestion: 'Rename variables to ensure uniqueness',
|
|
150
|
+
category: 'naming'
|
|
151
|
+
});
|
|
152
|
+
} else {
|
|
153
|
+
seen.set(name, node);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = C023SymbolBasedAnalyzer;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C024 Main Analyzer - Do not scatter hardcoded constants throughout the logic
|
|
3
|
+
* Primary: Do not scatter hardcoded constants throughout the logic.
|
|
4
|
+
* Fallback: Regex-based for all other cases
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const C024SymbolBasedAnalyzer = require('./symbol-based-analyzer');
|
|
8
|
+
|
|
9
|
+
class C024Analyzer {
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
12
|
+
console.log(`🔧 [C024] Constructor called with options:`, !!options);
|
|
13
|
+
console.log(`🔧 [C024] Options type:`, typeof options, Object.keys(options || {}));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
this.ruleId = 'C024';
|
|
17
|
+
this.ruleName = 'Do not scatter hardcoded constants throughout the logic';
|
|
18
|
+
this.description = 'The rule prevents scattering hardcoded constants throughout the logic. Instead, constants should be defined in a single place to improve maintainability and readability.';
|
|
19
|
+
this.semanticEngine = options.semanticEngine || null;
|
|
20
|
+
this.verbose = options.verbose || false;
|
|
21
|
+
|
|
22
|
+
// Configuration
|
|
23
|
+
this.config = {
|
|
24
|
+
useSymbolBased: true, // Primary approach
|
|
25
|
+
fallbackToRegex: false, // Only when symbol fails completely
|
|
26
|
+
symbolBasedOnly: false // Can be set to true for pure mode
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Initialize both analyzers
|
|
30
|
+
try {
|
|
31
|
+
this.symbolAnalyzer = new C024SymbolBasedAnalyzer(this.semanticEngine);
|
|
32
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
33
|
+
console.log(`🔧 [C024] Symbol analyzer created successfully`);
|
|
34
|
+
}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error(`🔧 [C024] Error creating symbol analyzer:`, error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Initialize with semantic engine
|
|
42
|
+
*/
|
|
43
|
+
async initialize(semanticEngine = null) {
|
|
44
|
+
if (semanticEngine) {
|
|
45
|
+
this.semanticEngine = semanticEngine;
|
|
46
|
+
}
|
|
47
|
+
this.verbose = semanticEngine?.verbose || false;
|
|
48
|
+
|
|
49
|
+
// Initialize both analyzers
|
|
50
|
+
await this.symbolAnalyzer.initialize(semanticEngine);
|
|
51
|
+
|
|
52
|
+
// Ensure verbose flag is propagated
|
|
53
|
+
this.symbolAnalyzer.verbose = this.verbose;
|
|
54
|
+
|
|
55
|
+
if (this.verbose) {
|
|
56
|
+
console.log(`🔧 [C024 Hybrid] Analyzer initialized - verbose: ${this.verbose}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async analyze(files, language, options = {}) {
|
|
61
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
62
|
+
console.log(`🔧 [C024] analyze() method called with ${files.length} files, language: ${language}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const violations = [];
|
|
66
|
+
|
|
67
|
+
for (const filePath of files) {
|
|
68
|
+
try {
|
|
69
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
70
|
+
console.log(`🔧 [C024] Processing file: ${filePath}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const fileViolations = await this.analyzeFile(filePath, options);
|
|
74
|
+
violations.push(...fileViolations);
|
|
75
|
+
|
|
76
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
77
|
+
console.log(`🔧 [C024] File ${filePath}: Found ${fileViolations.length} violations`);
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.warn(`❌ [C024] Analysis failed for ${filePath}:`, error.message);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
85
|
+
console.log(`🔧 [C024] Total violations found: ${violations.length}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return violations;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async analyzeFile(filePath, options = {}) {
|
|
92
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
93
|
+
console.log(`🔧 [C024] analyzeFile() called for: ${filePath}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 1. Try Symbol-based analysis first (primary)
|
|
97
|
+
if (this.config.useSymbolBased &&
|
|
98
|
+
this.semanticEngine?.project &&
|
|
99
|
+
this.semanticEngine?.initialized) {
|
|
100
|
+
try {
|
|
101
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
102
|
+
console.log(`🔧 [C024] Trying symbol-based analysis...`);
|
|
103
|
+
}
|
|
104
|
+
const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
|
|
105
|
+
if (sourceFile) {
|
|
106
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
107
|
+
console.log(`🔧 [C024] Source file found, analyzing with symbol-based...`);
|
|
108
|
+
}
|
|
109
|
+
const violations = await this.symbolAnalyzer.analyzeFileWithSymbols(filePath, { ...options, verbose: options.verbose });
|
|
110
|
+
|
|
111
|
+
// Mark violations with analysis strategy
|
|
112
|
+
violations.forEach(v => v.analysisStrategy = 'symbol-based');
|
|
113
|
+
|
|
114
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
115
|
+
console.log(`✅ [C024] Symbol-based analysis: ${violations.length} violations`);
|
|
116
|
+
}
|
|
117
|
+
return violations; // Return even if 0 violations - symbol analysis completed successfully
|
|
118
|
+
} else {
|
|
119
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
120
|
+
console.log(`⚠️ [C024] Source file not found in project`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.warn(`⚠️ [C024] Symbol analysis failed: ${error.message}`);
|
|
125
|
+
// Continue to fallback
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
129
|
+
console.log(`🔄 [C024] Symbol analysis conditions check:`);
|
|
130
|
+
console.log(` - useSymbolBased: ${this.config.useSymbolBased}`);
|
|
131
|
+
console.log(` - semanticEngine: ${!!this.semanticEngine}`);
|
|
132
|
+
console.log(` - semanticEngine.project: ${!!this.semanticEngine?.project}`);
|
|
133
|
+
console.log(` - semanticEngine.initialized: ${this.semanticEngine?.initialized}`);
|
|
134
|
+
console.log(`🔄 [C024] Symbol analysis unavailable, using regex fallback`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (options?.verbose) {
|
|
139
|
+
console.log(`🔧 [C024] No analysis methods succeeded, returning empty`);
|
|
140
|
+
}
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async analyzeFileBasic(filePath, options = {}) {
|
|
145
|
+
console.log(`🔧 [C024] analyzeFileBasic() called for: ${filePath}`);
|
|
146
|
+
console.log(`🔧 [C024] semanticEngine exists: ${!!this.semanticEngine}`);
|
|
147
|
+
console.log(`🔧 [C024] symbolAnalyzer exists: ${!!this.symbolAnalyzer}`);
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
// Try symbol-based analysis first
|
|
151
|
+
if (this.semanticEngine?.isSymbolEngineReady?.() &&
|
|
152
|
+
this.semanticEngine.project) {
|
|
153
|
+
|
|
154
|
+
if (this.verbose) {
|
|
155
|
+
console.log(`🔍 [C024] Using symbol-based analysis for ${filePath}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const violations = await this.symbolAnalyzer.analyzeFileBasic(filePath, options);
|
|
159
|
+
return violations;
|
|
160
|
+
}
|
|
161
|
+
} catch (error) {
|
|
162
|
+
if (this.verbose) {
|
|
163
|
+
console.warn(`⚠️ [C024] Symbol analysis failed: ${error.message}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Methods for compatibility with different engine invocation patterns
|
|
170
|
+
*/
|
|
171
|
+
async analyzeFileWithSymbols(filePath, options = {}) {
|
|
172
|
+
return this.analyzeFile(filePath, options);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async analyzeWithSemantics(filePath, options = {}) {
|
|
176
|
+
return this.analyzeFile(filePath, options);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
module.exports = C024Analyzer;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "C024",
|
|
3
|
+
"name": "C024_do_not_scatter_hardcoded_constants_throughout_the_logic",
|
|
4
|
+
"category": "architecture",
|
|
5
|
+
"description": "C024 - Do not scatter hardcoded constants throughout the logic.",
|
|
6
|
+
"severity": "warning",
|
|
7
|
+
"enabled": true,
|
|
8
|
+
"semantic": {
|
|
9
|
+
"enabled": true,
|
|
10
|
+
"priority": "high",
|
|
11
|
+
"fallback": "heuristic"
|
|
12
|
+
},
|
|
13
|
+
"patterns": {
|
|
14
|
+
"include": [
|
|
15
|
+
"**/*.js",
|
|
16
|
+
"**/*.ts",
|
|
17
|
+
"**/*.jsx",
|
|
18
|
+
"**/*.tsx"
|
|
19
|
+
],
|
|
20
|
+
"exclude": [
|
|
21
|
+
"**/*.test.*",
|
|
22
|
+
"**/*.spec.*",
|
|
23
|
+
"**/*.mock.*",
|
|
24
|
+
"**/test/**",
|
|
25
|
+
"**/tests/**",
|
|
26
|
+
"**/spec/**"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"options": {
|
|
30
|
+
"strictMode": false,
|
|
31
|
+
"allowedDbMethods": [],
|
|
32
|
+
"repositoryPatterns": [
|
|
33
|
+
"*Repository*",
|
|
34
|
+
"*Repo*",
|
|
35
|
+
"*DAO*",
|
|
36
|
+
"*Store*"
|
|
37
|
+
],
|
|
38
|
+
"servicePatterns": [
|
|
39
|
+
"*Service*",
|
|
40
|
+
"*UseCase*",
|
|
41
|
+
"*Handler*",
|
|
42
|
+
"*Manager*"
|
|
43
|
+
],
|
|
44
|
+
"complexityThreshold": {
|
|
45
|
+
"methodLength": 200,
|
|
46
|
+
"cyclomaticComplexity": 5,
|
|
47
|
+
"nestedDepth": 3
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|