@sun-asterisk/sunlint 1.3.18 → 1.3.19
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/config/rules/enhanced-rules-registry.json +77 -18
- package/core/cli-program.js +2 -1
- package/core/github-annotate-service.js +89 -0
- package/core/output-service.js +25 -0
- package/core/summary-report-service.js +30 -30
- package/package.json +3 -2
- package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +392 -280
- package/rules/common/C017_constructor_logic/analyzer.js +137 -503
- package/rules/common/C017_constructor_logic/config.json +50 -0
- package/rules/common/C017_constructor_logic/symbol-based-analyzer.js +463 -0
- package/rules/security/S006_no_plaintext_recovery_codes/symbol-based-analyzer.js +463 -21
- package/rules/security/S011_secure_guid_generation/README.md +255 -0
- package/rules/security/S011_secure_guid_generation/analyzer.js +135 -0
- package/rules/security/S011_secure_guid_generation/config.json +56 -0
- package/rules/security/S011_secure_guid_generation/symbol-based-analyzer.js +609 -0
- package/rules/security/S028_file_upload_size_limits/README.md +537 -0
- package/rules/security/S028_file_upload_size_limits/analyzer.js +202 -0
- package/rules/security/S028_file_upload_size_limits/config.json +186 -0
- package/rules/security/S028_file_upload_size_limits/symbol-based-analyzer.js +530 -0
- package/rules/security/S041_session_token_invalidation/README.md +303 -0
- package/rules/security/S041_session_token_invalidation/analyzer.js +242 -0
- package/rules/security/S041_session_token_invalidation/config.json +175 -0
- package/rules/security/S041_session_token_invalidation/regex-based-analyzer.js +411 -0
- package/rules/security/S041_session_token_invalidation/symbol-based-analyzer.js +674 -0
- package/rules/security/S044_re_authentication_required/README.md +136 -0
- package/rules/security/S044_re_authentication_required/analyzer.js +242 -0
- package/rules/security/S044_re_authentication_required/config.json +161 -0
- package/rules/security/S044_re_authentication_required/regex-based-analyzer.js +329 -0
- package/rules/security/S044_re_authentication_required/symbol-based-analyzer.js +537 -0
- package/rules/security/S045_brute_force_protection/README.md +345 -0
- package/rules/security/S045_brute_force_protection/analyzer.js +336 -0
- package/rules/security/S045_brute_force_protection/config.json +139 -0
- package/rules/security/S045_brute_force_protection/symbol-based-analyzer.js +646 -0
- package/rules/common/C017_constructor_logic/semantic-analyzer.js +0 -340
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# S041 - Session Tokens must be invalidated after logout or expiration
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This rule enforces proper session token invalidation after logout or expiration to prevent session hijacking and unauthorized access. It ensures that session data is properly cleared and tokens are invalidated when users log out.
|
|
6
|
+
|
|
7
|
+
## Description
|
|
8
|
+
|
|
9
|
+
Session token invalidation is a critical security measure that helps prevent:
|
|
10
|
+
|
|
11
|
+
- **Session hijacking** through stolen or leaked tokens
|
|
12
|
+
- **Unauthorized access** using expired or invalidated sessions
|
|
13
|
+
- **Token reuse attacks** where old tokens remain valid
|
|
14
|
+
- **Privilege escalation** through persistent session data
|
|
15
|
+
|
|
16
|
+
## Rule Details
|
|
17
|
+
|
|
18
|
+
**Rule ID**: S041
|
|
19
|
+
**Category**: Security
|
|
20
|
+
**Severity**: Error
|
|
21
|
+
**Type**: Hybrid Analysis (Symbol-based + Regex-based)
|
|
22
|
+
|
|
23
|
+
## Examples
|
|
24
|
+
|
|
25
|
+
### ❌ Violations
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// Express.js - Logout without session cleanup
|
|
29
|
+
app.post('/logout', (req, res) => {
|
|
30
|
+
// Missing session cleanup
|
|
31
|
+
res.json({ message: 'Logged out' });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// NestJS - Logout handler without token invalidation
|
|
35
|
+
@Post('logout')
|
|
36
|
+
async logout(@Req() req: Request, @Res() res: Response) {
|
|
37
|
+
// Missing token invalidation
|
|
38
|
+
res.redirect('/login');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// JWT token signing during logout (wrong approach)
|
|
42
|
+
app.post('/logout', (req, res) => {
|
|
43
|
+
const token = jwt.sign({ userId: req.user.id }, secret);
|
|
44
|
+
res.json({ token }); // Should invalidate, not sign new token
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Session method without proper cleanup
|
|
48
|
+
app.post('/logout', (req, res) => {
|
|
49
|
+
req.session.userId = null; // Should use destroy()
|
|
50
|
+
res.json({ message: 'Logged out' });
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Logout handler function without session cleanup
|
|
54
|
+
function logoutHandler(req, res) {
|
|
55
|
+
// Missing session cleanup and token invalidation
|
|
56
|
+
res.json({ message: 'Logged out successfully' });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Session storage without token invalidation
|
|
60
|
+
app.post('/logout', (req, res) => {
|
|
61
|
+
sessionStorage.removeItem('userToken');
|
|
62
|
+
// Missing proper session cleanup
|
|
63
|
+
res.json({ message: 'Logged out' });
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### ✅ Correct Usage
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// Express.js - Complete logout with session cleanup
|
|
71
|
+
app.post('/logout', (req, res) => {
|
|
72
|
+
req.session.destroy((err) => {
|
|
73
|
+
if (err) {
|
|
74
|
+
return res.status(500).json({ error: 'Logout failed' });
|
|
75
|
+
}
|
|
76
|
+
res.clearCookie('sessionId');
|
|
77
|
+
res.json({ message: 'Logged out successfully' });
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// NestJS - Logout with token invalidation
|
|
82
|
+
@Post('logout')
|
|
83
|
+
async logout(@Req() req: Request, @Res() res: Response) {
|
|
84
|
+
const token = req.headers.authorization?.split(' ')[1];
|
|
85
|
+
if (token) {
|
|
86
|
+
await this.tokenService.blacklist(token);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
req.session.destroy();
|
|
90
|
+
res.clearCookie('sessionId');
|
|
91
|
+
res.redirect('/login');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// JWT token blacklisting during logout
|
|
95
|
+
app.post('/logout', async (req, res) => {
|
|
96
|
+
const token = req.headers.authorization?.split(' ')[1];
|
|
97
|
+
if (token) {
|
|
98
|
+
await tokenService.blacklist(token);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
req.session.destroy();
|
|
102
|
+
res.clearCookie('sessionId');
|
|
103
|
+
res.json({ message: 'Logged out successfully' });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Complete logout implementation
|
|
107
|
+
app.post('/logout', async (req, res) => {
|
|
108
|
+
try {
|
|
109
|
+
// 1. Invalidate JWT token
|
|
110
|
+
const token = req.headers.authorization?.split(' ')[1];
|
|
111
|
+
if (token) {
|
|
112
|
+
await this.invalidateToken(token);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 2. Clear session data
|
|
116
|
+
req.session.destroy((err) => {
|
|
117
|
+
if (err) {
|
|
118
|
+
return res.status(500).json({ error: 'Session cleanup failed' });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 3. Clear all cookies
|
|
122
|
+
res.clearCookie('sessionId');
|
|
123
|
+
res.clearCookie('token');
|
|
124
|
+
res.clearCookie('refreshToken');
|
|
125
|
+
|
|
126
|
+
res.json({
|
|
127
|
+
message: 'Logged out successfully',
|
|
128
|
+
clearStorage: true
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
} catch (error) {
|
|
132
|
+
res.status(500).json({ error: 'Logout failed' });
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Express session with regeneration
|
|
137
|
+
app.post('/secure-logout', (req, res) => {
|
|
138
|
+
req.session.regenerate((err) => {
|
|
139
|
+
if (err) {
|
|
140
|
+
return res.status(500).json({ error: 'Logout failed' });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
res.clearCookie('connect.sid', {
|
|
144
|
+
httpOnly: true,
|
|
145
|
+
secure: process.env.NODE_ENV === 'production',
|
|
146
|
+
sameSite: 'strict'
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
res.json({ message: 'Securely logged out' });
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Configuration
|
|
155
|
+
|
|
156
|
+
### Detected Patterns
|
|
157
|
+
|
|
158
|
+
This rule detects session token invalidation issues in multiple scenarios:
|
|
159
|
+
|
|
160
|
+
### Logout Methods
|
|
161
|
+
|
|
162
|
+
- `logout()`, `signOut()`, `logOut()` methods without session cleanup
|
|
163
|
+
- `invalidate()`, `revoke()`, `expire()` methods without proper implementation
|
|
164
|
+
- Logout handler functions without token invalidation
|
|
165
|
+
|
|
166
|
+
### Session Methods
|
|
167
|
+
|
|
168
|
+
- `req.session.destroy()` calls in logout context
|
|
169
|
+
- `req.session.clear()`, `req.session.remove()` without token invalidation
|
|
170
|
+
- `sessionStorage.clear()`, `localStorage.clear()` without proper cleanup
|
|
171
|
+
|
|
172
|
+
### JWT Token Handling
|
|
173
|
+
|
|
174
|
+
- `jwt.sign()` calls during logout (should invalidate instead)
|
|
175
|
+
- JWT token creation in logout context
|
|
176
|
+
- Missing token blacklisting/revocation
|
|
177
|
+
|
|
178
|
+
### Session Storage
|
|
179
|
+
|
|
180
|
+
- `sessionStorage.removeItem()`, `localStorage.removeItem()` without token invalidation
|
|
181
|
+
- Session data clearing without proper token cleanup
|
|
182
|
+
- Cookie clearing without session destruction
|
|
183
|
+
|
|
184
|
+
**Logout Method Names:**
|
|
185
|
+
|
|
186
|
+
- `logout`, `signOut`, `logOut`
|
|
187
|
+
- `destroy`, `clear`, `invalidate`
|
|
188
|
+
- `revoke`, `expire`
|
|
189
|
+
|
|
190
|
+
**Session Methods:**
|
|
191
|
+
|
|
192
|
+
- `req.session.destroy`, `req.session.clear`, `req.session.remove`
|
|
193
|
+
- `sessionStorage.clear`, `localStorage.clear`
|
|
194
|
+
- `res.clearCookie`, `res.clearCookie()`
|
|
195
|
+
|
|
196
|
+
**Token Invalidation Patterns:**
|
|
197
|
+
|
|
198
|
+
- `blacklist`, `revoke`, `invalidate`
|
|
199
|
+
- `expire`, `remove.*token`, `delete.*token`
|
|
200
|
+
- `clear.*token`, `destroy.*token`
|
|
201
|
+
|
|
202
|
+
### Required Session Cleanup Patterns
|
|
203
|
+
|
|
204
|
+
**Session Cleanup:**
|
|
205
|
+
|
|
206
|
+
- `req.session.destroy()`
|
|
207
|
+
- `req.session = null`
|
|
208
|
+
- `req.session = {}`
|
|
209
|
+
- `session.destroy()`
|
|
210
|
+
- `session.clear()`
|
|
211
|
+
- `session.remove()`
|
|
212
|
+
|
|
213
|
+
**Cookie Cleanup:**
|
|
214
|
+
|
|
215
|
+
- `res.clearCookie()`
|
|
216
|
+
- `sessionStorage.clear()`
|
|
217
|
+
- `localStorage.clear()`
|
|
218
|
+
|
|
219
|
+
**Token Invalidation:**
|
|
220
|
+
|
|
221
|
+
- Token blacklisting
|
|
222
|
+
- Token revocation
|
|
223
|
+
- Token expiration
|
|
224
|
+
- Token removal/deletion
|
|
225
|
+
|
|
226
|
+
## Security Benefits
|
|
227
|
+
|
|
228
|
+
1. **Session Security**: Prevents unauthorized access through persistent sessions
|
|
229
|
+
2. **Token Invalidation**: Ensures old tokens cannot be reused
|
|
230
|
+
3. **Data Cleanup**: Removes sensitive session data from memory
|
|
231
|
+
4. **Attack Prevention**: Mitigates session hijacking and token reuse attacks
|
|
232
|
+
|
|
233
|
+
## Analysis Approach
|
|
234
|
+
|
|
235
|
+
### Symbol-based Analysis (Primary)
|
|
236
|
+
|
|
237
|
+
- Uses TypeScript AST for semantic analysis
|
|
238
|
+
- Detects logout methods and session handling
|
|
239
|
+
- Analyzes function context for proper cleanup
|
|
240
|
+
- Provides precise line/column positions
|
|
241
|
+
|
|
242
|
+
### Regex-based Analysis (Fallback)
|
|
243
|
+
|
|
244
|
+
- Pattern-based detection for complex cases
|
|
245
|
+
- Handles string-based session methods
|
|
246
|
+
- Covers edge cases missed by AST analysis
|
|
247
|
+
- Maintains line number accuracy
|
|
248
|
+
|
|
249
|
+
## Best Practices
|
|
250
|
+
|
|
251
|
+
1. **Always destroy sessions** when users log out
|
|
252
|
+
2. **Invalidate JWT tokens** by blacklisting or revoking them
|
|
253
|
+
3. **Clear all cookies** related to authentication
|
|
254
|
+
4. **Remove session data** from client-side storage
|
|
255
|
+
5. **Handle errors gracefully** during logout process
|
|
256
|
+
6. **Use proper session methods** like `destroy()` instead of setting to null
|
|
257
|
+
7. **Implement token blacklisting** for JWT-based authentication
|
|
258
|
+
8. **Clear multiple storage types** (sessionStorage, localStorage, cookies)
|
|
259
|
+
|
|
260
|
+
## Testing
|
|
261
|
+
|
|
262
|
+
Run the rule on test fixtures:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
# Test violations
|
|
266
|
+
node cli.js --rule=S041 --input=examples/rule-test-fixtures/rules/S041_session_token_invalidation/violations --engine=heuristic
|
|
267
|
+
|
|
268
|
+
# Test clean examples
|
|
269
|
+
node cli.js --rule=S041 --input=examples/rule-test-fixtures/rules/S041_session_token_invalidation/clean --engine=heuristic
|
|
270
|
+
|
|
271
|
+
# Test all examples
|
|
272
|
+
node cli.js --rule=S041 --input=examples/rule-test-fixtures/rules/S041_session_token_invalidation --engine=heuristic
|
|
273
|
+
|
|
274
|
+
# Framework-specific testing
|
|
275
|
+
# Test with ESLint engine (fast)
|
|
276
|
+
node cli.js --rule=S041 --input=path/to/your/files
|
|
277
|
+
|
|
278
|
+
# Test with heuristic engine (comprehensive)
|
|
279
|
+
node cli.js --rule=S041 --input=path/to/your/files --engine=heuristic
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Framework Compatibility
|
|
283
|
+
|
|
284
|
+
- **Node.js**: All versions
|
|
285
|
+
- **Frameworks**: Express.js, NestJS, Next.js, NextAuth.js
|
|
286
|
+
- **Languages**: JavaScript, TypeScript
|
|
287
|
+
- **Analysis Engines**: ESLint (fast), Heuristic (comprehensive)
|
|
288
|
+
|
|
289
|
+
## Related Rules
|
|
290
|
+
|
|
291
|
+
- **S031**: Set Secure flag for Session Cookies
|
|
292
|
+
- **S032**: Set HttpOnly attribute for Session Cookies
|
|
293
|
+
- **S033**: Set SameSite attribute for Session Cookies
|
|
294
|
+
- **S034**: Use __Host- prefix for Session Cookies
|
|
295
|
+
- **S049**: Authentication tokens should have short validity periods
|
|
296
|
+
|
|
297
|
+
Together, these rules provide comprehensive session and token security.
|
|
298
|
+
|
|
299
|
+
## OWASP References
|
|
300
|
+
|
|
301
|
+
- [OWASP Session Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html)
|
|
302
|
+
- [OWASP Session Hijacking Attack](https://owasp.org/www-community/attacks/Session_hijacking_attack)
|
|
303
|
+
- [OWASP Authentication Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html)
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S041 Main Analyzer - Session Tokens must be invalidated after logout or expiration
|
|
3
|
+
* Primary: Symbol-based analysis (when available)
|
|
4
|
+
* Fallback: Regex-based for all other cases
|
|
5
|
+
* Command: node cli.js --rule=S041 --input=examples/rule-test-fixtures/rules/S041_session_token_invalidation --engine=heuristic
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const S041SymbolBasedAnalyzer = require("./symbol-based-analyzer.js");
|
|
9
|
+
const S041RegexBasedAnalyzer = require("./regex-based-analyzer.js");
|
|
10
|
+
|
|
11
|
+
class S041Analyzer {
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
14
|
+
console.log(`🔧 [S041] Constructor called with options:`, !!options);
|
|
15
|
+
console.log(
|
|
16
|
+
`🔧 [S041] Options type:`,
|
|
17
|
+
typeof options,
|
|
18
|
+
Object.keys(options || {})
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
this.ruleId = "S041";
|
|
23
|
+
this.ruleName = "Session Tokens must be invalidated after logout or expiration";
|
|
24
|
+
this.description =
|
|
25
|
+
"Session tokens must be properly invalidated after logout or expiration to prevent session hijacking and unauthorized access. This includes clearing session data, invalidating JWT tokens, and ensuring proper session cleanup.";
|
|
26
|
+
this.semanticEngine = options.semanticEngine || null;
|
|
27
|
+
this.verbose = options.verbose || false;
|
|
28
|
+
|
|
29
|
+
// Configuration
|
|
30
|
+
this.config = {
|
|
31
|
+
useSymbolBased: true, // Primary approach
|
|
32
|
+
fallbackToRegex: true, // Secondary approach
|
|
33
|
+
regexBasedOnly: false, // Can be set to true for pure mode
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Initialize analyzers
|
|
37
|
+
try {
|
|
38
|
+
this.symbolAnalyzer = new S041SymbolBasedAnalyzer(this.semanticEngine);
|
|
39
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
40
|
+
console.log(`🔧 [S041] Symbol analyzer created successfully`);
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error(`🔧 [S041] Error creating symbol analyzer:`, error);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
this.regexAnalyzer = new S041RegexBasedAnalyzer(this.semanticEngine);
|
|
48
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
49
|
+
console.log(`🔧 [S041] Regex analyzer created successfully`);
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error(`🔧 [S041] Error creating regex analyzer:`, error);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Initialize analyzer with semantic engine
|
|
58
|
+
*/
|
|
59
|
+
async initialize(semanticEngine) {
|
|
60
|
+
this.semanticEngine = semanticEngine;
|
|
61
|
+
|
|
62
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
63
|
+
console.log(`🔧 [S041] Main analyzer initializing...`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Initialize both analyzers
|
|
67
|
+
if (this.symbolAnalyzer) {
|
|
68
|
+
await this.symbolAnalyzer.initialize?.(semanticEngine);
|
|
69
|
+
}
|
|
70
|
+
if (this.regexAnalyzer) {
|
|
71
|
+
await this.regexAnalyzer.initialize?.(semanticEngine);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Clean up if needed
|
|
75
|
+
if (this.regexAnalyzer) {
|
|
76
|
+
this.regexAnalyzer.cleanup?.();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
80
|
+
console.log(`🔧 [S041] Main analyzer initialized successfully`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Single file analysis method for testing
|
|
86
|
+
*/
|
|
87
|
+
analyzeSingle(filePath, options = {}) {
|
|
88
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
89
|
+
console.log(`📊 [S041] analyzeSingle() called for: ${filePath}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Return result using same format as analyze method
|
|
93
|
+
return this.analyze([filePath], "typescript", options);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async analyze(files, language, options = {}) {
|
|
97
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
98
|
+
console.log(
|
|
99
|
+
`🔧 [S041] analyze() method called with ${files.length} files, language: ${language}`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const violations = [];
|
|
104
|
+
|
|
105
|
+
for (const filePath of files) {
|
|
106
|
+
try {
|
|
107
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
108
|
+
console.log(`🔧 [S041] Processing file: ${filePath}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const fileViolations = await this.analyzeFile(filePath, options);
|
|
112
|
+
violations.push(...fileViolations);
|
|
113
|
+
|
|
114
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
115
|
+
console.log(
|
|
116
|
+
`🔧 [S041] File ${filePath}: Found ${fileViolations.length} violations`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.warn(
|
|
121
|
+
`⚠ [S041] Analysis failed for ${filePath}:`,
|
|
122
|
+
error.message
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
128
|
+
console.log(`🔧 [S041] Total violations found: ${violations.length}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return violations;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async analyzeFile(filePath, options = {}) {
|
|
135
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
136
|
+
console.log(`🔍 [S041] analyzeFile() called for: ${filePath}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Create a Map to track unique violations and prevent duplicates
|
|
140
|
+
const violationMap = new Map();
|
|
141
|
+
|
|
142
|
+
// 1. Try Symbol-based analysis first (primary)
|
|
143
|
+
if (
|
|
144
|
+
this.config.useSymbolBased &&
|
|
145
|
+
this.semanticEngine?.project &&
|
|
146
|
+
this.semanticEngine?.initialized
|
|
147
|
+
) {
|
|
148
|
+
try {
|
|
149
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
150
|
+
console.log(`🔧 [S041] Trying symbol-based analysis...`);
|
|
151
|
+
}
|
|
152
|
+
const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
|
|
153
|
+
if (sourceFile) {
|
|
154
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
155
|
+
console.log(`🔧 [S041] Source file found, analyzing...`);
|
|
156
|
+
}
|
|
157
|
+
const symbolViolations = await this.symbolAnalyzer.analyze(
|
|
158
|
+
sourceFile,
|
|
159
|
+
filePath
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
// Add to violation map with deduplication
|
|
163
|
+
symbolViolations.forEach((violation) => {
|
|
164
|
+
const key = `${violation.line}:${violation.column}:${violation.message}`;
|
|
165
|
+
if (!violationMap.has(key)) {
|
|
166
|
+
violationMap.set(key, violation);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
171
|
+
console.log(
|
|
172
|
+
`🔧 [S041] Symbol analysis completed: ${symbolViolations.length} violations`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
177
|
+
console.log(`🔧 [S041] Source file not found, falling back...`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.warn(`⚠ [S041] Symbol analysis failed:`, error.message);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 2. Try Regex-based analysis (fallback or additional)
|
|
186
|
+
if (this.config.fallbackToRegex || this.config.regexBasedOnly) {
|
|
187
|
+
try {
|
|
188
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
189
|
+
console.log(`🔧 [S041] Trying regex-based analysis...`);
|
|
190
|
+
}
|
|
191
|
+
const regexViolations = await this.regexAnalyzer.analyze(filePath);
|
|
192
|
+
|
|
193
|
+
// Add to violation map with deduplication
|
|
194
|
+
regexViolations.forEach((violation) => {
|
|
195
|
+
const key = `${violation.line}:${violation.column}:${violation.message}`;
|
|
196
|
+
if (!violationMap.has(key)) {
|
|
197
|
+
violationMap.set(key, violation);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
202
|
+
console.log(
|
|
203
|
+
`🔧 [S041] Regex analysis completed: ${regexViolations.length} violations`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.warn(`⚠ [S041] Regex analysis failed:`, error.message);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Convert Map values to array and add filePath to each violation
|
|
212
|
+
const finalViolations = Array.from(violationMap.values()).map(
|
|
213
|
+
(violation) => ({
|
|
214
|
+
...violation,
|
|
215
|
+
filePath: filePath,
|
|
216
|
+
file: filePath, // Also add 'file' for compatibility
|
|
217
|
+
})
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
221
|
+
console.log(
|
|
222
|
+
`🔧 [S041] File analysis completed: ${finalViolations.length} unique violations`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return finalViolations;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Clean up resources
|
|
231
|
+
*/
|
|
232
|
+
cleanup() {
|
|
233
|
+
if (this.symbolAnalyzer?.cleanup) {
|
|
234
|
+
this.symbolAnalyzer.cleanup();
|
|
235
|
+
}
|
|
236
|
+
if (this.regexAnalyzer?.cleanup) {
|
|
237
|
+
this.regexAnalyzer.cleanup();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
module.exports = S041Analyzer;
|