|
| 1 | +/// <reference path="./node.ts" preserve="true" /> |
| 2 | +import { SymbolFlags } from "#symbolFlags"; |
| 3 | +import { TypeFlags } from "#typeFlags"; |
| 4 | +import type { |
| 5 | + Node, |
| 6 | + SourceFile, |
| 7 | +} from "@typescript/ast"; |
| 8 | +import { Client } from "./client.ts"; |
| 9 | +import type { FileSystem } from "./fs.ts"; |
| 10 | +import { RemoteSourceFile } from "./node.ts"; |
| 11 | +import { ObjectRegistry } from "./objectRegistry.ts"; |
| 12 | +import type { |
| 13 | + ConfigResponse, |
| 14 | + ProjectResponse, |
| 15 | + SymbolResponse, |
| 16 | + TypeResponse, |
| 17 | +} from "./proto.ts"; |
| 18 | + |
| 19 | +export { SymbolFlags, TypeFlags }; |
| 20 | + |
| 21 | +export interface APIOptions { |
| 22 | + tsserverPath: string; |
| 23 | + cwd?: string; |
| 24 | + logFile?: string; |
| 25 | + fs?: FileSystem; |
| 26 | +} |
| 27 | + |
| 28 | +export class API { |
| 29 | + private client: Client; |
| 30 | + private objectRegistry: ObjectRegistry; |
| 31 | + constructor(options: APIOptions) { |
| 32 | + this.client = new Client(options); |
| 33 | + this.objectRegistry = new ObjectRegistry(this.client); |
| 34 | + } |
| 35 | + |
| 36 | + parseConfigFile(fileName: string): ConfigResponse { |
| 37 | + return this.client.request("parseConfigFile", { fileName }); |
| 38 | + } |
| 39 | + |
| 40 | + loadProject(configFileName: string): Project { |
| 41 | + const data = this.client.request("loadProject", { configFileName }); |
| 42 | + return this.objectRegistry.getProject(data); |
| 43 | + } |
| 44 | + |
| 45 | + echo(message: string): string { |
| 46 | + return this.client.echo(message); |
| 47 | + } |
| 48 | + |
| 49 | + echoBinary(message: Uint8Array): Uint8Array { |
| 50 | + return this.client.echoBinary(message); |
| 51 | + } |
| 52 | + |
| 53 | + close(): void { |
| 54 | + this.client.close(); |
| 55 | + } |
| 56 | +} |
| 57 | + |
| 58 | +export class DisposableObject { |
| 59 | + private disposed: boolean = false; |
| 60 | + protected objectRegistry: ObjectRegistry; |
| 61 | + constructor(objectRegistry: ObjectRegistry) { |
| 62 | + this.objectRegistry = objectRegistry; |
| 63 | + } |
| 64 | + [globalThis.Symbol.dispose](): void { |
| 65 | + this.objectRegistry.release(this); |
| 66 | + this.disposed = true; |
| 67 | + } |
| 68 | + dispose(): void { |
| 69 | + this[globalThis.Symbol.dispose](); |
| 70 | + } |
| 71 | + isDisposed(): boolean { |
| 72 | + return this.disposed; |
| 73 | + } |
| 74 | + ensureNotDisposed(): this { |
| 75 | + if (this.disposed) { |
| 76 | + throw new Error(`${this.constructor.name} is disposed`); |
| 77 | + } |
| 78 | + return this; |
| 79 | + } |
| 80 | +} |
| 81 | + |
| 82 | +export class Project extends DisposableObject { |
| 83 | + private decoder = new TextDecoder(); |
| 84 | + private client: Client; |
| 85 | + |
| 86 | + id: string; |
| 87 | + configFileName!: string; |
| 88 | + compilerOptions!: Record<string, unknown>; |
| 89 | + rootFiles!: readonly string[]; |
| 90 | + |
| 91 | + constructor(client: Client, objectRegistry: ObjectRegistry, data: ProjectResponse) { |
| 92 | + super(objectRegistry); |
| 93 | + this.id = data.id; |
| 94 | + this.client = client; |
| 95 | + this.loadData(data); |
| 96 | + } |
| 97 | + |
| 98 | + loadData(data: ProjectResponse): void { |
| 99 | + this.configFileName = data.configFileName; |
| 100 | + this.compilerOptions = data.compilerOptions; |
| 101 | + this.rootFiles = data.rootFiles; |
| 102 | + } |
| 103 | + |
| 104 | + reload(): void { |
| 105 | + this.ensureNotDisposed(); |
| 106 | + this.loadData(this.client.request("loadProject", { configFileName: this.configFileName })); |
| 107 | + } |
| 108 | + |
| 109 | + getSourceFile(fileName: string): SourceFile | undefined { |
| 110 | + this.ensureNotDisposed(); |
| 111 | + const data = this.client.requestBinary("getSourceFile", { project: this.id, fileName }); |
| 112 | + return data ? new RemoteSourceFile(data, this.decoder) as unknown as SourceFile : undefined; |
| 113 | + } |
| 114 | + |
| 115 | + getSymbolAtLocation(node: Node): Symbol | undefined; |
| 116 | + getSymbolAtLocation(nodes: readonly Node[]): (Symbol | undefined)[]; |
| 117 | + getSymbolAtLocation(nodeOrNodes: Node | readonly Node[]): Symbol | (Symbol | undefined)[] | undefined { |
| 118 | + this.ensureNotDisposed(); |
| 119 | + if (Array.isArray(nodeOrNodes)) { |
| 120 | + const data = this.client.request("getSymbolsAtLocations", { project: this.id, locations: nodeOrNodes.map(node => node.id) }); |
| 121 | + return data.map((d: SymbolResponse | null) => d ? this.objectRegistry.getSymbol(d) : undefined); |
| 122 | + } |
| 123 | + const data = this.client.request("getSymbolAtLocation", { project: this.id, location: (nodeOrNodes as Node).id }); |
| 124 | + return data ? this.objectRegistry.getSymbol(data) : undefined; |
| 125 | + } |
| 126 | + |
| 127 | + getSymbolAtPosition(fileName: string, position: number): Symbol | undefined; |
| 128 | + getSymbolAtPosition(fileName: string, positions: readonly number[]): (Symbol | undefined)[]; |
| 129 | + getSymbolAtPosition(fileName: string, positionOrPositions: number | readonly number[]): Symbol | (Symbol | undefined)[] | undefined { |
| 130 | + this.ensureNotDisposed(); |
| 131 | + if (typeof positionOrPositions === "number") { |
| 132 | + const data = this.client.request("getSymbolAtPosition", { project: this.id, fileName, position: positionOrPositions }); |
| 133 | + return data ? this.objectRegistry.getSymbol(data) : undefined; |
| 134 | + } |
| 135 | + const data = this.client.request("getSymbolsAtPositions", { project: this.id, fileName, positions: positionOrPositions }); |
| 136 | + return data.map((d: SymbolResponse | null) => d ? this.objectRegistry.getSymbol(d) : undefined); |
| 137 | + } |
| 138 | + |
| 139 | + getTypeOfSymbol(symbol: Symbol): Type | undefined; |
| 140 | + getTypeOfSymbol(symbols: readonly Symbol[]): (Type | undefined)[]; |
| 141 | + getTypeOfSymbol(symbolOrSymbols: Symbol | readonly Symbol[]): Type | (Type | undefined)[] | undefined { |
| 142 | + this.ensureNotDisposed(); |
| 143 | + if (Array.isArray(symbolOrSymbols)) { |
| 144 | + const data = this.client.request("getTypesOfSymbols", { project: this.id, symbols: symbolOrSymbols.map(symbol => symbol.ensureNotDisposed().id) }); |
| 145 | + return data.map((d: TypeResponse | null) => d ? this.objectRegistry.getType(d) : undefined); |
| 146 | + } |
| 147 | + const data = this.client.request("getTypeOfSymbol", { project: this.id, symbol: (symbolOrSymbols as Symbol).ensureNotDisposed().id }); |
| 148 | + return data ? this.objectRegistry.getType(data) : undefined; |
| 149 | + } |
| 150 | +} |
| 151 | + |
| 152 | +export class Symbol extends DisposableObject { |
| 153 | + private client: Client; |
| 154 | + id: string; |
| 155 | + name: string; |
| 156 | + flags: SymbolFlags; |
| 157 | + checkFlags: number; |
| 158 | + |
| 159 | + constructor(client: Client, objectRegistry: ObjectRegistry, data: SymbolResponse) { |
| 160 | + super(objectRegistry); |
| 161 | + this.client = client; |
| 162 | + this.id = data.id; |
| 163 | + this.name = data.name; |
| 164 | + this.flags = data.flags; |
| 165 | + this.checkFlags = data.checkFlags; |
| 166 | + } |
| 167 | +} |
| 168 | + |
| 169 | +export class Type extends DisposableObject { |
| 170 | + private client: Client; |
| 171 | + id: string; |
| 172 | + flags: TypeFlags; |
| 173 | + constructor(client: Client, objectRegistry: ObjectRegistry, data: TypeResponse) { |
| 174 | + super(objectRegistry); |
| 175 | + this.client = client; |
| 176 | + this.id = data.id; |
| 177 | + this.flags = data.flags; |
| 178 | + } |
| 179 | +} |
0 commit comments