@sun-asterisk/sunlint 1.3.1 → 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 +47 -0
- package/CONTRIBUTING.md +210 -1691
- package/config/rule-analysis-strategies.js +17 -1
- package/config/rules/enhanced-rules-registry.json +369 -1135
- package/config/rules/rules-registry-generated.json +1 -1
- package/core/enhanced-rules-registry.js +2 -1
- package/core/semantic-engine.js +15 -3
- package/core/semantic-rule-base.js +4 -2
- package/engines/heuristic-engine.js +65 -4
- 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 +11 -7
- 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/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/C035_error_logging_context/analyzer.js +3 -1
- package/rules/index.js +5 -1
- 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/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/config/rules/S027-categories.json +0 -122
- package/config/rules/rules-registry.json +0 -777
- package/rules/common/C006_function_naming/smart-analyzer.js +0 -503
package/CONTRIBUTING.md
CHANGED
|
@@ -1,113 +1,78 @@
|
|
|
1
|
-
# Contributing to
|
|
1
|
+
# Contributing to SunLint - Rule Development Guide
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## 🎯 Quick Start for Rule Development
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This guide focuses on developing new rules for SunLint. Based on practical experience from refactoring rules like C013, C035, we recommend a **symbol-based only** approach for maximum accuracy.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- Node.js 16+
|
|
9
|
-
- npm 8+
|
|
10
|
-
- Git
|
|
7
|
+
## 📋 Rule Development Steps
|
|
11
8
|
|
|
12
|
-
###
|
|
9
|
+
### Step 1: Register Rule in Registry
|
|
13
10
|
|
|
14
|
-
|
|
15
|
-
# Clone the repository
|
|
16
|
-
git clone https://github.com/sun-engineering/sunlint.git
|
|
17
|
-
cd sunlint
|
|
18
|
-
|
|
19
|
-
# Install dependencies
|
|
20
|
-
npm install
|
|
21
|
-
|
|
22
|
-
# Run tests
|
|
23
|
-
npm test
|
|
11
|
+
Add your rule to `config/rules/enhanced-rules-registry.json`:
|
|
24
12
|
|
|
25
|
-
|
|
26
|
-
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"rules": {
|
|
16
|
+
"C010": {
|
|
17
|
+
"name": "Limit Block Nesting",
|
|
18
|
+
"description": "Limit nested blocks (if/for/while/switch) to maximum 3 levels for readability",
|
|
19
|
+
"category": "complexity",
|
|
20
|
+
"severity": "warning",
|
|
21
|
+
"languages": ["typescript", "javascript", "dart", "kotlin"],
|
|
22
|
+
"analyzer": "./rules/common/C010_limit_block_nesting/analyzer.js",
|
|
23
|
+
"config": "./rules/common/C010_limit_block_nesting/config.json",
|
|
24
|
+
"version": "1.0.0",
|
|
25
|
+
"status": "stable",
|
|
26
|
+
"tags": ["complexity", "readability", "nesting", "maintainability"],
|
|
27
|
+
"strategy": {
|
|
28
|
+
"preferred": "ast",
|
|
29
|
+
"fallbacks": ["ast"],
|
|
30
|
+
"accuracy": {
|
|
31
|
+
"ast": 95
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"engineMappings": {
|
|
35
|
+
"eslint": ["complexity", "max-depth"]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
27
40
|
```
|
|
28
41
|
|
|
29
|
-
|
|
42
|
+
**Key Registry Fields:**
|
|
43
|
+
- `analyzer`: Path to main analyzer file
|
|
44
|
+
- `strategy.preferred`: Use "ast" for symbol-based analysis
|
|
45
|
+
- `strategy.fallbacks`: Recommend ["ast"] only for accuracy
|
|
46
|
+
- `strategy.accuracy`: Expected accuracy percentage
|
|
30
47
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
### **Code Quality Rules**
|
|
34
|
-
- **Rule C005** – Each function should do one thing only
|
|
35
|
-
- **Rule C006** – Function names must be verb/verb-noun
|
|
36
|
-
- **Rule C007** – Avoid comments that just describe the code
|
|
37
|
-
- **Rule C012** – Separate Command and Query operations (CQS principle)
|
|
38
|
-
- **Rule C014** – Use Dependency Injection instead of direct instantiation
|
|
39
|
-
- **Rule C015** – Use domain language in class/function names
|
|
40
|
-
- **Rule C019** – Don't use `error` log level for non-critical errors
|
|
41
|
-
- **Rule C031** – Keep validation logic separate
|
|
42
|
-
- **Rule C032** – Don't call external APIs in constructors or static blocks
|
|
43
|
-
- **Rule C033** – Separate processing logic and data queries in service layer
|
|
44
|
-
- **Rule C034** – Limit direct access to global state in domain logic
|
|
45
|
-
- **Rule C035** – When handling errors, log complete relevant information
|
|
46
|
-
- **Rule C037** – API handlers should return standard response objects (not raw strings)
|
|
47
|
-
- **Rule C038** – Avoid logic depending on file/module loading order
|
|
48
|
-
- **Rule C040** – Don't scatter validation logic across multiple classes
|
|
49
|
-
|
|
50
|
-
## 🔧 **Development Workflow**
|
|
51
|
-
|
|
52
|
-
### **SunLint Architecture Overview**
|
|
53
|
-
|
|
54
|
-
SunLint uses a **multi-engine architecture** with rule mapping system:
|
|
55
|
-
|
|
56
|
-
- **Heuristic Engine**: Pattern-based analysis using AST (Abstract Syntax Tree)
|
|
57
|
-
- **ESLint Engine**: JavaScript/TypeScript linting using ESLint rules
|
|
58
|
-
- **OpenAI Engine**: AI-powered code analysis
|
|
59
|
-
|
|
60
|
-
**Rule Configuration Files:**
|
|
61
|
-
- `rules/` - Unified rule registry (auto-generated from origin-rules)
|
|
62
|
-
- `config/eslint-rule-mapping.json` - ESLint engine rule mappings
|
|
63
|
-
- `origin-rules/` - Original rule definitions (markdown format)
|
|
64
|
-
|
|
65
|
-
### **Adding a New Rule (Modern Approach - 2025)**
|
|
66
|
-
|
|
67
|
-
#### **Step 1: Choose Rule Implementation Strategy**
|
|
68
|
-
|
|
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
|
-
|
|
72
|
-
#### **Step 2: Create Rule Directory Structure**
|
|
48
|
+
### Step 2: Create Rule Directory Structure
|
|
73
49
|
|
|
74
50
|
```bash
|
|
75
|
-
|
|
76
|
-
|
|
51
|
+
mkdir -p rules/common/C010_limit_block_nesting
|
|
52
|
+
cd rules/common/C010_limit_block_nesting
|
|
77
53
|
|
|
78
|
-
#
|
|
79
|
-
touch
|
|
80
|
-
touch
|
|
81
|
-
touch
|
|
82
|
-
touch rules/common/C042_no_hardcoded_config/config.json
|
|
83
|
-
touch rules/common/C042_no_hardcoded_config/STRATEGY.md
|
|
54
|
+
# Required files
|
|
55
|
+
touch analyzer.js # Main orchestrator
|
|
56
|
+
touch symbol-based-analyzer.js # Core analysis logic
|
|
57
|
+
touch config.json # Rule configuration
|
|
84
58
|
```
|
|
85
59
|
|
|
86
|
-
|
|
60
|
+
### Step 3: Implement Main Analyzer (analyzer.js)
|
|
87
61
|
|
|
88
62
|
```javascript
|
|
89
|
-
// rules/common/
|
|
90
|
-
const
|
|
91
|
-
const C042RegexBasedAnalyzer = require('./regex-based-analyzer.js');
|
|
63
|
+
// rules/common/C010_limit_block_nesting/analyzer.js
|
|
64
|
+
const C010SymbolBasedAnalyzer = require('./symbol-based-analyzer.js');
|
|
92
65
|
|
|
93
|
-
class
|
|
66
|
+
class C010Analyzer {
|
|
94
67
|
constructor(semanticEngine = null) {
|
|
95
|
-
this.ruleId = '
|
|
96
|
-
this.ruleName = '
|
|
97
|
-
this.description = '
|
|
68
|
+
this.ruleId = 'C010';
|
|
69
|
+
this.ruleName = 'Limit Block Nesting';
|
|
70
|
+
this.description = 'Limit nested blocks to maximum 3 levels';
|
|
98
71
|
this.semanticEngine = semanticEngine;
|
|
99
72
|
this.verbose = false;
|
|
100
73
|
|
|
101
|
-
//
|
|
102
|
-
this.
|
|
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);
|
|
74
|
+
// Use symbol-based only for accuracy
|
|
75
|
+
this.symbolAnalyzer = new C010SymbolBasedAnalyzer(semanticEngine);
|
|
111
76
|
}
|
|
112
77
|
|
|
113
78
|
async initialize(semanticEngine = null) {
|
|
@@ -115,82 +80,63 @@ class C042Analyzer {
|
|
|
115
80
|
this.semanticEngine = semanticEngine;
|
|
116
81
|
}
|
|
117
82
|
this.verbose = semanticEngine?.verbose || false;
|
|
118
|
-
|
|
119
|
-
// Initialize both analyzers
|
|
120
83
|
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
84
|
}
|
|
127
85
|
|
|
128
86
|
async analyzeFileBasic(filePath, options = {}) {
|
|
129
87
|
try {
|
|
130
|
-
//
|
|
131
|
-
if (this.semanticEngine?.isSymbolEngineReady?.()
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
88
|
+
// Check if symbol engine is ready
|
|
89
|
+
if (!this.semanticEngine?.isSymbolEngineReady?.() || !this.semanticEngine.project) {
|
|
90
|
+
throw new Error('Symbol engine not available');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (this.verbose) {
|
|
94
|
+
console.log(`[DEBUG] 🎯 C010: Using symbol-based analysis for ${filePath.split('/').pop()}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const violations = await this.symbolAnalyzer.analyzeFileBasic(filePath, options);
|
|
98
|
+
|
|
99
|
+
if (this.verbose) {
|
|
100
|
+
console.log(`[DEBUG] 🎯 C010: Symbol-based analysis found ${violations.length} violations`);
|
|
140
101
|
}
|
|
102
|
+
|
|
103
|
+
return violations;
|
|
141
104
|
} catch (error) {
|
|
142
105
|
if (this.verbose) {
|
|
143
|
-
console.
|
|
106
|
+
console.error(`[DEBUG] ❌ C010: Analysis failed: ${error.message}`);
|
|
144
107
|
}
|
|
108
|
+
throw new Error(`C010 analysis failed: ${error.message}`);
|
|
145
109
|
}
|
|
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
110
|
}
|
|
154
111
|
|
|
155
|
-
async
|
|
156
|
-
const
|
|
157
|
-
|
|
112
|
+
async analyzeFiles(files, options = {}) {
|
|
113
|
+
const allViolations = [];
|
|
158
114
|
for (const filePath of files) {
|
|
159
115
|
try {
|
|
160
|
-
const
|
|
161
|
-
|
|
116
|
+
const violations = await this.analyzeFileBasic(filePath, options);
|
|
117
|
+
allViolations.push(...violations);
|
|
162
118
|
} catch (error) {
|
|
163
|
-
|
|
164
|
-
console.warn(`❌ [C042] Analysis failed for ${filePath}:`, error.message);
|
|
165
|
-
}
|
|
119
|
+
console.warn(`C010: Skipping ${filePath}: ${error.message}`);
|
|
166
120
|
}
|
|
167
121
|
}
|
|
168
|
-
|
|
169
|
-
return violations;
|
|
122
|
+
return allViolations;
|
|
170
123
|
}
|
|
171
124
|
}
|
|
172
125
|
|
|
173
|
-
module.exports =
|
|
126
|
+
module.exports = C010Analyzer;
|
|
174
127
|
```
|
|
175
128
|
|
|
176
|
-
|
|
129
|
+
### Step 4: Implement Symbol-Based Analyzer
|
|
177
130
|
|
|
178
131
|
```javascript
|
|
179
|
-
// rules/common/
|
|
180
|
-
|
|
132
|
+
// rules/common/C010_limit_block_nesting/symbol-based-analyzer.js
|
|
133
|
+
const { SyntaxKind } = require('ts-morph');
|
|
134
|
+
|
|
135
|
+
class C010SymbolBasedAnalyzer {
|
|
181
136
|
constructor(semanticEngine = null) {
|
|
182
|
-
this.ruleId = 'C042';
|
|
183
|
-
this.ruleName = 'No Hardcoded Configuration Values (Symbol-Based)';
|
|
184
137
|
this.semanticEngine = semanticEngine;
|
|
138
|
+
this.maxNestingLevel = 3;
|
|
185
139
|
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
140
|
}
|
|
195
141
|
|
|
196
142
|
async initialize(semanticEngine = null) {
|
|
@@ -198,1626 +144,199 @@ class C042SymbolBasedAnalyzer {
|
|
|
198
144
|
this.semanticEngine = semanticEngine;
|
|
199
145
|
}
|
|
200
146
|
this.verbose = semanticEngine?.verbose || false;
|
|
201
|
-
|
|
202
|
-
if (this.verbose) {
|
|
203
|
-
console.log(`🔧 [C042 Symbol-Based] Analyzer initialized`);
|
|
204
|
-
}
|
|
205
147
|
}
|
|
206
148
|
|
|
207
149
|
async analyzeFileBasic(filePath, options = {}) {
|
|
208
150
|
const violations = [];
|
|
209
151
|
|
|
210
|
-
if (!this.semanticEngine?.project) {
|
|
211
|
-
return violations;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
152
|
try {
|
|
215
|
-
const sourceFile = this.semanticEngine.project.
|
|
153
|
+
const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
|
|
216
154
|
if (!sourceFile) {
|
|
217
|
-
|
|
155
|
+
throw new Error(`Source file not found: ${filePath}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (this.verbose) {
|
|
159
|
+
console.log(`[DEBUG] 🔍 C010: Analyzing nesting in ${filePath.split('/').pop()}`);
|
|
218
160
|
}
|
|
219
161
|
|
|
220
|
-
//
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (
|
|
225
|
-
violations.push(
|
|
162
|
+
// Find nested blocks
|
|
163
|
+
const nestedBlocks = this.findNestedBlocks(sourceFile);
|
|
164
|
+
|
|
165
|
+
for (const block of nestedBlocks) {
|
|
166
|
+
if (block.nestingLevel > this.maxNestingLevel) {
|
|
167
|
+
violations.push({
|
|
168
|
+
ruleId: 'C010',
|
|
169
|
+
message: `Nested block exceeds maximum depth of ${this.maxNestingLevel} (current: ${block.nestingLevel}). Consider extracting to separate functions.`,
|
|
170
|
+
filePath: filePath,
|
|
171
|
+
line: block.line,
|
|
172
|
+
column: block.column,
|
|
173
|
+
severity: 'warning',
|
|
174
|
+
category: 'complexity'
|
|
175
|
+
});
|
|
226
176
|
}
|
|
227
177
|
}
|
|
228
178
|
|
|
179
|
+
if (this.verbose) {
|
|
180
|
+
console.log(`[DEBUG] 🔍 C010: Found ${violations.length} nesting violations`);
|
|
181
|
+
}
|
|
182
|
+
|
|
229
183
|
return violations;
|
|
230
184
|
} catch (error) {
|
|
231
185
|
if (this.verbose) {
|
|
232
|
-
console.
|
|
186
|
+
console.error(`[DEBUG] ❌ C010: Symbol analysis error: ${error.message}`);
|
|
233
187
|
}
|
|
234
|
-
|
|
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)**
|
|
294
|
-
|
|
295
|
-
```javascript
|
|
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`);
|
|
188
|
+
throw error;
|
|
320
189
|
}
|
|
321
190
|
}
|
|
322
191
|
|
|
323
|
-
|
|
324
|
-
const
|
|
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');
|
|
192
|
+
findNestedBlocks(sourceFile) {
|
|
193
|
+
const blocks = [];
|
|
333
194
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
pattern.lastIndex = 0; // Reset regex
|
|
339
|
-
let match;
|
|
195
|
+
function traverse(node, currentDepth = 0) {
|
|
196
|
+
// Check for block statements that increase nesting
|
|
197
|
+
if (this.isNestingNode(node)) {
|
|
198
|
+
currentDepth++;
|
|
340
199
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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'
|
|
358
|
-
});
|
|
359
|
-
}
|
|
200
|
+
const position = sourceFile.getLineAndColumnAtPos(node.getStart());
|
|
201
|
+
blocks.push({
|
|
202
|
+
node: node,
|
|
203
|
+
nestingLevel: currentDepth,
|
|
204
|
+
line: position.line,
|
|
205
|
+
column: position.column
|
|
206
|
+
});
|
|
360
207
|
}
|
|
208
|
+
|
|
209
|
+
// Traverse children
|
|
210
|
+
node.forEachChild(child => traverse.call(this, child, currentDepth));
|
|
361
211
|
}
|
|
362
|
-
|
|
363
|
-
|
|
212
|
+
|
|
213
|
+
traverse.call(this, sourceFile, 0);
|
|
214
|
+
return blocks;
|
|
364
215
|
}
|
|
365
216
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
return violations;
|
|
217
|
+
isNestingNode(node) {
|
|
218
|
+
return [
|
|
219
|
+
SyntaxKind.IfStatement,
|
|
220
|
+
SyntaxKind.ForStatement,
|
|
221
|
+
SyntaxKind.ForInStatement,
|
|
222
|
+
SyntaxKind.ForOfStatement,
|
|
223
|
+
SyntaxKind.WhileStatement,
|
|
224
|
+
SyntaxKind.DoStatement,
|
|
225
|
+
SyntaxKind.SwitchStatement,
|
|
226
|
+
SyntaxKind.TryStatement,
|
|
227
|
+
SyntaxKind.CatchClause
|
|
228
|
+
].includes(node.getKind());
|
|
381
229
|
}
|
|
382
230
|
}
|
|
383
231
|
|
|
384
|
-
module.exports =
|
|
232
|
+
module.exports = C010SymbolBasedAnalyzer;
|
|
385
233
|
```
|
|
386
234
|
|
|
387
|
-
|
|
235
|
+
### Step 5: Create Rule Configuration
|
|
388
236
|
|
|
389
237
|
```json
|
|
390
|
-
// rules/common/
|
|
238
|
+
// rules/common/C010_limit_block_nesting/config.json
|
|
391
239
|
{
|
|
392
|
-
"
|
|
393
|
-
"
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
"
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
"
|
|
402
|
-
|
|
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
|
-
}
|
|
240
|
+
"maxNestingLevel": 3,
|
|
241
|
+
"excludePatterns": [
|
|
242
|
+
"**/*.test.js",
|
|
243
|
+
"**/*.spec.js"
|
|
244
|
+
],
|
|
245
|
+
"includePatterns": [
|
|
246
|
+
"**/*.js",
|
|
247
|
+
"**/*.ts",
|
|
248
|
+
"**/*.jsx",
|
|
249
|
+
"**/*.tsx"
|
|
250
|
+
]
|
|
421
251
|
}
|
|
422
252
|
```
|
|
423
253
|
|
|
424
|
-
|
|
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)**
|
|
254
|
+
## 🚨 Common Pitfalls & Solutions
|
|
455
255
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
### **Critical Success Patterns**
|
|
459
|
-
|
|
460
|
-
#### **✅ 1. Constructor Pattern (REQUIRED)**
|
|
256
|
+
### 1. Symbol Engine Not Ready
|
|
461
257
|
```javascript
|
|
462
|
-
//
|
|
463
|
-
|
|
464
|
-
constructor(options = {}) {
|
|
465
|
-
this.semanticEngine = options.semanticEngine || null;
|
|
466
|
-
this.verbose = options.verbose || false;
|
|
467
|
-
// ...
|
|
468
|
-
}
|
|
469
|
-
}
|
|
258
|
+
// ❌ Wrong - Will fail if symbol engine not ready
|
|
259
|
+
const violations = await this.symbolAnalyzer.analyzeFileBasic(filePath);
|
|
470
260
|
|
|
471
|
-
//
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
this.semanticEngine = semanticEngine;
|
|
475
|
-
// ...
|
|
476
|
-
}
|
|
261
|
+
// ✅ Correct - Check engine availability
|
|
262
|
+
if (!this.semanticEngine?.isSymbolEngineReady?.() || !this.semanticEngine.project) {
|
|
263
|
+
throw new Error('Symbol engine not available');
|
|
477
264
|
}
|
|
478
265
|
```
|
|
479
266
|
|
|
480
|
-
|
|
267
|
+
### 2. Missing Source File Check
|
|
268
|
+
```javascript
|
|
269
|
+
// ❌ Wrong - Will crash if file not found
|
|
270
|
+
const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
|
|
481
271
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
{
|
|
485
|
-
|
|
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"]
|
|
498
|
-
}
|
|
499
|
-
}
|
|
272
|
+
// ✅ Correct - Always check source file existence
|
|
273
|
+
const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
|
|
274
|
+
if (!sourceFile) {
|
|
275
|
+
throw new Error(`Source file not found: ${filePath}`);
|
|
500
276
|
}
|
|
501
277
|
```
|
|
502
278
|
|
|
503
|
-
|
|
279
|
+
### 3. Improper Error Handling
|
|
504
280
|
```javascript
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
strategy: 'semantic-primary-regex-fallback',
|
|
511
|
-
accuracy: { semantic: 90, regex: 75, combined: 95 }
|
|
512
|
-
}
|
|
513
|
-
}
|
|
281
|
+
// ❌ Wrong - Silent failures
|
|
282
|
+
try {
|
|
283
|
+
const violations = await this.analyzeFile(filePath);
|
|
284
|
+
} catch (error) {
|
|
285
|
+
return [];
|
|
514
286
|
}
|
|
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
|
-
};
|
|
543
|
-
}
|
|
544
|
-
|
|
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
287
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
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 [];
|
|
288
|
+
// ✅ Correct - Proper error propagation
|
|
289
|
+
try {
|
|
290
|
+
const violations = await this.analyzeFile(filePath);
|
|
291
|
+
return violations;
|
|
292
|
+
} catch (error) {
|
|
293
|
+
if (this.verbose) {
|
|
294
|
+
console.error(`Analysis failed: ${error.message}`);
|
|
581
295
|
}
|
|
296
|
+
throw error;
|
|
582
297
|
}
|
|
583
|
-
|
|
584
|
-
module.exports = C042Analyzer;
|
|
585
298
|
```
|
|
586
299
|
|
|
587
|
-
|
|
300
|
+
## 🧪 Testing Your Rule
|
|
588
301
|
|
|
589
|
-
**Debug Output Verification**:
|
|
590
302
|
```bash
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
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**
|
|
634
|
-
|
|
635
|
-
```markdown
|
|
636
|
-
<!-- rules/common/C042_no_hardcoded_config/STRATEGY.md -->
|
|
637
|
-
# C042 Analysis Strategy: No Hardcoded Configuration Values
|
|
638
|
-
|
|
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
|
-
```
|
|
303
|
+
# Test single file
|
|
304
|
+
node cli.js --input=test-file.js --rule=C010 --engine=heuristic --verbose
|
|
682
305
|
|
|
683
|
-
|
|
306
|
+
# Test project
|
|
307
|
+
node cli.js --input=src/ --rule=C010 --engine=heuristic --max-semantic-files=-1
|
|
684
308
|
|
|
685
|
-
|
|
686
|
-
|
|
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
|
-
};
|
|
309
|
+
# Performance test
|
|
310
|
+
time node cli.js --input=large-project/ --rule=C010 --engine=heuristic
|
|
710
311
|
```
|
|
711
312
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
**Examples**:
|
|
715
|
-
❌ Bad:
|
|
716
|
-
```javascript
|
|
717
|
-
const API_URL = "https://api.example.com";
|
|
718
|
-
const MAX_RETRIES = 5;
|
|
719
|
-
```
|
|
720
|
-
|
|
721
|
-
✅ Good:
|
|
722
|
-
```javascript
|
|
723
|
-
const API_URL = process.env.API_URL;
|
|
724
|
-
const MAX_RETRIES = parseInt(process.env.MAX_RETRIES || '3');
|
|
725
|
-
```
|
|
726
|
-
|
|
727
|
-
#### **Step 4: Add Tests**
|
|
728
|
-
|
|
729
|
-
```javascript
|
|
730
|
-
// test/rules/c042.test.js
|
|
731
|
-
const { testRule } = require('../test-utils');
|
|
313
|
+
## 📝 Best Practices
|
|
732
314
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
invalid: [
|
|
740
|
-
'const API_URL = "https://api.example.com";',
|
|
741
|
-
'const MAX_RETRIES = 5;'
|
|
742
|
-
]
|
|
743
|
-
});
|
|
744
|
-
});
|
|
745
|
-
```
|
|
315
|
+
1. **Use Symbol-Based Only**: More accurate than regex patterns
|
|
316
|
+
2. **Always Check Engine State**: Verify semantic engine is ready
|
|
317
|
+
3. **Handle Errors Gracefully**: Don't silently ignore failures
|
|
318
|
+
4. **Add Debug Logging**: Use `this.verbose` for troubleshooting
|
|
319
|
+
5. **Test on Real Projects**: Validate with large codebases
|
|
320
|
+
6. **Document Accuracy**: Update strategy.accuracy in registry
|
|
746
321
|
|
|
747
|
-
|
|
322
|
+
## 🔧 Development Environment
|
|
748
323
|
|
|
749
324
|
```bash
|
|
750
|
-
#
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
# Test rule behavior
|
|
755
|
-
node cli.js --rule=C042 --input=test/fixtures --verbose
|
|
756
|
-
```
|
|
757
|
-
|
|
758
|
-
### **Engine-Specific Development**
|
|
759
|
-
|
|
760
|
-
#### **Heuristic Engine Rules**
|
|
761
|
-
|
|
762
|
-
For custom pattern-based analysis:
|
|
763
|
-
|
|
764
|
-
1. **Create Rule Class**:
|
|
765
|
-
```javascript
|
|
766
|
-
// rules/custom/your-rule.js
|
|
767
|
-
const HeuristicRuleBase = require('../../core/heuristic-rule-base');
|
|
768
|
-
|
|
769
|
-
class YourCustomRule extends HeuristicRuleBase {
|
|
770
|
-
constructor() {
|
|
771
|
-
super('CXXX', 'Your Rule Name');
|
|
772
|
-
}
|
|
325
|
+
# Setup
|
|
326
|
+
npm install
|
|
327
|
+
npm test
|
|
773
328
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
// Return violation or null
|
|
777
|
-
}
|
|
778
|
-
}
|
|
329
|
+
# Development workflow
|
|
330
|
+
node cli.js --input=examples/ --rule=YOUR_RULE --engine=heuristic --verbose
|
|
779
331
|
```
|
|
780
332
|
|
|
781
|
-
|
|
782
|
-
```json
|
|
783
|
-
{
|
|
784
|
-
"CXXX": {
|
|
785
|
-
"engineMappings": {
|
|
786
|
-
"heuristic": {
|
|
787
|
-
"implementation": "custom/your-rule",
|
|
788
|
-
"supportedLanguages": ["typescript", "javascript"]
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
```
|
|
794
|
-
|
|
795
|
-
#### **ESLint Engine Rules**
|
|
796
|
-
|
|
797
|
-
For JavaScript/TypeScript linting:
|
|
798
|
-
|
|
799
|
-
1. **Use Existing ESLint Rules**:
|
|
800
|
-
```json
|
|
801
|
-
{
|
|
802
|
-
"CXXX": {
|
|
803
|
-
"engineMappings": {
|
|
804
|
-
"eslint": {
|
|
805
|
-
"rules": ["no-console", "prefer-const"],
|
|
806
|
-
"config": {
|
|
807
|
-
"no-console": ["error"],
|
|
808
|
-
"prefer-const": ["warn"]
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
```
|
|
815
|
-
|
|
816
|
-
2. **Create Custom ESLint Rule** (advanced):
|
|
817
|
-
```javascript
|
|
818
|
-
// eslint-custom-rules/your-rule.js
|
|
819
|
-
module.exports = {
|
|
820
|
-
meta: {
|
|
821
|
-
type: 'problem',
|
|
822
|
-
docs: { description: 'Your rule description' }
|
|
823
|
-
},
|
|
824
|
-
create(context) {
|
|
825
|
-
return {
|
|
826
|
-
FunctionDeclaration(node) {
|
|
827
|
-
// Your ESLint rule logic
|
|
828
|
-
}
|
|
829
|
-
};
|
|
830
|
-
}
|
|
831
|
-
};
|
|
832
|
-
```
|
|
833
|
-
|
|
834
|
-
#### **OpenAI Engine Rules**
|
|
835
|
-
|
|
836
|
-
For AI-powered analysis:
|
|
837
|
-
|
|
838
|
-
```json
|
|
839
|
-
{
|
|
840
|
-
"CXXX": {
|
|
841
|
-
"engineMappings": {
|
|
842
|
-
"openai": {
|
|
843
|
-
"prompt": "Analyze code for specific pattern or anti-pattern",
|
|
844
|
-
"examples": [
|
|
845
|
-
"// Bad example",
|
|
846
|
-
"// Good example"
|
|
847
|
-
],
|
|
848
|
-
"temperature": 0.1,
|
|
849
|
-
"maxTokens": 500
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
```
|
|
855
|
-
|
|
856
|
-
### **Adding a New Security Rule**
|
|
857
|
-
|
|
858
|
-
Same process as above, just use `"category": "security"` in the rule definition.
|
|
859
|
-
|
|
860
|
-
### **Testing Your New Rule**
|
|
861
|
-
|
|
862
|
-
```bash
|
|
863
|
-
# Test all engines with your new rule
|
|
864
|
-
node cli.js --rule=C042 --input=test/fixtures --format=json
|
|
865
|
-
|
|
866
|
-
# Test specific engine
|
|
867
|
-
node cli.js --rule=C042 --engine=heuristic --input=test/fixtures
|
|
868
|
-
node cli.js --rule=C042 --engine=eslint --input=test/fixtures
|
|
869
|
-
node cli.js --rule=C042 --engine=openai --input=test/fixtures
|
|
870
|
-
|
|
871
|
-
# Validate rule registry
|
|
872
|
-
node validate-system.js
|
|
873
|
-
```
|
|
874
|
-
|
|
875
|
-
### **Rule Definition Schema**
|
|
876
|
-
|
|
877
|
-
Complete schema for rule definitions:
|
|
878
|
-
|
|
879
|
-
```json
|
|
880
|
-
{
|
|
881
|
-
"CXXX": {
|
|
882
|
-
"id": "CXXX", // Required: Rule ID
|
|
883
|
-
"title": "Rule Title", // Required: Human-readable title
|
|
884
|
-
"description": "Detailed description", // Required: Rule description
|
|
885
|
-
"severity": "error|warning|info", // Required: Severity level
|
|
886
|
-
"category": "quality|security|performance|maintainability", // Required
|
|
887
|
-
"tags": ["tag1", "tag2"], // Optional: Tags for filtering
|
|
888
|
-
"languages": ["typescript", "javascript"], // Optional: Supported languages
|
|
889
|
-
"engineMappings": { // Required: Engine configurations
|
|
890
|
-
"heuristic": {
|
|
891
|
-
"implementation": "custom/rule-name",
|
|
892
|
-
"supportedLanguages": ["typescript"],
|
|
893
|
-
"astTargets": ["FunctionDeclaration"]
|
|
894
|
-
},
|
|
895
|
-
"eslint": {
|
|
896
|
-
"rules": ["eslint-rule-name"],
|
|
897
|
-
"config": { "rule-option": true }
|
|
898
|
-
},
|
|
899
|
-
"openai": {
|
|
900
|
-
"prompt": "AI analysis prompt",
|
|
901
|
-
"examples": ["code example"],
|
|
902
|
-
"temperature": 0.1
|
|
903
|
-
}
|
|
904
|
-
},
|
|
905
|
-
"analysisStrategy": { // Optional: Analysis metadata
|
|
906
|
-
"type": "ast|regex|semantic",
|
|
907
|
-
"patterns": ["pattern1"],
|
|
908
|
-
"astTargets": ["NodeType"],
|
|
909
|
-
"heuristics": ["detection-method"]
|
|
910
|
-
},
|
|
911
|
-
"metadata": { // Optional: Additional metadata
|
|
912
|
-
"author": "Developer Name",
|
|
913
|
-
"created": "2025-08-07",
|
|
914
|
-
"updated": "2025-08-07",
|
|
915
|
-
"version": "1.0.0"
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
```
|
|
920
|
-
|
|
921
|
-
### **Architecture Deep Dive**
|
|
922
|
-
|
|
923
|
-
#### **Unified Rule Registry**
|
|
924
|
-
- **Location**: `rules/` (auto-generated from origin-rules)
|
|
925
|
-
- **Source**: `origin-rules/` (markdown files)
|
|
926
|
-
- **ESLint Mappings**: `config/eslint-rule-mapping.json`
|
|
927
|
-
- **Loader**: `core/unified-rule-registry.js`
|
|
928
|
-
|
|
929
|
-
#### **Engine Architecture**
|
|
930
|
-
```
|
|
931
|
-
┌─────────────────────────────────────────┐
|
|
932
|
-
│ SunLint CLI │
|
|
933
|
-
├─────────────────────────────────────────┤
|
|
934
|
-
│ Analysis Orchestrator │
|
|
935
|
-
│ (Strict Engine Mode Support) │
|
|
936
|
-
├─────────────────────────────────────────┤
|
|
937
|
-
│ ┌─────────────┬─────────────┬─────────┐ │
|
|
938
|
-
│ │ Heuristic │ ESLint │ OpenAI │ │
|
|
939
|
-
│ │ Engine │ Engine │ Engine │ │
|
|
940
|
-
│ │ (244 rules) │ (57 rules) │(256 all)│ │
|
|
941
|
-
│ └─────────────┴─────────────┴─────────┘ │
|
|
942
|
-
├─────────────────────────────────────────┤
|
|
943
|
-
│ Rule Configuration │
|
|
944
|
-
│ ┌─────────────┬─────────────────────────┐ │
|
|
945
|
-
│ │ ESLint │ Unified Registry │ │
|
|
946
|
-
│ │ Mappings │ (Generated Rules) │ │
|
|
947
|
-
│ │ (.json) │ (origin-rules) │ │
|
|
948
|
-
│ └─────────────┴─────────────────────────┘ │
|
|
949
|
-
└─────────────────────────────────────────┘
|
|
950
|
-
```
|
|
951
|
-
|
|
952
|
-
#### **How Engines Load Rules**
|
|
953
|
-
1. **Initialization**: Each engine calls `getInstance()` from unified registry
|
|
954
|
-
2. **Rule Loading**: Registry loads rules from auto-generated `rules/` directory
|
|
955
|
-
3. **ESLint Mapping**: ESLint engine loads rule mappings from `config/eslint-rule-mapping.json`
|
|
956
|
-
4. **Engine Filtering**: Each engine filters rules based on their capabilities
|
|
957
|
-
5. **Analysis**: Engines analyze code using their specific rule implementations
|
|
958
|
-
|
|
959
|
-
**Key Features:**
|
|
960
|
-
- ✅ **Strict Engine Mode**: `--engine=eslint` only runs ESLint, skips unsupported rules
|
|
961
|
-
- ✅ **Fallback Mode**: Auto-engine selection with fallback (ESLint → Heuristic → OpenAI)
|
|
962
|
-
- ✅ **Rule Skipping**: Graceful handling of unsupported rules by specific engines
|
|
963
|
-
|
|
964
|
-
## 🧪 **Testing**
|
|
965
|
-
|
|
966
|
-
### **Run All Tests**
|
|
967
|
-
```bash
|
|
968
|
-
npm test
|
|
969
|
-
```
|
|
970
|
-
|
|
971
|
-
### **Test Rule Registry System**
|
|
972
|
-
```bash
|
|
973
|
-
# Validate unified rule registry
|
|
974
|
-
node validate-system.js
|
|
975
|
-
|
|
976
|
-
# Check rule loading for each engine
|
|
977
|
-
npm run test:engines
|
|
978
|
-
```
|
|
979
|
-
|
|
980
|
-
### **Test Specific Rules**
|
|
981
|
-
```bash
|
|
982
|
-
# Test specific rule with all engines
|
|
983
|
-
node cli.js --rule=C042 --input=test/fixtures
|
|
984
|
-
|
|
985
|
-
# Test with specific engine
|
|
986
|
-
node cli.js --rule=C042 --engine=heuristic --input=test/fixtures
|
|
987
|
-
node cli.js --rule=C042 --engine=eslint --input=test/fixtures
|
|
988
|
-
node cli.js --rule=C042 --engine=openai --input=test/fixtures
|
|
989
|
-
|
|
990
|
-
# Test multiple rules
|
|
991
|
-
node cli.js --rule=C006,C019,C042 --input=test/fixtures
|
|
992
|
-
```
|
|
993
|
-
|
|
994
|
-
### **Test Rule Development**
|
|
995
|
-
```bash
|
|
996
|
-
# Create test fixtures
|
|
997
|
-
mkdir -p test/fixtures/c042
|
|
998
|
-
echo 'const API_URL = "https://api.example.com";' > test/fixtures/c042/invalid.ts
|
|
999
|
-
echo 'const apiUrl = process.env.API_URL;' > test/fixtures/c042/valid.ts
|
|
1000
|
-
|
|
1001
|
-
# Test your rule
|
|
1002
|
-
node cli.js --rule=C042 --input=test/fixtures/c042 --format=json
|
|
1003
|
-
```
|
|
1004
|
-
|
|
1005
|
-
### **Integration Testing**
|
|
1006
|
-
```bash
|
|
1007
|
-
# Test all engines work together
|
|
1008
|
-
npm run test:integration
|
|
1009
|
-
|
|
1010
|
-
# Test rule registry loading
|
|
1011
|
-
npm run test:registry
|
|
1012
|
-
|
|
1013
|
-
# Performance testing
|
|
1014
|
-
npm run test:performance
|
|
1015
|
-
```
|
|
1016
|
-
|
|
1017
|
-
## 📊 **Code Review Process**
|
|
1018
|
-
|
|
1019
|
-
1. **Self-Review Checklist**
|
|
1020
|
-
- [ ] Follows all Sun Lint coding rules (C005, C006, etc.)
|
|
1021
|
-
- [ ] Rule C035: Error handling includes complete logging
|
|
1022
|
-
- [ ] Rule C037: API responses use standard format
|
|
1023
|
-
- [ ] Rule C040: Validation logic is centralized
|
|
1024
|
-
- [ ] Tests pass and cover edge cases
|
|
1025
|
-
- [ ] Documentation updated
|
|
1026
|
-
|
|
1027
|
-
2. **Submit Pull Request**
|
|
1028
|
-
- Clear title and description
|
|
1029
|
-
- Reference related issues
|
|
1030
|
-
- Include test results
|
|
1031
|
-
- Follow template
|
|
1032
|
-
- **NEW**: Validate rule registry with `node validate-system.js`
|
|
1033
|
-
|
|
1034
|
-
3. **Review Criteria**
|
|
1035
|
-
- Code quality (follows our own rules!)
|
|
1036
|
-
- Rule properly defined in `enhanced-rules-registry.json`
|
|
1037
|
-
- All engines can load the rule correctly
|
|
1038
|
-
- Test coverage for all supported engines
|
|
1039
|
-
- Documentation completeness
|
|
1040
|
-
- Performance impact
|
|
1041
|
-
- Backward compatibility
|
|
1042
|
-
|
|
1043
|
-
## 📝 **Documentation**
|
|
1044
|
-
|
|
1045
|
-
### **Update Documentation**
|
|
1046
|
-
When adding features:
|
|
1047
|
-
- Update `README.md`
|
|
1048
|
-
- Add rule to `enhanced-rules-registry.json` (this is your main documentation!)
|
|
1049
|
-
- Update configuration examples
|
|
1050
|
-
- Add usage examples
|
|
1051
|
-
- Update `RULE_MIGRATION_SUMMARY.md` if changing rule system
|
|
1052
|
-
|
|
1053
|
-
### **Rule Documentation Template**
|
|
1054
|
-
|
|
1055
|
-
All rule documentation is now centralized in `enhanced-rules-registry.json`:
|
|
1056
|
-
|
|
1057
|
-
```json
|
|
1058
|
-
{
|
|
1059
|
-
"C042": {
|
|
1060
|
-
"title": "Clear, descriptive rule title",
|
|
1061
|
-
"description": "Detailed explanation of what the rule checks, why it matters, and how to fix violations. Follow Rule C015 (domain language) - use clear business terms.",
|
|
1062
|
-
"examples": {
|
|
1063
|
-
"invalid": [
|
|
1064
|
-
"// Bad example that violates the rule",
|
|
1065
|
-
"const API_URL = 'https://hardcoded-url.com';"
|
|
1066
|
-
],
|
|
1067
|
-
"valid": [
|
|
1068
|
-
"// Good example that follows the rule",
|
|
1069
|
-
"const apiUrl = process.env.API_URL;"
|
|
1070
|
-
]
|
|
1071
|
-
},
|
|
1072
|
-
"fixSuggestions": [
|
|
1073
|
-
"Move configuration to environment variables",
|
|
1074
|
-
"Use a configuration management system",
|
|
1075
|
-
"Extract constants to a separate config file"
|
|
1076
|
-
],
|
|
1077
|
-
"relatedRules": ["C031", "C034"]
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
```
|
|
1081
|
-
|
|
1082
|
-
### **Engine-Specific Documentation**
|
|
1083
|
-
|
|
1084
|
-
#### **Heuristic Engine Rules**
|
|
1085
|
-
Document custom analysis patterns:
|
|
1086
|
-
```json
|
|
1087
|
-
{
|
|
1088
|
-
"analysisStrategy": {
|
|
1089
|
-
"type": "ast",
|
|
1090
|
-
"description": "Analyzes AST nodes for hardcoded configuration patterns",
|
|
1091
|
-
"astTargets": ["VariableDeclaration", "PropertyAssignment"],
|
|
1092
|
-
"patterns": ["literal-values-in-config-context"],
|
|
1093
|
-
"complexity": "O(n) where n is number of variable declarations"
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
```
|
|
1097
|
-
|
|
1098
|
-
#### **ESLint Engine Rules**
|
|
1099
|
-
Document ESLint rule mappings:
|
|
1100
|
-
```json
|
|
1101
|
-
{
|
|
1102
|
-
"engineMappings": {
|
|
1103
|
-
"eslint": {
|
|
1104
|
-
"rules": ["no-magic-numbers"],
|
|
1105
|
-
"rationale": "ESLint's no-magic-numbers rule catches hardcoded values",
|
|
1106
|
-
"limitations": "May have false positives for legitimate constants",
|
|
1107
|
-
"customConfig": {
|
|
1108
|
-
"no-magic-numbers": ["error", { "ignore": [0, 1, -1] }]
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
```
|
|
1114
|
-
|
|
1115
|
-
#### **OpenAI Engine Rules**
|
|
1116
|
-
Document AI prompts and examples:
|
|
1117
|
-
```json
|
|
1118
|
-
{
|
|
1119
|
-
"engineMappings": {
|
|
1120
|
-
"openai": {
|
|
1121
|
-
"prompt": "Identify hardcoded configuration values that should be externalized to environment variables or config files",
|
|
1122
|
-
"context": "Look for URLs, timeouts, limits, and other configuration that might change between environments",
|
|
1123
|
-
"examples": [
|
|
1124
|
-
"❌ const API_URL = 'https://api.example.com';",
|
|
1125
|
-
"✅ const API_URL = process.env.API_URL || 'https://default-api.com';"
|
|
1126
|
-
]
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
```
|
|
1131
|
-
|
|
1132
|
-
## 🐛 **Bug Reports**
|
|
1133
|
-
|
|
1134
|
-
When reporting bugs:
|
|
1135
|
-
1. Use clear, descriptive title
|
|
1136
|
-
2. Include reproduction steps
|
|
1137
|
-
3. Provide sample code
|
|
1138
|
-
4. Include environment details
|
|
1139
|
-
5. Include sunlint output
|
|
1140
|
-
6. **NEW**: Run `node validate-system.js` and include output
|
|
1141
|
-
|
|
1142
|
-
## 🔧 **Troubleshooting**
|
|
1143
|
-
|
|
1144
|
-
### **Common Issues**
|
|
1145
|
-
|
|
1146
|
-
#### **Rule Not Loading**
|
|
1147
|
-
```bash
|
|
1148
|
-
# Check if rule exists in registry
|
|
1149
|
-
node -e "const registry = require('./core/unified-rule-registry'); registry.getInstance().initialize().then(() => console.log('Rule C042:', registry.getInstance().rules.has('C042')))"
|
|
1150
|
-
|
|
1151
|
-
# Validate registry syntax
|
|
1152
|
-
node validate-system.js
|
|
1153
|
-
```
|
|
1154
|
-
|
|
1155
|
-
#### **Engine Not Finding Rule**
|
|
1156
|
-
```bash
|
|
1157
|
-
# Check engine-specific mapping
|
|
1158
|
-
node -e "const registry = require('./core/unified-rule-registry'); registry.getInstance().initialize().then(r => { const rule = r.rules.get('C042'); console.log('ESLint mapping:', rule?.engineMappings?.eslint); })"
|
|
1159
|
-
```
|
|
1160
|
-
|
|
1161
|
-
#### **Rule Registry Errors**
|
|
1162
|
-
```bash
|
|
1163
|
-
# Common issues:
|
|
1164
|
-
# 1. JSON syntax errors in enhanced-rules-registry.json
|
|
1165
|
-
# 2. Missing required fields (id, title, description, severity, category)
|
|
1166
|
-
# 3. Invalid engine mapping structure
|
|
1167
|
-
|
|
1168
|
-
# Validate JSON syntax
|
|
1169
|
-
node -c config/enhanced-rules-registry.json
|
|
1170
|
-
|
|
1171
|
-
# Check required fields
|
|
1172
|
-
node validate-system.js
|
|
1173
|
-
```
|
|
1174
|
-
|
|
1175
|
-
### **Performance Issues**
|
|
1176
|
-
```bash
|
|
1177
|
-
# Profile rule loading
|
|
1178
|
-
node --prof cli.js --rule=C042 --input=large-project/
|
|
1179
|
-
|
|
1180
|
-
# Check memory usage
|
|
1181
|
-
node --inspect cli.js --rule=C042 --input=test/fixtures/
|
|
1182
|
-
```
|
|
1183
|
-
|
|
1184
|
-
## 💡 **Feature Requests**
|
|
1185
|
-
|
|
1186
|
-
For new features:
|
|
1187
|
-
1. Check existing issues first
|
|
1188
|
-
2. Describe the use case
|
|
1189
|
-
3. Provide examples
|
|
1190
|
-
4. Consider implementation complexity
|
|
1191
|
-
5. Think about backward compatibility
|
|
1192
|
-
|
|
1193
|
-
## 📋 **Quick Reference**
|
|
1194
|
-
|
|
1195
|
-
### **Essential Commands**
|
|
1196
|
-
```bash
|
|
1197
|
-
# Add ESLint engine rule mapping
|
|
1198
|
-
vim config/eslint-rule-mapping.json
|
|
1199
|
-
|
|
1200
|
-
# Create custom heuristic rule
|
|
1201
|
-
vim custom-rules/c042-rule-name.js
|
|
1202
|
-
|
|
1203
|
-
# Add rule documentation
|
|
1204
|
-
vim origin-rules/common-en.md
|
|
1205
|
-
|
|
1206
|
-
# Test new rule with specific engine
|
|
1207
|
-
node cli.js --rule=CXXX --engine=eslint --input=test/fixtures
|
|
1208
|
-
node cli.js --rule=CXXX --engine=heuristic --input=test/fixtures
|
|
1209
|
-
|
|
1210
|
-
# Test strict engine mode (no fallback)
|
|
1211
|
-
node cli.js --rule=CXXX --engine=eslint --input=test/fixtures
|
|
1212
|
-
|
|
1213
|
-
# Test fallback mode (auto engine selection)
|
|
1214
|
-
node cli.js --rule=CXXX --input=test/fixtures
|
|
1215
|
-
```
|
|
1216
|
-
|
|
1217
|
-
### **Key Files**
|
|
1218
|
-
- `config/eslint-rule-mapping.json` - **ESLint engine rule mappings**
|
|
1219
|
-
- `origin-rules/` - **Original rule definitions** (markdown format)
|
|
1220
|
-
- `rules/` - **Generated rule registry** (auto-generated)
|
|
1221
|
-
- `core/unified-rule-registry.js` - Rule registry loader
|
|
1222
|
-
- `engines/heuristic-engine.js` - Custom pattern analysis
|
|
1223
|
-
- `engines/eslint-engine.js` - JavaScript/TypeScript linting
|
|
1224
|
-
- `engines/openai-engine.js` - AI-powered analysis
|
|
1225
|
-
- `integrations/eslint/plugin/` - Custom ESLint rules
|
|
1226
|
-
- `custom-rules/` - Heuristic engine custom rules
|
|
1227
|
-
|
|
1228
|
-
### **Rule Development Checklist**
|
|
1229
|
-
- [ ] Choose appropriate engine (ESLint for JS/TS, Heuristic for universal)
|
|
1230
|
-
- [ ] Add ESLint mapping to `config/eslint-rule-mapping.json` (if using ESLint engine)
|
|
1231
|
-
- [ ] Create custom rule implementation (if needed)
|
|
1232
|
-
- [ ] Add rule definition to `origin-rules/` (markdown format)
|
|
1233
|
-
- [ ] Add test cases and examples
|
|
1234
|
-
- [ ] Test with `node cli.js --rule=CXXX --input=test/fixtures`
|
|
1235
|
-
- [ ] Test engine-specific behavior (`--engine=eslint`, `--engine=heuristic`)
|
|
1236
|
-
- [ ] Update documentation if needed
|
|
1237
|
-
|
|
1238
|
-
---
|
|
1239
|
-
|
|
1240
|
-
**🚀 Ready to contribute? Start by choosing your engine and editing the appropriate mapping file!**
|
|
1241
|
-
|
|
1242
|
-
**For ESLint rules**: Edit `config/eslint-rule-mapping.json`
|
|
1243
|
-
**For Heuristic rules**: Create files in `custom-rules/`
|
|
1244
|
-
**For documentation**: Add to `origin-rules/` markdown files
|
|
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
|
-
|
|
1811
|
-
## 🤝 **Community**
|
|
1812
|
-
|
|
1813
|
-
- **Discord**: [Sun Engineering Discord](https://discord.gg/sun-engineering)
|
|
1814
|
-
- **Issues**: [GitHub Issues](https://github.com/sun-engineering/sunlint/issues)
|
|
1815
|
-
- **Discussions**: [GitHub Discussions](https://github.com/sun-engineering/sunlint/discussions)
|
|
1816
|
-
|
|
1817
|
-
## 📄 **License**
|
|
333
|
+
## 📚 Advanced Topics
|
|
1818
334
|
|
|
1819
|
-
|
|
335
|
+
- **AST Navigation**: Use ts-morph documentation for node traversal
|
|
336
|
+
- **Performance**: Symbol-based analysis is ~15s for 2000+ files
|
|
337
|
+
- **Multi-language**: Extend analyzers for Dart, Kotlin support
|
|
338
|
+
- **Custom Patterns**: Leverage SyntaxKind for specific constructs
|
|
1820
339
|
|
|
1821
340
|
---
|
|
1822
341
|
|
|
1823
|
-
|
|
342
|
+
This guide is based on real experience refactoring C013 and other rules. Focus on accuracy over speed - symbol-based analysis provides much better results than regex patterns.
|