Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/typescript/scripts/generateSync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,11 @@ function addSyncEdit(node: ts.Node, source: string, sourceFile: ts.SourceFile, e
edits.push({ start: modifier.getStart(sourceFile) - offset, end: end - offset, newText: "" });
}
}
if (ts.isVariableDeclarationList(node) && (node.flags & ts.NodeFlags.AwaitUsing) === ts.NodeFlags.AwaitUsing) {
const awaitKeyword = node.getFirstToken(sourceFile);
if (awaitKeyword?.kind !== ts.SyntaxKind.AwaitKeyword) throw new Error("Expected await using declaration");
edits.push({ start: awaitKeyword.getStart(sourceFile) - offset, end: awaitKeyword.end - offset, newText: "" });
}
if (ts.isAwaitExpression(node)) {
edits.push({
start: node.getStart(sourceFile) - offset,
Expand Down
63 changes: 48 additions & 15 deletions packages/typescript/src/api/async/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ export class API<FromLSP extends boolean = false> implements FormatDiagnosticsHo
private currentDirectory: string | undefined;
private getCanonicalFileNameWorker: ((fileName: string) => string) | undefined;
private initialized: boolean = false;
private initializing: Promise<void> | undefined;
private activeSnapshots: Set<Snapshot> = new Set();
private latestSnapshot: Snapshot | undefined;
readonly internal: InternalAPI;
Expand Down Expand Up @@ -269,7 +270,12 @@ export class API<FromLSP extends boolean = false> implements FormatDiagnosticsHo
// @sync-only-end

private async ensureInitialized(): Promise<void> {
if (!this.initialized) {
if (this.initialized) return;
return this.initializing ??= this.initializeWorker();
}

private async initializeWorker(): Promise<void> {
try {
const response = await this.client.apiRequest("initialize", null);
const getCanonicalFileName = createGetCanonicalFileName(response.useCaseSensitiveFileNames);
const currentDirectory = response.currentDirectory;
Expand All @@ -278,6 +284,10 @@ export class API<FromLSP extends boolean = false> implements FormatDiagnosticsHo
this.toPath = (fileName: string) => toPath(fileName, currentDirectory, getCanonicalFileName) as Path;
this.initialized = true;
}
catch (error) {
this.initializing = undefined;
throw error;
}
}

getCurrentDirectory(): string {
Expand Down Expand Up @@ -376,18 +386,27 @@ export class API<FromLSP extends boolean = false> implements FormatDiagnosticsHo
return snapshot;
}

async [globalThis.Symbol.asyncDispose](): Promise<void> { // @sync: [globalThis.Symbol.dispose](): void {
await this.close(); // @sync: this.close();
}

async close(): Promise<void> {
await this.initializing?.catch(() => {}); // @sync-skip
Comment thread
weswigham marked this conversation as resolved.
// Dispose all active snapshots
for (const snapshot of [...this.activeSnapshots]) {
await snapshot.dispose();
try {
for (const snapshot of [...this.activeSnapshots]) {
await snapshot.dispose();
}
// Release the latest snapshot's cache refs if still held
if (this.latestSnapshot) {
this.sourceFileCache.releaseSnapshot(this.latestSnapshot.id);
this.latestSnapshot = undefined;
}
this.sourceFileCache.clear();
}
// Release the latest snapshot's cache refs if still held
if (this.latestSnapshot) {
this.sourceFileCache.releaseSnapshot(this.latestSnapshot.id);
this.latestSnapshot = undefined;
finally {
await this.client.close(); // always close the underlying connection
}
await this.client.close();
this.sourceFileCache.clear();
}

clearSourceFileCache(): void {
Expand Down Expand Up @@ -539,6 +558,7 @@ export class Snapshot {
private toPath: (fileName: string) => Path;
private client: Client;
private disposed: boolean = false;
private disposePromise: Promise<void> | undefined;
private onDispose: () => void;
private snapshotRegistry: SnapshotObjectRegistry;
readonly internal: SnapshotInternalAPI;
Expand Down Expand Up @@ -587,19 +607,27 @@ export class Snapshot {
}

[globalThis.Symbol.dispose](): void {
this.dispose();
void this.dispose();
}

async dispose(): Promise<void> {
dispose(): Promise<void> {
return this.disposePromise ??= this.disposeWorker();
}

private async disposeWorker(): Promise<void> {
if (this.disposed) return;
this.disposed = true;
for (const project of this.projectMap.values()) {
project.dispose();
}
this.projectMap.clear();
this.snapshotRegistry.clear();
this.onDispose();
await this.client.apiRequest("release", { snapshot: this.id });
try {
await this.client.apiRequest("release", { snapshot: this.id });
}
finally {
this.onDispose();
}
}

isDisposed(): boolean {
Expand Down Expand Up @@ -1082,6 +1110,7 @@ export class Program implements FormatDiagnosticsHost {
private readonly decoder = new Wtf8Decoder();
private readonly sourceFileMetadataCache = new Map<Path, Promise<SourceFileMetadata | undefined>>();
private ownedSnapshot: Snapshot | undefined;
private disposePromise: Promise<void> | undefined;

constructor(
snapshotId: number,
Expand Down Expand Up @@ -1117,10 +1146,14 @@ export class Program implements FormatDiagnosticsHost {
}

[globalThis.Symbol.dispose](): void {
this.dispose();
void this.dispose();
}

dispose(): Promise<void> {
return this.disposePromise ??= this.disposeWorker();
}

async dispose(): Promise<void> {
private async disposeWorker(): Promise<void> {
const snapshot = this.ownedSnapshot;
this.ownedSnapshot = undefined;
if (snapshot) await snapshot.dispose();
Expand Down
15 changes: 13 additions & 2 deletions packages/typescript/src/api/async/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export class Client {
private connection: MessageConnection | undefined;
private options: ClientOptions;
private connected = false;
private closed = false;
private connecting: Promise<void> | undefined;
private timing: TimingCollector | undefined;
private batchedRequests: { method: APIRequest["method"]; params: APIRequest["params"]; resolve: (value: unknown) => void; reject: (reason?: any) => void; }[] = [];
private nextBatch: NodeJS.Immediate | "manual" | undefined;
Expand All @@ -60,9 +62,15 @@ export class Client {
}
}

async connect(): Promise<void> {
if (this.connected) return;
connect(): Promise<void> {
if (this.closed) return Promise.reject(new Error("Client is closed"));
if (this.connected) return Promise.resolve();
return this.connecting ??= this.connectWorker().finally(() => {
this.connecting = undefined;
});
}

private async connectWorker(): Promise<void> {
Comment thread
weswigham marked this conversation as resolved.
if (isSpawnOptions(this.options)) {
await this.connectViaSpawn(this.options);
}
Expand Down Expand Up @@ -254,6 +262,7 @@ export class Client {
}

async apiRequest<K extends keyof APIMethodInfo>(method: K, params: APIMethodInfo[K]["params"]): Promise<APIMethodInfo[K]["result"]> {
if (this.closed) throw new Error("Client is closed");
if (!this.connected) {
await this.connect();
}
Expand Down Expand Up @@ -323,6 +332,8 @@ export class Client {
}

async close(): Promise<void> {
await this.connecting?.catch(() => {}); // if connection is still in-progress, wait for it to finish before closing the connection
this.closed = true;
if (this.connection) {
this.connection.dispose();
this.connection = undefined;
Expand Down
Loading