Skip to main content
Glama

Codebase MCP Server

by cmaujean
js-ts-parser.ts10.1 kB
import { parse } from '@babel/parser'; import traverse, { NodePath } from '@babel/traverse'; import * as t from '@babel/types'; import { ASTParser, ParseResult, ASTNode, Symbol, Dependency, SymbolReference, ExportInfo, ImportInfo } from './ast-types.js'; export class JavaScriptTypeScriptParser implements ASTParser { canParse(filePath: string, extension: string): boolean { return ['js', 'jsx', 'ts', 'tsx', 'mjs', 'cjs'].includes(extension); } async parse(content: string, filePath: string): Promise<ParseResult> { try { const isTypeScript = filePath.endsWith('.ts') || filePath.endsWith('.tsx'); const isJSX = filePath.endsWith('.jsx') || filePath.endsWith('.tsx'); const ast = parse(content, { sourceType: 'module', allowImportExportEverywhere: true, allowReturnOutsideFunction: true, plugins: [ 'asyncGenerators', 'bigInt', 'classProperties', 'doExpressions', 'dynamicImport', 'exportDefaultFrom', 'exportNamespaceFrom', 'functionBind', 'functionSent', 'importMeta', 'nullishCoalescingOperator', 'numericSeparator', 'objectRestSpread', 'optionalCatchBinding', 'optionalChaining', 'throwExpressions', 'topLevelAwait', 'trailingFunctionCommas', ...(isJSX ? ['jsx'] : []), ...(isTypeScript ? ['typescript', ['decorators', { decoratorsBeforeExport: false }]] : ['decorators-legacy']) ] }); const symbols: Symbol[] = []; const dependencies: Dependency[] = []; let nodeIdCounter = 0; const createASTNode = (node: t.Node, name?: string): ASTNode => { const id = `${filePath}:${nodeIdCounter++}`; return { id, type: node.type, name, location: { filePath, start: { line: node.loc?.start.line || 0, column: node.loc?.start.column || 0 }, end: { line: node.loc?.end.line || 0, column: node.loc?.end.column || 0 } }, children: [], metadata: {} }; }; const createSymbol = ( name: string, type: Symbol['type'], node: t.Node, metadata: Record<string, any> = {} ): Symbol => { const id = `${filePath}:${name}:${type}:${node.loc?.start.line}`; return { id, name, type, filePath, location: { start: { line: node.loc?.start.line || 0, column: node.loc?.start.column || 0 }, end: { line: node.loc?.end.line || 0, column: node.loc?.end.column || 0 } }, references: [], metadata }; }; const rootNode = createASTNode(ast, 'Program'); traverse(ast, { // Function declarations FunctionDeclaration(path: NodePath<t.FunctionDeclaration>) { if (path.node.id?.name) { const symbol = createSymbol(path.node.id.name, 'function', path.node, { async: path.node.async, generator: path.node.generator, params: path.node.params.length }); symbols.push(symbol); } }, // Arrow functions and function expressions VariableDeclarator(path: NodePath<t.VariableDeclarator>) { if (t.isIdentifier(path.node.id) && (t.isArrowFunctionExpression(path.node.init) || t.isFunctionExpression(path.node.init))) { const symbol = createSymbol(path.node.id.name, 'function', path.node, { type: 'arrow', async: path.node.init.async, generator: t.isFunctionExpression(path.node.init) ? path.node.init.generator : false }); symbols.push(symbol); } else if (t.isIdentifier(path.node.id)) { // Regular variables const symbol = createSymbol(path.node.id.name, 'variable', path.node); symbols.push(symbol); } }, // Class declarations ClassDeclaration(path: NodePath<t.ClassDeclaration>) { if (path.node.id?.name) { const methods = path.node.body.body .filter(member => t.isClassMethod(member)) .map(member => { if (t.isClassMethod(member) && t.isIdentifier(member.key)) { return member.key.name; } return 'unknown'; }); const symbol = createSymbol(path.node.id.name, 'class', path.node, { superClass: path.node.superClass ? (t.isIdentifier(path.node.superClass) ? path.node.superClass.name : 'unknown') : null, methods }); symbols.push(symbol); } }, // TypeScript interfaces TSInterfaceDeclaration(path: NodePath<t.TSInterfaceDeclaration>) { const symbol = createSymbol(path.node.id.name, 'interface', path.node, { extends: path.node.extends?.map(ext => t.isIdentifier(ext.expression) ? ext.expression.name : 'unknown' ) || [] }); symbols.push(symbol); }, // TypeScript type aliases TSTypeAliasDeclaration(path: NodePath<t.TSTypeAliasDeclaration>) { const symbol = createSymbol(path.node.id.name, 'type', path.node); symbols.push(symbol); }, // Import declarations ImportDeclaration(path: NodePath<t.ImportDeclaration>) { const source = path.node.source.value; const specifiers: string[] = []; path.node.specifiers.forEach(spec => { let importName: string; let importedAs: string; let isDefault = false; if (t.isImportDefaultSpecifier(spec)) { importName = 'default'; importedAs = spec.local.name; isDefault = true; } else if (t.isImportSpecifier(spec)) { importName = t.isIdentifier(spec.imported) ? spec.imported.name : 'unknown'; importedAs = spec.local.name; } else if (t.isImportNamespaceSpecifier(spec)) { importName = '*'; importedAs = spec.local.name; } else { return; } specifiers.push(importedAs); const symbol = createSymbol(importedAs, 'import', path.node); symbol.imports = { source, isDefault, importName, importedAs }; symbols.push(symbol); }); dependencies.push({ from: filePath, to: source, type: 'import', specifiers }); }, // Export declarations ExportNamedDeclaration(path: NodePath<t.ExportNamedDeclaration>) { if (path.node.declaration) { // export const foo = ... // export function foo() {} // export class Foo {} if (t.isFunctionDeclaration(path.node.declaration) && path.node.declaration.id) { const symbol = createSymbol(path.node.declaration.id.name, 'export', path.node); symbol.exports = { isDefault: false, exportName: path.node.declaration.id.name }; symbols.push(symbol); } else if (t.isClassDeclaration(path.node.declaration) && path.node.declaration.id) { const symbol = createSymbol(path.node.declaration.id.name, 'export', path.node); symbol.exports = { isDefault: false, exportName: path.node.declaration.id.name }; symbols.push(symbol); } else if (t.isVariableDeclaration(path.node.declaration)) { path.node.declaration.declarations.forEach(decl => { if (t.isIdentifier(decl.id)) { const symbol = createSymbol(decl.id.name, 'export', path.node); symbol.exports = { isDefault: false, exportName: decl.id.name }; symbols.push(symbol); } }); } } else if (path.node.specifiers) { // export { foo, bar } path.node.specifiers.forEach(spec => { if (t.isExportSpecifier(spec)) { const exportName = t.isIdentifier(spec.exported) ? spec.exported.name : 'unknown'; const localName = t.isIdentifier(spec.local) ? spec.local.name : 'unknown'; const symbol = createSymbol(localName, 'export', path.node); symbol.exports = { isDefault: false, exportName, exportedAs: exportName !== localName ? exportName : undefined }; symbols.push(symbol); } }); } }, // Export default declarations ExportDefaultDeclaration(path: NodePath<t.ExportDefaultDeclaration>) { let name = 'default'; if (t.isFunctionDeclaration(path.node.declaration) && path.node.declaration.id) { name = path.node.declaration.id.name; } else if (t.isClassDeclaration(path.node.declaration) && path.node.declaration.id) { name = path.node.declaration.id.name; } else if (t.isIdentifier(path.node.declaration)) { name = path.node.declaration.name; } const symbol = createSymbol(name, 'export', path.node); symbol.exports = { isDefault: true, exportName: name }; symbols.push(symbol); } }); return { success: true, ast: rootNode, symbols, dependencies }; } catch (error) { return { success: false, error: `Failed to parse ${filePath}: ${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/cmaujean/codebase-mcp'

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