@stackguide/mcp-server 3.1.0 → 3.2.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/dist/config/types.d.ts +1 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js.map +1 -1
- package/dist/services/codeAnalyzer.d.ts +5 -1
- package/dist/services/codeAnalyzer.d.ts.map +1 -1
- package/dist/services/codeAnalyzer.js +39 -2
- package/dist/services/codeAnalyzer.js.map +1 -1
- package/dist/services/parsers/baseParser.d.ts +44 -0
- package/dist/services/parsers/baseParser.d.ts.map +1 -0
- package/dist/services/parsers/baseParser.js +177 -0
- package/dist/services/parsers/baseParser.js.map +1 -0
- package/dist/services/parsers/goParser.d.ts +28 -0
- package/dist/services/parsers/goParser.d.ts.map +1 -0
- package/dist/services/parsers/goParser.js +590 -0
- package/dist/services/parsers/goParser.js.map +1 -0
- package/dist/services/parsers/index.d.ts +62 -0
- package/dist/services/parsers/index.d.ts.map +1 -0
- package/dist/services/parsers/index.js +121 -0
- package/dist/services/parsers/index.js.map +1 -0
- package/dist/services/parsers/pythonParser.d.ts +28 -0
- package/dist/services/parsers/pythonParser.d.ts.map +1 -0
- package/dist/services/parsers/pythonParser.js +663 -0
- package/dist/services/parsers/pythonParser.js.map +1 -0
- package/dist/services/parsers/rustParser.d.ts +28 -0
- package/dist/services/parsers/rustParser.d.ts.map +1 -0
- package/dist/services/parsers/rustParser.js +654 -0
- package/dist/services/parsers/rustParser.js.map +1 -0
- package/dist/services/parsers/types.d.ts +241 -0
- package/dist/services/parsers/types.d.ts.map +1 -0
- package/dist/services/parsers/types.js +47 -0
- package/dist/services/parsers/types.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python Language Parser
|
|
3
|
+
* Semantic analysis for Python code
|
|
4
|
+
* @version 3.2.0
|
|
5
|
+
*/
|
|
6
|
+
import { BaseLanguageParser } from './baseParser.js';
|
|
7
|
+
/**
|
|
8
|
+
* Python-specific parser with semantic rules
|
|
9
|
+
*/
|
|
10
|
+
export class PythonParser extends BaseLanguageParser {
|
|
11
|
+
language = 'python';
|
|
12
|
+
extensions = ['.py', '.pyi', '.pyw'];
|
|
13
|
+
constructor() {
|
|
14
|
+
super();
|
|
15
|
+
this.rules = PYTHON_RULES;
|
|
16
|
+
}
|
|
17
|
+
parse(code, filePath) {
|
|
18
|
+
const startTime = Date.now();
|
|
19
|
+
const symbols = [];
|
|
20
|
+
const imports = [];
|
|
21
|
+
const functions = [];
|
|
22
|
+
const classes = [];
|
|
23
|
+
const variables = [];
|
|
24
|
+
// Extract components
|
|
25
|
+
imports.push(...this.extractImports(code));
|
|
26
|
+
functions.push(...this.extractFunctions(code));
|
|
27
|
+
classes.push(...this.extractClasses(code));
|
|
28
|
+
variables.push(...this.extractVariables(code));
|
|
29
|
+
// Build symbol list
|
|
30
|
+
for (const imp of imports) {
|
|
31
|
+
symbols.push({
|
|
32
|
+
type: 'import',
|
|
33
|
+
name: imp.module,
|
|
34
|
+
line: imp.line,
|
|
35
|
+
column: 1
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
for (const func of functions) {
|
|
39
|
+
symbols.push({
|
|
40
|
+
type: 'function',
|
|
41
|
+
name: func.name,
|
|
42
|
+
line: func.line,
|
|
43
|
+
column: 1,
|
|
44
|
+
modifiers: func.decorators
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
for (const cls of classes) {
|
|
48
|
+
symbols.push({
|
|
49
|
+
type: 'class',
|
|
50
|
+
name: cls.name,
|
|
51
|
+
line: cls.line,
|
|
52
|
+
column: 1,
|
|
53
|
+
modifiers: cls.decorators
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
// Extract decorators as symbols
|
|
57
|
+
const decoratorMatches = code.matchAll(/@(\w+)(?:\([^)]*\))?/g);
|
|
58
|
+
for (const match of decoratorMatches) {
|
|
59
|
+
const beforeMatch = code.substring(0, match.index);
|
|
60
|
+
const line = beforeMatch.split('\n').length;
|
|
61
|
+
symbols.push({
|
|
62
|
+
type: 'decorator',
|
|
63
|
+
name: match[1],
|
|
64
|
+
line,
|
|
65
|
+
column: 1
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
const comments = this.extractComments(code, '#', '"""', '"""');
|
|
69
|
+
return {
|
|
70
|
+
language: 'python',
|
|
71
|
+
filePath,
|
|
72
|
+
symbols,
|
|
73
|
+
imports,
|
|
74
|
+
functions,
|
|
75
|
+
classes,
|
|
76
|
+
variables,
|
|
77
|
+
comments,
|
|
78
|
+
errors: [],
|
|
79
|
+
parseTime: Date.now() - startTime
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
extractImports(code) {
|
|
83
|
+
const imports = [];
|
|
84
|
+
const lines = code.split('\n');
|
|
85
|
+
// import module
|
|
86
|
+
// import module as alias
|
|
87
|
+
// from module import item1, item2
|
|
88
|
+
// from module import item as alias
|
|
89
|
+
for (let i = 0; i < lines.length; i++) {
|
|
90
|
+
const line = lines[i].trim();
|
|
91
|
+
const lineNum = i + 1;
|
|
92
|
+
// from X import Y
|
|
93
|
+
const fromMatch = line.match(/^from\s+([\w.]+)\s+import\s+(.+)$/);
|
|
94
|
+
if (fromMatch) {
|
|
95
|
+
const module = fromMatch[1];
|
|
96
|
+
const itemsPart = fromMatch[2];
|
|
97
|
+
const items = [];
|
|
98
|
+
let alias;
|
|
99
|
+
// Check for "import *"
|
|
100
|
+
if (itemsPart.trim() === '*') {
|
|
101
|
+
items.push('*');
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
// Parse individual items
|
|
105
|
+
const itemMatches = itemsPart.matchAll(/(\w+)(?:\s+as\s+(\w+))?/g);
|
|
106
|
+
for (const itemMatch of itemMatches) {
|
|
107
|
+
items.push(itemMatch[1]);
|
|
108
|
+
if (itemMatch[2]) {
|
|
109
|
+
alias = itemMatch[2];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
imports.push({
|
|
114
|
+
module,
|
|
115
|
+
items,
|
|
116
|
+
alias,
|
|
117
|
+
line: lineNum,
|
|
118
|
+
isRelative: module.startsWith('.')
|
|
119
|
+
});
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
// import X [as Y]
|
|
123
|
+
const importMatch = line.match(/^import\s+([\w.]+)(?:\s+as\s+(\w+))?$/);
|
|
124
|
+
if (importMatch) {
|
|
125
|
+
imports.push({
|
|
126
|
+
module: importMatch[1],
|
|
127
|
+
items: [],
|
|
128
|
+
alias: importMatch[2],
|
|
129
|
+
line: lineNum,
|
|
130
|
+
isDefault: true
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return imports;
|
|
135
|
+
}
|
|
136
|
+
extractFunctions(code) {
|
|
137
|
+
const functions = [];
|
|
138
|
+
const lines = code.split('\n');
|
|
139
|
+
// Match function definitions with decorators
|
|
140
|
+
const funcRegex = /^(\s*)(?:(@\w+(?:\([^)]*\))?)\s*\n\s*)*def\s+(\w+)\s*\(([^)]*)\)(?:\s*->\s*([^:]+))?\s*:/gm;
|
|
141
|
+
let match;
|
|
142
|
+
while ((match = funcRegex.exec(code)) !== null) {
|
|
143
|
+
const indent = match[1] || '';
|
|
144
|
+
const funcName = match[3];
|
|
145
|
+
const paramsStr = match[4] || '';
|
|
146
|
+
const returnType = match[5]?.trim();
|
|
147
|
+
const beforeMatch = code.substring(0, match.index);
|
|
148
|
+
const lineNum = beforeMatch.split('\n').length;
|
|
149
|
+
// Find decorators above this function
|
|
150
|
+
const decorators = [];
|
|
151
|
+
for (let i = lineNum - 2; i >= 0; i--) {
|
|
152
|
+
const prevLine = lines[i]?.trim();
|
|
153
|
+
if (prevLine?.startsWith('@')) {
|
|
154
|
+
decorators.unshift(prevLine);
|
|
155
|
+
}
|
|
156
|
+
else if (prevLine && !prevLine.startsWith('#')) {
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Parse parameters
|
|
161
|
+
const parameters = this.parseParameters(paramsStr);
|
|
162
|
+
// Find function end
|
|
163
|
+
const indentLevel = indent.length;
|
|
164
|
+
let endLine = lineNum;
|
|
165
|
+
for (let i = lineNum; i < lines.length; i++) {
|
|
166
|
+
const currentLine = lines[i];
|
|
167
|
+
if (currentLine.trim() && !currentLine.startsWith(' '.repeat(indentLevel + 1)) && !currentLine.match(/^\s*#/)) {
|
|
168
|
+
if (i > lineNum) {
|
|
169
|
+
endLine = i;
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
endLine = i + 1;
|
|
174
|
+
}
|
|
175
|
+
// Check for docstring
|
|
176
|
+
let docstring;
|
|
177
|
+
const firstBodyLine = lines[lineNum]?.trim();
|
|
178
|
+
if (firstBodyLine?.startsWith('"""') || firstBodyLine?.startsWith("'''")) {
|
|
179
|
+
const quote = firstBodyLine.startsWith('"""') ? '"""' : "'''";
|
|
180
|
+
if (firstBodyLine.endsWith(quote) && firstBodyLine.length > 6) {
|
|
181
|
+
docstring = firstBodyLine.slice(3, -3);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
// Multi-line docstring
|
|
185
|
+
for (let i = lineNum; i < endLine; i++) {
|
|
186
|
+
if (lines[i].includes(quote) && i > lineNum) {
|
|
187
|
+
docstring = lines.slice(lineNum, i + 1).join('\n');
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Calculate complexity for the function body
|
|
194
|
+
const bodyLines = lines.slice(lineNum, endLine);
|
|
195
|
+
const complexity = this.calculateComplexity(bodyLines.join('\n'));
|
|
196
|
+
functions.push({
|
|
197
|
+
name: funcName,
|
|
198
|
+
line: lineNum,
|
|
199
|
+
endLine,
|
|
200
|
+
parameters,
|
|
201
|
+
returnType,
|
|
202
|
+
isAsync: lines[lineNum - 1]?.includes('async def'),
|
|
203
|
+
isExported: true, // Python has no explicit export
|
|
204
|
+
decorators,
|
|
205
|
+
docstring,
|
|
206
|
+
complexity,
|
|
207
|
+
bodyLines: endLine - lineNum
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return functions;
|
|
211
|
+
}
|
|
212
|
+
parseParameters(paramsStr) {
|
|
213
|
+
const params = [];
|
|
214
|
+
if (!paramsStr.trim())
|
|
215
|
+
return params;
|
|
216
|
+
// Split by comma, but handle nested brackets
|
|
217
|
+
const paramList = this.splitParams(paramsStr);
|
|
218
|
+
for (const param of paramList) {
|
|
219
|
+
const trimmed = param.trim();
|
|
220
|
+
if (!trimmed || trimmed === 'self' || trimmed === 'cls')
|
|
221
|
+
continue;
|
|
222
|
+
// name: type = default
|
|
223
|
+
const paramMatch = trimmed.match(/^(\*{0,2})(\w+)(?:\s*:\s*([^=]+))?(?:\s*=\s*(.+))?$/);
|
|
224
|
+
if (paramMatch) {
|
|
225
|
+
params.push({
|
|
226
|
+
name: paramMatch[2],
|
|
227
|
+
type: paramMatch[3]?.trim(),
|
|
228
|
+
defaultValue: paramMatch[4]?.trim(),
|
|
229
|
+
isOptional: !!paramMatch[4],
|
|
230
|
+
isRest: paramMatch[1] === '*' || paramMatch[1] === '**'
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return params;
|
|
235
|
+
}
|
|
236
|
+
splitParams(paramsStr) {
|
|
237
|
+
const params = [];
|
|
238
|
+
let current = '';
|
|
239
|
+
let depth = 0;
|
|
240
|
+
for (const char of paramsStr) {
|
|
241
|
+
if (char === '(' || char === '[' || char === '{')
|
|
242
|
+
depth++;
|
|
243
|
+
if (char === ')' || char === ']' || char === '}')
|
|
244
|
+
depth--;
|
|
245
|
+
if (char === ',' && depth === 0) {
|
|
246
|
+
params.push(current);
|
|
247
|
+
current = '';
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
current += char;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (current)
|
|
254
|
+
params.push(current);
|
|
255
|
+
return params;
|
|
256
|
+
}
|
|
257
|
+
extractClasses(code) {
|
|
258
|
+
const classes = [];
|
|
259
|
+
const lines = code.split('\n');
|
|
260
|
+
const classRegex = /^(\s*)class\s+(\w+)(?:\s*\(([^)]*)\))?\s*:/gm;
|
|
261
|
+
let match;
|
|
262
|
+
while ((match = classRegex.exec(code)) !== null) {
|
|
263
|
+
const indent = match[1] || '';
|
|
264
|
+
const className = match[2];
|
|
265
|
+
const basesStr = match[3] || '';
|
|
266
|
+
const beforeMatch = code.substring(0, match.index);
|
|
267
|
+
const lineNum = beforeMatch.split('\n').length;
|
|
268
|
+
// Parse base classes
|
|
269
|
+
const baseClasses = basesStr.split(',').map(b => b.trim()).filter(b => b);
|
|
270
|
+
// Find decorators
|
|
271
|
+
const decorators = [];
|
|
272
|
+
for (let i = lineNum - 2; i >= 0; i--) {
|
|
273
|
+
const prevLine = lines[i]?.trim();
|
|
274
|
+
if (prevLine?.startsWith('@')) {
|
|
275
|
+
decorators.unshift(prevLine);
|
|
276
|
+
}
|
|
277
|
+
else if (prevLine && !prevLine.startsWith('#')) {
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// Find class end and members
|
|
282
|
+
const indentLevel = indent.length;
|
|
283
|
+
let endLine = lineNum;
|
|
284
|
+
const members = [];
|
|
285
|
+
for (let i = lineNum; i < lines.length; i++) {
|
|
286
|
+
const currentLine = lines[i];
|
|
287
|
+
const trimmedLine = currentLine.trim();
|
|
288
|
+
if (trimmedLine && !currentLine.startsWith(' '.repeat(indentLevel + 1)) && !trimmedLine.startsWith('#')) {
|
|
289
|
+
if (i > lineNum) {
|
|
290
|
+
endLine = i;
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// Find methods
|
|
295
|
+
const methodMatch = trimmedLine.match(/^def\s+(\w+)\s*\(/);
|
|
296
|
+
if (methodMatch) {
|
|
297
|
+
const memberName = methodMatch[1];
|
|
298
|
+
members.push({
|
|
299
|
+
name: memberName,
|
|
300
|
+
type: memberName === '__init__' ? 'constructor' : 'method',
|
|
301
|
+
visibility: memberName.startsWith('_') ? 'private' : 'public',
|
|
302
|
+
line: i + 1,
|
|
303
|
+
isStatic: lines[i - 1]?.trim().includes('@staticmethod')
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
// Find class attributes
|
|
307
|
+
const attrMatch = trimmedLine.match(/^(\w+)\s*(?::\s*\w+)?\s*=/);
|
|
308
|
+
if (attrMatch && !trimmedLine.startsWith('def ')) {
|
|
309
|
+
members.push({
|
|
310
|
+
name: attrMatch[1],
|
|
311
|
+
type: 'field',
|
|
312
|
+
visibility: attrMatch[1].startsWith('_') ? 'private' : 'public',
|
|
313
|
+
line: i + 1,
|
|
314
|
+
isStatic: true
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
endLine = i + 1;
|
|
318
|
+
}
|
|
319
|
+
// Find docstring
|
|
320
|
+
let docstring;
|
|
321
|
+
const firstBodyLine = lines[lineNum]?.trim();
|
|
322
|
+
if (firstBodyLine?.startsWith('"""') || firstBodyLine?.startsWith("'''")) {
|
|
323
|
+
docstring = firstBodyLine;
|
|
324
|
+
}
|
|
325
|
+
classes.push({
|
|
326
|
+
name: className,
|
|
327
|
+
line: lineNum,
|
|
328
|
+
endLine,
|
|
329
|
+
baseClasses,
|
|
330
|
+
members,
|
|
331
|
+
decorators,
|
|
332
|
+
docstring
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
return classes;
|
|
336
|
+
}
|
|
337
|
+
extractVariables(code) {
|
|
338
|
+
const variables = [];
|
|
339
|
+
const lines = code.split('\n');
|
|
340
|
+
// Module-level variables (not indented)
|
|
341
|
+
for (let i = 0; i < lines.length; i++) {
|
|
342
|
+
const line = lines[i];
|
|
343
|
+
if (!line.startsWith(' ') && !line.startsWith('\t') && !line.trim().startsWith('#')) {
|
|
344
|
+
// name = value or name: type = value
|
|
345
|
+
const varMatch = line.match(/^([A-Z_][A-Z0-9_]*)\s*(?::\s*(\w+))?\s*=\s*(.+)$/);
|
|
346
|
+
if (varMatch) {
|
|
347
|
+
variables.push({
|
|
348
|
+
name: varMatch[1],
|
|
349
|
+
line: i + 1,
|
|
350
|
+
type: varMatch[2],
|
|
351
|
+
isConst: varMatch[1] === varMatch[1].toUpperCase(),
|
|
352
|
+
scope: 'module',
|
|
353
|
+
value: varMatch[3]
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return variables;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Python-specific semantic rules
|
|
363
|
+
*/
|
|
364
|
+
const PYTHON_RULES = [
|
|
365
|
+
{
|
|
366
|
+
id: 'PY001',
|
|
367
|
+
name: 'missing-type-hints',
|
|
368
|
+
description: 'Function parameters should have type hints',
|
|
369
|
+
language: 'python',
|
|
370
|
+
severity: 'warning',
|
|
371
|
+
category: 'type-safety',
|
|
372
|
+
enabled: true,
|
|
373
|
+
priority: 70,
|
|
374
|
+
check: (ctx) => {
|
|
375
|
+
for (const func of ctx.functions) {
|
|
376
|
+
const hasUntypedParams = func.parameters.some(p => !p.type && !p.isRest);
|
|
377
|
+
if (hasUntypedParams && !func.name.startsWith('_')) {
|
|
378
|
+
return {
|
|
379
|
+
hasIssue: true,
|
|
380
|
+
message: `Function '${func.name}' has parameters without type hints`,
|
|
381
|
+
line: func.line,
|
|
382
|
+
suggestion: 'Add type hints to all parameters for better code clarity'
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
id: 'PY002',
|
|
391
|
+
name: 'missing-return-type',
|
|
392
|
+
description: 'Functions should have return type hints',
|
|
393
|
+
language: 'python',
|
|
394
|
+
severity: 'warning',
|
|
395
|
+
category: 'type-safety',
|
|
396
|
+
enabled: true,
|
|
397
|
+
priority: 70,
|
|
398
|
+
check: (ctx) => {
|
|
399
|
+
for (const func of ctx.functions) {
|
|
400
|
+
if (!func.returnType && !func.name.startsWith('_') && func.name !== '__init__') {
|
|
401
|
+
return {
|
|
402
|
+
hasIssue: true,
|
|
403
|
+
message: `Function '${func.name}' has no return type hint`,
|
|
404
|
+
line: func.line,
|
|
405
|
+
suggestion: 'Add return type annotation: -> ReturnType'
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
id: 'PY003',
|
|
414
|
+
name: 'missing-docstring',
|
|
415
|
+
description: 'Public functions and classes should have docstrings',
|
|
416
|
+
language: 'python',
|
|
417
|
+
severity: 'info',
|
|
418
|
+
category: 'documentation',
|
|
419
|
+
enabled: true,
|
|
420
|
+
priority: 50,
|
|
421
|
+
check: (ctx) => {
|
|
422
|
+
for (const func of ctx.functions) {
|
|
423
|
+
if (!func.docstring && !func.name.startsWith('_')) {
|
|
424
|
+
return {
|
|
425
|
+
hasIssue: true,
|
|
426
|
+
message: `Function '${func.name}' has no docstring`,
|
|
427
|
+
line: func.line,
|
|
428
|
+
suggestion: 'Add a docstring explaining the function purpose'
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
for (const cls of ctx.classes) {
|
|
433
|
+
if (!cls.docstring) {
|
|
434
|
+
return {
|
|
435
|
+
hasIssue: true,
|
|
436
|
+
message: `Class '${cls.name}' has no docstring`,
|
|
437
|
+
line: cls.line,
|
|
438
|
+
suggestion: 'Add a docstring explaining the class purpose'
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
id: 'PY004',
|
|
447
|
+
name: 'star-import',
|
|
448
|
+
description: 'Avoid wildcard imports (from X import *)',
|
|
449
|
+
language: 'python',
|
|
450
|
+
severity: 'warning',
|
|
451
|
+
category: 'best-practices',
|
|
452
|
+
enabled: true,
|
|
453
|
+
priority: 80,
|
|
454
|
+
check: (ctx) => {
|
|
455
|
+
for (const imp of ctx.imports) {
|
|
456
|
+
if (imp.items.includes('*')) {
|
|
457
|
+
return {
|
|
458
|
+
hasIssue: true,
|
|
459
|
+
message: `Wildcard import from '${imp.module}'`,
|
|
460
|
+
line: imp.line,
|
|
461
|
+
suggestion: 'Import specific names instead of using *'
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
id: 'PY005',
|
|
470
|
+
name: 'bare-except',
|
|
471
|
+
description: 'Avoid bare except clauses',
|
|
472
|
+
language: 'python',
|
|
473
|
+
severity: 'error',
|
|
474
|
+
category: 'error-handling',
|
|
475
|
+
enabled: true,
|
|
476
|
+
priority: 90,
|
|
477
|
+
check: (ctx) => {
|
|
478
|
+
const matches = ctx.findPatternMatches(/except\s*:/);
|
|
479
|
+
if (matches.length > 0) {
|
|
480
|
+
return {
|
|
481
|
+
hasIssue: true,
|
|
482
|
+
message: 'Bare except clause catches all exceptions including SystemExit and KeyboardInterrupt',
|
|
483
|
+
line: matches[0].line,
|
|
484
|
+
suggestion: 'Use except Exception: or a more specific exception type'
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
id: 'PY006',
|
|
492
|
+
name: 'mutable-default-arg',
|
|
493
|
+
description: 'Avoid mutable default arguments',
|
|
494
|
+
language: 'python',
|
|
495
|
+
severity: 'error',
|
|
496
|
+
category: 'bugs',
|
|
497
|
+
enabled: true,
|
|
498
|
+
priority: 95,
|
|
499
|
+
check: (ctx) => {
|
|
500
|
+
for (const func of ctx.functions) {
|
|
501
|
+
for (const param of func.parameters) {
|
|
502
|
+
if (param.defaultValue) {
|
|
503
|
+
const val = param.defaultValue;
|
|
504
|
+
if (val === '[]' || val === '{}' || val.startsWith('[') || val.startsWith('{')) {
|
|
505
|
+
return {
|
|
506
|
+
hasIssue: true,
|
|
507
|
+
message: `Mutable default argument '${param.name}=${val}' in '${func.name}'`,
|
|
508
|
+
line: func.line,
|
|
509
|
+
suggestion: 'Use None as default and create mutable in function body'
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return null;
|
|
516
|
+
}
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
id: 'PY007',
|
|
520
|
+
name: 'complex-function',
|
|
521
|
+
description: 'Function has high cyclomatic complexity',
|
|
522
|
+
language: 'python',
|
|
523
|
+
severity: 'warning',
|
|
524
|
+
category: 'maintainability',
|
|
525
|
+
enabled: true,
|
|
526
|
+
priority: 60,
|
|
527
|
+
check: (ctx) => {
|
|
528
|
+
for (const func of ctx.functions) {
|
|
529
|
+
if (func.complexity && func.complexity > 10) {
|
|
530
|
+
return {
|
|
531
|
+
hasIssue: true,
|
|
532
|
+
message: `Function '${func.name}' has complexity of ${func.complexity}`,
|
|
533
|
+
line: func.line,
|
|
534
|
+
details: 'Cyclomatic complexity above 10 indicates hard to test code',
|
|
535
|
+
suggestion: 'Break down into smaller functions'
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
id: 'PY008',
|
|
544
|
+
name: 'long-function',
|
|
545
|
+
description: 'Function body is too long',
|
|
546
|
+
language: 'python',
|
|
547
|
+
severity: 'info',
|
|
548
|
+
category: 'maintainability',
|
|
549
|
+
enabled: true,
|
|
550
|
+
priority: 50,
|
|
551
|
+
check: (ctx) => {
|
|
552
|
+
for (const func of ctx.functions) {
|
|
553
|
+
if (func.bodyLines && func.bodyLines > 50) {
|
|
554
|
+
return {
|
|
555
|
+
hasIssue: true,
|
|
556
|
+
message: `Function '${func.name}' is ${func.bodyLines} lines long`,
|
|
557
|
+
line: func.line,
|
|
558
|
+
suggestion: 'Consider splitting into smaller, focused functions'
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
id: 'PY009',
|
|
567
|
+
name: 'assert-in-production',
|
|
568
|
+
description: 'Assert statements are disabled with python -O',
|
|
569
|
+
language: 'python',
|
|
570
|
+
severity: 'warning',
|
|
571
|
+
category: 'best-practices',
|
|
572
|
+
enabled: true,
|
|
573
|
+
priority: 70,
|
|
574
|
+
check: (ctx) => {
|
|
575
|
+
const matches = ctx.findPatternMatches(/^\s*assert\s+/m);
|
|
576
|
+
if (matches.length > 3) { // Allow some asserts
|
|
577
|
+
return {
|
|
578
|
+
hasIssue: true,
|
|
579
|
+
message: `${matches.length} assert statements found - disabled with -O flag`,
|
|
580
|
+
line: matches[0].line,
|
|
581
|
+
suggestion: 'Use explicit validation with if/raise for production code'
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
return null;
|
|
585
|
+
}
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
id: 'PY010',
|
|
589
|
+
name: 'print-statement',
|
|
590
|
+
description: 'Avoid print() for logging in production code',
|
|
591
|
+
language: 'python',
|
|
592
|
+
severity: 'info',
|
|
593
|
+
category: 'coding-standards',
|
|
594
|
+
enabled: true,
|
|
595
|
+
priority: 40,
|
|
596
|
+
check: (ctx) => {
|
|
597
|
+
const matches = ctx.findPatternMatches(/\bprint\s*\(/);
|
|
598
|
+
if (matches.length > 0) {
|
|
599
|
+
return {
|
|
600
|
+
hasIssue: true,
|
|
601
|
+
message: `Found ${matches.length} print() statements`,
|
|
602
|
+
line: matches[0].line,
|
|
603
|
+
suggestion: 'Use logging module for production code'
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
return null;
|
|
607
|
+
}
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
id: 'PY011',
|
|
611
|
+
name: 'unused-import',
|
|
612
|
+
description: 'Import appears to be unused',
|
|
613
|
+
language: 'python',
|
|
614
|
+
severity: 'info',
|
|
615
|
+
category: 'code-quality',
|
|
616
|
+
enabled: true,
|
|
617
|
+
priority: 40,
|
|
618
|
+
check: (ctx) => {
|
|
619
|
+
for (const imp of ctx.imports) {
|
|
620
|
+
for (const item of imp.items) {
|
|
621
|
+
if (item !== '*') {
|
|
622
|
+
// Simple check - see if the name appears elsewhere in code
|
|
623
|
+
const pattern = new RegExp(`\\b${item}\\b`, 'g');
|
|
624
|
+
const matches = ctx.code.match(pattern);
|
|
625
|
+
if (matches && matches.length === 1) {
|
|
626
|
+
return {
|
|
627
|
+
hasIssue: true,
|
|
628
|
+
message: `Import '${item}' from '${imp.module}' appears unused`,
|
|
629
|
+
line: imp.line,
|
|
630
|
+
suggestion: 'Remove unused import'
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return null;
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
id: 'PY012',
|
|
641
|
+
name: 'global-statement',
|
|
642
|
+
description: 'Avoid using global statement',
|
|
643
|
+
language: 'python',
|
|
644
|
+
severity: 'warning',
|
|
645
|
+
category: 'best-practices',
|
|
646
|
+
enabled: true,
|
|
647
|
+
priority: 75,
|
|
648
|
+
check: (ctx) => {
|
|
649
|
+
const matches = ctx.findPatternMatches(/\bglobal\s+\w/);
|
|
650
|
+
if (matches.length > 0) {
|
|
651
|
+
return {
|
|
652
|
+
hasIssue: true,
|
|
653
|
+
message: 'Use of global statement detected',
|
|
654
|
+
line: matches[0].line,
|
|
655
|
+
suggestion: 'Pass variables as parameters or use a class instead'
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
return null;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
];
|
|
662
|
+
export { PYTHON_RULES };
|
|
663
|
+
//# sourceMappingURL=pythonParser.js.map
|