@stackguide/mcp-server 2.4.0 → 3.1.0
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/README.md +82 -16
- package/dist/config/healthWeights.d.ts +87 -0
- package/dist/config/healthWeights.d.ts.map +1 -0
- package/dist/config/healthWeights.js +238 -0
- package/dist/config/healthWeights.js.map +1 -0
- package/dist/config/types.d.ts +150 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js.map +1 -1
- package/dist/handlers/generate.d.ts +6 -0
- package/dist/handlers/generate.d.ts.map +1 -1
- package/dist/handlers/generate.js +81 -5
- package/dist/handlers/generate.js.map +1 -1
- package/dist/handlers/health.d.ts +7 -0
- package/dist/handlers/health.d.ts.map +1 -1
- package/dist/handlers/health.js +151 -69
- package/dist/handlers/health.js.map +1 -1
- package/dist/handlers/review.d.ts +12 -0
- package/dist/handlers/review.d.ts.map +1 -1
- package/dist/handlers/review.js +220 -21
- package/dist/handlers/review.js.map +1 -1
- package/dist/services/analysisCache.d.ts +110 -0
- package/dist/services/analysisCache.d.ts.map +1 -0
- package/dist/services/analysisCache.js +233 -0
- package/dist/services/analysisCache.js.map +1 -0
- package/dist/services/astAnalyzer.d.ts +21 -0
- package/dist/services/astAnalyzer.d.ts.map +1 -0
- package/dist/services/astAnalyzer.js +593 -0
- package/dist/services/astAnalyzer.js.map +1 -0
- package/dist/services/codeAnalyzer.d.ts +109 -30
- package/dist/services/codeAnalyzer.d.ts.map +1 -1
- package/dist/services/codeAnalyzer.js +519 -36
- package/dist/services/codeAnalyzer.js.map +1 -1
- package/dist/services/conventionDetector.d.ts +40 -0
- package/dist/services/conventionDetector.d.ts.map +1 -0
- package/dist/services/conventionDetector.js +465 -0
- package/dist/services/conventionDetector.js.map +1 -0
- package/dist/services/cursorDirectory.d.ts +29 -2
- package/dist/services/cursorDirectory.d.ts.map +1 -1
- package/dist/services/cursorDirectory.js +260 -9
- package/dist/services/cursorDirectory.js.map +1 -1
- package/dist/utils/validation.d.ts +2 -2
- package/package.json +2 -1
|
@@ -1,19 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Code Analyzer Service
|
|
3
|
-
*
|
|
2
|
+
* Code Analyzer Service - v3.1.0
|
|
3
|
+
* Unified rule pipeline that supports builtin, user, and project rules
|
|
4
|
+
* Now with AST-based analysis using ts-morph
|
|
4
5
|
*/
|
|
5
6
|
import { logger } from '../utils/logger.js';
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
import { analyzeWithAST, BUILTIN_AST_RULES, clearASTCache } from './astAnalyzer.js';
|
|
8
|
+
// Re-export AST utilities
|
|
9
|
+
export { clearASTCache };
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// BUILTIN PATTERN RULES
|
|
12
|
+
// =============================================================================
|
|
13
|
+
const BUILTIN_PATTERN_RULES = [
|
|
8
14
|
// Security
|
|
9
15
|
{
|
|
10
16
|
id: 'SEC001',
|
|
17
|
+
type: 'pattern',
|
|
11
18
|
category: 'security',
|
|
12
19
|
pattern: /eval\s*\(/g,
|
|
13
20
|
severity: 'error',
|
|
14
21
|
message: 'Avoid using eval() - it can execute arbitrary code',
|
|
15
22
|
suggestion: 'Use JSON.parse() for JSON data or safer alternatives',
|
|
16
23
|
languages: ['javascript', 'typescript'],
|
|
24
|
+
enabled: true,
|
|
25
|
+
priority: 100,
|
|
26
|
+
source: 'builtin',
|
|
17
27
|
quickFix: (match) => ({
|
|
18
28
|
description: 'Replace eval() with JSON.parse()',
|
|
19
29
|
before: match,
|
|
@@ -22,12 +32,16 @@ const PATTERN_RULES = [
|
|
|
22
32
|
},
|
|
23
33
|
{
|
|
24
34
|
id: 'SEC002',
|
|
35
|
+
type: 'pattern',
|
|
25
36
|
category: 'security',
|
|
26
37
|
pattern: /innerHTML\s*=/g,
|
|
27
38
|
severity: 'warning',
|
|
28
39
|
message: 'innerHTML can lead to XSS vulnerabilities',
|
|
29
40
|
suggestion: 'Use textContent or sanitize HTML input',
|
|
30
41
|
languages: ['javascript', 'typescript'],
|
|
42
|
+
enabled: true,
|
|
43
|
+
priority: 100,
|
|
44
|
+
source: 'builtin',
|
|
31
45
|
quickFix: (match) => ({
|
|
32
46
|
description: 'Replace innerHTML with textContent',
|
|
33
47
|
before: match,
|
|
@@ -36,20 +50,28 @@ const PATTERN_RULES = [
|
|
|
36
50
|
},
|
|
37
51
|
{
|
|
38
52
|
id: 'SEC003',
|
|
53
|
+
type: 'pattern',
|
|
39
54
|
category: 'security',
|
|
40
55
|
pattern: /dangerouslySetInnerHTML/g,
|
|
41
56
|
severity: 'warning',
|
|
42
57
|
message: 'dangerouslySetInnerHTML can lead to XSS - ensure content is sanitized',
|
|
43
58
|
suggestion: 'Sanitize HTML with DOMPurify or similar library',
|
|
44
|
-
languages: ['javascript', 'typescript', 'jsx', 'tsx']
|
|
59
|
+
languages: ['javascript', 'typescript', 'jsx', 'tsx'],
|
|
60
|
+
enabled: true,
|
|
61
|
+
priority: 100,
|
|
62
|
+
source: 'builtin'
|
|
45
63
|
},
|
|
46
64
|
{
|
|
47
65
|
id: 'SEC004',
|
|
66
|
+
type: 'pattern',
|
|
48
67
|
category: 'security',
|
|
49
68
|
pattern: /password\s*[:=]\s*['"`][^'"`]+['"`]/gi,
|
|
50
69
|
severity: 'error',
|
|
51
70
|
message: 'Hardcoded password detected',
|
|
52
71
|
suggestion: 'Use environment variables for sensitive data',
|
|
72
|
+
enabled: true,
|
|
73
|
+
priority: 100,
|
|
74
|
+
source: 'builtin',
|
|
53
75
|
quickFix: (match) => ({
|
|
54
76
|
description: 'Replace hardcoded password with environment variable',
|
|
55
77
|
before: match,
|
|
@@ -58,11 +80,15 @@ const PATTERN_RULES = [
|
|
|
58
80
|
},
|
|
59
81
|
{
|
|
60
82
|
id: 'SEC005',
|
|
83
|
+
type: 'pattern',
|
|
61
84
|
category: 'security',
|
|
62
85
|
pattern: /api[_-]?key\s*[:=]\s*['"`][a-zA-Z0-9]{20,}['"`]/gi,
|
|
63
86
|
severity: 'error',
|
|
64
87
|
message: 'Hardcoded API key detected',
|
|
65
88
|
suggestion: 'Use environment variables for API keys',
|
|
89
|
+
enabled: true,
|
|
90
|
+
priority: 100,
|
|
91
|
+
source: 'builtin',
|
|
66
92
|
quickFix: (match) => ({
|
|
67
93
|
description: 'Replace hardcoded API key with environment variable',
|
|
68
94
|
before: match,
|
|
@@ -71,81 +97,117 @@ const PATTERN_RULES = [
|
|
|
71
97
|
},
|
|
72
98
|
{
|
|
73
99
|
id: 'SEC006',
|
|
100
|
+
type: 'pattern',
|
|
74
101
|
category: 'security',
|
|
75
102
|
pattern: /SELECT\s+\*\s+FROM\s+\w+\s+WHERE.*\+|SELECT\s+\*\s+FROM\s+\w+\s+WHERE.*\$\{/gi,
|
|
76
103
|
severity: 'error',
|
|
77
104
|
message: 'Potential SQL injection - string concatenation in query',
|
|
78
105
|
suggestion: 'Use parameterized queries or an ORM',
|
|
106
|
+
enabled: true,
|
|
107
|
+
priority: 100,
|
|
108
|
+
source: 'builtin'
|
|
79
109
|
},
|
|
80
110
|
{
|
|
81
111
|
id: 'SEC007',
|
|
112
|
+
type: 'pattern',
|
|
82
113
|
category: 'security',
|
|
83
114
|
pattern: /exec\s*\([^)]*\+|exec\s*\([^)]*\$\{/g,
|
|
84
115
|
severity: 'error',
|
|
85
116
|
message: 'Command injection risk - dynamic command execution',
|
|
86
117
|
suggestion: 'Validate and sanitize all user input',
|
|
87
|
-
languages: ['python']
|
|
118
|
+
languages: ['python'],
|
|
119
|
+
enabled: true,
|
|
120
|
+
priority: 100,
|
|
121
|
+
source: 'builtin'
|
|
88
122
|
},
|
|
89
123
|
{
|
|
90
124
|
id: 'SEC008',
|
|
125
|
+
type: 'pattern',
|
|
91
126
|
category: 'security',
|
|
92
127
|
pattern: /jwt\.decode\s*\([^)]*verify\s*=\s*False/gi,
|
|
93
128
|
severity: 'error',
|
|
94
129
|
message: 'JWT decoded without verification',
|
|
95
130
|
suggestion: 'Always verify JWT signatures',
|
|
96
|
-
languages: ['python']
|
|
131
|
+
languages: ['python'],
|
|
132
|
+
enabled: true,
|
|
133
|
+
priority: 100,
|
|
134
|
+
source: 'builtin'
|
|
97
135
|
},
|
|
98
136
|
// Performance
|
|
99
137
|
{
|
|
100
138
|
id: 'PERF001',
|
|
139
|
+
type: 'pattern',
|
|
101
140
|
category: 'performance',
|
|
102
141
|
pattern: /document\.querySelectorAll\([^)]+\)\.forEach/g,
|
|
103
142
|
severity: 'info',
|
|
104
143
|
message: 'Consider caching querySelectorAll result for multiple operations',
|
|
105
144
|
suggestion: 'Store the result in a variable before iterating',
|
|
106
|
-
languages: ['javascript', 'typescript']
|
|
145
|
+
languages: ['javascript', 'typescript'],
|
|
146
|
+
enabled: true,
|
|
147
|
+
priority: 50,
|
|
148
|
+
source: 'builtin'
|
|
107
149
|
},
|
|
108
150
|
{
|
|
109
151
|
id: 'PERF002',
|
|
152
|
+
type: 'pattern',
|
|
110
153
|
category: 'performance',
|
|
111
154
|
pattern: /JSON\.parse\(JSON\.stringify\(/g,
|
|
112
155
|
severity: 'warning',
|
|
113
156
|
message: 'Deep clone with JSON is slow for large objects',
|
|
114
157
|
suggestion: 'Use structuredClone() or a dedicated library like lodash.cloneDeep',
|
|
158
|
+
enabled: true,
|
|
159
|
+
priority: 50,
|
|
160
|
+
source: 'builtin'
|
|
115
161
|
},
|
|
116
162
|
{
|
|
117
163
|
id: 'PERF003',
|
|
164
|
+
type: 'pattern',
|
|
118
165
|
category: 'performance',
|
|
119
166
|
pattern: /useEffect\(\s*\(\)\s*=>\s*\{[^}]*\},\s*\[\s*\]\s*\)/gs,
|
|
120
167
|
severity: 'info',
|
|
121
168
|
message: 'Empty dependency array - effect runs only once',
|
|
122
169
|
suggestion: 'Verify this is intentional behavior',
|
|
123
|
-
languages: ['javascript', 'typescript', 'jsx', 'tsx']
|
|
170
|
+
languages: ['javascript', 'typescript', 'jsx', 'tsx'],
|
|
171
|
+
enabled: true,
|
|
172
|
+
priority: 50,
|
|
173
|
+
source: 'builtin'
|
|
124
174
|
},
|
|
125
175
|
{
|
|
126
176
|
id: 'PERF004',
|
|
177
|
+
type: 'pattern',
|
|
127
178
|
category: 'performance',
|
|
128
179
|
pattern: /await\s+\w+\([^)]*\)\s*;\s*await\s+\w+\([^)]*\)\s*;/g,
|
|
129
180
|
severity: 'suggestion',
|
|
130
181
|
message: 'Sequential awaits might be parallelizable',
|
|
131
182
|
suggestion: 'Consider Promise.all() for independent async operations',
|
|
183
|
+
enabled: true,
|
|
184
|
+
priority: 50,
|
|
185
|
+
source: 'builtin'
|
|
132
186
|
},
|
|
133
187
|
{
|
|
134
188
|
id: 'PERF005',
|
|
189
|
+
type: 'pattern',
|
|
135
190
|
category: 'performance',
|
|
136
191
|
pattern: /\.map\([^)]+\)\.filter\([^)]+\)/g,
|
|
137
192
|
severity: 'suggestion',
|
|
138
193
|
message: 'Chained map().filter() iterates twice',
|
|
139
194
|
suggestion: 'Consider using reduce() for single-pass transformation',
|
|
195
|
+
enabled: true,
|
|
196
|
+
priority: 50,
|
|
197
|
+
source: 'builtin'
|
|
140
198
|
},
|
|
141
199
|
// Coding Standards
|
|
142
200
|
{
|
|
143
201
|
id: 'STD001',
|
|
202
|
+
type: 'pattern',
|
|
144
203
|
category: 'coding-standards',
|
|
145
204
|
pattern: /console\.(log|debug|info)\s*\(/g,
|
|
146
205
|
severity: 'warning',
|
|
147
206
|
message: 'Console statement found - remove for production',
|
|
148
207
|
suggestion: 'Use a proper logging library or remove before deployment',
|
|
208
|
+
enabled: true,
|
|
209
|
+
priority: 50,
|
|
210
|
+
source: 'builtin',
|
|
149
211
|
quickFix: (match) => ({
|
|
150
212
|
description: 'Remove console statement',
|
|
151
213
|
before: match,
|
|
@@ -154,18 +216,26 @@ const PATTERN_RULES = [
|
|
|
154
216
|
},
|
|
155
217
|
{
|
|
156
218
|
id: 'STD002',
|
|
219
|
+
type: 'pattern',
|
|
157
220
|
category: 'coding-standards',
|
|
158
221
|
pattern: /\/\/\s*TODO:|\/\/\s*FIXME:|\/\/\s*HACK:/gi,
|
|
159
222
|
severity: 'info',
|
|
160
223
|
message: 'TODO/FIXME comment found - address before release',
|
|
224
|
+
enabled: true,
|
|
225
|
+
priority: 25,
|
|
226
|
+
source: 'builtin'
|
|
161
227
|
},
|
|
162
228
|
{
|
|
163
229
|
id: 'STD003',
|
|
230
|
+
type: 'pattern',
|
|
164
231
|
category: 'coding-standards',
|
|
165
232
|
pattern: /debugger\s*;/g,
|
|
166
233
|
severity: 'error',
|
|
167
234
|
message: 'Debugger statement found - remove before deployment',
|
|
168
235
|
languages: ['javascript', 'typescript'],
|
|
236
|
+
enabled: true,
|
|
237
|
+
priority: 100,
|
|
238
|
+
source: 'builtin',
|
|
169
239
|
quickFix: () => ({
|
|
170
240
|
description: 'Remove debugger statement',
|
|
171
241
|
before: 'debugger;',
|
|
@@ -174,12 +244,16 @@ const PATTERN_RULES = [
|
|
|
174
244
|
},
|
|
175
245
|
{
|
|
176
246
|
id: 'STD004',
|
|
247
|
+
type: 'pattern',
|
|
177
248
|
category: 'coding-standards',
|
|
178
249
|
pattern: /var\s+(\w+)\s*=/g,
|
|
179
250
|
severity: 'warning',
|
|
180
251
|
message: 'Prefer const or let over var',
|
|
181
252
|
suggestion: 'Use const for immutable values, let for mutable',
|
|
182
253
|
languages: ['javascript', 'typescript'],
|
|
254
|
+
enabled: true,
|
|
255
|
+
priority: 50,
|
|
256
|
+
source: 'builtin',
|
|
183
257
|
quickFix: (match) => ({
|
|
184
258
|
description: 'Replace var with const',
|
|
185
259
|
before: match,
|
|
@@ -188,11 +262,15 @@ const PATTERN_RULES = [
|
|
|
188
262
|
},
|
|
189
263
|
{
|
|
190
264
|
id: 'STD005',
|
|
265
|
+
type: 'pattern',
|
|
191
266
|
category: 'coding-standards',
|
|
192
267
|
pattern: /==(?!=)/g,
|
|
193
268
|
severity: 'warning',
|
|
194
269
|
message: 'Use strict equality (===) instead of loose equality (==)',
|
|
195
270
|
languages: ['javascript', 'typescript'],
|
|
271
|
+
enabled: true,
|
|
272
|
+
priority: 50,
|
|
273
|
+
source: 'builtin',
|
|
196
274
|
quickFix: () => ({
|
|
197
275
|
description: 'Replace == with ===',
|
|
198
276
|
before: '==',
|
|
@@ -201,11 +279,15 @@ const PATTERN_RULES = [
|
|
|
201
279
|
},
|
|
202
280
|
{
|
|
203
281
|
id: 'STD006',
|
|
282
|
+
type: 'pattern',
|
|
204
283
|
category: 'coding-standards',
|
|
205
284
|
pattern: /!=(?!=)/g,
|
|
206
285
|
severity: 'warning',
|
|
207
286
|
message: 'Use strict inequality (!==) instead of loose inequality (!=)',
|
|
208
287
|
languages: ['javascript', 'typescript'],
|
|
288
|
+
enabled: true,
|
|
289
|
+
priority: 50,
|
|
290
|
+
source: 'builtin',
|
|
209
291
|
quickFix: () => ({
|
|
210
292
|
description: 'Replace != with !==',
|
|
211
293
|
before: '!=',
|
|
@@ -214,141 +296,429 @@ const PATTERN_RULES = [
|
|
|
214
296
|
},
|
|
215
297
|
{
|
|
216
298
|
id: 'STD007',
|
|
299
|
+
type: 'pattern',
|
|
217
300
|
category: 'coding-standards',
|
|
218
301
|
pattern: /function\s+\w+\s*\([^)]*\)\s*\{[\s\S]{500,}\}/g,
|
|
219
302
|
severity: 'warning',
|
|
220
303
|
message: 'Function appears too long (>500 chars)',
|
|
221
304
|
suggestion: 'Consider breaking into smaller functions',
|
|
305
|
+
enabled: true,
|
|
306
|
+
priority: 25,
|
|
307
|
+
source: 'builtin'
|
|
222
308
|
},
|
|
223
309
|
{
|
|
224
310
|
id: 'STD008',
|
|
311
|
+
type: 'pattern',
|
|
225
312
|
category: 'coding-standards',
|
|
226
313
|
pattern: /catch\s*\(\s*\w*\s*\)\s*\{\s*\}/g,
|
|
227
314
|
severity: 'warning',
|
|
228
315
|
message: 'Empty catch block - errors are silently ignored',
|
|
229
316
|
suggestion: 'Log the error or handle it appropriately',
|
|
317
|
+
enabled: true,
|
|
318
|
+
priority: 75,
|
|
319
|
+
source: 'builtin'
|
|
230
320
|
},
|
|
231
321
|
// Architecture
|
|
232
322
|
{
|
|
233
323
|
id: 'ARCH001',
|
|
324
|
+
type: 'pattern',
|
|
234
325
|
category: 'architecture',
|
|
235
326
|
pattern: /import.*from\s+['"]\.\.\/\.\.\/\.\.\/\.\./g,
|
|
236
327
|
severity: 'warning',
|
|
237
328
|
message: 'Deep relative import - consider path aliases',
|
|
238
329
|
suggestion: 'Configure path aliases in tsconfig.json',
|
|
239
|
-
languages: ['javascript', 'typescript']
|
|
330
|
+
languages: ['javascript', 'typescript'],
|
|
331
|
+
enabled: true,
|
|
332
|
+
priority: 25,
|
|
333
|
+
source: 'builtin'
|
|
240
334
|
},
|
|
241
335
|
{
|
|
242
336
|
id: 'ARCH002',
|
|
337
|
+
type: 'pattern',
|
|
243
338
|
category: 'architecture',
|
|
244
339
|
pattern: /class\s+\w+\s*\{[\s\S]*constructor\s*\([^)]{200,}\)/g,
|
|
245
340
|
severity: 'warning',
|
|
246
341
|
message: 'Constructor has many parameters - consider dependency injection',
|
|
247
342
|
suggestion: 'Use a DI container or builder pattern',
|
|
343
|
+
enabled: true,
|
|
344
|
+
priority: 25,
|
|
345
|
+
source: 'builtin'
|
|
248
346
|
},
|
|
249
347
|
{
|
|
250
348
|
id: 'ARCH003',
|
|
349
|
+
type: 'pattern',
|
|
251
350
|
category: 'architecture',
|
|
252
351
|
pattern: /new\s+(Date|Math\.random)\s*\(\)/g,
|
|
253
352
|
severity: 'suggestion',
|
|
254
353
|
message: 'Direct instantiation makes testing harder',
|
|
255
354
|
suggestion: 'Inject dependencies for better testability',
|
|
355
|
+
enabled: true,
|
|
356
|
+
priority: 25,
|
|
357
|
+
source: 'builtin'
|
|
256
358
|
},
|
|
257
359
|
// Python specific
|
|
258
360
|
{
|
|
259
361
|
id: 'PY001',
|
|
260
|
-
|
|
362
|
+
type: 'pattern',
|
|
363
|
+
category: 'python',
|
|
261
364
|
pattern: /except\s*:/g,
|
|
262
365
|
severity: 'warning',
|
|
263
366
|
message: 'Bare except catches all exceptions including SystemExit',
|
|
264
367
|
suggestion: 'Specify exception type: except Exception:',
|
|
265
|
-
languages: ['python']
|
|
368
|
+
languages: ['python'],
|
|
369
|
+
enabled: true,
|
|
370
|
+
priority: 75,
|
|
371
|
+
source: 'builtin'
|
|
266
372
|
},
|
|
267
373
|
{
|
|
268
374
|
id: 'PY002',
|
|
269
|
-
|
|
375
|
+
type: 'pattern',
|
|
376
|
+
category: 'python',
|
|
270
377
|
pattern: /print\s*\(/g,
|
|
271
378
|
severity: 'info',
|
|
272
379
|
message: 'Print statement found - consider using logging',
|
|
273
380
|
suggestion: 'Use the logging module for production code',
|
|
274
|
-
languages: ['python']
|
|
381
|
+
languages: ['python'],
|
|
382
|
+
enabled: true,
|
|
383
|
+
priority: 25,
|
|
384
|
+
source: 'builtin'
|
|
275
385
|
},
|
|
276
386
|
{
|
|
277
387
|
id: 'PY003',
|
|
388
|
+
type: 'pattern',
|
|
278
389
|
category: 'security',
|
|
279
390
|
pattern: /pickle\.load|pickle\.loads/g,
|
|
280
391
|
severity: 'error',
|
|
281
392
|
message: 'Pickle is unsafe for untrusted data',
|
|
282
393
|
suggestion: 'Use JSON or a safe serialization format',
|
|
283
|
-
languages: ['python']
|
|
394
|
+
languages: ['python'],
|
|
395
|
+
enabled: true,
|
|
396
|
+
priority: 100,
|
|
397
|
+
source: 'builtin'
|
|
284
398
|
},
|
|
285
399
|
{
|
|
286
400
|
id: 'PY004',
|
|
287
|
-
|
|
401
|
+
type: 'pattern',
|
|
402
|
+
category: 'python',
|
|
288
403
|
pattern: /from\s+\w+\s+import\s+\*/g,
|
|
289
404
|
severity: 'warning',
|
|
290
405
|
message: 'Wildcard imports make code harder to understand',
|
|
291
406
|
suggestion: 'Import specific names explicitly',
|
|
292
|
-
languages: ['python']
|
|
407
|
+
languages: ['python'],
|
|
408
|
+
enabled: true,
|
|
409
|
+
priority: 50,
|
|
410
|
+
source: 'builtin'
|
|
293
411
|
},
|
|
294
412
|
// Go specific
|
|
295
413
|
{
|
|
296
414
|
id: 'GO001',
|
|
297
|
-
|
|
415
|
+
type: 'pattern',
|
|
416
|
+
category: 'go',
|
|
298
417
|
pattern: /fmt\.Print|fmt\.Println/g,
|
|
299
418
|
severity: 'info',
|
|
300
419
|
message: 'fmt.Print found - consider using structured logging',
|
|
301
420
|
suggestion: 'Use log/slog or a logging library',
|
|
302
|
-
languages: ['go']
|
|
421
|
+
languages: ['go'],
|
|
422
|
+
enabled: true,
|
|
423
|
+
priority: 25,
|
|
424
|
+
source: 'builtin'
|
|
303
425
|
},
|
|
304
426
|
{
|
|
305
427
|
id: 'GO002',
|
|
306
|
-
|
|
428
|
+
type: 'pattern',
|
|
429
|
+
category: 'go',
|
|
307
430
|
pattern: /panic\s*\(/g,
|
|
308
431
|
severity: 'warning',
|
|
309
432
|
message: 'Panic should be used sparingly',
|
|
310
433
|
suggestion: 'Return errors instead of panicking',
|
|
311
|
-
languages: ['go']
|
|
434
|
+
languages: ['go'],
|
|
435
|
+
enabled: true,
|
|
436
|
+
priority: 75,
|
|
437
|
+
source: 'builtin'
|
|
312
438
|
},
|
|
313
439
|
{
|
|
314
440
|
id: 'GO003',
|
|
315
|
-
|
|
441
|
+
type: 'pattern',
|
|
442
|
+
category: 'go',
|
|
316
443
|
pattern: /if\s+err\s*!=\s*nil\s*\{\s*return\s+nil\s*,?\s*err\s*\}/g,
|
|
317
444
|
severity: 'suggestion',
|
|
318
445
|
message: 'Consider wrapping errors with context',
|
|
319
446
|
suggestion: 'Use fmt.Errorf("context: %w", err)',
|
|
320
|
-
languages: ['go']
|
|
447
|
+
languages: ['go'],
|
|
448
|
+
enabled: true,
|
|
449
|
+
priority: 25,
|
|
450
|
+
source: 'builtin'
|
|
321
451
|
},
|
|
322
452
|
// Rust specific
|
|
323
453
|
{
|
|
324
454
|
id: 'RS001',
|
|
325
|
-
|
|
455
|
+
type: 'pattern',
|
|
456
|
+
category: 'rust',
|
|
326
457
|
pattern: /\.unwrap\(\)/g,
|
|
327
458
|
severity: 'warning',
|
|
328
459
|
message: 'unwrap() can panic on None/Err',
|
|
329
460
|
suggestion: 'Use ? operator or match/if let for proper error handling',
|
|
330
|
-
languages: ['rust']
|
|
461
|
+
languages: ['rust'],
|
|
462
|
+
enabled: true,
|
|
463
|
+
priority: 75,
|
|
464
|
+
source: 'builtin'
|
|
331
465
|
},
|
|
332
466
|
{
|
|
333
467
|
id: 'RS002',
|
|
334
|
-
|
|
468
|
+
type: 'pattern',
|
|
469
|
+
category: 'rust',
|
|
335
470
|
pattern: /\.expect\("[^"]*"\)/g,
|
|
336
471
|
severity: 'info',
|
|
337
472
|
message: 'expect() can panic - ensure this is intentional',
|
|
338
473
|
suggestion: 'Use ? operator for propagating errors',
|
|
339
|
-
languages: ['rust']
|
|
474
|
+
languages: ['rust'],
|
|
475
|
+
enabled: true,
|
|
476
|
+
priority: 50,
|
|
477
|
+
source: 'builtin'
|
|
340
478
|
},
|
|
341
479
|
{
|
|
342
480
|
id: 'RS003',
|
|
343
|
-
|
|
481
|
+
type: 'pattern',
|
|
482
|
+
category: 'rust',
|
|
344
483
|
pattern: /unsafe\s*\{/g,
|
|
345
484
|
severity: 'warning',
|
|
346
485
|
message: 'Unsafe block found - ensure safety invariants are documented',
|
|
347
486
|
suggestion: 'Add SAFETY comment explaining why this is safe',
|
|
348
|
-
languages: ['rust']
|
|
487
|
+
languages: ['rust'],
|
|
488
|
+
enabled: true,
|
|
489
|
+
priority: 75,
|
|
490
|
+
source: 'builtin'
|
|
349
491
|
}
|
|
350
492
|
];
|
|
351
|
-
|
|
493
|
+
// =============================================================================
|
|
494
|
+
// RULE REGISTRY - Unified Management
|
|
495
|
+
// =============================================================================
|
|
496
|
+
/**
|
|
497
|
+
* In-memory registry of all active rules (Pattern + AST)
|
|
498
|
+
*/
|
|
499
|
+
class RuleRegistry {
|
|
500
|
+
builtinPatternRules = [...BUILTIN_PATTERN_RULES];
|
|
501
|
+
builtinASTRules = [...BUILTIN_AST_RULES];
|
|
502
|
+
userPatternRules = [];
|
|
503
|
+
userASTRules = [];
|
|
504
|
+
projectPatternRules = [];
|
|
505
|
+
projectASTRules = [];
|
|
506
|
+
// For backwards compatibility
|
|
507
|
+
get builtinRules() {
|
|
508
|
+
return this.builtinPatternRules;
|
|
509
|
+
}
|
|
510
|
+
get userRules() {
|
|
511
|
+
return this.userPatternRules;
|
|
512
|
+
}
|
|
513
|
+
set userRules(rules) {
|
|
514
|
+
this.userPatternRules = rules;
|
|
515
|
+
}
|
|
516
|
+
get projectRules() {
|
|
517
|
+
return this.projectPatternRules;
|
|
518
|
+
}
|
|
519
|
+
set projectRules(rules) {
|
|
520
|
+
this.projectPatternRules = rules;
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Get all builtin pattern rules
|
|
524
|
+
*/
|
|
525
|
+
getBuiltinRules() {
|
|
526
|
+
return this.builtinPatternRules.filter(r => r.enabled);
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Get all builtin AST rules
|
|
530
|
+
*/
|
|
531
|
+
getBuiltinASTRules() {
|
|
532
|
+
return this.builtinASTRules.filter(r => r.enabled);
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Get all user-defined pattern rules
|
|
536
|
+
*/
|
|
537
|
+
getUserRules() {
|
|
538
|
+
return this.userPatternRules.filter(r => r.enabled);
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Get all user-defined AST rules
|
|
542
|
+
*/
|
|
543
|
+
getUserASTRules() {
|
|
544
|
+
return this.userASTRules.filter(r => r.enabled);
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Get all project-specific pattern rules
|
|
548
|
+
*/
|
|
549
|
+
getProjectRules() {
|
|
550
|
+
return this.projectPatternRules.filter(r => r.enabled);
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Get all project-specific AST rules
|
|
554
|
+
*/
|
|
555
|
+
getProjectASTRules() {
|
|
556
|
+
return this.projectASTRules.filter(r => r.enabled);
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Get all active pattern rules, sorted by priority (highest first)
|
|
560
|
+
*/
|
|
561
|
+
getAllRules() {
|
|
562
|
+
const all = [
|
|
563
|
+
...this.builtinPatternRules,
|
|
564
|
+
...this.userPatternRules,
|
|
565
|
+
...this.projectPatternRules
|
|
566
|
+
].filter(r => r.enabled);
|
|
567
|
+
// Sort by priority (higher priority = runs first)
|
|
568
|
+
return all.sort((a, b) => b.priority - a.priority);
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Get all active AST rules, sorted by priority (highest first)
|
|
572
|
+
*/
|
|
573
|
+
getAllASTRules() {
|
|
574
|
+
const all = [
|
|
575
|
+
...this.builtinASTRules,
|
|
576
|
+
...this.userASTRules,
|
|
577
|
+
...this.projectASTRules
|
|
578
|
+
].filter(r => r.enabled);
|
|
579
|
+
return all.sort((a, b) => b.priority - a.priority);
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Register a user-defined pattern rule
|
|
583
|
+
*/
|
|
584
|
+
registerUserRule(rule) {
|
|
585
|
+
const fullRule = {
|
|
586
|
+
...rule,
|
|
587
|
+
type: 'pattern',
|
|
588
|
+
source: 'user'
|
|
589
|
+
};
|
|
590
|
+
// Remove existing rule with same ID
|
|
591
|
+
this.userPatternRules = this.userPatternRules.filter(r => r.id !== rule.id);
|
|
592
|
+
this.userPatternRules.push(fullRule);
|
|
593
|
+
logger.info('Registered user rule', { ruleId: rule.id });
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Register a project-specific pattern rule
|
|
597
|
+
*/
|
|
598
|
+
registerProjectRule(rule) {
|
|
599
|
+
const fullRule = {
|
|
600
|
+
...rule,
|
|
601
|
+
type: 'pattern',
|
|
602
|
+
source: 'project'
|
|
603
|
+
};
|
|
604
|
+
// Remove existing rule with same ID
|
|
605
|
+
this.projectPatternRules = this.projectPatternRules.filter(r => r.id !== rule.id);
|
|
606
|
+
this.projectPatternRules.push(fullRule);
|
|
607
|
+
logger.info('Registered project rule', { ruleId: rule.id });
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Register multiple project rules at once
|
|
611
|
+
*/
|
|
612
|
+
registerProjectRules(rules) {
|
|
613
|
+
for (const rule of rules) {
|
|
614
|
+
this.registerProjectRule(rule);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Register a user-defined AST rule
|
|
619
|
+
*/
|
|
620
|
+
registerUserASTRule(rule) {
|
|
621
|
+
const fullRule = {
|
|
622
|
+
...rule,
|
|
623
|
+
type: 'ast',
|
|
624
|
+
source: 'user'
|
|
625
|
+
};
|
|
626
|
+
this.userASTRules = this.userASTRules.filter(r => r.id !== rule.id);
|
|
627
|
+
this.userASTRules.push(fullRule);
|
|
628
|
+
logger.info('Registered user AST rule', { ruleId: rule.id });
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Register a project-specific AST rule
|
|
632
|
+
*/
|
|
633
|
+
registerProjectASTRule(rule) {
|
|
634
|
+
const fullRule = {
|
|
635
|
+
...rule,
|
|
636
|
+
type: 'ast',
|
|
637
|
+
source: 'project'
|
|
638
|
+
};
|
|
639
|
+
this.projectASTRules = this.projectASTRules.filter(r => r.id !== rule.id);
|
|
640
|
+
this.projectASTRules.push(fullRule);
|
|
641
|
+
logger.info('Registered project AST rule', { ruleId: rule.id });
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Clear all user rules (pattern + AST)
|
|
645
|
+
*/
|
|
646
|
+
clearUserRules() {
|
|
647
|
+
this.userPatternRules = [];
|
|
648
|
+
this.userASTRules = [];
|
|
649
|
+
logger.info('Cleared user rules');
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Clear all project rules (pattern + AST)
|
|
653
|
+
*/
|
|
654
|
+
clearProjectRules() {
|
|
655
|
+
this.projectPatternRules = [];
|
|
656
|
+
this.projectASTRules = [];
|
|
657
|
+
logger.info('Cleared project rules');
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Disable a builtin rule by ID (pattern or AST)
|
|
661
|
+
*/
|
|
662
|
+
disableBuiltinRule(ruleId) {
|
|
663
|
+
// Check pattern rules
|
|
664
|
+
const patternRule = this.builtinPatternRules.find(r => r.id === ruleId);
|
|
665
|
+
if (patternRule) {
|
|
666
|
+
patternRule.enabled = false;
|
|
667
|
+
logger.info('Disabled builtin pattern rule', { ruleId });
|
|
668
|
+
return true;
|
|
669
|
+
}
|
|
670
|
+
// Check AST rules
|
|
671
|
+
const astRule = this.builtinASTRules.find(r => r.id === ruleId);
|
|
672
|
+
if (astRule) {
|
|
673
|
+
astRule.enabled = false;
|
|
674
|
+
logger.info('Disabled builtin AST rule', { ruleId });
|
|
675
|
+
return true;
|
|
676
|
+
}
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Enable a builtin rule by ID (pattern or AST)
|
|
681
|
+
*/
|
|
682
|
+
enableBuiltinRule(ruleId) {
|
|
683
|
+
// Check pattern rules
|
|
684
|
+
const patternRule = this.builtinPatternRules.find(r => r.id === ruleId);
|
|
685
|
+
if (patternRule) {
|
|
686
|
+
patternRule.enabled = true;
|
|
687
|
+
logger.info('Enabled builtin pattern rule', { ruleId });
|
|
688
|
+
return true;
|
|
689
|
+
}
|
|
690
|
+
// Check AST rules
|
|
691
|
+
const astRule = this.builtinASTRules.find(r => r.id === ruleId);
|
|
692
|
+
if (astRule) {
|
|
693
|
+
astRule.enabled = true;
|
|
694
|
+
logger.info('Enabled builtin AST rule', { ruleId });
|
|
695
|
+
return true;
|
|
696
|
+
}
|
|
697
|
+
return false;
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Get statistics about registered rules
|
|
701
|
+
*/
|
|
702
|
+
getStats() {
|
|
703
|
+
const builtinPattern = this.builtinPatternRules.filter(r => r.enabled).length;
|
|
704
|
+
const builtinAST = this.builtinASTRules.filter(r => r.enabled).length;
|
|
705
|
+
const userPattern = this.userPatternRules.filter(r => r.enabled).length;
|
|
706
|
+
const userAST = this.userASTRules.filter(r => r.enabled).length;
|
|
707
|
+
const projectPattern = this.projectPatternRules.filter(r => r.enabled).length;
|
|
708
|
+
const projectAST = this.projectASTRules.filter(r => r.enabled).length;
|
|
709
|
+
const builtin = builtinPattern + builtinAST;
|
|
710
|
+
const user = userPattern + userAST;
|
|
711
|
+
const project = projectPattern + projectAST;
|
|
712
|
+
const ast = builtinAST + userAST + projectAST;
|
|
713
|
+
return { builtin, user, project, total: builtin + user + project, ast };
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
// Global rule registry instance
|
|
717
|
+
export const ruleRegistry = new RuleRegistry();
|
|
718
|
+
// =============================================================================
|
|
719
|
+
// ANALYSIS FUNCTIONS
|
|
720
|
+
// =============================================================================
|
|
721
|
+
function detectLanguage(file, _content) {
|
|
352
722
|
const ext = file.split('.').pop()?.toLowerCase() || '';
|
|
353
723
|
const langMap = {
|
|
354
724
|
'ts': 'typescript',
|
|
@@ -370,20 +740,35 @@ function detectLanguage(file, content) {
|
|
|
370
740
|
function getLineNumber(content, index) {
|
|
371
741
|
return content.substring(0, index).split('\n').length;
|
|
372
742
|
}
|
|
743
|
+
/**
|
|
744
|
+
* Analyze code using the unified rule pipeline
|
|
745
|
+
*/
|
|
373
746
|
export function analyzeCode(file, content, focus = 'all') {
|
|
374
747
|
const language = detectLanguage(file, content);
|
|
375
748
|
const issues = [];
|
|
376
749
|
logger.debug('Analyzing code', { file, language, focus, contentLength: content.length });
|
|
750
|
+
// Get all active rules from registry
|
|
751
|
+
const allRules = ruleRegistry.getAllRules();
|
|
752
|
+
// Track rules applied by source
|
|
753
|
+
const rulesApplied = { builtin: 0, user: 0, project: 0 };
|
|
377
754
|
// Filter rules by focus and language
|
|
378
|
-
const applicableRules =
|
|
755
|
+
const applicableRules = allRules.filter(rule => {
|
|
379
756
|
if (focus !== 'all' && rule.category !== focus)
|
|
380
757
|
return false;
|
|
381
758
|
if (rule.languages && !rule.languages.includes(language))
|
|
382
759
|
return false;
|
|
383
760
|
return true;
|
|
384
761
|
});
|
|
762
|
+
logger.debug('Applying rules', {
|
|
763
|
+
totalRules: allRules.length,
|
|
764
|
+
applicableRules: applicableRules.length,
|
|
765
|
+
focus,
|
|
766
|
+
language
|
|
767
|
+
});
|
|
385
768
|
// Apply pattern rules
|
|
386
769
|
for (const rule of applicableRules) {
|
|
770
|
+
if (rule.type !== 'pattern')
|
|
771
|
+
continue; // Skip non-pattern rules for now
|
|
387
772
|
let match;
|
|
388
773
|
// Reset regex state
|
|
389
774
|
rule.pattern.lastIndex = 0;
|
|
@@ -400,14 +785,49 @@ export function analyzeCode(file, content, focus = 'all') {
|
|
|
400
785
|
line,
|
|
401
786
|
code: matchedText.substring(0, 100),
|
|
402
787
|
suggestion: rule.suggestion,
|
|
403
|
-
quickFix
|
|
788
|
+
quickFix,
|
|
789
|
+
source: rule.source
|
|
404
790
|
});
|
|
791
|
+
// Track which source contributed
|
|
792
|
+
rulesApplied[rule.source]++;
|
|
405
793
|
// Prevent infinite loop on zero-length matches
|
|
406
794
|
if (match.index === rule.pattern.lastIndex) {
|
|
407
795
|
rule.pattern.lastIndex++;
|
|
408
796
|
}
|
|
409
797
|
}
|
|
410
798
|
}
|
|
799
|
+
// ==========================================================================
|
|
800
|
+
// PHASE 2: Apply AST rules (for TS/JS files)
|
|
801
|
+
// ==========================================================================
|
|
802
|
+
const isJsTsFile = ['typescript', 'tsx', 'javascript', 'jsx'].includes(language);
|
|
803
|
+
if (isJsTsFile) {
|
|
804
|
+
const astRules = ruleRegistry.getAllASTRules();
|
|
805
|
+
// Filter AST rules by focus and language
|
|
806
|
+
const applicableASTRules = astRules.filter(rule => {
|
|
807
|
+
if (focus !== 'all' && rule.category !== focus)
|
|
808
|
+
return false;
|
|
809
|
+
if (rule.languages && !rule.languages.includes(language))
|
|
810
|
+
return false;
|
|
811
|
+
return true;
|
|
812
|
+
});
|
|
813
|
+
if (applicableASTRules.length > 0) {
|
|
814
|
+
logger.debug('Applying AST rules', {
|
|
815
|
+
file,
|
|
816
|
+
astRuleCount: applicableASTRules.length
|
|
817
|
+
});
|
|
818
|
+
try {
|
|
819
|
+
const astIssues = analyzeWithAST(file, content, applicableASTRules);
|
|
820
|
+
// Add AST issues and track sources
|
|
821
|
+
for (const issue of astIssues) {
|
|
822
|
+
issues.push(issue);
|
|
823
|
+
rulesApplied[issue.source]++;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
catch (error) {
|
|
827
|
+
logger.warn('AST analysis failed', { file, error: String(error) });
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
411
831
|
// Sort issues by severity and line
|
|
412
832
|
const severityOrder = { error: 0, warning: 1, info: 2, suggestion: 3 };
|
|
413
833
|
issues.sort((a, b) => {
|
|
@@ -435,14 +855,21 @@ export function analyzeCode(file, content, focus = 'all') {
|
|
|
435
855
|
.filter(i => i.quickFix)
|
|
436
856
|
.map(i => i.quickFix)
|
|
437
857
|
.filter((fix, idx, arr) => arr.findIndex(f => f.before === fix.before) === idx);
|
|
438
|
-
logger.info('Analysis complete', {
|
|
858
|
+
logger.info('Analysis complete', {
|
|
859
|
+
file,
|
|
860
|
+
issuesFound: issues.length,
|
|
861
|
+
score,
|
|
862
|
+
quickFixesAvailable: quickFixes.length,
|
|
863
|
+
rulesApplied
|
|
864
|
+
});
|
|
439
865
|
return {
|
|
440
866
|
file,
|
|
441
867
|
language,
|
|
442
868
|
issues,
|
|
443
869
|
score: Math.round(score),
|
|
444
870
|
summary,
|
|
445
|
-
quickFixes: quickFixes.length > 0 ? quickFixes : undefined
|
|
871
|
+
quickFixes: quickFixes.length > 0 ? quickFixes : undefined,
|
|
872
|
+
rulesApplied
|
|
446
873
|
};
|
|
447
874
|
}
|
|
448
875
|
export function analyzeMultipleFiles(files, focus = 'all') {
|
|
@@ -457,13 +884,19 @@ export function analyzeMultipleFiles(files, focus = 'all') {
|
|
|
457
884
|
info: results.reduce((sum, r) => sum + r.summary.info, 0),
|
|
458
885
|
suggestions: results.reduce((sum, r) => sum + r.summary.suggestions, 0)
|
|
459
886
|
};
|
|
887
|
+
const rulesApplied = {
|
|
888
|
+
builtin: results.reduce((sum, r) => sum + r.rulesApplied.builtin, 0),
|
|
889
|
+
user: results.reduce((sum, r) => sum + r.rulesApplied.user, 0),
|
|
890
|
+
project: results.reduce((sum, r) => sum + r.rulesApplied.project, 0)
|
|
891
|
+
};
|
|
460
892
|
return {
|
|
461
893
|
files: results,
|
|
462
894
|
overall: {
|
|
463
895
|
totalFiles: results.length,
|
|
464
896
|
totalIssues,
|
|
465
897
|
averageScore,
|
|
466
|
-
summary
|
|
898
|
+
summary,
|
|
899
|
+
rulesApplied
|
|
467
900
|
}
|
|
468
901
|
};
|
|
469
902
|
}
|
|
@@ -471,6 +904,7 @@ export function formatAnalysisReport(result) {
|
|
|
471
904
|
const lines = [];
|
|
472
905
|
lines.push(`## Code Review: ${result.file}`);
|
|
473
906
|
lines.push(`**Language:** ${result.language} | **Score:** ${result.score}/100`);
|
|
907
|
+
lines.push(`**Rules Applied:** ${result.rulesApplied.builtin} builtin, ${result.rulesApplied.user} user, ${result.rulesApplied.project} project`);
|
|
474
908
|
lines.push('');
|
|
475
909
|
if (result.issues.length === 0) {
|
|
476
910
|
lines.push('✅ No issues found!');
|
|
@@ -487,7 +921,8 @@ export function formatAnalysisReport(result) {
|
|
|
487
921
|
const icon = issue.severity === 'error' ? '🔴' :
|
|
488
922
|
issue.severity === 'warning' ? '🟡' :
|
|
489
923
|
issue.severity === 'info' ? '🔵' : '💡';
|
|
490
|
-
|
|
924
|
+
const sourceTag = issue.source !== 'builtin' ? ` [${issue.source}]` : '';
|
|
925
|
+
lines.push(`#### ${icon} [${issue.rule}]${sourceTag} ${issue.message}`);
|
|
491
926
|
if (issue.line)
|
|
492
927
|
lines.push(`- Line: ${issue.line}`);
|
|
493
928
|
if (issue.code)
|
|
@@ -514,4 +949,52 @@ export function formatAnalysisReport(result) {
|
|
|
514
949
|
}
|
|
515
950
|
return lines.join('\n');
|
|
516
951
|
}
|
|
952
|
+
// =============================================================================
|
|
953
|
+
// HELPER: Convert user rules from RuleManager format to PatternRule
|
|
954
|
+
// =============================================================================
|
|
955
|
+
/**
|
|
956
|
+
* Parse a user-defined rule content to extract pattern rules
|
|
957
|
+
* This bridges the gap between RuleManager's documentation rules and analysis rules
|
|
958
|
+
*/
|
|
959
|
+
export function parseUserRuleToPatternRule(ruleId, content, category) {
|
|
960
|
+
// Look for patterns defined in markdown code blocks with special syntax
|
|
961
|
+
// Example: ```pattern:error
|
|
962
|
+
// /console\.log/g
|
|
963
|
+
// ```
|
|
964
|
+
const patternMatch = content.match(/```pattern:(error|warning|info|suggestion)\s*\n(\/[^/]+\/[gimsu]*)\s*\n```/);
|
|
965
|
+
if (!patternMatch) {
|
|
966
|
+
return null;
|
|
967
|
+
}
|
|
968
|
+
const severity = patternMatch[1];
|
|
969
|
+
const patternStr = patternMatch[2];
|
|
970
|
+
try {
|
|
971
|
+
// Parse the regex string
|
|
972
|
+
const regexMatch = patternStr.match(/^\/(.+)\/([gimsu]*)$/);
|
|
973
|
+
if (!regexMatch)
|
|
974
|
+
return null;
|
|
975
|
+
const pattern = new RegExp(regexMatch[1], regexMatch[2] || 'g');
|
|
976
|
+
// Extract message from the rule content
|
|
977
|
+
const messageMatch = content.match(/##?\s*(?:Rule|Message):\s*(.+)/i);
|
|
978
|
+
const message = messageMatch?.[1] || `Custom rule: ${ruleId}`;
|
|
979
|
+
// Extract suggestion if present
|
|
980
|
+
const suggestionMatch = content.match(/##?\s*Suggestion:\s*(.+)/i);
|
|
981
|
+
const suggestion = suggestionMatch?.[1];
|
|
982
|
+
return {
|
|
983
|
+
id: ruleId,
|
|
984
|
+
type: 'pattern',
|
|
985
|
+
category: category,
|
|
986
|
+
pattern,
|
|
987
|
+
severity,
|
|
988
|
+
message,
|
|
989
|
+
suggestion,
|
|
990
|
+
enabled: true,
|
|
991
|
+
priority: 150, // User rules have higher priority than builtin
|
|
992
|
+
source: 'user'
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
catch {
|
|
996
|
+
logger.warn('Failed to parse pattern rule', { ruleId });
|
|
997
|
+
return null;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
517
1000
|
//# sourceMappingURL=codeAnalyzer.js.map
|