Skip to main content
Glama
handleGetAbapSemanticAnalysis.tsâ€ĸ14.9 kB
import { McpError, ErrorCode } from '../lib/utils'; import { writeResultToFile } from '../lib/writeResultToFile'; export const TOOL_DEFINITION = { name: "GetAbapSemanticAnalysis", description: "Perform semantic analysis on ABAP code and return symbols, types, scopes, and dependencies.", inputSchema: { type: "object", properties: { code: { type: "string", description: "ABAP source code to analyze" }, filePath: { type: "string", description: "Optional file path to write the result to" } }, required: ["code"] } } as const; interface AbapSymbolInfo { name: string; type: 'class' | 'method' | 'function' | 'variable' | 'constant' | 'type' | 'interface' | 'form' | 'program' | 'report' | 'include'; scope: string; line: number; column: number; description?: string; package?: string; visibility?: 'public' | 'protected' | 'private'; dataType?: string; parameters?: AbapParameterInfo[]; } interface AbapParameterInfo { name: string; type: 'importing' | 'exporting' | 'changing' | 'returning'; dataType?: string; optional?: boolean; defaultValue?: string; } interface AbapSemanticAnalysisResult { symbols: AbapSymbolInfo[]; dependencies: string[]; errors: AbapParseError[]; scopes: AbapScopeInfo[]; } interface AbapParseError { message: string; line: number; column: number; severity: 'error' | 'warning' | 'info'; } interface AbapScopeInfo { name: string; type: 'global' | 'class' | 'method' | 'form' | 'function' | 'local'; startLine: number; endLine: number; parent?: string; } // Simplified semantic analyzer that doesn't depend on ANTLR until it's properly set up class SimpleAbapSemanticAnalyzer { private symbols: AbapSymbolInfo[] = []; private scopes: AbapScopeInfo[] = []; private dependencies: string[] = []; private errors: AbapParseError[] = []; private currentScope: string = 'global'; public analyze(code: string): AbapSemanticAnalysisResult { // Reset state this.symbols = []; this.scopes = []; this.dependencies = []; this.errors = []; this.currentScope = 'global'; try { this.analyzeCode(code); } catch (error) { this.errors.push({ message: error instanceof Error ? error.message : String(error), line: 1, column: 1, severity: 'error' }); } return { symbols: this.symbols, dependencies: this.dependencies, errors: this.errors, scopes: this.scopes }; } private analyzeCode(code: string): void { const lines = code.split('\n'); let currentClassScope: string | null = null; let currentMethodScope: string | null = null; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); const lineNumber = i + 1; if (line === '' || line.startsWith('*') || line.startsWith('"')) { continue; // Skip empty lines and comments } try { // Analyze different ABAP constructs this.analyzeClassDefinition(line, lineNumber); this.analyzeClassImplementation(line, lineNumber); this.analyzeMethodDefinition(line, lineNumber); this.analyzeMethodImplementation(line, lineNumber); this.analyzeDataDeclaration(line, lineNumber); this.analyzeConstantsDeclaration(line, lineNumber); this.analyzeTypesDeclaration(line, lineNumber); this.analyzeFormDefinition(line, lineNumber); this.analyzeFunctionDefinition(line, lineNumber); this.analyzeIncludeStatement(line, lineNumber); this.analyzeInterfaceDefinition(line, lineNumber); this.analyzeScopeEnders(line, lineNumber); } catch (error) { this.errors.push({ message: `Error analyzing line ${lineNumber}: ${error instanceof Error ? error.message : String(error)}`, line: lineNumber, column: 1, severity: 'warning' }); } } } private analyzeClassDefinition(line: string, lineNumber: number): void { const classDefMatch = line.toLowerCase().match(/^class\s+([a-zA-Z0-9_]+)\s+definition/); if (classDefMatch) { const className = classDefMatch[1].toUpperCase(); this.addSymbol({ name: className, type: 'class', scope: this.currentScope, line: lineNumber, column: 1, visibility: this.extractVisibility(line) }); this.pushScope(className, 'class', lineNumber); } } private analyzeClassImplementation(line: string, lineNumber: number): void { const classImplMatch = line.toLowerCase().match(/^class\s+([a-zA-Z0-9_]+)\s+implementation/); if (classImplMatch) { const className = classImplMatch[1].toUpperCase(); this.pushScope(`${className}_IMPL`, 'class', lineNumber); } } private analyzeMethodDefinition(line: string, lineNumber: number): void { const methodMatch = line.toLowerCase().match(/^(methods|class-methods)\s+([a-zA-Z0-9_]+)/); if (methodMatch) { const methodName = methodMatch[2].toUpperCase(); const isStatic = methodMatch[1] === 'class-methods'; this.addSymbol({ name: methodName, type: 'method', scope: this.currentScope, line: lineNumber, column: 1, visibility: this.extractVisibility(line), description: isStatic ? 'Static method' : 'Instance method', parameters: this.extractMethodParameters(line) }); } } private analyzeMethodImplementation(line: string, lineNumber: number): void { const methodImplMatch = line.toLowerCase().match(/^method\s+([a-zA-Z0-9_~\->]+)/); if (methodImplMatch) { const methodName = methodImplMatch[1].toUpperCase(); this.pushScope(methodName, 'method', lineNumber); } } private analyzeDataDeclaration(line: string, lineNumber: number): void { const dataMatches = [ line.toLowerCase().match(/^data:?\s+([a-zA-Z0-9_]+)/), line.toLowerCase().match(/^class-data:?\s+([a-zA-Z0-9_]+)/), line.toLowerCase().match(/^statics:?\s+([a-zA-Z0-9_]+)/) ]; for (const match of dataMatches) { if (match) { const varName = match[1].toUpperCase(); this.addSymbol({ name: varName, type: 'variable', scope: this.currentScope, line: lineNumber, column: 1, dataType: this.extractDataType(line), visibility: this.extractVisibility(line) }); break; } } } private analyzeConstantsDeclaration(line: string, lineNumber: number): void { const constantMatch = line.toLowerCase().match(/^constants:?\s+([a-zA-Z0-9_]+)/); if (constantMatch) { const constName = constantMatch[1].toUpperCase(); this.addSymbol({ name: constName, type: 'constant', scope: this.currentScope, line: lineNumber, column: 1, dataType: this.extractDataType(line), visibility: this.extractVisibility(line) }); } } private analyzeTypesDeclaration(line: string, lineNumber: number): void { const typeMatch = line.toLowerCase().match(/^types:?\s+([a-zA-Z0-9_]+)/); if (typeMatch) { const typeName = typeMatch[1].toUpperCase(); this.addSymbol({ name: typeName, type: 'type', scope: this.currentScope, line: lineNumber, column: 1, dataType: this.extractDataType(line), visibility: this.extractVisibility(line) }); } } private analyzeFormDefinition(line: string, lineNumber: number): void { const formMatch = line.toLowerCase().match(/^form\s+([a-zA-Z0-9_]+)/); if (formMatch) { const formName = formMatch[1].toUpperCase(); this.addSymbol({ name: formName, type: 'form', scope: this.currentScope, line: lineNumber, column: 1 }); this.pushScope(formName, 'form', lineNumber); } } private analyzeFunctionDefinition(line: string, lineNumber: number): void { const functionMatch = line.toLowerCase().match(/^function\s+([a-zA-Z0-9_]+)/); if (functionMatch) { const functionName = functionMatch[1].toUpperCase(); this.addSymbol({ name: functionName, type: 'function', scope: this.currentScope, line: lineNumber, column: 1 }); this.pushScope(functionName, 'function', lineNumber); } } private analyzeIncludeStatement(line: string, lineNumber: number): void { const includeMatch = line.toLowerCase().match(/^include\s+([a-zA-Z0-9_/<>]+)/); if (includeMatch) { const includeName = includeMatch[1].toUpperCase(); this.dependencies.push(includeName); this.addSymbol({ name: includeName, type: 'include', scope: this.currentScope, line: lineNumber, column: 1 }); } } private analyzeInterfaceDefinition(line: string, lineNumber: number): void { const interfaceMatch = line.toLowerCase().match(/^interface\s+([a-zA-Z0-9_]+)/); if (interfaceMatch) { const interfaceName = interfaceMatch[1].toUpperCase(); this.addSymbol({ name: interfaceName, type: 'interface', scope: this.currentScope, line: lineNumber, column: 1, visibility: this.extractVisibility(line) }); this.pushScope(interfaceName, 'class', lineNumber); // Interface acts like class scope } } private analyzeScopeEnders(line: string, lineNumber: number): void { const lowerLine = line.toLowerCase(); if (lowerLine.match(/^(endclass|endmethod|endform|endfunction|endinterface)\.?$/)) { this.popScope(lineNumber); } } private addSymbol(symbol: AbapSymbolInfo): void { this.symbols.push(symbol); } private pushScope(name: string, type: AbapScopeInfo['type'], startLine: number): void { const scope: AbapScopeInfo = { name, type, startLine, endLine: startLine, // Will be updated when scope is popped parent: this.currentScope !== 'global' ? this.currentScope : undefined }; this.scopes.push(scope); this.currentScope = name; } private popScope(endLine: number): void { const currentScopeInfo = this.scopes.find(s => s.name === this.currentScope); if (currentScopeInfo) { currentScopeInfo.endLine = endLine; } // Find parent scope const parentScope = this.scopes.find(s => s.name === currentScopeInfo?.parent); this.currentScope = parentScope?.name || 'global'; } private extractVisibility(line: string): 'public' | 'protected' | 'private' { const lowerLine = line.toLowerCase(); if (lowerLine.includes('private')) return 'private'; if (lowerLine.includes('protected')) return 'protected'; return 'public'; } private extractDataType(line: string): string | undefined { const typeMatches = [ line.toLowerCase().match(/type\s+([a-zA-Z0-9_]+)/), line.toLowerCase().match(/like\s+([a-zA-Z0-9_]+)/), line.toLowerCase().match(/type\s+ref\s+to\s+([a-zA-Z0-9_]+)/) ]; for (const match of typeMatches) { if (match) { return match[1].toUpperCase(); } } return undefined; } private extractMethodParameters(line: string): AbapParameterInfo[] { const parameters: AbapParameterInfo[] = []; // This is a simplified parameter extraction // In a real implementation, this would be more sophisticated const paramTypes = ['importing', 'exporting', 'changing', 'returning']; for (const paramType of paramTypes) { const regex = new RegExp(`${paramType}\\s+([a-zA-Z0-9_\\s,]+)`, 'gi'); const match = regex.exec(line); if (match) { const paramNames = match[1].split(',').map(p => p.trim()); for (const paramName of paramNames) { if (paramName) { parameters.push({ name: paramName.toUpperCase(), type: paramType as any, optional: line.toLowerCase().includes('optional') }); } } } } return parameters; } } export async function handleGetAbapSemanticAnalysis(args: any) { try { if (!args?.code) { throw new McpError(ErrorCode.InvalidParams, 'ABAP code is required'); } const analyzer = new SimpleAbapSemanticAnalyzer(); const analysis = analyzer.analyze(args.code); const result = { isError: false, content: [ { type: "text", text: JSON.stringify(analysis, null, 2) } ] }; if (args.filePath) { writeResultToFile(JSON.stringify(analysis, null, 2), args.filePath); } return result; } catch (error) { return { isError: true, content: [ { type: "text", text: error instanceof Error ? error.message : String(error) } ] }; } }

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/fr0ster/mcp-abap-adt'

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