typeref-mcp
Version:
TypeScript type inference and symbol navigation MCP server for Claude Code
973 lines • 38.8 kB
JavaScript
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