@sun-asterisk/sunlint 1.3.0 → 1.3.1
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 +68 -1
- package/CONTRIBUTING.md +1179 -54
- 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 +23 -4
- package/config/rules/S027-categories.json +122 -0
- package/config/rules/enhanced-rules-registry.json +136 -75
- package/config/rules/rules-registry-generated.json +2 -2
- package/config/rules/rules-registry.json +13 -1
- 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 -3
- package/core/semantic-engine.js +117 -19
- 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 +71 -13
- package/integrations/eslint/plugin/index.js +0 -2
- package/origin-rules/common-en.md +8 -8
- package/package.json +1 -1
- package/rules/common/C017_constructor_logic/analyzer.js +254 -17
- package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -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 +230 -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 +1 -0
- 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/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/scripts/prepare-release.sh +1 -1
- 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/CONTRIBUTING.md
CHANGED
|
@@ -62,92 +62,652 @@ SunLint uses a **multi-engine architecture** with rule mapping system:
|
|
|
62
62
|
- `config/eslint-rule-mapping.json` - ESLint engine rule mappings
|
|
63
63
|
- `origin-rules/` - Original rule definitions (markdown format)
|
|
64
64
|
|
|
65
|
-
### **Adding a New Rule (
|
|
65
|
+
### **Adding a New Rule (Modern Approach - 2025)**
|
|
66
66
|
|
|
67
|
-
#### **Step 1: Choose Rule Implementation
|
|
67
|
+
#### **Step 1: Choose Rule Implementation Strategy**
|
|
68
68
|
|
|
69
|
-
**
|
|
70
|
-
|
|
69
|
+
**Modern Approach: Hybrid Symbol + Regex Analysis (Recommended)**
|
|
70
|
+
Following the successful C035 pattern, implement rules with both symbol-based and regex-based analyzers for maximum accuracy and fallback coverage.
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
#### **Step 2: Create Rule Directory Structure**
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# Create rule directory
|
|
76
|
+
mkdir -p rules/common/C042_no_hardcoded_config
|
|
77
|
+
|
|
78
|
+
# Create required files
|
|
79
|
+
touch rules/common/C042_no_hardcoded_config/analyzer.js
|
|
80
|
+
touch rules/common/C042_no_hardcoded_config/symbol-based-analyzer.js
|
|
81
|
+
touch rules/common/C042_no_hardcoded_config/regex-based-analyzer.js
|
|
82
|
+
touch rules/common/C042_no_hardcoded_config/config.json
|
|
83
|
+
touch rules/common/C042_no_hardcoded_config/STRATEGY.md
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### **Step 3: Implement Main Analyzer (Hybrid Orchestrator)**
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
// rules/common/C042_no_hardcoded_config/analyzer.js
|
|
90
|
+
const C042SymbolBasedAnalyzer = require('./symbol-based-analyzer.js');
|
|
91
|
+
const C042RegexBasedAnalyzer = require('./regex-based-analyzer.js');
|
|
92
|
+
|
|
93
|
+
class C042Analyzer {
|
|
94
|
+
constructor(semanticEngine = null) {
|
|
95
|
+
this.ruleId = 'C042';
|
|
96
|
+
this.ruleName = 'No Hardcoded Configuration Values';
|
|
97
|
+
this.description = 'Avoid hardcoding configuration values in business logic';
|
|
98
|
+
this.semanticEngine = semanticEngine;
|
|
99
|
+
this.verbose = false;
|
|
100
|
+
|
|
101
|
+
// Configuration
|
|
102
|
+
this.config = {
|
|
103
|
+
useSymbolBased: true, // Primary approach
|
|
104
|
+
fallbackToRegex: true, // Only when symbol fails completely
|
|
105
|
+
symbolBasedOnly: false // Can be set to true for pure mode
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Initialize both analyzers
|
|
109
|
+
this.symbolAnalyzer = new C042SymbolBasedAnalyzer(semanticEngine);
|
|
110
|
+
this.regexAnalyzer = new C042RegexBasedAnalyzer(semanticEngine);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async initialize(semanticEngine = null) {
|
|
114
|
+
if (semanticEngine) {
|
|
115
|
+
this.semanticEngine = semanticEngine;
|
|
116
|
+
}
|
|
117
|
+
this.verbose = semanticEngine?.verbose || false;
|
|
118
|
+
|
|
119
|
+
// Initialize both analyzers
|
|
120
|
+
await this.symbolAnalyzer.initialize(semanticEngine);
|
|
121
|
+
await this.regexAnalyzer.initialize(semanticEngine);
|
|
122
|
+
|
|
123
|
+
if (this.verbose) {
|
|
124
|
+
console.log(`🔧 [C042 Hybrid] Analyzer initialized`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async analyzeFileBasic(filePath, options = {}) {
|
|
129
|
+
try {
|
|
130
|
+
// Try symbol-based analysis first
|
|
131
|
+
if (this.semanticEngine?.isSymbolEngineReady?.() &&
|
|
132
|
+
this.semanticEngine.project) {
|
|
133
|
+
|
|
134
|
+
if (this.verbose) {
|
|
135
|
+
console.log(`🔍 [C042] Using symbol-based analysis for ${filePath}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const violations = await this.symbolAnalyzer.analyzeFileBasic(filePath, options);
|
|
139
|
+
return violations;
|
|
140
|
+
}
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (this.verbose) {
|
|
143
|
+
console.warn(`⚠️ [C042] Symbol analysis failed: ${error.message}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Fallback to regex-based analysis
|
|
148
|
+
if (this.verbose) {
|
|
149
|
+
console.log(`🔄 [C042] Using regex-based analysis (fallback) for ${filePath}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return await this.regexAnalyzer.analyzeFileBasic(filePath, options);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async analyze(files, language, options = {}) {
|
|
156
|
+
const violations = [];
|
|
157
|
+
|
|
158
|
+
for (const filePath of files) {
|
|
159
|
+
try {
|
|
160
|
+
const fileViolations = await this.analyzeFileBasic(filePath, options);
|
|
161
|
+
violations.push(...fileViolations);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
if (this.verbose) {
|
|
164
|
+
console.warn(`❌ [C042] Analysis failed for ${filePath}:`, error.message);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return violations;
|
|
170
|
+
}
|
|
76
171
|
}
|
|
172
|
+
|
|
173
|
+
module.exports = C042Analyzer;
|
|
77
174
|
```
|
|
78
175
|
|
|
79
|
-
|
|
176
|
+
#### **Step 4: Implement Symbol-Based Analyzer (Primary)**
|
|
177
|
+
|
|
178
|
+
```javascript
|
|
179
|
+
// rules/common/C042_no_hardcoded_config/symbol-based-analyzer.js
|
|
180
|
+
class C042SymbolBasedAnalyzer {
|
|
181
|
+
constructor(semanticEngine = null) {
|
|
182
|
+
this.ruleId = 'C042';
|
|
183
|
+
this.ruleName = 'No Hardcoded Configuration Values (Symbol-Based)';
|
|
184
|
+
this.semanticEngine = semanticEngine;
|
|
185
|
+
this.verbose = false;
|
|
186
|
+
|
|
187
|
+
// Configuration patterns to detect
|
|
188
|
+
this.configPatterns = [
|
|
189
|
+
/^API_/i, /^DATABASE_/i, /^DB_/i,
|
|
190
|
+
/^MAX_/i, /^MIN_/i, /^LIMIT_/i,
|
|
191
|
+
/^TIMEOUT/i, /^PORT$/i, /^HOST$/i,
|
|
192
|
+
/^SECRET/i, /^KEY$/i, /^TOKEN/i
|
|
193
|
+
];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async initialize(semanticEngine = null) {
|
|
197
|
+
if (semanticEngine) {
|
|
198
|
+
this.semanticEngine = semanticEngine;
|
|
199
|
+
}
|
|
200
|
+
this.verbose = semanticEngine?.verbose || false;
|
|
201
|
+
|
|
202
|
+
if (this.verbose) {
|
|
203
|
+
console.log(`🔧 [C042 Symbol-Based] Analyzer initialized`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async analyzeFileBasic(filePath, options = {}) {
|
|
208
|
+
const violations = [];
|
|
209
|
+
|
|
210
|
+
if (!this.semanticEngine?.project) {
|
|
211
|
+
return violations;
|
|
212
|
+
}
|
|
80
213
|
|
|
81
|
-
|
|
82
|
-
|
|
214
|
+
try {
|
|
215
|
+
const sourceFile = this.semanticEngine.project.getSourceFileByFilePath(filePath);
|
|
216
|
+
if (!sourceFile) {
|
|
217
|
+
return violations;
|
|
218
|
+
}
|
|
83
219
|
|
|
84
|
-
|
|
220
|
+
// Analyze variable declarations
|
|
221
|
+
const variableDeclarations = sourceFile.getVariableDeclarations();
|
|
222
|
+
for (const declaration of variableDeclarations) {
|
|
223
|
+
const violation = this.analyzeVariableDeclaration(declaration, filePath);
|
|
224
|
+
if (violation) {
|
|
225
|
+
violations.push(violation);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
85
228
|
|
|
86
|
-
|
|
229
|
+
return violations;
|
|
230
|
+
} catch (error) {
|
|
231
|
+
if (this.verbose) {
|
|
232
|
+
console.warn(`⚠️ [C042 Symbol-Based] Analysis failed for ${filePath}:`, error.message);
|
|
233
|
+
}
|
|
234
|
+
return violations;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
analyzeVariableDeclaration(declaration, filePath) {
|
|
239
|
+
const name = declaration.getName();
|
|
240
|
+
const initializer = declaration.getInitializer();
|
|
241
|
+
|
|
242
|
+
// Check if variable name suggests configuration
|
|
243
|
+
if (!this.isConfigVariable(name)) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Check if value is hardcoded
|
|
248
|
+
if (this.isHardcodedValue(initializer)) {
|
|
249
|
+
const startLineNumber = declaration.getStartLineNumber();
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
ruleId: this.ruleId,
|
|
253
|
+
severity: 'warning',
|
|
254
|
+
message: `Configuration variable '${name}' should not be hardcoded`,
|
|
255
|
+
source: this.ruleId,
|
|
256
|
+
file: filePath,
|
|
257
|
+
line: startLineNumber,
|
|
258
|
+
column: declaration.getStart() - declaration.getStartLinePos() + 1,
|
|
259
|
+
description: `Symbol-based analysis detected hardcoded configuration value. Use environment variables or config files.`,
|
|
260
|
+
suggestion: `Use process.env.${name} or load from configuration file`,
|
|
261
|
+
category: 'maintainability'
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
isConfigVariable(name) {
|
|
269
|
+
return this.configPatterns.some(pattern => pattern.test(name));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
isHardcodedValue(initializer) {
|
|
273
|
+
if (!initializer) return false;
|
|
274
|
+
|
|
275
|
+
// Check for literal values (strings, numbers)
|
|
276
|
+
if (initializer.getKind() === ts.SyntaxKind.StringLiteral ||
|
|
277
|
+
initializer.getKind() === ts.SyntaxKind.NumericLiteral) {
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Check for property access to process.env
|
|
282
|
+
if (initializer.getText().includes('process.env')) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
module.exports = C042SymbolBasedAnalyzer;
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
#### **Step 5: Implement Regex-Based Analyzer (Fallback)**
|
|
87
294
|
|
|
88
|
-
**For ESLint Engine Rules:**
|
|
89
295
|
```javascript
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
296
|
+
// rules/common/C042_no_hardcoded_config/regex-based-analyzer.js
|
|
297
|
+
class C042RegexBasedAnalyzer {
|
|
298
|
+
constructor(semanticEngine = null) {
|
|
299
|
+
this.ruleId = 'C042';
|
|
300
|
+
this.ruleName = 'No Hardcoded Configuration Values (Regex-Based)';
|
|
301
|
+
this.semanticEngine = semanticEngine;
|
|
302
|
+
this.verbose = false;
|
|
303
|
+
|
|
304
|
+
// Patterns for hardcoded config detection
|
|
305
|
+
this.hardcodedPatterns = [
|
|
306
|
+
/const\s+(API_\w+|DATABASE_\w+|DB_\w+)\s*=\s*["'][^"']+["']/gi,
|
|
307
|
+
/const\s+(MAX_\w+|MIN_\w+|LIMIT_\w+)\s*=\s*\d+/gi,
|
|
308
|
+
/const\s+(TIMEOUT|PORT|HOST)\s*=\s*["']\w+["']|\d+/gi
|
|
309
|
+
];
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async initialize(semanticEngine = null) {
|
|
313
|
+
if (semanticEngine) {
|
|
314
|
+
this.semanticEngine = semanticEngine;
|
|
315
|
+
}
|
|
316
|
+
this.verbose = semanticEngine?.verbose || false;
|
|
317
|
+
|
|
318
|
+
if (this.verbose) {
|
|
319
|
+
console.log(`🔧 [C042 Regex-Based] Analyzer initialized`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async analyzeFileBasic(filePath, options = {}) {
|
|
324
|
+
const fs = require('fs');
|
|
325
|
+
const violations = [];
|
|
326
|
+
|
|
327
|
+
if (!fs.existsSync(filePath)) {
|
|
328
|
+
return violations;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
332
|
+
const lines = content.split('\n');
|
|
333
|
+
|
|
334
|
+
for (let i = 0; i < lines.length; i++) {
|
|
335
|
+
const line = lines[i];
|
|
336
|
+
|
|
337
|
+
for (const pattern of this.hardcodedPatterns) {
|
|
338
|
+
pattern.lastIndex = 0; // Reset regex
|
|
339
|
+
let match;
|
|
340
|
+
|
|
341
|
+
while ((match = pattern.exec(line)) !== null) {
|
|
342
|
+
// Skip if using process.env
|
|
343
|
+
if (line.includes('process.env')) {
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
violations.push({
|
|
348
|
+
ruleId: this.ruleId,
|
|
349
|
+
severity: 'warning',
|
|
350
|
+
message: `Configuration variable '${match[1]}' should not be hardcoded`,
|
|
351
|
+
source: this.ruleId,
|
|
352
|
+
file: filePath,
|
|
353
|
+
line: i + 1,
|
|
354
|
+
column: match.index + 1,
|
|
355
|
+
description: `[REGEX-FALLBACK] Hardcoded configuration detected. Use environment variables or config files.`,
|
|
356
|
+
suggestion: `Use process.env.${match[1]} or load from configuration file`,
|
|
357
|
+
category: 'maintainability'
|
|
105
358
|
});
|
|
106
359
|
}
|
|
107
360
|
}
|
|
108
|
-
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return violations;
|
|
109
364
|
}
|
|
110
|
-
|
|
365
|
+
|
|
366
|
+
async analyze(files, language, options = {}) {
|
|
367
|
+
const violations = [];
|
|
368
|
+
|
|
369
|
+
for (const filePath of files) {
|
|
370
|
+
try {
|
|
371
|
+
const fileViolations = await this.analyzeFileBasic(filePath, options);
|
|
372
|
+
violations.push(...fileViolations);
|
|
373
|
+
} catch (error) {
|
|
374
|
+
if (this.verbose) {
|
|
375
|
+
console.warn(`❌ [C042 Regex] Analysis failed for ${filePath}:`, error.message);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return violations;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
module.exports = C042RegexBasedAnalyzer;
|
|
111
385
|
```
|
|
112
386
|
|
|
113
|
-
**
|
|
387
|
+
#### **Step 6: Create Rule Configuration**
|
|
388
|
+
|
|
389
|
+
```json
|
|
390
|
+
// rules/common/C042_no_hardcoded_config/config.json
|
|
391
|
+
{
|
|
392
|
+
"ruleId": "C042",
|
|
393
|
+
"name": "No Hardcoded Configuration Values",
|
|
394
|
+
"description": "Avoid hardcoding configuration values in business logic",
|
|
395
|
+
"category": "maintainability",
|
|
396
|
+
"severity": "warning",
|
|
397
|
+
"languages": ["typescript", "javascript"],
|
|
398
|
+
"version": "1.0.0",
|
|
399
|
+
"tags": ["configuration", "maintainability", "environment"],
|
|
400
|
+
"patterns": {
|
|
401
|
+
"configVariables": [
|
|
402
|
+
"API_", "DATABASE_", "DB_", "MAX_", "MIN_", "LIMIT_",
|
|
403
|
+
"TIMEOUT", "PORT", "HOST", "SECRET", "KEY", "TOKEN"
|
|
404
|
+
],
|
|
405
|
+
"excludePatterns": [
|
|
406
|
+
"process.env", "config.", "getConfig(", "loadConfig("
|
|
407
|
+
]
|
|
408
|
+
},
|
|
409
|
+
"examples": {
|
|
410
|
+
"bad": [
|
|
411
|
+
"const API_URL = 'https://api.example.com';",
|
|
412
|
+
"const MAX_RETRIES = 5;",
|
|
413
|
+
"const DATABASE_PASSWORD = 'secret123';"
|
|
414
|
+
],
|
|
415
|
+
"good": [
|
|
416
|
+
"const API_URL = process.env.API_URL;",
|
|
417
|
+
"const MAX_RETRIES = parseInt(process.env.MAX_RETRIES || '3');",
|
|
418
|
+
"const config = loadConfigFromFile();"
|
|
419
|
+
]
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
#### **Step 7: Document Analysis Strategy**
|
|
425
|
+
|
|
426
|
+
```markdown
|
|
427
|
+
<!-- rules/common/C042_no_hardcoded_config/STRATEGY.md -->
|
|
428
|
+
# C042 Analysis Strategy: No Hardcoded Configuration Values
|
|
429
|
+
|
|
430
|
+
## Overview
|
|
431
|
+
Hybrid approach using symbol-based analysis as primary with regex fallback.
|
|
432
|
+
|
|
433
|
+
## Symbol-Based Analysis (Primary)
|
|
434
|
+
- Uses ts-morph AST to analyze variable declarations
|
|
435
|
+
- Detects configuration variable patterns by name
|
|
436
|
+
- Checks initializer values for hardcoded literals
|
|
437
|
+
- Excludes process.env and config function calls
|
|
438
|
+
|
|
439
|
+
## Regex-Based Analysis (Fallback)
|
|
440
|
+
- Pattern matching for common hardcoded config scenarios
|
|
441
|
+
- Simpler but broader detection
|
|
442
|
+
- Used when symbol analysis unavailable
|
|
443
|
+
|
|
444
|
+
## Strategy Selection
|
|
445
|
+
1. **Symbol-based**: When ts-morph project available (95% accuracy)
|
|
446
|
+
2. **Regex-fallback**: When symbol analysis fails (80% accuracy)
|
|
447
|
+
|
|
448
|
+
## Testing Approach
|
|
449
|
+
Test both analyzers independently and verify hybrid orchestration.
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
## 🆕 **Modern Rule Implementation Guide (2025)**
|
|
455
|
+
|
|
456
|
+
Based on the successful C035 implementation, here's the **current recommended approach** for adding new rules:
|
|
457
|
+
|
|
458
|
+
### **Critical Success Patterns**
|
|
459
|
+
|
|
460
|
+
#### **✅ 1. Constructor Pattern (REQUIRED)**
|
|
114
461
|
```javascript
|
|
115
|
-
//
|
|
116
|
-
|
|
462
|
+
// ✅ CORRECT - Works with semantic engine injection
|
|
463
|
+
class C042Analyzer {
|
|
464
|
+
constructor(options = {}) {
|
|
465
|
+
this.semanticEngine = options.semanticEngine || null;
|
|
466
|
+
this.verbose = options.verbose || false;
|
|
467
|
+
// ...
|
|
468
|
+
}
|
|
469
|
+
}
|
|
117
470
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
471
|
+
// ❌ WRONG - Causes semantic engine injection failure
|
|
472
|
+
class C042Analyzer {
|
|
473
|
+
constructor(semanticEngine = null) {
|
|
474
|
+
this.semanticEngine = semanticEngine;
|
|
475
|
+
// ...
|
|
121
476
|
}
|
|
477
|
+
}
|
|
478
|
+
```
|
|
122
479
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
480
|
+
#### **✅ 2. Registry Configuration (REQUIRED)**
|
|
481
|
+
|
|
482
|
+
**Enhanced Rules Registry** (`config/rules/enhanced-rules-registry.json`):
|
|
483
|
+
```json
|
|
484
|
+
{
|
|
485
|
+
"C042": {
|
|
486
|
+
"name": "No Hardcoded Configuration Values",
|
|
487
|
+
"description": "Avoid hardcoding configuration values in business logic",
|
|
488
|
+
"category": "maintainability",
|
|
489
|
+
"severity": "warning",
|
|
490
|
+
"languages": ["typescript", "javascript", "dart", "kotlin"],
|
|
491
|
+
"analyzer": "./rules/common/C042_no_hardcoded_config/analyzer.js",
|
|
492
|
+
"config": "./rules/common/C042_no_hardcoded_config/config.json",
|
|
493
|
+
"version": "1.0.0",
|
|
494
|
+
"status": "stable",
|
|
495
|
+
"tags": ["configuration", "hardcoding", "maintainability"],
|
|
496
|
+
"engineMappings": {
|
|
497
|
+
"eslint": ["no-magic-numbers", "@typescript-eslint/no-magic-numbers"]
|
|
127
498
|
}
|
|
128
|
-
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
**Rule Analysis Strategies** (`config/rule-analysis-strategies.js`):
|
|
504
|
+
```javascript
|
|
505
|
+
module.exports = {
|
|
506
|
+
hybridOptimal: {
|
|
507
|
+
'C042': {
|
|
508
|
+
reason: 'Configuration detection requires symbol resolution + pattern matching',
|
|
509
|
+
methods: ['semantic', 'regex'],
|
|
510
|
+
strategy: 'semantic-primary-regex-fallback',
|
|
511
|
+
accuracy: { semantic: 90, regex: 75, combined: 95 }
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
#### **✅ 3. Hybrid Architecture Template**
|
|
518
|
+
|
|
519
|
+
**Main Analyzer** (`analyzer.js`):
|
|
520
|
+
```javascript
|
|
521
|
+
const C042SymbolBasedAnalyzer = require('./symbol-based-analyzer.js');
|
|
522
|
+
const C042RegexBasedAnalyzer = require('./regex-based-analyzer.js');
|
|
523
|
+
|
|
524
|
+
class C042Analyzer {
|
|
525
|
+
constructor(options = {}) {
|
|
526
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
527
|
+
console.log(`🔧 [C042] Constructor called with options:`, !!options);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
this.ruleId = 'C042';
|
|
531
|
+
this.semanticEngine = options.semanticEngine || null;
|
|
532
|
+
this.verbose = options.verbose || false;
|
|
533
|
+
|
|
534
|
+
// Initialize analyzers with THIS.semanticEngine
|
|
535
|
+
this.symbolAnalyzer = new C042SymbolBasedAnalyzer(this.semanticEngine);
|
|
536
|
+
this.regexAnalyzer = new C042RegexBasedAnalyzer(this.semanticEngine);
|
|
537
|
+
|
|
538
|
+
this.config = {
|
|
539
|
+
useSymbolBased: true,
|
|
540
|
+
fallbackToRegex: true,
|
|
541
|
+
symbolBasedOnly: false
|
|
542
|
+
};
|
|
129
543
|
}
|
|
130
544
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
545
|
+
async analyze(files, language = 'javascript', options = {}) {
|
|
546
|
+
const allViolations = [];
|
|
547
|
+
|
|
548
|
+
for (const filePath of files) {
|
|
549
|
+
const violations = await this.analyzeFile(filePath, options);
|
|
550
|
+
allViolations.push(...violations);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
return allViolations;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
async analyzeFile(filePath, options = {}) {
|
|
557
|
+
// 1. Try Symbol-based analysis first (primary)
|
|
558
|
+
if (this.config.useSymbolBased &&
|
|
559
|
+
this.semanticEngine?.project &&
|
|
560
|
+
this.semanticEngine?.initialized) {
|
|
561
|
+
try {
|
|
562
|
+
const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
|
|
563
|
+
if (sourceFile) {
|
|
564
|
+
const violations = await this.symbolAnalyzer.analyzeFileWithSymbols(filePath, options);
|
|
565
|
+
violations.forEach(v => v.analysisStrategy = 'symbol-based');
|
|
566
|
+
return violations;
|
|
567
|
+
}
|
|
568
|
+
} catch (error) {
|
|
569
|
+
console.warn(`⚠️ [C042] Symbol analysis failed: ${error.message}`);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// 2. Fallback to regex-based analysis
|
|
574
|
+
if (this.config.fallbackToRegex) {
|
|
575
|
+
const violations = await this.regexAnalyzer.analyzeFileBasic(filePath, options);
|
|
576
|
+
violations.forEach(v => v.analysisStrategy = 'regex-based');
|
|
577
|
+
return violations;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
return [];
|
|
136
581
|
}
|
|
137
582
|
}
|
|
138
583
|
|
|
139
|
-
module.exports =
|
|
584
|
+
module.exports = C042Analyzer;
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
#### **✅ 4. Testing & Validation**
|
|
588
|
+
|
|
589
|
+
**Debug Output Verification**:
|
|
590
|
+
```bash
|
|
591
|
+
SUNLINT_DEBUG=true node cli.js --input test-files/c042-test.js --rules C042 --engine=heuristic --debug
|
|
140
592
|
```
|
|
141
593
|
|
|
142
|
-
|
|
594
|
+
**Expected Output**:
|
|
595
|
+
```
|
|
596
|
+
🔧 [HeuristicEngine] Rule C042: semantic (approach: hybrid-combined)
|
|
597
|
+
🔧 [C042] Constructor called with options: true
|
|
598
|
+
🔧 [C042] Options type: object [ 'verbose', 'semanticEngine' ]
|
|
599
|
+
🔧 [C042] Trying symbol-based analysis...
|
|
600
|
+
✅ [C042] Symbol-based analysis: X violations
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
#### **✅ 5. Common Pitfalls & Solutions**
|
|
604
|
+
|
|
605
|
+
| ❌ Problem | ✅ Solution |
|
|
606
|
+
|------------|-------------|
|
|
607
|
+
| `constructor(semanticEngine)` | `constructor(options = {})` |
|
|
608
|
+
| Missing from `rule-analysis-strategies.js` | Add to `hybridOptimal` section |
|
|
609
|
+
| `ast (approach: ast-primary)` output | Ensure rule in strategies config |
|
|
610
|
+
| `this.semanticEngine?.project: false` | Check engine injection & initialization |
|
|
611
|
+
| No debug output from constructor | Verify constructor signature |
|
|
612
|
+
|
|
613
|
+
#### **✅ 6. Validation Checklist**
|
|
614
|
+
|
|
615
|
+
Before submitting a new rule:
|
|
616
|
+
|
|
617
|
+
- [ ] Constructor uses `options = {}` pattern
|
|
618
|
+
- [ ] Rule registered in `enhanced-rules-registry.json`
|
|
619
|
+
- [ ] Strategy defined in `rule-analysis-strategies.js` `hybridOptimal`
|
|
620
|
+
- [ ] Symbol-based analyzer implemented with ts-morph
|
|
621
|
+
- [ ] Regex-based analyzer implemented with pattern matching
|
|
622
|
+
- [ ] Debug output shows `semantic (approach: hybrid-combined)`
|
|
623
|
+
- [ ] Test cases detect expected violations accurately
|
|
624
|
+
- [ ] No modifications to engine core files (registry-driven only)
|
|
625
|
+
- [ ] Performance acceptable for large codebases
|
|
626
|
+
|
|
627
|
+
---
|
|
628
|
+
|
|
629
|
+
## 📚 **Legacy Implementation Examples**
|
|
630
|
+
|
|
631
|
+
The following examples show older patterns for reference, but **use the Modern Guide above** for new rules:
|
|
632
|
+
|
|
633
|
+
#### **Step 7: Document Analysis Strategy**
|
|
143
634
|
|
|
144
|
-
Add rule definition to origin rules:
|
|
145
635
|
```markdown
|
|
146
|
-
<!--
|
|
147
|
-
|
|
636
|
+
<!-- rules/common/C042_no_hardcoded_config/STRATEGY.md -->
|
|
637
|
+
# C042 Analysis Strategy: No Hardcoded Configuration Values
|
|
148
638
|
|
|
149
|
-
|
|
150
|
-
|
|
639
|
+
## Overview
|
|
640
|
+
Hybrid approach using symbol-based analysis as primary with regex fallback.
|
|
641
|
+
|
|
642
|
+
## Symbol-Based Analysis (Primary)
|
|
643
|
+
- Uses ts-morph AST to analyze variable declarations
|
|
644
|
+
- Detects configuration variable patterns by name
|
|
645
|
+
- Checks initializer values for hardcoded literals
|
|
646
|
+
- Excludes process.env and config function calls
|
|
647
|
+
|
|
648
|
+
## Regex-Based Analysis (Fallback)
|
|
649
|
+
- Pattern matching for common hardcoded config scenarios
|
|
650
|
+
- Simpler but broader detection
|
|
651
|
+
- Used when symbol analysis unavailable
|
|
652
|
+
|
|
653
|
+
## Strategy Selection
|
|
654
|
+
1. **Symbol-based**: When ts-morph project available (95% accuracy)
|
|
655
|
+
2. **Regex-fallback**: When symbol analysis fails (80% accuracy)
|
|
656
|
+
|
|
657
|
+
## Testing Approach
|
|
658
|
+
Test both analyzers independently and verify hybrid orchestration.
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
#### **Step 8: Register Rule in Rules Registry**
|
|
662
|
+
|
|
663
|
+
```json
|
|
664
|
+
// config/rules/rules-registry.json
|
|
665
|
+
{
|
|
666
|
+
"rules": {
|
|
667
|
+
"C042": {
|
|
668
|
+
"name": "No Hardcoded Configuration Values",
|
|
669
|
+
"description": "Avoid hardcoding configuration values in business logic",
|
|
670
|
+
"category": "maintainability",
|
|
671
|
+
"severity": "warning",
|
|
672
|
+
"languages": ["typescript", "javascript"],
|
|
673
|
+
"analyzer": "./rules/common/C042_no_hardcoded_config/analyzer.js",
|
|
674
|
+
"config": "./rules/common/C042_no_hardcoded_config/config.json",
|
|
675
|
+
"version": "1.0.0",
|
|
676
|
+
"status": "experimental",
|
|
677
|
+
"tags": ["configuration", "maintainability", "environment"]
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
#### **Step 9: Add to Enhanced Rules Registry**
|
|
684
|
+
|
|
685
|
+
```javascript
|
|
686
|
+
// core/enhanced-rules-registry.js
|
|
687
|
+
loadEnginePreferences(options = {}) {
|
|
688
|
+
const preferences = {
|
|
689
|
+
// ... existing rules ...
|
|
690
|
+
'C042': ['heuristic', 'openai'], // Symbol + regex hybrid
|
|
691
|
+
// ... rest of rules ...
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
#### **Step 10: Add to Analysis Strategies**
|
|
697
|
+
|
|
698
|
+
```javascript
|
|
699
|
+
// config/rule-analysis-strategies.js
|
|
700
|
+
module.exports = {
|
|
701
|
+
astPreferred: {
|
|
702
|
+
// ... existing rules ...
|
|
703
|
+
'C042': {
|
|
704
|
+
reason: 'Configuration detection requires precise symbol analysis for variable declarations',
|
|
705
|
+
methods: ['ast', 'regex'],
|
|
706
|
+
accuracy: { ast: 95, regex: 80 }
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
```
|
|
151
711
|
|
|
152
712
|
**Description**: Avoid hardcoding configuration values in business logic
|
|
153
713
|
|
|
@@ -683,6 +1243,571 @@ node cli.js --rule=CXXX --input=test/fixtures
|
|
|
683
1243
|
**For Heuristic rules**: Create files in `custom-rules/`
|
|
684
1244
|
**For documentation**: Add to `origin-rules/` markdown files
|
|
685
1245
|
|
|
1246
|
+
---
|
|
1247
|
+
|
|
1248
|
+
## 🔬 **Modern Rule Development Guide (2024)**
|
|
1249
|
+
|
|
1250
|
+
*This section provides a comprehensive, step-by-step guide for implementing new rules using the modern hybrid architecture pattern (symbol-based + regex fallback), based on the successful C035 implementation.*
|
|
1251
|
+
|
|
1252
|
+
### **Quick Start: Creating a New Rule**
|
|
1253
|
+
|
|
1254
|
+
#### **Step 1: Create Rule Directory Structure**
|
|
1255
|
+
|
|
1256
|
+
```bash
|
|
1257
|
+
# Example: Creating C042 (No Hardcoded Configuration Values)
|
|
1258
|
+
mkdir -p rules/common/C042_no_hardcoded_config
|
|
1259
|
+
cd rules/common/C042_no_hardcoded_config
|
|
1260
|
+
```
|
|
1261
|
+
|
|
1262
|
+
#### **Step 2: Implement Main Analyzer (Hybrid Orchestrator)**
|
|
1263
|
+
|
|
1264
|
+
```javascript
|
|
1265
|
+
// analyzer.js - Main hybrid orchestrator
|
|
1266
|
+
const SymbolBasedAnalyzer = require('./symbol-based-analyzer');
|
|
1267
|
+
const RegexBasedAnalyzer = require('./regex-based-analyzer');
|
|
1268
|
+
|
|
1269
|
+
class C042NoHardcodedConfigAnalyzer {
|
|
1270
|
+
constructor(verbose = false) {
|
|
1271
|
+
this.verbose = verbose;
|
|
1272
|
+
this.symbolAnalyzer = new SymbolBasedAnalyzer(verbose);
|
|
1273
|
+
this.regexAnalyzer = new RegexBasedAnalyzer(verbose);
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
async analyzeFileBasic(filePath, options = {}) {
|
|
1277
|
+
if (this.verbose) {
|
|
1278
|
+
console.log(`🔍 [C042] Analyzing file: ${filePath}`);
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
try {
|
|
1282
|
+
// Primary: Symbol-based analysis
|
|
1283
|
+
const violations = await this.symbolAnalyzer.analyzeFileBasic(filePath, options);
|
|
1284
|
+
|
|
1285
|
+
if (this.verbose) {
|
|
1286
|
+
console.log(`✅ [C042] Symbol-based analysis completed. Found ${violations.length} violations.`);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
return violations;
|
|
1290
|
+
} catch (error) {
|
|
1291
|
+
if (this.verbose) {
|
|
1292
|
+
console.warn(`⚠️ [C042] Symbol analysis failed: ${error.message}`);
|
|
1293
|
+
console.log(`🔄 [C042] Falling back to regex analysis...`);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
// Fallback: Regex-based analysis
|
|
1297
|
+
try {
|
|
1298
|
+
const violations = await this.regexAnalyzer.analyzeFileBasic(filePath, options);
|
|
1299
|
+
|
|
1300
|
+
if (this.verbose) {
|
|
1301
|
+
console.log(`✅ [C042] Regex-fallback analysis completed. Found ${violations.length} violations.`);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
return violations;
|
|
1305
|
+
} catch (fallbackError) {
|
|
1306
|
+
if (this.verbose) {
|
|
1307
|
+
console.error(`❌ [C042] Both symbol and regex analysis failed: ${fallbackError.message}`);
|
|
1308
|
+
}
|
|
1309
|
+
return [];
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
module.exports = C042NoHardcodedConfigAnalyzer;
|
|
1316
|
+
```
|
|
1317
|
+
|
|
1318
|
+
#### **Step 3: Implement Symbol-Based Analyzer (Primary)**
|
|
1319
|
+
|
|
1320
|
+
```javascript
|
|
1321
|
+
// symbol-based-analyzer.js - High-accuracy AST analysis
|
|
1322
|
+
const { Project } = require('ts-morph');
|
|
1323
|
+
|
|
1324
|
+
class C042SymbolBasedAnalyzer {
|
|
1325
|
+
constructor(verbose = false) {
|
|
1326
|
+
this.verbose = verbose;
|
|
1327
|
+
this.project = new Project({
|
|
1328
|
+
useInMemoryFileSystem: true,
|
|
1329
|
+
compilerOptions: {
|
|
1330
|
+
allowJs: true,
|
|
1331
|
+
checkJs: false,
|
|
1332
|
+
},
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
async analyzeFileBasic(filePath, options = {}) {
|
|
1337
|
+
const violations = [];
|
|
1338
|
+
|
|
1339
|
+
try {
|
|
1340
|
+
const sourceFile = this.project.addSourceFileAtPath(filePath);
|
|
1341
|
+
|
|
1342
|
+
// Analyze variable declarations
|
|
1343
|
+
sourceFile.getVariableDeclarations().forEach(declaration => {
|
|
1344
|
+
const initializer = declaration.getInitializer();
|
|
1345
|
+
if (initializer && this.isHardcodedConfig(initializer, declaration.getName())) {
|
|
1346
|
+
violations.push({
|
|
1347
|
+
line: declaration.getStartLineNumber(),
|
|
1348
|
+
column: declaration.getStart() - declaration.getStartLinePos() + 1,
|
|
1349
|
+
message: `Hardcoded configuration value detected in variable '${declaration.getName()}'`,
|
|
1350
|
+
severity: 'warning',
|
|
1351
|
+
rule: 'C042'
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1354
|
+
});
|
|
1355
|
+
|
|
1356
|
+
// Analyze property assignments
|
|
1357
|
+
sourceFile.getDescendantsOfKind(ts.SyntaxKind.PropertyAssignment).forEach(prop => {
|
|
1358
|
+
const value = prop.getInitializer();
|
|
1359
|
+
if (value && this.isHardcodedConfig(value, prop.getName())) {
|
|
1360
|
+
violations.push({
|
|
1361
|
+
line: prop.getStartLineNumber(),
|
|
1362
|
+
column: prop.getStart() - prop.getStartLinePos() + 1,
|
|
1363
|
+
message: `Hardcoded configuration value in property '${prop.getName()}'`,
|
|
1364
|
+
severity: 'warning',
|
|
1365
|
+
rule: 'C042'
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
});
|
|
1369
|
+
|
|
1370
|
+
this.project.removeSourceFile(sourceFile);
|
|
1371
|
+
return violations;
|
|
1372
|
+
} catch (error) {
|
|
1373
|
+
throw new Error(`Symbol analysis failed: ${error.message}`);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
isHardcodedConfig(node, name) {
|
|
1378
|
+
// Check if the value looks like configuration
|
|
1379
|
+
const configPatterns = [
|
|
1380
|
+
/url/i, /host/i, /port/i, /timeout/i, /secret/i,
|
|
1381
|
+
/api[_-]?key/i, /password/i, /connection/i, /endpoint/i
|
|
1382
|
+
];
|
|
1383
|
+
|
|
1384
|
+
const nameMatches = configPatterns.some(pattern => pattern.test(name));
|
|
1385
|
+
|
|
1386
|
+
if (!nameMatches) return false;
|
|
1387
|
+
|
|
1388
|
+
// Check if value is hardcoded (string/number literal)
|
|
1389
|
+
return node.getKind() === ts.SyntaxKind.StringLiteral ||
|
|
1390
|
+
node.getKind() === ts.SyntaxKind.NumericLiteral;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
module.exports = C042SymbolBasedAnalyzer;
|
|
1395
|
+
```
|
|
1396
|
+
|
|
1397
|
+
#### **Step 4: Implement Regex-Based Analyzer (Fallback)**
|
|
1398
|
+
|
|
1399
|
+
```javascript
|
|
1400
|
+
// regex-based-analyzer.js - Broad coverage pattern matching
|
|
1401
|
+
const fs = require('fs');
|
|
1402
|
+
|
|
1403
|
+
class C042RegexBasedAnalyzer {
|
|
1404
|
+
constructor(verbose = false) {
|
|
1405
|
+
this.verbose = verbose;
|
|
1406
|
+
|
|
1407
|
+
// Configuration-related patterns
|
|
1408
|
+
this.configPatterns = [
|
|
1409
|
+
/(?:const|let|var)\s+(\w*(?:url|host|port|timeout|secret|key|password|connection|endpoint)\w*)\s*=\s*['"`]([^'"`]+)['"`]/gi,
|
|
1410
|
+
/(\w*(?:url|host|port|timeout|secret|key|password|connection|endpoint)\w*)\s*:\s*['"`]([^'"`]+)['"`]/gi,
|
|
1411
|
+
/(\w*(?:url|host|port|timeout|secret|key|password|connection|endpoint)\w*)\s*=\s*(\d+)/gi
|
|
1412
|
+
];
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
async analyzeFileBasic(filePath, options = {}) {
|
|
1416
|
+
const violations = [];
|
|
1417
|
+
|
|
1418
|
+
try {
|
|
1419
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
1420
|
+
const lines = content.split('\n');
|
|
1421
|
+
|
|
1422
|
+
this.configPatterns.forEach(pattern => {
|
|
1423
|
+
lines.forEach((line, index) => {
|
|
1424
|
+
let match;
|
|
1425
|
+
while ((match = pattern.exec(line)) !== null) {
|
|
1426
|
+
const variableName = match[1];
|
|
1427
|
+
const value = match[2];
|
|
1428
|
+
|
|
1429
|
+
if (this.isLikelyHardcodedConfig(variableName, value)) {
|
|
1430
|
+
violations.push({
|
|
1431
|
+
line: index + 1,
|
|
1432
|
+
column: match.index + 1,
|
|
1433
|
+
message: `Potential hardcoded configuration: '${variableName}' = '${value}'`,
|
|
1434
|
+
severity: 'warning',
|
|
1435
|
+
rule: 'C042'
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
pattern.lastIndex = 0; // Reset regex
|
|
1440
|
+
});
|
|
1441
|
+
});
|
|
1442
|
+
|
|
1443
|
+
return violations;
|
|
1444
|
+
} catch (error) {
|
|
1445
|
+
throw new Error(`Regex analysis failed: ${error.message}`);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
isLikelyHardcodedConfig(name, value) {
|
|
1450
|
+
// Skip obvious non-config values
|
|
1451
|
+
const skipPatterns = [
|
|
1452
|
+
/^(true|false|null|undefined)$/i,
|
|
1453
|
+
/^process\.env\./i,
|
|
1454
|
+
/^require\(/i,
|
|
1455
|
+
/^\w+\(/i // Function calls
|
|
1456
|
+
];
|
|
1457
|
+
|
|
1458
|
+
return !skipPatterns.some(pattern => pattern.test(value));
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
module.exports = C042RegexBasedAnalyzer;
|
|
1463
|
+
```
|
|
1464
|
+
|
|
1465
|
+
#### **Step 5: Create Rule Configuration**
|
|
1466
|
+
|
|
1467
|
+
```json
|
|
1468
|
+
// config.json - Rule metadata and examples
|
|
1469
|
+
{
|
|
1470
|
+
"id": "C042",
|
|
1471
|
+
"name": "No Hardcoded Configuration Values",
|
|
1472
|
+
"category": "Maintainability",
|
|
1473
|
+
"severity": "warning",
|
|
1474
|
+
"description": "Detects hardcoded configuration values that should use environment variables or config files",
|
|
1475
|
+
"rationale": "Hardcoded configuration makes applications less flexible and harder to deploy across different environments",
|
|
1476
|
+
"examples": {
|
|
1477
|
+
"bad": [
|
|
1478
|
+
"const API_URL = 'https://api.example.com';",
|
|
1479
|
+
"const DB_HOST = 'localhost';",
|
|
1480
|
+
"const MAX_CONNECTIONS = 100;",
|
|
1481
|
+
"config: { port: 3000, host: 'localhost' }"
|
|
1482
|
+
],
|
|
1483
|
+
"good": [
|
|
1484
|
+
"const API_URL = process.env.API_URL;",
|
|
1485
|
+
"const DB_HOST = process.env.DB_HOST || 'localhost';",
|
|
1486
|
+
"const config = loadConfigFromFile();",
|
|
1487
|
+
"const PORT = parseInt(process.env.PORT || '3000');"
|
|
1488
|
+
]
|
|
1489
|
+
},
|
|
1490
|
+
"tags": ["configuration", "deployment", "security", "maintainability"],
|
|
1491
|
+
"accuracy": {
|
|
1492
|
+
"symbol_based": "high",
|
|
1493
|
+
"regex_based": "medium"
|
|
1494
|
+
},
|
|
1495
|
+
"performance": {
|
|
1496
|
+
"symbol_based": "medium",
|
|
1497
|
+
"regex_based": "fast"
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
```
|
|
1501
|
+
|
|
1502
|
+
#### **Step 6: Document Analysis Strategy**
|
|
1503
|
+
|
|
1504
|
+
```markdown
|
|
1505
|
+
<!-- STRATEGY.md - Analysis approach documentation -->
|
|
1506
|
+
# C042: No Hardcoded Configuration Values - Analysis Strategy
|
|
1507
|
+
|
|
1508
|
+
## Overview
|
|
1509
|
+
This rule detects hardcoded configuration values using a hybrid approach combining symbol-based AST analysis with regex-based pattern matching.
|
|
1510
|
+
|
|
1511
|
+
## Analysis Approaches
|
|
1512
|
+
|
|
1513
|
+
### Symbol-Based Analysis (Primary)
|
|
1514
|
+
- **Tool**: ts-morph AST parser
|
|
1515
|
+
- **Accuracy**: High (95%+)
|
|
1516
|
+
- **Coverage**: Variable declarations, property assignments
|
|
1517
|
+
- **Strengths**:
|
|
1518
|
+
- Understands code context
|
|
1519
|
+
- Low false positives
|
|
1520
|
+
- Handles complex expressions
|
|
1521
|
+
- **Limitations**:
|
|
1522
|
+
- Requires valid syntax
|
|
1523
|
+
- Slower performance
|
|
1524
|
+
|
|
1525
|
+
### Regex-Based Analysis (Fallback)
|
|
1526
|
+
- **Tool**: Regular expressions
|
|
1527
|
+
- **Accuracy**: Medium (80%+)
|
|
1528
|
+
- **Coverage**: String/number patterns in config-like names
|
|
1529
|
+
- **Strengths**:
|
|
1530
|
+
- Fast execution
|
|
1531
|
+
- Works with any file
|
|
1532
|
+
- Broad pattern coverage
|
|
1533
|
+
- **Limitations**:
|
|
1534
|
+
- Higher false positives
|
|
1535
|
+
- Limited context understanding
|
|
1536
|
+
|
|
1537
|
+
## Detection Patterns
|
|
1538
|
+
|
|
1539
|
+
### Configuration Indicators
|
|
1540
|
+
- Variable/property names containing: url, host, port, timeout, secret, key, password, connection, endpoint
|
|
1541
|
+
- Literal string/number values
|
|
1542
|
+
- Assignment patterns
|
|
1543
|
+
|
|
1544
|
+
### Exclusions
|
|
1545
|
+
- Environment variable references (process.env.*)
|
|
1546
|
+
- Function calls
|
|
1547
|
+
- Boolean/null literals
|
|
1548
|
+
- Computed values
|
|
1549
|
+
|
|
1550
|
+
## Testing Strategy
|
|
1551
|
+
- Unit tests for each analyzer
|
|
1552
|
+
- Integration tests for hybrid orchestration
|
|
1553
|
+
- Real-world project validation
|
|
1554
|
+
- Performance benchmarking
|
|
1555
|
+
```
|
|
1556
|
+
|
|
1557
|
+
#### **Step 7: Add to Rules Registry**
|
|
1558
|
+
|
|
1559
|
+
```json
|
|
1560
|
+
// config/rules/rules-registry.json - Add rule definition
|
|
1561
|
+
{
|
|
1562
|
+
"C042": {
|
|
1563
|
+
"id": "C042",
|
|
1564
|
+
"name": "No Hardcoded Configuration Values",
|
|
1565
|
+
"category": "Maintainability",
|
|
1566
|
+
"severity": "warning",
|
|
1567
|
+
"description": "Detects hardcoded configuration values that should use environment variables or config files",
|
|
1568
|
+
"engine": "heuristic",
|
|
1569
|
+
"analyzerPath": "rules/common/C042_no_hardcoded_config/analyzer.js",
|
|
1570
|
+
"configPath": "rules/common/C042_no_hardcoded_config/config.json",
|
|
1571
|
+
"enabled": true,
|
|
1572
|
+
"languages": ["javascript", "typescript"],
|
|
1573
|
+
"tags": ["configuration", "deployment", "security"]
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
```
|
|
1577
|
+
|
|
1578
|
+
#### **Step 8: Register with Enhanced Rules Registry**
|
|
1579
|
+
|
|
1580
|
+
```javascript
|
|
1581
|
+
// core/enhanced-rules-registry.js - Add engine preferences
|
|
1582
|
+
const enginePreferences = {
|
|
1583
|
+
// ... existing rules
|
|
1584
|
+
'C042': ['heuristic'], // Add your rule here
|
|
1585
|
+
};
|
|
1586
|
+
```
|
|
1587
|
+
|
|
1588
|
+
#### **Step 9: Add to Analysis Strategies**
|
|
1589
|
+
|
|
1590
|
+
```javascript
|
|
1591
|
+
// config/rule-analysis-strategies.js - Add to AST preferred rules
|
|
1592
|
+
const strategies = {
|
|
1593
|
+
astPreferred: [
|
|
1594
|
+
// ... existing rules
|
|
1595
|
+
'C042', // Add your rule here
|
|
1596
|
+
],
|
|
1597
|
+
regexOnly: [
|
|
1598
|
+
// Rules that only use regex
|
|
1599
|
+
],
|
|
1600
|
+
hybridOnly: [
|
|
1601
|
+
// Rules requiring both approaches
|
|
1602
|
+
]
|
|
1603
|
+
};
|
|
1604
|
+
```
|
|
1605
|
+
|
|
1606
|
+
#### **Step 10: Create Test Script for Direct Testing**
|
|
1607
|
+
|
|
1608
|
+
```javascript
|
|
1609
|
+
// test-c042-direct.js - Direct analyzer testing
|
|
1610
|
+
const C042Analyzer = require('./rules/common/C042_no_hardcoded_config/analyzer');
|
|
1611
|
+
|
|
1612
|
+
async function testC042() {
|
|
1613
|
+
const analyzer = new C042Analyzer(true); // Enable verbose mode
|
|
1614
|
+
|
|
1615
|
+
try {
|
|
1616
|
+
const violations = await analyzer.analyzeFileBasic('test-files/c042-test.js');
|
|
1617
|
+
console.log(`\n📊 C042 Analysis Results:`);
|
|
1618
|
+
console.log(` Found ${violations.length} violations`);
|
|
1619
|
+
|
|
1620
|
+
violations.forEach((violation, index) => {
|
|
1621
|
+
console.log(` ${index + 1}. Line ${violation.line}: ${violation.message}`);
|
|
1622
|
+
});
|
|
1623
|
+
} catch (error) {
|
|
1624
|
+
console.error('❌ C042 test failed:', error.message);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
testC042();
|
|
1629
|
+
```
|
|
1630
|
+
|
|
1631
|
+
#### **Step 11: Create Test File**
|
|
1632
|
+
|
|
1633
|
+
```javascript
|
|
1634
|
+
// test-files/c042-test.js
|
|
1635
|
+
/**
|
|
1636
|
+
* Test file for C042 No Hardcoded Configuration Values rule
|
|
1637
|
+
* Contains various configuration patterns to validate analyzer
|
|
1638
|
+
*/
|
|
1639
|
+
|
|
1640
|
+
// Good Examples: Using environment variables
|
|
1641
|
+
const API_URL = process.env.API_URL;
|
|
1642
|
+
const MAX_RETRIES = parseInt(process.env.MAX_RETRIES || '3');
|
|
1643
|
+
const config = loadConfigFromFile();
|
|
1644
|
+
|
|
1645
|
+
// Bad Examples: Hardcoded values (should trigger violations)
|
|
1646
|
+
const DATABASE_URL = 'mongodb://localhost:27017/myapp';
|
|
1647
|
+
const API_SECRET = 'sk-1234567890abcdef';
|
|
1648
|
+
const MAX_CONNECTIONS = 100;
|
|
1649
|
+
const TIMEOUT_MS = 5000;
|
|
1650
|
+
|
|
1651
|
+
// Edge Cases
|
|
1652
|
+
const DEBUG_MODE = true; // Should not trigger (not config pattern)
|
|
1653
|
+
const DEFAULT_CONFIG = { // Should trigger for nested hardcoded values
|
|
1654
|
+
port: 3000,
|
|
1655
|
+
host: 'localhost'
|
|
1656
|
+
};
|
|
1657
|
+
|
|
1658
|
+
module.exports = {
|
|
1659
|
+
goodConfig,
|
|
1660
|
+
badConfig
|
|
1661
|
+
};
|
|
1662
|
+
```
|
|
1663
|
+
|
|
1664
|
+
#### **Step 12: Test Your Rule**
|
|
1665
|
+
|
|
1666
|
+
```bash
|
|
1667
|
+
# Test the new rule
|
|
1668
|
+
node cli.js --rule=C042 --input=test-files/c042-test.js --verbose --engine=heuristic
|
|
1669
|
+
|
|
1670
|
+
# Expected output: Should detect 4-5 violations for hardcoded config values
|
|
1671
|
+
|
|
1672
|
+
# Test rule in real projects
|
|
1673
|
+
node cli.js --rule=C042 --input=src --engine=heuristic
|
|
1674
|
+
|
|
1675
|
+
# Test with different engines
|
|
1676
|
+
node cli.js --rule=C042 --input=test-files/c042-test.js --engine=openai
|
|
1677
|
+
```
|
|
1678
|
+
|
|
1679
|
+
#### **Step 13: Validate Integration**
|
|
1680
|
+
|
|
1681
|
+
```bash
|
|
1682
|
+
# Check rule is loaded
|
|
1683
|
+
node cli.js --rule=C042 --input=test-files/c042-test.js --verbose | grep "Loaded.*rule: C042"
|
|
1684
|
+
|
|
1685
|
+
# Verify both analyzers work
|
|
1686
|
+
node cli.js --rule=C042 --input=test-files/c042-test.js --verbose | grep -E "(Symbol-based|Regex-fallback)"
|
|
1687
|
+
|
|
1688
|
+
# Check for violations
|
|
1689
|
+
node cli.js --rule=C042 --input=test-files/c042-test.js --format=json | jq '.results[].violations | length'
|
|
1690
|
+
```
|
|
1691
|
+
|
|
1692
|
+
### **Modern Rule Development Best Practices**
|
|
1693
|
+
|
|
1694
|
+
#### **Architecture Patterns**
|
|
1695
|
+
|
|
1696
|
+
1. **Hybrid Symbol + Regex Approach** (Recommended)
|
|
1697
|
+
- Primary: Symbol-based analysis (high accuracy)
|
|
1698
|
+
- Fallback: Regex-based analysis (broad coverage)
|
|
1699
|
+
- Example: C035, C033
|
|
1700
|
+
|
|
1701
|
+
2. **Symbol-Only Approach**
|
|
1702
|
+
- For complex AST analysis requiring deep context
|
|
1703
|
+
- Higher accuracy but limited fallback
|
|
1704
|
+
- Example: Advanced OOP rules
|
|
1705
|
+
|
|
1706
|
+
3. **Regex-Only Approach**
|
|
1707
|
+
- For simple pattern matching
|
|
1708
|
+
- Fast but lower accuracy
|
|
1709
|
+
- Example: Basic naming conventions
|
|
1710
|
+
|
|
1711
|
+
#### **Code Quality Standards for Rules**
|
|
1712
|
+
|
|
1713
|
+
- **Rule C005**: Each analyzer should have single responsibility
|
|
1714
|
+
- **Rule C006**: Method names should be verb-noun (e.g., `analyzeVariableDeclaration`)
|
|
1715
|
+
- **Rule C031**: Keep validation logic separate from detection logic
|
|
1716
|
+
- **Rule C035**: Log detailed information for debugging (verbose mode)
|
|
1717
|
+
|
|
1718
|
+
#### **File Structure Standards**
|
|
1719
|
+
|
|
1720
|
+
```
|
|
1721
|
+
rules/common/C042_rule_name/
|
|
1722
|
+
├── analyzer.js # Main hybrid orchestrator
|
|
1723
|
+
├── symbol-based-analyzer.js # Primary AST analysis
|
|
1724
|
+
├── regex-based-analyzer.js # Fallback pattern matching
|
|
1725
|
+
├── config.json # Rule configuration
|
|
1726
|
+
├── STRATEGY.md # Analysis approach documentation
|
|
1727
|
+
└── README.md # Rule documentation (optional)
|
|
1728
|
+
```
|
|
1729
|
+
|
|
1730
|
+
#### **Error Handling Standards**
|
|
1731
|
+
|
|
1732
|
+
```javascript
|
|
1733
|
+
// Always include proper error handling
|
|
1734
|
+
try {
|
|
1735
|
+
const violations = await this.symbolAnalyzer.analyzeFileBasic(filePath, options);
|
|
1736
|
+
return violations;
|
|
1737
|
+
} catch (error) {
|
|
1738
|
+
if (this.verbose) {
|
|
1739
|
+
console.warn(`⚠️ [C042] Symbol analysis failed: ${error.message}`);
|
|
1740
|
+
}
|
|
1741
|
+
// Graceful fallback to regex analysis
|
|
1742
|
+
}
|
|
1743
|
+
```
|
|
1744
|
+
|
|
1745
|
+
#### **Testing Standards**
|
|
1746
|
+
|
|
1747
|
+
1. **Unit Tests**: Test each analyzer independently
|
|
1748
|
+
2. **Integration Tests**: Test hybrid orchestration
|
|
1749
|
+
3. **Real Project Tests**: Validate on actual codebases
|
|
1750
|
+
4. **Performance Tests**: Ensure reasonable execution time
|
|
1751
|
+
|
|
1752
|
+
#### **Documentation Standards**
|
|
1753
|
+
|
|
1754
|
+
- **STRATEGY.md**: Document analysis approach and accuracy
|
|
1755
|
+
- **config.json**: Include examples of good/bad patterns
|
|
1756
|
+
- **CONTRIBUTING.md**: Update with new patterns learned
|
|
1757
|
+
- **Inline comments**: Explain complex detection logic
|
|
1758
|
+
|
|
1759
|
+
### **Rule Development Checklist**
|
|
1760
|
+
|
|
1761
|
+
- [ ] Created rule directory structure
|
|
1762
|
+
- [ ] Implemented main analyzer (hybrid orchestrator)
|
|
1763
|
+
- [ ] Implemented symbol-based analyzer (primary)
|
|
1764
|
+
- [ ] Implemented regex-based analyzer (fallback)
|
|
1765
|
+
- [ ] Created rule configuration (config.json)
|
|
1766
|
+
- [ ] Documented analysis strategy (STRATEGY.md)
|
|
1767
|
+
- [ ] Added to rules registry
|
|
1768
|
+
- [ ] Added to enhanced rules registry
|
|
1769
|
+
- [ ] Added to analysis strategies
|
|
1770
|
+
- [ ] Created test file
|
|
1771
|
+
- [ ] Tested rule individually
|
|
1772
|
+
- [ ] Tested rule integration
|
|
1773
|
+
- [ ] Validated on real projects
|
|
1774
|
+
- [ ] Updated documentation
|
|
1775
|
+
|
|
1776
|
+
### **Common Issues and Solutions**
|
|
1777
|
+
|
|
1778
|
+
#### **Issue: Rule not loaded**
|
|
1779
|
+
```bash
|
|
1780
|
+
# Check if rule exists in registry
|
|
1781
|
+
node cli.js --rule=C042 --input=test-files --verbose | grep "No compatible analyzer found for C042"
|
|
1782
|
+
|
|
1783
|
+
# Solution: Add rule to config/rules/rules-registry.json
|
|
1784
|
+
```
|
|
1785
|
+
|
|
1786
|
+
#### **Issue: Symbol analysis fails**
|
|
1787
|
+
```bash
|
|
1788
|
+
# Check semantic engine availability
|
|
1789
|
+
node cli.js --rule=C042 --input=test-files --verbose | grep "Symbol analysis failed"
|
|
1790
|
+
|
|
1791
|
+
# Solution: Ensure proper error handling and regex fallback
|
|
1792
|
+
```
|
|
1793
|
+
|
|
1794
|
+
#### **Issue: No violations detected**
|
|
1795
|
+
```bash
|
|
1796
|
+
# Test analyzers independently
|
|
1797
|
+
node test-c042-direct.js
|
|
1798
|
+
|
|
1799
|
+
# Check file patterns match expectations
|
|
1800
|
+
```
|
|
1801
|
+
|
|
1802
|
+
#### **Issue: Too many false positives**
|
|
1803
|
+
```bash
|
|
1804
|
+
# Refine detection patterns
|
|
1805
|
+
# Add exclusion rules
|
|
1806
|
+
# Test on diverse codebases
|
|
1807
|
+
```
|
|
1808
|
+
|
|
1809
|
+
---
|
|
1810
|
+
|
|
686
1811
|
## 🤝 **Community**
|
|
687
1812
|
|
|
688
1813
|
- **Discord**: [Sun Engineering Discord](https://discord.gg/sun-engineering)
|