UNPKG

bugger-mcp

Version:

MCP Server for managing bugs, feature requests, and improvements

409 lines 16.7 kB
// Stack trace parser for automated context collection // Supports multiple programming languages and integrates with existing context collection engine /** * Enhanced stack trace parser that can handle multiple programming languages * and extract code context information for automated bug analysis */ export class StackTraceParser { static LANGUAGE_PATTERNS = { javascript: { // JavaScript/Node.js patterns patterns: [ // Standard V8 stack trace: "at functionName (file.js:line:col)" /^\s*at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?$/, // Browser stack trace: "functionName@file.js:line:col" /^\s*(.+?)@(.+?):(\d+):(\d+)$/, // Webpack/bundled: "at Object.functionName (webpack:///./src/file.js:line:col)" /^\s*at\s+(?:Object\.)?(.+?)\s+\(webpack:\/\/\/\.\/(.+?):(\d+):(\d+)\)/, // Simple format: "file.js:line:col" /^\s*(.+?):(\d+):(\d+)$/ ], errorPattern: /^(\w+(?:Error|Exception)?):\s*(.+)$/, extensions: ['.js', '.jsx', '.ts', '.tsx', '.mjs'], confidence: 0.9 }, python: { // Python traceback patterns patterns: [ // " File "/path/to/file.py", line 123, in function_name" /^\s*File\s+"(.+?)",\s+line\s+(\d+),\s+in\s+(.+)$/, // Alternative format: "File "file.py", line 123" /^\s*File\s+"(.+?)",\s+line\s+(\d+)$/ ], errorPattern: /^(\w+(?:Error|Exception)):\s*(.+)$/, extensions: ['.py', '.pyw'], confidence: 0.95 }, java: { // Java stack trace patterns patterns: [ // "at com.example.Class.method(File.java:123)" /^\s*at\s+([a-zA-Z_$][a-zA-Z0-9_$.]*?)\.([a-zA-Z_$][a-zA-Z0-9_$]*?)\((.+?):(\d+)\)$/, // "at com.example.Class.method(Unknown Source)" /^\s*at\s+([a-zA-Z_$][a-zA-Z0-9_$.]*?)\.([a-zA-Z_$][a-zA-Z0-9_$]*?)\(Unknown Source\)$/, // "at com.example.Class.method(Native Method)" /^\s*at\s+([a-zA-Z_$][a-zA-Z0-9_$.]*?)\.([a-zA-Z_$][a-zA-Z0-9_$]*?)\(Native Method\)$/ ], errorPattern: /^(\w+(?:Exception|Error)):\s*(.+)$/, extensions: ['.java'], confidence: 0.9 }, csharp: { // C# stack trace patterns patterns: [ // "at Namespace.Class.Method() in C:\path\to\File.cs:line 123" /^\s*at\s+([a-zA-Z_][a-zA-Z0-9_.]*?)\.([a-zA-Z_][a-zA-Z0-9_]*?)\([^)]*\)\s+in\s+(.+?):line\s+(\d+)$/, // "at Namespace.Class.Method()" /^\s*at\s+([a-zA-Z_][a-zA-Z0-9_.]*?)\.([a-zA-Z_][a-zA-Z0-9_]*?)\([^)]*\)$/ ], errorPattern: /^(System\.\w+(?:Exception)?|\w+(?:Exception|Error)):\s*(.+)$/, extensions: ['.cs'], confidence: 0.85 }, go: { // Go panic stack trace patterns patterns: [ // "main.functionName() /path/to/file.go:123 +0x456" /^\s*(.+?)\(\)\s+(.+?):(\d+)\s+\+0x[0-9a-f]+$/, // "github.com/user/repo/package.functionName() /path/to/file.go:123" /^\s*(.+?)\.(.+?)\(\)\s+(.+?):(\d+)$/ ], errorPattern: /^panic:\s*(.+)$/, extensions: ['.go'], confidence: 0.8 }, rust: { // Rust panic stack trace patterns patterns: [ // "at crate_name::module::function (/path/to/file.rs:123:45)" /^\s*at\s+(.+?)::(.+?)\s+\((.+?):(\d+):(\d+)\)$/, // "crate_name::module::function at /path/to/file.rs:123:45" /^\s*(.+?)::(.+?)\s+at\s+(.+?):(\d+):(\d+)$/ ], errorPattern: /^thread\s+'[^']+'\s+panicked\s+at\s+'(.+)',\s+(.+?):(\d+):(\d+)$/, extensions: ['.rs'], confidence: 0.8 } }; /** * Parse a stack trace from text and extract structured information */ static parseStackTrace(stackTraceText) { if (!stackTraceText || typeof stackTraceText !== 'string') { return { language: 'unknown', frames: [], isValid: false, confidence: 0 }; } const lines = stackTraceText.split('\n').map(line => line.trim()).filter(Boolean); // Try to detect the language and parse accordingly for (const [language, config] of Object.entries(this.LANGUAGE_PATTERNS)) { const result = this.parseWithLanguageConfig(lines, language, config); if (result.isValid && result.frames.length > 0) { return result; } } // Fallback: try to extract any file references const fallbackFrames = this.extractFallbackFrames(lines); return { language: 'unknown', frames: fallbackFrames, isValid: fallbackFrames.length > 0, confidence: fallbackFrames.length > 0 ? 0.3 : 0 }; } /** * Extract context information from parsed stack trace for code collection */ static extractStackTraceContexts(parsedTrace) { if (!parsedTrace.isValid || parsedTrace.frames.length === 0) { return []; } const contexts = []; for (let i = 0; i < parsedTrace.frames.length; i++) { const frame = parsedTrace.frames[i]; if (frame.filePath && frame.lineNumber) { // Skip system/library files for most languages if (this.isSystemFile(frame.filePath, parsedTrace.language)) { continue; } const priority = this.determinePriority(i, parsedTrace.frames.length); const contextLines = this.determineContextLines(priority); contexts.push({ filePath: frame.filePath, functionName: frame.functionName || frame.methodName || undefined, lineNumber: frame.lineNumber, contextLines, priority }); } } return contexts; } /** * Check if the given text contains a potential stack trace */ static containsStackTrace(text) { if (!text || typeof text !== 'string') { return false; } const lines = text.split('\n'); let stackTraceLines = 0; for (const line of lines) { // Check for common stack trace indicators if (this.looksLikeStackTraceLine(line)) { stackTraceLines++; if (stackTraceLines >= 2) { return true; } } } return false; } /** * Extract all stack traces from a larger text block */ static extractStackTraces(text) { if (!text || typeof text !== 'string') { return []; } const stackTraces = []; const lines = text.split('\n'); let currentStackTrace = []; let inStackTrace = false; for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (this.looksLikeStackTraceLine(line)) { if (!inStackTrace) { // Starting a new stack trace, include potential error message from previous lines const errorContext = this.findErrorContext(lines, i); currentStackTrace = errorContext; inStackTrace = true; } currentStackTrace.push(line); } else if (inStackTrace) { // Check if this might be a continuation (like error message) if (line.trim() && !this.looksLikeRegularCode(line)) { currentStackTrace.push(line); } else { // End of current stack trace if (currentStackTrace.length >= 2) { const parsed = this.parseStackTrace(currentStackTrace.join('\n')); if (parsed.isValid) { stackTraces.push(parsed); } } currentStackTrace = []; inStackTrace = false; } } } // Handle case where stack trace goes to end of text if (currentStackTrace.length >= 2) { const parsed = this.parseStackTrace(currentStackTrace.join('\n')); if (parsed.isValid) { stackTraces.push(parsed); } } return stackTraces; } // Private helper methods static parseWithLanguageConfig(lines, language, config) { const frames = []; let errorType; let errorMessage; let confidence = 0; for (const line of lines) { // Try to match error pattern first if (!errorType && config.errorPattern) { const errorMatch = line.match(config.errorPattern); if (errorMatch) { errorType = errorMatch[1]; errorMessage = errorMatch[2]; confidence += 0.3; continue; } } // Try to match stack trace patterns for (const pattern of config.patterns) { const match = line.match(pattern); if (match) { const frame = this.createFrameFromMatch(match, language, line, pattern); if (frame) { frames.push(frame); confidence += 0.1; break; } } } } const isValid = frames.length > 0 && confidence > 0.3; const finalConfidence = Math.min(1.0, confidence * config.confidence); return { language, errorType, errorMessage, frames, isValid, confidence: finalConfidence }; } static createFrameFromMatch(match, language, rawLine, pattern) { // Different patterns have different group arrangements // This is a simplified approach - in practice, you'd want more specific handling per pattern const frame = { language, rawLine }; // Extract information based on what's available in the match const groups = match.slice(1); // Remove full match switch (language) { case 'javascript': if (groups.length >= 3) { frame.functionName = groups[0] || undefined; frame.filePath = groups[1] || undefined; frame.lineNumber = groups[2] ? parseInt(groups[2]) : undefined; frame.columnNumber = groups[3] ? parseInt(groups[3]) : undefined; } break; case 'python': if (groups.length >= 2) { frame.filePath = groups[0] || undefined; frame.lineNumber = groups[1] ? parseInt(groups[1]) : undefined; frame.functionName = groups[2] || undefined; } break; case 'java': if (groups.length >= 3) { frame.className = groups[0] || undefined; frame.methodName = groups[1] || undefined; frame.filePath = groups[2] || undefined; frame.lineNumber = groups[3] ? parseInt(groups[3]) : undefined; } break; case 'csharp': if (groups.length >= 2) { frame.className = groups[0] || undefined; frame.methodName = groups[1] || undefined; frame.filePath = groups[2] || undefined; frame.lineNumber = groups[3] ? parseInt(groups[3]) : undefined; } break; case 'go': if (groups.length >= 3) { frame.functionName = groups[0] || groups[1] || undefined; frame.filePath = groups[2] || groups[3] || undefined; frame.lineNumber = groups[3] || groups[4] ? parseInt(groups[3] || groups[4]) : undefined; } break; case 'rust': if (groups.length >= 4) { frame.functionName = groups[0] && groups[1] ? `${groups[0]}::${groups[1]}` : undefined; frame.filePath = groups[2] || undefined; frame.lineNumber = groups[3] ? parseInt(groups[3]) : undefined; frame.columnNumber = groups[4] ? parseInt(groups[4]) : undefined; } break; } // Validate that we extracted at least a file path return frame.filePath ? frame : null; } static extractFallbackFrames(lines) { const frames = []; for (const line of lines) { // Look for any file:line patterns const fileLineMatch = line.match(/([a-zA-Z0-9_\-/.\\]+\.(js|ts|py|java|cs|go|rs|jsx|tsx)):(\d+)/); if (fileLineMatch) { frames.push({ filePath: fileLineMatch[1], lineNumber: parseInt(fileLineMatch[3]), language: 'unknown', rawLine: line }); } } return frames; } static looksLikeStackTraceLine(line) { const trimmed = line.trim(); // Common stack trace indicators const indicators = [ /^\s*at\s+/, // JavaScript "at" /^\s*File\s+"/, // Python "File" /^\s*\w+\.\w+\(/, // Java class.method( /\.(js|ts|py|java|cs|go|rs|jsx|tsx):\d+/, // file.ext:line /^\s*\d+\.\s+/, // Numbered stack trace /^thread\s+'[^']+'\s+panicked/, // Rust panic ]; return indicators.some(pattern => pattern.test(trimmed)); } static findErrorContext(lines, stackTraceStartIndex) { const context = []; // Look backwards for error message for (let i = Math.max(0, stackTraceStartIndex - 3); i < stackTraceStartIndex; i++) { const line = lines[i].trim(); if (line && (line.includes('Error') || line.includes('Exception') || line.includes('panic'))) { context.push(lines[i]); } } return context; } static looksLikeRegularCode(line) { const trimmed = line.trim(); // Indicators that this is regular code, not part of a stack trace const codeIndicators = [ /^(function|class|def|public|private|if|for|while|return)/, /^(import|from|#include|using)/, /^\s*\/\/|^\s*\/\*|^\s*#/, // Comments /^\s*[{}]\s*$/, // Braces alone /^\s*[a-zA-Z_$][a-zA-Z0-9_$]*\s*[=:]/, // Variable assignments ]; return codeIndicators.some(pattern => pattern.test(trimmed)); } static isSystemFile(filePath, language) { const systemPatterns = { javascript: [ /node_modules/, /internal\/.*\.js$/, /^node:/ ], python: [ /\/usr\/lib\/python/, /site-packages/, /<frozen / ], java: [ /^java\./, /^javax\./, /^sun\./, /^com\.sun\./ ], csharp: [ /^System\./, /^Microsoft\./ ] }; const patterns = systemPatterns[language] || []; return patterns.some(pattern => pattern.test(filePath)); } static determinePriority(frameIndex, totalFrames) { // First few frames are usually most relevant if (frameIndex < 2) return 'high'; if (frameIndex < Math.ceil(totalFrames / 2)) return 'medium'; return 'low'; } static determineContextLines(priority) { switch (priority) { case 'high': return 10; // ±10 lines around the error case 'medium': return 5; // ±5 lines around the error case 'low': return 3; // ±3 lines around the error } } } //# sourceMappingURL=stack-trace-parser.js.map