UNPKG

typeref-mcp

Version:

TypeScript type inference and symbol navigation MCP server for Claude Code

973 lines 38.8 kB
import { Project, Node, SyntaxKind } from 'ts-morph'; import * as ts from 'typescript'; import * as path from 'path'; import * as fs from 'fs/promises'; import { BaseLanguageAdapter } from '../core/BaseLanguageAdapter.js'; import { ParquetCache } from '../core/ParquetCache.js'; import { ProjectTemplate } from '../utils/ProjectTemplate.js'; import { DiagnosticSeverity, } from '../interfaces.js'; import { SymbolKind, TypeKind, } from '../types.js'; export class TypeScriptAdapter extends BaseLanguageAdapter { projects = new Map(); projectMemoryUsage = new Map(); MEMORY_CLEANUP_INTERVAL = 30 * 60 * 1000; MAX_MEMORY_PER_PROJECT = 100 * 1024 * 1024; MAX_IDLE_TIME = 20 * 60 * 1000; projectLastAccess = new Map(); parquetCache; static CONFIG = { name: 'typescript', fileExtensions: ['.ts', '.tsx'], configFiles: ['tsconfig.json'], excludePatterns: [ 'node_modules/**', 'dist/**', 'build/**', ], }; constructor(cache, watcher, logger) { super(TypeScriptAdapter.CONFIG, cache, watcher, logger); this.parquetCache = new ParquetCache(logger); this.startMemoryManagement(); } startMemoryManagement() { setInterval(() => { this.performMemoryCleanup(); }, this.MEMORY_CLEANUP_INTERVAL); setInterval(() => { this.monitorMemoryUsage(); }, 5 * 60 * 1000); } performMemoryCleanup() { const now = Date.now(); this.logger.info('Performing memory cleanup...'); let cleanedProjects = 0; let freedMemory = 0; for (const [projectPath, project] of this.projects) { const lastAccess = this.projectLastAccess.get(projectPath) || 0; const memoryUsage = this.projectMemoryUsage.get(projectPath) || 0; if ((now - lastAccess > this.MAX_IDLE_TIME) || (memoryUsage > this.MAX_MEMORY_PER_PROJECT)) { this.cleanupProject(projectPath, project); freedMemory += memoryUsage; cleanedProjects++; } } this.cache.clear?.(); this.logger.info(`Cleanup completed: ${cleanedProjects} projects cleaned, ~${Math.round(freedMemory / 1024 / 1024)}MB freed`); } cleanupProject(projectPath, _project) { try { const cachePattern = this.getCacheKey(projectPath, ''); this.clearProjectCache(cachePattern); this.projects.delete(projectPath); this.projectMemoryUsage.delete(projectPath); this.projectLastAccess.delete(projectPath); this.logger.debug(`Cleaned up project: ${projectPath}`); } catch (error) { this.logger.error(`Failed to cleanup project ${projectPath}:`, error); } } clearProjectCache(pattern) { if (this.cache && typeof this.cache.deleteByPattern === 'function') { const deletedCount = this.cache.deleteByPattern(pattern + '*'); this.logger.debug(`Cleared ${deletedCount} cache entries for pattern: ${pattern}`); } else { this.cache.clear?.(); this.logger.debug('Cleared all cache entries (pattern deletion not supported)'); } } monitorMemoryUsage() { const memUsage = process.memoryUsage(); const totalMB = Math.round(memUsage.heapUsed / 1024 / 1024); this.logger.debug(`Memory usage: ${totalMB}MB heap, ${this.projects.size} projects loaded`); if (this.projects.size > 0) { const avgPerProject = memUsage.heapUsed / this.projects.size; for (const projectPath of this.projects.keys()) { this.projectMemoryUsage.set(projectPath, avgPerProject); } } if (memUsage.heapUsed > 500 * 1024 * 1024) { this.logger.warn(`High memory usage detected (${totalMB}MB), forcing cleanup`); this.performMemoryCleanup(); } } trackProjectAccess(projectPath) { this.projectLastAccess.set(projectPath, Date.now()); } cleanup() { for (const [projectPath, project] of this.projects) { this.cleanupProject(projectPath, project); } this.logger.info('TypeScript adapter cleanup completed'); } async clearProjectDiskCache(projectPath) { await this.parquetCache.clearProjectCache(projectPath); } async createProjectConfig(projectPath) { const projectYmlPath = path.join(projectPath, 'project.yml'); try { await fs.access(projectYmlPath); this.logger.debug(`project.yml already exists at ${projectYmlPath}`); return; } catch { } try { const projectName = path.basename(projectPath); let version = '1.0.0'; try { const packageJsonPath = path.join(projectPath, 'package.json'); const packageJsonContent = await fs.readFile(packageJsonPath, 'utf8'); const packageJson = JSON.parse(packageJsonContent); version = packageJson.version || '1.0.0'; } catch { } await ProjectTemplate.createProjectConfig(projectYmlPath, { PROJECT_NAME: projectName, VERSION: version, CREATED_DATE: new Date().toISOString(), }); this.logger.info(`Created project.yml at ${projectYmlPath}`); } catch (error) { this.logger.warn(`Failed to create project.yml at ${projectYmlPath}:`, error); } } async ensureProjectLoaded(projectPath) { if (this.projects.has(projectPath) && this.getProjectIndex(projectPath)) { return; } const isCacheValid = await this.parquetCache.isCacheValid(projectPath); if (isCacheValid) { const cachedIndex = await this.parquetCache.loadProjectIndex(projectPath); if (cachedIndex) { this.logger.info(`Auto-loaded cached index from Parquet files: ${projectPath} (${cachedIndex.symbols.size} symbols, ${cachedIndex.types.size} types)`); this.setProjectIndex(projectPath, cachedIndex); const cacheKey = this.getCacheKey(projectPath, 'index'); this.cache.set(cacheKey, cachedIndex, 10 * 60 * 1000); const project = await this.createProject(projectPath); this.projects.set(projectPath, project); return; } } this.logger.info(`No valid cache found for ${projectPath}. Project needs to be indexed.`); } async indexProject(projectPath, force = false) { this.validateProjectPath(projectPath); this.trackProjectAccess(projectPath); if (!force) { const isCacheValid = await this.parquetCache.isCacheValid(projectPath); if (isCacheValid) { const cachedIndex = await this.parquetCache.loadProjectIndex(projectPath); if (cachedIndex) { this.logger.info(`Loaded cached index from Parquet files: ${projectPath} (${cachedIndex.symbols.size} symbols, ${cachedIndex.types.size} types)`); this.setProjectIndex(projectPath, cachedIndex); const cacheKey = this.getCacheKey(projectPath, 'index'); this.cache.set(cacheKey, cachedIndex, 10 * 60 * 1000); return cachedIndex; } } } this.logger.info(`Indexing TypeScript project: ${projectPath}`); try { const project = await this.createProject(projectPath); this.projects.set(projectPath, project); const index = await this.buildProjectIndex(project, projectPath); await this.createProjectConfig(projectPath); await this.parquetCache.saveProjectIndex(index); const cacheKey = this.getCacheKey(projectPath, 'index'); this.cache.set(cacheKey, index, 10 * 60 * 1000); this.setProjectIndex(projectPath, index); this.logger.info(`Indexed and cached ${index.symbols.size} symbols and ${index.types.size} types`); return index; } catch (error) { this.logger.error(`Failed to index project ${projectPath}:`, error); throw error; } } async getTypeInference(filePath, position, projectPath) { this.validateProjectPath(projectPath); await this.ensureProjectLoaded(projectPath); const cacheKey = this.getCacheKey(projectPath, 'type-inference', filePath, position.toString()); const cached = this.cache.get(cacheKey); if (cached) { return cached; } try { const project = this.projects.get(projectPath); if (!project) { throw new Error(`Project not indexed: ${projectPath}. Please run index_project first.`); } const sourceFile = project.getSourceFile(filePath); if (!sourceFile) { return null; } const node = sourceFile.getDescendantAtPos(position); if (!node) { return null; } const type = node.getType(); const symbol = node.getSymbol(); const result = { type: type.getText(), kind: this.mapTypeToKind(type), documentation: symbol?.getJsDocTags().map(tag => tag.getText()).join('\n'), location: this.getSourceLocation(node), }; if (type.isObject()) { result.properties = this.extractProperties(type); } if (type.getCallSignatures().length > 0) { result.callSignatures = type.getCallSignatures().map(sig => sig.getDeclaration()?.getText() || ''); } this.cache.set(cacheKey, result); return result; } catch (error) { this.logger.error(`Type inference failed for ${filePath}:${position}:`, error); return null; } } async getTypeDefinition(typeName, projectPath, contextFile) { this.validateProjectPath(projectPath); await this.ensureProjectLoaded(projectPath); const cacheKey = this.getCacheKey(projectPath, 'type-definition', typeName, contextFile || ''); const cached = this.cache.get(cacheKey); if (cached) { return cached; } try { const project = this.projects.get(projectPath); if (!project) { throw new Error(`Project not indexed: ${projectPath}`); } const sourceFiles = contextFile ? [project.getSourceFile(contextFile)].filter((f) => f !== undefined) : project.getSourceFiles(); for (const sourceFile of sourceFiles) { const interfaces = sourceFile.getInterfaces().filter(iface => iface.getName() === typeName); for (const iface of interfaces) { const typeInfo = this.extractInterfaceInfo(iface); this.cache.set(cacheKey, typeInfo); return typeInfo; } const typeAliases = sourceFile.getTypeAliases().filter(alias => alias.getName() === typeName); for (const alias of typeAliases) { const typeInfo = this.extractTypeAliasInfo(alias); this.cache.set(cacheKey, typeInfo); return typeInfo; } const classes = sourceFile.getClasses().filter(cls => cls.getName() === typeName); for (const cls of classes) { const typeInfo = this.extractClassInfo(cls); this.cache.set(cacheKey, typeInfo); return typeInfo; } const enums = sourceFile.getEnums().filter(e => e.getName() === typeName); for (const enumDecl of enums) { const typeInfo = this.extractEnumInfo(enumDecl); this.cache.set(cacheKey, typeInfo); return typeInfo; } } return null; } catch (error) { this.logger.error(`Type definition lookup failed for ${typeName}:`, error); return null; } } async findSymbol(symbolName, projectPath, options = { query: '' }) { this.validateProjectPath(projectPath); await this.ensureProjectLoaded(projectPath); const index = this.getProjectIndex(projectPath); if (!index) { throw new Error(`Project not indexed: ${projectPath}`); } const symbols = index.symbols.get(symbolName) || []; let results = [...symbols]; if (options.kind) { results = results.filter(s => s.kind === options.kind); } if (!options.includePrivate) { results = results.filter(s => s.isExported); } if (options.fuzzyMatch && symbolName.length > 2) { const fuzzyResults = this.performFuzzySearch(symbolName, index); results = [...results, ...fuzzyResults]; } if (options.maxResults && results.length > options.maxResults) { results = results.slice(0, options.maxResults); } return results; } async findReferences(symbolName, filePath, projectPath) { this.validateProjectPath(projectPath); await this.ensureProjectLoaded(projectPath); const cacheKey = this.getCacheKey(projectPath, 'references', symbolName, filePath); const cached = this.cache.get(cacheKey); if (cached) { return cached; } try { const project = this.projects.get(projectPath); if (!project) { throw new Error(`Project not indexed: ${projectPath}`); } const sourceFile = project.getSourceFile(filePath); if (!sourceFile) { return []; } const references = []; const symbol = this.findSymbolInFile(sourceFile, symbolName); if (!symbol) { return []; } const sourceFiles = project.getSourceFiles(); for (const file of sourceFiles) { const fileReferences = this.findSymbolReferencesInFile(file, symbolName, symbol); references.push(...fileReferences); } this.cache.set(cacheKey, references); return references; } catch (error) { this.logger.error(`Reference search failed for ${symbolName}:`, error); return []; } } async getAvailableSymbols(filePath, position, projectPath) { this.validateProjectPath(projectPath); await this.ensureProjectLoaded(projectPath); const cacheKey = this.getCacheKey(projectPath, 'available-symbols', filePath, position.toString()); const cached = this.cache.get(cacheKey); if (cached) { return cached; } try { const project = this.projects.get(projectPath); if (!project) { throw new Error(`Project not indexed: ${projectPath}`); } const sourceFile = project.getSourceFile(filePath); if (!sourceFile) { return []; } const symbols = []; const localSymbols = this.getLocalSymbolsInScope(sourceFile, position); symbols.push(...localSymbols); const importedSymbols = this.getImportedSymbols(sourceFile); symbols.push(...importedSymbols); const globalSymbols = this.getGlobalSymbols(sourceFile); symbols.push(...globalSymbols); this.cache.set(cacheKey, symbols); return symbols; } catch (error) { this.logger.error(`Available symbols lookup failed for ${filePath}:`, error); return []; } } async getModuleInfo(modulePath, projectPath) { this.validateProjectPath(projectPath); await this.ensureProjectLoaded(projectPath); const cacheKey = this.getCacheKey(projectPath, 'module-info', modulePath); const cached = this.cache.get(cacheKey); if (cached) { return cached; } try { const project = this.projects.get(projectPath); if (!project) { throw new Error(`Project not indexed: ${projectPath}`); } const sourceFile = project.getSourceFile(modulePath); if (!sourceFile) { return null; } const moduleInfo = { path: modulePath, exports: this.extractExports(sourceFile), imports: this.extractImports(sourceFile), dependencies: this.extractDependencies(sourceFile), }; this.cache.set(cacheKey, moduleInfo); return moduleInfo; } catch (error) { this.logger.error(`Module info lookup failed for ${modulePath}:`, error); return null; } } async searchTypes(options, projectPath) { this.validateProjectPath(projectPath); await this.ensureProjectLoaded(projectPath); const index = this.getProjectIndex(projectPath); if (!index) { throw new Error(`Project not indexed: ${projectPath}`); } const results = []; const query = (options.query || '').toLowerCase(); for (const [typeName, typeInfo] of index.types) { if (typeName.toLowerCase().includes(query)) { results.push(typeInfo); continue; } if (options.kind && typeInfo.kind === options.kind) { results.push(typeInfo); continue; } } if (options.maxResults && results.length > options.maxResults) { return results.slice(0, options.maxResults); } return results; } async getDiagnostics(filePath, projectPath) { this.validateProjectPath(projectPath); await this.ensureProjectLoaded(projectPath); try { const project = this.projects.get(projectPath); if (!project) { throw new Error(`Project not indexed: ${projectPath}`); } const sourceFile = project.getSourceFile(filePath); if (!sourceFile) { return []; } const diagnostics = sourceFile.getPreEmitDiagnostics(); return diagnostics.map(diagnostic => ({ filePath, line: diagnostic.getLineNumber() || 1, column: diagnostic.getStart() || 0, message: diagnostic.getMessageText().toString(), severity: this.mapDiagnosticSeverity(diagnostic.getCategory()), code: diagnostic.getCode(), source: 'typescript', })); } catch (error) { this.logger.error(`Diagnostics lookup failed for ${filePath}:`, error); return []; } } async createProject(projectPath) { const tsconfigPath = path.join(projectPath, 'tsconfig.json'); try { await fs.access(tsconfigPath); return new Project({ tsConfigFilePath: tsconfigPath, skipAddingFilesFromTsConfig: false, skipFileDependencyResolution: false, }); } catch { this.logger.warn(`No tsconfig.json found in ${projectPath}, using default configuration`); return new Project({ useInMemoryFileSystem: true, compilerOptions: { esModuleInterop: true, allowSyntheticDefaultImports: true, strict: true, skipLibCheck: true, }, }); } } async buildProjectIndex(project, projectPath) { const symbols = new Map(); const types = new Map(); const modules = new Map(); const dependencies = new Map(); const allSourceFiles = project.getSourceFiles(); this.logger.info(`Found ${allSourceFiles.length} total source files in project`); const sourceFiles = allSourceFiles.filter(file => this.shouldIncludeFile(file.getFilePath())); this.logger.info(`After filtering, ${sourceFiles.length} files will be indexed`); if (sourceFiles.length === 0 && allSourceFiles.length > 0) { this.logger.warn(`All ${allSourceFiles.length} source files were filtered out. Sample paths:`, allSourceFiles.slice(0, 3).map(f => f.getFilePath())); } for (const sourceFile of sourceFiles) { const fileSymbols = this.extractSymbolsFromFile(sourceFile); for (const symbol of fileSymbols) { const existing = symbols.get(symbol.name) || []; symbols.set(symbol.name, [...existing, symbol]); } const fileTypes = this.extractTypesFromFile(sourceFile); for (const type of fileTypes) { types.set(type.name, type); } const moduleInfo = { path: sourceFile.getFilePath(), exports: this.extractExports(sourceFile), imports: this.extractImports(sourceFile), dependencies: this.extractDependencies(sourceFile), }; modules.set(sourceFile.getFilePath(), moduleInfo); dependencies.set(sourceFile.getFilePath(), moduleInfo.dependencies); } return { projectPath, symbols, types, modules, dependencies, lastIndexed: new Date(), }; } extractSymbolsFromFile(sourceFile) { const symbols = []; sourceFile.forEachDescendant(node => { if (Node.hasName(node)) { const symbol = this.nodeToSymbolInfo(node, sourceFile); if (symbol) { symbols.push(symbol); } } }); return symbols; } extractTypesFromFile(sourceFile) { const types = []; sourceFile.getInterfaces().forEach(iface => { types.push(this.extractInterfaceInfo(iface)); }); sourceFile.getTypeAliases().forEach(alias => { types.push(this.extractTypeAliasInfo(alias)); }); sourceFile.getClasses().forEach(cls => { types.push(this.extractClassInfo(cls)); }); sourceFile.getEnums().forEach(enumDecl => { types.push(this.extractEnumInfo(enumDecl)); }); return types; } nodeToSymbolInfo(node, sourceFile) { if (!Node.hasName(node)) return null; const name = node.getName?.() || ''; if (!name) return null; return { name, kind: this.mapNodeToSymbolKind(node), type: this.getNodeType(node), location: this.getSourceLocation(node), documentation: this.getNodeDocumentation(node), isExported: Node.isExportable(node) && node.getModifiers().some(mod => mod.getKind() === SyntaxKind.ExportKeyword), module: sourceFile.getFilePath(), signature: this.getNodeSignature(node), }; } extractInterfaceInfo(node) { return { name: node.getName(), kind: TypeKind.Interface, properties: node.getProperties().map((prop) => this.extractPropertyInfo(prop)), methods: node.getMethods().map((method) => this.extractMethodInfo(method)), extends: node.getExtends().map((ext) => ext.getText()), location: this.getSourceLocation(node), documentation: this.getNodeDocumentation(node), }; } extractTypeAliasInfo(node) { return { name: node.getName(), kind: TypeKind.Generic, properties: [], location: this.getSourceLocation(node), documentation: this.getNodeDocumentation(node), }; } extractClassInfo(node) { return { name: node.getName() || 'Anonymous', kind: TypeKind.Object, properties: node.getProperties().map((prop) => this.extractPropertyInfo(prop)), methods: node.getMethods().map((method) => this.extractMethodInfo(method)), extends: node.getExtends() ? [node.getExtends().getText()] : [], implements: node.getImplements().map((impl) => impl.getText()), location: this.getSourceLocation(node), documentation: this.getNodeDocumentation(node), }; } extractEnumInfo(node) { return { name: node.getName(), kind: TypeKind.Primitive, properties: node.getMembers().map((member) => ({ name: member.getName(), type: 'number | string', optional: false, readonly: true, documentation: this.getNodeDocumentation(member), })), location: this.getSourceLocation(node), documentation: this.getNodeDocumentation(node), }; } extractPropertyInfo(prop) { return { name: prop.getName(), type: this.getNodeType(prop), optional: prop.hasQuestionToken?.() || false, readonly: prop.isReadonly?.() || false, documentation: this.getNodeDocumentation(prop), }; } extractMethodInfo(method) { return { name: method.getName(), signature: method.getText(), returnType: this.getNodeType(method), parameters: method.getParameters().map((param) => this.extractParameterInfo(param)), documentation: this.getNodeDocumentation(method), }; } extractParameterInfo(param) { return { name: param.getName(), type: this.getNodeType(param), optional: param.hasQuestionToken?.() || false, defaultValue: param.getDefaultValue?.()?.getText(), }; } extractExports(sourceFile) { const exports = []; sourceFile.getInterfaces().forEach(iface => { if (iface.isExported()) { exports.push({ name: iface.getName(), type: 'interface', kind: SymbolKind.Interface, isDefault: false, documentation: this.getNodeDocumentation(iface), }); } }); sourceFile.getClasses().forEach(cls => { if (cls.isExported()) { exports.push({ name: cls.getName() || 'anonymous', type: 'class', kind: SymbolKind.Class, isDefault: false, documentation: this.getNodeDocumentation(cls), }); } }); sourceFile.getEnums().forEach(enumDecl => { if (enumDecl.isExported()) { exports.push({ name: enumDecl.getName(), type: 'enum', kind: SymbolKind.Enum, isDefault: false, documentation: this.getNodeDocumentation(enumDecl), }); } }); sourceFile.getTypeAliases().forEach(typeAlias => { if (typeAlias.isExported()) { exports.push({ name: typeAlias.getName(), type: 'type', kind: SymbolKind.Type, isDefault: false, documentation: this.getNodeDocumentation(typeAlias), }); } }); sourceFile.getVariableDeclarations().forEach(varDecl => { if (varDecl.isExported()) { exports.push({ name: varDecl.getName(), type: 'variable', kind: SymbolKind.Variable, isDefault: false, documentation: this.getNodeDocumentation(varDecl), }); } }); sourceFile.getFunctions().forEach(func => { if (func.isExported()) { exports.push({ name: func.getName() || 'anonymous', type: 'function', kind: SymbolKind.Function, isDefault: false, documentation: this.getNodeDocumentation(func), }); } }); sourceFile.getExportDeclarations().forEach(exportDecl => { exportDecl.getNamedExports().forEach(namedExport => { exports.push({ name: namedExport.getName(), type: 'unknown', kind: SymbolKind.Variable, isDefault: false, documentation: this.getNodeDocumentation(namedExport), }); }); }); const defaultExport = sourceFile.getDefaultExportSymbol(); if (defaultExport) { exports.push({ name: 'default', type: 'unknown', kind: SymbolKind.Variable, isDefault: true, }); } return exports; } extractImports(sourceFile) { const imports = []; sourceFile.getImportDeclarations().forEach(importDecl => { const moduleSpecifier = importDecl.getModuleSpecifierValue(); importDecl.getNamedImports().forEach(namedImport => { imports.push({ name: namedImport.getName(), localName: namedImport.getAliasNode()?.getText() || namedImport.getName(), source: moduleSpecifier, isDefault: false, isNamespace: false, }); }); const defaultImport = importDecl.getDefaultImport(); if (defaultImport) { imports.push({ name: 'default', localName: defaultImport.getText(), source: moduleSpecifier, isDefault: true, isNamespace: false, }); } const namespaceImport = importDecl.getNamespaceImport(); if (namespaceImport) { imports.push({ name: '*', localName: namespaceImport.getText(), source: moduleSpecifier, isDefault: false, isNamespace: true, }); } }); return imports; } extractDependencies(sourceFile) { const dependencies = []; sourceFile.getImportDeclarations().forEach(importDecl => { dependencies.push(importDecl.getModuleSpecifierValue()); }); return dependencies; } mapTypeToKind(type) { if (type.isString() || type.isNumber() || type.isBoolean()) { return TypeKind.Primitive; } if (type.isArray()) { return TypeKind.Array; } if (type.isUnion()) { return TypeKind.Union; } if (type.isIntersection()) { return TypeKind.Intersection; } if (type.getCallSignatures().length > 0) { return TypeKind.Function; } return TypeKind.Object; } mapNodeToSymbolKind(node) { if (Node.isVariableDeclaration(node)) return SymbolKind.Variable; if (Node.isFunctionDeclaration(node)) return SymbolKind.Function; if (Node.isClassDeclaration(node)) return SymbolKind.Class; if (Node.isInterfaceDeclaration(node)) return SymbolKind.Interface; if (Node.isTypeAliasDeclaration(node)) return SymbolKind.Type; if (Node.isEnumDeclaration(node)) return SymbolKind.Enum; if (Node.isMethodDeclaration(node)) return SymbolKind.Method; if (Node.isPropertyDeclaration(node)) return SymbolKind.Property; return SymbolKind.Variable; } mapDiagnosticSeverity(category) { switch (category) { case ts.DiagnosticCategory.Error: return DiagnosticSeverity.Error; case ts.DiagnosticCategory.Warning: return DiagnosticSeverity.Warning; case ts.DiagnosticCategory.Message: return DiagnosticSeverity.Information; case ts.DiagnosticCategory.Suggestion: return DiagnosticSeverity.Hint; default: return DiagnosticSeverity.Information; } } getSourceLocation(node) { const sourceFile = node.getSourceFile(); const start = node.getStart(); const end = node.getEnd(); const startLineAndColumn = sourceFile.getLineAndColumnAtPos(start); const endLineAndColumn = sourceFile.getLineAndColumnAtPos(end); return { filePath: sourceFile.getFilePath(), line: startLineAndColumn.line, column: startLineAndColumn.column, endLine: endLineAndColumn.line, endColumn: endLineAndColumn.column, }; } getNodeType(node) { try { return node.getType().getText(); } catch { return 'unknown'; } } getNodeDocumentation(node) { try { const jsDoc = node.getJsDocs?.(); return jsDoc?.map((doc) => doc.getDescription()).join('\n') || undefined; } catch { return undefined; } } getNodeSignature(node) { try { if (Node.isFunctionDeclaration(node) || Node.isMethodDeclaration(node)) { return node.getText(); } return undefined; } catch { return undefined; } } extractProperties(type) { const properties = []; try { const symbol = type.getSymbol(); if (symbol) { const declarations = symbol.getDeclarations(); for (const decl of declarations) { if (Node.isInterfaceDeclaration(decl) || Node.isClassDeclaration(decl)) { const props = decl.getProperties(); for (const prop of props) { properties.push(this.extractPropertyInfo(prop)); } } } } } catch (error) { this.logger.debug(`Failed to extract properties:`, error); } return properties; } performFuzzySearch(query, index) { const results = []; const queryLower = query.toLowerCase(); for (const [name, symbolList] of index.symbols) { if (name.toLowerCase().includes(queryLower)) { results.push(...symbolList); } } return results; } findSymbolInFile(_sourceFile, _symbolName) { return null; } findSymbolReferencesInFile(_sourceFile, _symbolName, _symbol) { return []; } getLocalSymbolsInScope(sourceFile, position) { const symbols = []; const node = sourceFile.getDescendantAtPos(position); if (!node) return symbols; sourceFile.getVariableDeclarations().forEach(varDecl => { symbols.push({ name: varDecl.getName(), kind: SymbolKind.Variable, type: varDecl.getType().getText(), location: this.getSourceLocation(varDecl), isExported: varDecl.isExported(), documentation: this.getNodeDocumentation(varDecl), }); }); const functionNode = node.getFirstAncestorByKind(SyntaxKind.FunctionDeclaration) || node.getFirstAncestorByKind(SyntaxKind.MethodDeclaration) || node.getFirstAncestorByKind(SyntaxKind.ArrowFunction); if (functionNode && 'getParameters' in functionNode) { functionNode.getParameters().forEach((param) => { symbols.push({ name: param.getName(), kind: SymbolKind.Parameter, type: param.getType().getText(), location: this.getSourceLocation(param), isExported: false, documentation: this.getNodeDocumentation(param), }); }); } return symbols; } getImportedSymbols(sourceFile) { const symbols = []; sourceFile.getImportDeclarations().forEach(importDecl => { const namedImports = importDecl.getNamedImports(); namedImports.forEach(namedImport => { symbols.push({ name: namedImport.getName(), kind: SymbolKind.Variable, type: 'imported', location: this.getSourceLocation(namedImport), isExported: false, documentation: this.getNodeDocumentation(namedImport), }); }); }); return symbols; } getGlobalSymbols(_sourceFile) { return [ { name: 'console', kind: SymbolKind.Variable, type: 'Console', location: { filePath: 'global', line: 0, column: 0 }, isExported: false, documentation: 'Global console object', }, { name: 'Promise', kind: SymbolKind.Class, type: 'PromiseConstructor', location: { filePath: 'global', line: 0, column: 0 }, isExported: false, documentation: 'Global Promise constructor', } ]; } } //# sourceMappingURL=TypeScriptAdapter.js.map