ts-project-indexer-mcp
Version:
A powerful MCP (Model Context Protocol) server that indexes and analyzes TypeScript/JavaScript projects, providing intelligent code exploration, dependency tracking, method discovery, and project structure analysis for AI assistants and development tools.
246 lines • 10.5 kB
JavaScript
import { promises as fs } from 'node:fs';
export class CodeParser {
pathResolver = null;
setPathResolver(pathResolver) {
this.pathResolver = pathResolver;
}
async parseFile(filePath) {
const content = await fs.readFile(filePath, 'utf-8');
const lines = content.split('\n');
const methods = [];
const paths = [];
const dependencies = [];
this.parseImports(content, filePath, dependencies);
this.parseMethods(content, filePath, methods);
this.parsePaths(content, filePath, paths);
return { methods, paths, dependencies };
}
parseImports(content, filePath, dependencies) {
const lines = content.split('\n');
lines.forEach((line, index) => {
// ES6 imports
const importMatch = line.match(/^\s*import\s+(?:(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s+from\s+)?['"`]([^'"`]+)['"`]/);
if (importMatch) {
const originalImportPath = importMatch[1];
let resolvedPath = originalImportPath;
// Use path resolver if available
if (this.pathResolver) {
const resolved = this.pathResolver.resolveImportPath(originalImportPath, filePath);
if (resolved) {
resolvedPath = resolved; // Keep absolute path, don't normalize
}
}
dependencies.push({
from: filePath,
to: originalImportPath,
resolvedTo: resolvedPath,
type: 'import',
file: filePath,
line: index + 1,
});
}
// CommonJS require
const requireMatch = line.match(/require\s*\(['"`]([^'"`]+)['"`]\)/);
if (requireMatch) {
const originalImportPath = requireMatch[1];
let resolvedPath = originalImportPath;
// Use path resolver if available
if (this.pathResolver) {
const resolved = this.pathResolver.resolveImportPath(originalImportPath, filePath);
if (resolved) {
resolvedPath = this.pathResolver.normalizeFilePath(resolved);
}
}
dependencies.push({
from: filePath,
to: originalImportPath,
resolvedTo: resolvedPath,
type: 'import',
file: filePath,
line: index + 1,
});
}
});
}
parseMethods(content, filePath, methods) {
const lines = content.split('\n');
lines.forEach((line, index) => {
const trimmedLine = line.trim();
// Function declarations
const functionMatch = trimmedLine.match(/^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)(?:\s*:\s*([^{]+))?/);
if (functionMatch) {
methods.push({
name: functionMatch[1],
type: 'function',
file: filePath,
line: index + 1,
column: line.indexOf('function'),
parameters: this.parseParameters(functionMatch[2]),
returnType: functionMatch[3]?.trim(),
isAsync: trimmedLine.includes('async'),
signature: trimmedLine,
});
}
// Arrow functions
const arrowMatch = trimmedLine.match(/(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s*)?\(([^)]*)\)\s*(?::\s*([^=]+?))?\s*=>/);
if (arrowMatch) {
methods.push({
name: arrowMatch[1],
type: 'function',
file: filePath,
line: index + 1,
column: line.indexOf(arrowMatch[1]),
parameters: this.parseParameters(arrowMatch[2]),
returnType: arrowMatch[3]?.trim(),
isAsync: trimmedLine.includes('async'),
signature: trimmedLine,
});
}
// Class declarations
const classMatch = trimmedLine.match(/^(?:export\s+)?(?:abstract\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?(?:\s+implements\s+([^{]+))?/);
if (classMatch) {
methods.push({
name: classMatch[1],
type: 'class',
file: filePath,
line: index + 1,
column: line.indexOf('class'),
signature: trimmedLine,
});
}
// Interface declarations
const interfaceMatch = trimmedLine.match(/^(?:export\s+)?interface\s+(\w+)(?:\s+extends\s+([^{]+))?/);
if (interfaceMatch) {
methods.push({
name: interfaceMatch[1],
type: 'interface',
file: filePath,
line: index + 1,
column: line.indexOf('interface'),
signature: trimmedLine,
});
}
// Type declarations
const typeMatch = trimmedLine.match(/^(?:export\s+)?type\s+(\w+)\s*=/);
if (typeMatch) {
methods.push({
name: typeMatch[1],
type: 'type',
file: filePath,
line: index + 1,
column: line.indexOf('type'),
signature: trimmedLine,
});
}
// Enum declarations
const enumMatch = trimmedLine.match(/^(?:export\s+)?enum\s+(\w+)/);
if (enumMatch) {
methods.push({
name: enumMatch[1],
type: 'enum',
file: filePath,
line: index + 1,
column: line.indexOf('enum'),
signature: trimmedLine,
});
}
// Method declarations (inside classes)
const methodMatch = trimmedLine.match(/^(?:(private|protected|public)\s+)?(?:(static)\s+)?(?:(async)\s+)?(\w+)\s*\(([^)]*)\)(?:\s*:\s*([^{]+))?/);
if (methodMatch && !trimmedLine.includes('function') && !trimmedLine.includes('=')) {
methods.push({
name: methodMatch[4],
type: 'method',
file: filePath,
line: index + 1,
column: line.indexOf(methodMatch[4]),
parameters: this.parseParameters(methodMatch[5]),
returnType: methodMatch[6]?.trim(),
visibility: methodMatch[1] || 'public',
isStatic: !!methodMatch[2],
isAsync: !!methodMatch[3],
signature: trimmedLine,
});
}
});
}
parsePaths(content, filePath, paths) {
const lines = content.split('\n');
lines.forEach((line, index) => {
// Hono.js route patterns
const honoMatch = line.match(/\.(?:get|post|put|delete|patch|head|options)\s*\(\s*['"`]([^'"`]+)['"`]/);
if (honoMatch) {
const method = line.match(/\.(\w+)\s*\(/)?.[1].toUpperCase();
if (['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'].includes(method)) {
paths.push({
path: honoMatch[1],
method,
file: filePath,
line: index + 1,
});
}
}
// Express.js route patterns
const expressMatch = line.match(/\.(?:get|post|put|delete|patch|head|options)\s*\(\s*['"`]([^'"`]+)['"`]/);
if (expressMatch) {
const method = line.match(/\.(\w+)\s*\(/)?.[1].toUpperCase();
if (['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'].includes(method)) {
paths.push({
path: expressMatch[1],
method,
file: filePath,
line: index + 1,
});
}
}
// FastAPI/decorator patterns
const decoratorMatch = line.match(/@(?:get|post|put|delete|patch|head|options)\s*\(\s*['"`]([^'"`]+)['"`]/);
if (decoratorMatch) {
const method = line.match(/@(\w+)\s*\(/)?.[1].toUpperCase();
if (['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'].includes(method)) {
paths.push({
path: decoratorMatch[1],
method,
file: filePath,
line: index + 1,
});
}
}
});
}
parseParameters(paramString) {
if (!paramString.trim())
return [];
return paramString.split(',').map(param => {
const trimmed = param.trim();
const match = trimmed.match(/(\w+)(\?)?\s*:\s*([^=]+)(?:\s*=\s*(.+))?/);
if (match) {
return {
name: match[1],
optional: !!match[2],
type: match[3]?.trim(),
defaultValue: match[4]?.trim(),
};
}
// Simple parameter without type
const simpleMatch = trimmed.match(/(\w+)(\?)?(?:\s*=\s*(.+))?/);
if (simpleMatch) {
return {
name: simpleMatch[1],
optional: !!simpleMatch[2],
defaultValue: simpleMatch[3]?.trim(),
};
}
return { name: trimmed };
});
}
isTypeScriptFile(filePath) {
return filePath.endsWith('.ts') || filePath.endsWith('.tsx');
}
isJavaScriptFile(filePath) {
return filePath.endsWith('.js') || filePath.endsWith('.jsx') || filePath.endsWith('.mjs');
}
shouldParseFile(filePath) {
return this.isTypeScriptFile(filePath) || this.isJavaScriptFile(filePath);
}
}
//# sourceMappingURL=parser.js.map