@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.
Files changed (32) hide show
  1. package/dist/config/types.d.ts +1 -0
  2. package/dist/config/types.d.ts.map +1 -1
  3. package/dist/config/types.js.map +1 -1
  4. package/dist/services/codeAnalyzer.d.ts +5 -1
  5. package/dist/services/codeAnalyzer.d.ts.map +1 -1
  6. package/dist/services/codeAnalyzer.js +39 -2
  7. package/dist/services/codeAnalyzer.js.map +1 -1
  8. package/dist/services/parsers/baseParser.d.ts +44 -0
  9. package/dist/services/parsers/baseParser.d.ts.map +1 -0
  10. package/dist/services/parsers/baseParser.js +177 -0
  11. package/dist/services/parsers/baseParser.js.map +1 -0
  12. package/dist/services/parsers/goParser.d.ts +28 -0
  13. package/dist/services/parsers/goParser.d.ts.map +1 -0
  14. package/dist/services/parsers/goParser.js +590 -0
  15. package/dist/services/parsers/goParser.js.map +1 -0
  16. package/dist/services/parsers/index.d.ts +62 -0
  17. package/dist/services/parsers/index.d.ts.map +1 -0
  18. package/dist/services/parsers/index.js +121 -0
  19. package/dist/services/parsers/index.js.map +1 -0
  20. package/dist/services/parsers/pythonParser.d.ts +28 -0
  21. package/dist/services/parsers/pythonParser.d.ts.map +1 -0
  22. package/dist/services/parsers/pythonParser.js +663 -0
  23. package/dist/services/parsers/pythonParser.js.map +1 -0
  24. package/dist/services/parsers/rustParser.d.ts +28 -0
  25. package/dist/services/parsers/rustParser.d.ts.map +1 -0
  26. package/dist/services/parsers/rustParser.js +654 -0
  27. package/dist/services/parsers/rustParser.js.map +1 -0
  28. package/dist/services/parsers/types.d.ts +241 -0
  29. package/dist/services/parsers/types.d.ts.map +1 -0
  30. package/dist/services/parsers/types.js +47 -0
  31. package/dist/services/parsers/types.js.map +1 -0
  32. 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