Skip to main content
Glama
cbunting99

MCP Code Analysis & Quality Server

by cbunting99
ASTProcessor.ts18.4 kB
// Copyright 2025 Chris Bunting // Brief: AST Processor for Code Complexity Analyzer MCP Server // Scope: Handles AST parsing and processing for multiple programming languages import * as fs from 'fs'; import * as path from 'path'; import { Logger } from '../utils/Logger.js'; import { Language } from '@mcp-code-analysis/shared-types'; export interface ASTNode { type: string; name?: string; start: { line: number; column: number }; end: { line: number; column: number }; body?: ASTNode[]; children?: ASTNode[]; value?: any; parent?: ASTNode; } export interface ASTProcessingResult { ast: ASTNode; language: Language; filePath: string; processingTime: number; errors: string[]; } export class ASTProcessor { private logger: Logger; private parsers: Map<Language, (content: string) => ASTNode>; constructor() { this.logger = new Logger(); this.parsers = new Map(); this.initializeParsers(); } private initializeParsers(): void { // Initialize parsers for different languages this.parsers.set(Language.JAVASCRIPT, this.parseJavaScript.bind(this)); this.parsers.set(Language.TYPESCRIPT, this.parseTypeScript.bind(this)); this.parsers.set(Language.PYTHON, this.parsePython.bind(this)); this.parsers.set(Language.JAVA, this.parseJava.bind(this)); this.parsers.set(Language.C, this.parseC.bind(this)); this.parsers.set(Language.CPP, this.parseCpp.bind(this)); this.parsers.set(Language.GO, this.parseGo.bind(this)); this.parsers.set(Language.RUST, this.parseRust.bind(this)); } public async processFile(filePath: string, language?: Language): Promise<ASTProcessingResult> { const startTime = Date.now(); const errors: string[] = []; try { // Check if file exists if (!fs.existsSync(filePath)) { throw new Error(`File not found: ${filePath}`); } // Read file content const content = fs.readFileSync(filePath, 'utf-8'); // Detect language if not provided const detectedLanguage = language || this.detectLanguage(filePath, content); // Get parser for the language const parser = this.parsers.get(detectedLanguage); if (!parser) { throw new Error(`Unsupported language: ${detectedLanguage}`); } // Parse the content const ast = parser(content); const processingTime = Date.now() - startTime; this.logger.info(`Successfully processed file: ${filePath}`, { language: detectedLanguage, processingTime, nodes: this.countNodes(ast) }); return { ast, language: detectedLanguage, filePath, processingTime, errors }; } catch (error) { const processingTime = Date.now() - startTime; const errorMessage = error instanceof Error ? error.message : 'Unknown error'; errors.push(errorMessage); this.logger.error(`Failed to process file: ${filePath}`, { error: errorMessage }); return { ast: { type: 'error', start: { line: 0, column: 0 }, end: { line: 0, column: 0 } }, language: language || Language.JAVASCRIPT, filePath, processingTime, errors }; } } private detectLanguage(filePath: string, content: string): Language { const ext = path.extname(filePath).toLowerCase(); switch (ext) { case '.js': case '.mjs': case '.cjs': return Language.JAVASCRIPT; case '.ts': case '.tsx': return Language.TYPESCRIPT; case '.py': case '.pyx': return Language.PYTHON; case '.java': return Language.JAVA; case '.c': case '.h': return Language.C; case '.cpp': case '.cxx': case '.cc': case '.hpp': case '.hxx': return Language.CPP; case '.go': return Language.GO; case '.rs': return Language.RUST; default: // Fallback to content-based detection if (content.includes('import ') && content.includes('function ')) { return Language.JAVASCRIPT; } else if (content.includes('def ') && content.includes(':')) { return Language.PYTHON; } else if (content.includes('public class ')) { return Language.JAVA; } else if (content.includes('#include') && content.includes('int main')) { return Language.C; } else if (content.includes('#include') && content.includes('using namespace')) { return Language.CPP; } else if (content.includes('package main') && content.includes('func ')) { return Language.GO; } else if (content.includes('fn ') && content.includes('let mut')) { return Language.RUST; } return Language.JAVASCRIPT; // Default fallback } } private parseJavaScript(content: string): ASTNode { try { // Use acorn for JavaScript parsing const acorn = require('acorn'); const ast = acorn.parse(content, { ecmaVersion: 2022, sourceType: 'module', locations: true }); return this.convertAcornToCustomAST(ast); } catch (error) { throw new Error(`JavaScript parsing failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } private parseTypeScript(content: string): ASTNode { try { // For TypeScript, we'll use the same parser as JavaScript for now // In a real implementation, you'd use TypeScript compiler API return this.parseJavaScript(content); } catch (error) { throw new Error(`TypeScript parsing failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } private parsePython(content: string): ASTNode { try { // Use esprima-like parsing for Python (simplified) // In a real implementation, you'd use a proper Python parser like ast-python const lines = content.split('\n'); const root: ASTNode = { type: 'module', start: { line: 1, column: 0 }, end: { line: lines.length, column: lines[lines.length - 1]?.length || 0 }, body: [] }; let currentNode: ASTNode | null = null; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); const lineNum = i + 1; if (line.startsWith('def ')) { const funcNode: ASTNode = { type: 'function', name: line.split('(')[0].replace('def ', '').trim(), start: { line: lineNum, column: 0 }, end: { line: lineNum, column: line.length }, body: [] }; if (currentNode) { currentNode.body?.push(funcNode); } else { root.body?.push(funcNode); } currentNode = funcNode; } else if (line.startsWith('class ')) { const classNode: ASTNode = { type: 'class', name: line.split(':')[0].replace('class ', '').trim(), start: { line: lineNum, column: 0 }, end: { line: lineNum, column: line.length }, body: [] }; if (currentNode) { currentNode.body?.push(classNode); } else { root.body?.push(classNode); } currentNode = classNode; } else if (line && !line.startsWith('#') && currentNode) { // Add statement to current function/class const stmtNode: ASTNode = { type: 'statement', start: { line: lineNum, column: 0 }, end: { line: lineNum, column: line.length }, value: line }; currentNode.body?.push(stmtNode); } } return root; } catch (error) { throw new Error(`Python parsing failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } private parseJava(content: string): ASTNode { try { // Simplified Java parsing const lines = content.split('\n'); const root: ASTNode = { type: 'compilation_unit', start: { line: 1, column: 0 }, end: { line: lines.length, column: lines[lines.length - 1]?.length || 0 }, body: [] }; let currentClass: ASTNode | null = null; let currentMethod: ASTNode | null = null; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); const lineNum = i + 1; if (line.includes('class ')) { const className = line.split('class ')[1].split('{')[0].trim(); const classNode: ASTNode = { type: 'class', name: className, start: { line: lineNum, column: 0 }, end: { line: lineNum, column: line.length }, body: [] }; root.body?.push(classNode); currentClass = classNode; currentMethod = null; } else if (line.includes('public ') || line.includes('private ') || line.includes('protected ')) { if (line.includes('(') && line.includes(')')) { const methodNode: ASTNode = { type: 'method', name: line.split('(')[0].split(' ').pop() || 'unknown', start: { line: lineNum, column: 0 }, end: { line: lineNum, column: line.length }, body: [] }; if (currentClass) { currentClass.body?.push(methodNode); } else { root.body?.push(methodNode); } currentMethod = methodNode; } } else if (line && currentMethod) { const stmtNode: ASTNode = { type: 'statement', start: { line: lineNum, column: 0 }, end: { line: lineNum, column: line.length }, value: line }; currentMethod.body?.push(stmtNode); } } return root; } catch (error) { throw new Error(`Java parsing failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } private parseC(content: string): ASTNode { // Simplified C parsing (similar to C++ but without classes) return this.parseCppLike(content, 'c'); } private parseCpp(content: string): ASTNode { // Simplified C++ parsing return this.parseCppLike(content, 'cpp'); } private parseCppLike(content: string, language: 'c' | 'cpp'): ASTNode { try { const lines = content.split('\n'); const root: ASTNode = { type: 'translation_unit', start: { line: 1, column: 0 }, end: { line: lines.length, column: lines[lines.length - 1]?.length || 0 }, body: [] }; let currentFunction: ASTNode | null = null; let currentClass: ASTNode | null = null; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); const lineNum = i + 1; if (language === 'cpp' && line.includes('class ')) { const className = line.split('class ')[1].split('{')[0].trim(); const classNode: ASTNode = { type: 'class', name: className, start: { line: lineNum, column: 0 }, end: { line: lineNum, column: line.length }, body: [] }; root.body?.push(classNode); currentClass = classNode; currentFunction = null; } else if (line.includes('(') && line.includes(')') && line.includes('{')) { const funcName = line.split('(')[0].split(' ').pop() || 'unknown'; const funcNode: ASTNode = { type: 'function', name: funcName, start: { line: lineNum, column: 0 }, end: { line: lineNum, column: line.length }, body: [] }; if (currentClass) { currentClass.body?.push(funcNode); } else { root.body?.push(funcNode); } currentFunction = funcNode; } else if (line && currentFunction) { const stmtNode: ASTNode = { type: 'statement', start: { line: lineNum, column: 0 }, end: { line: lineNum, column: line.length }, value: line }; currentFunction.body?.push(stmtNode); } } return root; } catch (error) { throw new Error(`${language.toUpperCase()} parsing failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } private parseGo(content: string): ASTNode { try { const lines = content.split('\n'); const root: ASTNode = { type: 'package', start: { line: 1, column: 0 }, end: { line: lines.length, column: lines[lines.length - 1]?.length || 0 }, body: [] }; let currentFunction: ASTNode | null = null; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); const lineNum = i + 1; if (line.startsWith('func ')) { const funcName = line.split('(')[0].replace('func ', '').trim(); const funcNode: ASTNode = { type: 'function', name: funcName, start: { line: lineNum, column: 0 }, end: { line: lineNum, column: line.length }, body: [] }; root.body?.push(funcNode); currentFunction = funcNode; } else if (line && currentFunction) { const stmtNode: ASTNode = { type: 'statement', start: { line: lineNum, column: 0 }, end: { line: lineNum, column: line.length }, value: line }; currentFunction.body?.push(stmtNode); } } return root; } catch (error) { throw new Error(`Go parsing failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } private parseRust(content: string): ASTNode { try { const lines = content.split('\n'); const root: ASTNode = { type: 'crate', start: { line: 1, column: 0 }, end: { line: lines.length, column: lines[lines.length - 1]?.length || 0 }, body: [] }; let currentFunction: ASTNode | null = null; let currentStruct: ASTNode | null = null; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); const lineNum = i + 1; if (line.startsWith('struct ')) { const structName = line.split('{')[0].replace('struct ', '').trim(); const structNode: ASTNode = { type: 'struct', name: structName, start: { line: lineNum, column: 0 }, end: { line: lineNum, column: line.length }, body: [] }; root.body?.push(structNode); currentStruct = structNode; currentFunction = null; } else if (line.startsWith('fn ')) { const funcName = line.split('(')[0].replace('fn ', '').trim(); const funcNode: ASTNode = { type: 'function', name: funcName, start: { line: lineNum, column: 0 }, end: { line: lineNum, column: line.length }, body: [] }; if (currentStruct) { currentStruct.body?.push(funcNode); } else { root.body?.push(funcNode); } currentFunction = funcNode; } else if (line && currentFunction) { const stmtNode: ASTNode = { type: 'statement', start: { line: lineNum, column: 0 }, end: { line: lineNum, column: line.length }, value: line }; currentFunction.body?.push(stmtNode); } } return root; } catch (error) { throw new Error(`Rust parsing failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } private convertAcornToCustomAST(acornAst: any): ASTNode { // Convert acorn AST to our custom AST format const convert = (node: any): ASTNode => { if (!node) { return { type: 'empty', start: { line: 0, column: 0 }, end: { line: 0, column: 0 } }; } const customNode: ASTNode = { type: node.type, start: node.loc ? { line: node.loc.start.line, column: node.loc.start.column } : { line: 0, column: 0 }, end: node.loc ? { line: node.loc.end.line, column: node.loc.end.column } : { line: 0, column: 0 } }; if (node.id?.name) { customNode.name = node.id.name; } if (node.body) { if (Array.isArray(node.body)) { customNode.body = node.body.map(convert); } else { customNode.body = [convert(node.body)]; } } if (node.consequent) { customNode.body = customNode.body || []; customNode.body.push(convert(node.consequent)); } if (node.alternate) { customNode.body = customNode.body || []; customNode.body.push(convert(node.alternate)); } return customNode; }; return convert(acornAst); } private countNodes(node: ASTNode): number { let count = 1; if (node.body) { for (const child of node.body) { count += this.countNodes(child); } } if (node.children) { for (const child of node.children) { count += this.countNodes(child); } } return count; } public traverseAST(ast: ASTNode, visitor: (node: ASTNode) => void): void { visitor(ast); if (ast.body) { for (const child of ast.body) { this.traverseAST(child, visitor); } } if (ast.children) { for (const child of ast.children) { this.traverseAST(child, visitor); } } } public findNodesByType(ast: ASTNode, type: string): ASTNode[] { const results: ASTNode[] = []; this.traverseAST(ast, (node) => { if (node.type === type) { results.push(node); } }); return results; } public findNodeByName(ast: ASTNode, name: string): ASTNode | null { let result: ASTNode | null = null; this.traverseAST(ast, (node) => { if (node.name === name && !result) { result = node; } }); return result; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/cbunting99/mcp-code-analysis-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server