Skip to content
Open
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
127 changes: 127 additions & 0 deletions packages/typescript/src/api/async/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import type {
SymbolsPropertyMethod,
TextEdit,
TypeAcquisition,
TypeArraysPropertyMethod,
TypePropertyMethod,
TypeResponse,
TypesPropertyMethod,
Expand Down Expand Up @@ -565,6 +566,51 @@ class SnapshotObjectRegistry {
if (symbolData == null) return [];
else return symbolData.map(data => this.getOrCreateSymbol(data));
}

async fetchSymbolsForTypes(
types: readonly Type[],
method: SymbolsPropertyMethod,
handles: readonly (number | undefined)[],
projectId: Path,
): Promise<(Symbol | undefined)[]> {
const result: (Symbol | undefined | null)[] = new Array(types.length);
const pendingTypeIds: number[] = [];
for (let i = 0; i < types.length; i++) {
const handle = handles[i];
if (!handle) {
result[i] = undefined; // no symbol/aliasSymbol for this type
continue;
}
const cached = this.getSymbol(handle);
if (cached) {
result[i] = cached;
}
else {
result[i] = null; // pending: resolved from the batched response below
pendingTypeIds.push(types[i].id);
}
}
if (pendingTypeIds.length > 0) {
// The response array can contain nulls at indices with no result (e.g. no aliasSymbol
// for that type); the generated SymbolResponse[] result type doesn't express this.
const data = await this.client.apiRequest(method, {
snapshot: this.snapshotId,
project: projectId,
types: pendingTypeIds,
}) as (SymbolResponse | null)[];
let j = 0;
for (let i = 0; i < result.length; i++) {
if (result[i] === null) {
const d = data[j++];
result[i] = d ? this.getOrCreateSymbol(d) : undefined;
}
}
if (j !== data.length) {
throw new Error(`${method} response was not fully consumed: used ${j} of ${data.length} results`);
}
}
return result as (Symbol | undefined)[];
}
}

class ProjectObjectRegistry {
Expand Down Expand Up @@ -689,10 +735,61 @@ class ProjectObjectRegistry {
else return typesData.map(data => this.getOrCreateType(data));
}

async fetchTypeArraysForTypes(
types: readonly Type[],
method: TypeArraysPropertyMethod,
handles: readonly (readonly number[])[],
): Promise<readonly Type[][]> {
const result: (Type[] | null)[] = new Array(types.length);
const pendingTypeIds: number[] = [];
for (let i = 0; i < types.length; i++) {
const typeHandles = handles[i];
const cached = new Array<Type>(typeHandles.length);
let allCached = true;
for (let j = 0; j < typeHandles.length; j++) {
const c = this.getType(typeHandles[j]);
if (!c) {
allCached = false;
break;
}
cached[j] = c;
}
if (allCached) {
result[i] = cached;
}
else {
result[i] = null; // pending: resolved from the batched response below
pendingTypeIds.push(types[i].id);
}
}
if (pendingTypeIds.length > 0) {
const data = await this.client.apiRequest(method, {
snapshot: this.snapshotId,
project: this.project.id,
types: pendingTypeIds,
});
let j = 0;
for (let i = 0; i < result.length; i++) {
if (result[i] === null) {
const d = data[j++];
result[i] = d ? d.map(x => this.getOrCreateType(x)) : [];
}
}
if (j !== data.length) {
throw new Error(`${method} response was not fully consumed: used ${j} of ${data.length} results`);
}
}
return result as Type[][];
}

async fetchSymbols(source: Symbol | Signature | Type, method: SymbolsPropertyMethod, handles?: readonly number[]): Promise<readonly Symbol[]> {
return this.snapshotRegistry.fetchSymbols(source, method, handles, this.project.id);
}

async fetchSymbolsForTypes(types: readonly Type[], method: SymbolsPropertyMethod, handles: readonly (number | undefined)[]): Promise<(Symbol | undefined)[]> {
return this.snapshotRegistry.fetchSymbolsForTypes(types, method, handles, this.project.id);
}

// getBaseTypes is a checker-level endpoint keyed by `type` (not `objectId`),
// so it cannot go through fetchTypes. This helper reuses that server method.
async fetchBaseTypes(source: Type): Promise<readonly Type[]> {
Expand Down Expand Up @@ -1945,6 +2042,36 @@ export class Checker {
return signature.id === (await this.getWellKnownSignatures()).unknown;
}

getSymbolOfType(type: Type): Promise<Symbol | undefined>;
getSymbolOfType(types: readonly Type[]): Promise<(Symbol | undefined)[]>;
async getSymbolOfType(typeOrTypes: Type | readonly Type[]): Promise<Symbol | (Symbol | undefined)[] | undefined> {
if (Array.isArray(typeOrTypes)) {
const types = typeOrTypes as readonly TypeObject[];
return this.objectRegistry.fetchSymbolsForTypes(types, "getSymbolsOfTypes", types.map(t => t.symbol));
}
return (typeOrTypes as Type).getSymbol();
}

getAliasSymbolOfType(type: Type): Promise<Symbol | undefined>;
getAliasSymbolOfType(types: readonly Type[]): Promise<(Symbol | undefined)[]>;
async getAliasSymbolOfType(typeOrTypes: Type | readonly Type[]): Promise<Symbol | (Symbol | undefined)[] | undefined> {
if (Array.isArray(typeOrTypes)) {
const types = typeOrTypes as readonly TypeObject[];
return this.objectRegistry.fetchSymbolsForTypes(types, "getAliasSymbolsOfTypes", types.map(t => t.aliasSymbol));
}
return (typeOrTypes as Type).getAliasSymbol();
}

getAliasTypeArgumentsOfType(type: Type): Promise<readonly Type[]>;
getAliasTypeArgumentsOfType(types: readonly Type[]): Promise<readonly Type[][]>;
async getAliasTypeArgumentsOfType(typeOrTypes: Type | readonly Type[]): Promise<readonly Type[] | readonly Type[][]> {
if (Array.isArray(typeOrTypes)) {
const types = typeOrTypes as readonly TypeObject[];
return this.objectRegistry.fetchTypeArraysForTypes(types, "getAliasTypeArgumentsOfTypes", types.map(t => t.aliasTypeArguments));
}
return (typeOrTypes as Type).getAliasTypeArguments();
}

async getExportsOfModule(symbol: Symbol): Promise<readonly Symbol[]> {
const data = await this.client.apiRequest("getExportsOfModule", {
snapshot: this.snapshotId,
Expand Down
13 changes: 13 additions & 0 deletions packages/typescript/src/api/proto.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export interface APIMethodInfo {
getExportsOfSymbol: APIMethod<GetSymbolPropertyParams, SymbolResponse[] | null>;
getExportSymbolOfSymbol: APIMethod<GetSymbolPropertyParams, SymbolResponse | null>;
getSymbolOfType: APIMethod<GetTypePropertyParams, SymbolResponse | null>;
getSymbolsOfTypes: APIMethod<GetSymbolsOfTypesParams, SymbolResponse[]>;
getTargetOfType: APIMethod<GetTypePropertyParams, TypeResponse>;
getFreshTypeOfType: APIMethod<GetTypePropertyParams, TypeResponse | null>;
getRegularTypeOfType: APIMethod<GetTypePropertyParams, TypeResponse | null>;
Expand All @@ -58,7 +59,9 @@ export interface APIMethodInfo {
getOuterTypeParametersOfType: APIMethod<GetTypePropertyParams, TypeResponse[] | null>;
getLocalTypeParametersOfType: APIMethod<GetTypePropertyParams, TypeResponse[] | null>;
getAliasTypeArgumentsOfType: APIMethod<GetTypePropertyParams, TypeResponse[] | null>;
getAliasTypeArgumentsOfTypes: APIMethod<GetSymbolsOfTypesParams, TypeResponse[][]>;
getAliasSymbolOfType: APIMethod<GetTypePropertyParams, SymbolResponse | null>;
getAliasSymbolsOfTypes: APIMethod<GetSymbolsOfTypesParams, SymbolResponse[]>;
getObjectTypeOfType: APIMethod<GetTypePropertyParams, TypeResponse>;
getIndexTypeOfType: APIMethod<GetTypePropertyParams, TypeResponse>;
getCheckTypeOfType: APIMethod<GetTypePropertyParams, TypeResponse>;
Expand Down Expand Up @@ -528,6 +531,16 @@ export interface GetTypePropertyParams {
objectId: number;
}

/**
* GetSymbolsOfTypesParams is used for batched endpoints that take a list of types and return one
* result per type (getSymbolsOfTypes, getAliasSymbolsOfTypes, getAliasTypeArgumentsOfTypes).
*/
export interface GetSymbolsOfTypesParams {
snapshot: number;
project: string;
types: readonly number[] | null;
}

/** GetSignaturePropertyParams is used for all signature sub-property endpoints. */
export interface GetSignaturePropertyParams {
snapshot: number;
Expand Down
1 change: 1 addition & 0 deletions packages/typescript/src/api/proto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type SymbolsPropertyMethod = APIMethodsReturning<SymbolResponse[]>;
export type SignaturePropertyMethod = APIMethodsReturning<SignatureResponse>;
export type TypePropertyMethod = Exclude<APIMethodsReturning<TypeResponse>, IntrinsicTypeMethod>;
export type TypesPropertyMethod = APIMethodsReturning<TypeResponse[]>;
export type TypeArraysPropertyMethod = APIMethodsReturning<TypeResponse[][]>;
export type IntrinsicTypeMethod = "getAnyType" | "getBigIntType" | "getBooleanType" | "getESSymbolType" | "getNeverType" | "getNonPrimitiveType" | "getNullType" | "getNumberType" | "getStringType" | "getUndefinedType" | "getUnknownType" | "getVoidType";

/**
Expand Down
127 changes: 127 additions & 0 deletions packages/typescript/src/api/sync/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import type {
SymbolsPropertyMethod,
TextEdit,
TypeAcquisition,
TypeArraysPropertyMethod,
TypePropertyMethod,
TypeResponse,
TypesPropertyMethod,
Expand Down Expand Up @@ -573,6 +574,51 @@ class SnapshotObjectRegistry {
if (symbolData == null) return [];
else return symbolData.map(data => this.getOrCreateSymbol(data));
}

fetchSymbolsForTypes(
types: readonly Type[],
method: SymbolsPropertyMethod,
handles: readonly (number | undefined)[],
projectId: Path,
): (Symbol | undefined)[] {
const result: (Symbol | undefined | null)[] = new Array(types.length);
const pendingTypeIds: number[] = [];
for (let i = 0; i < types.length; i++) {
const handle = handles[i];
if (!handle) {
result[i] = undefined; // no symbol/aliasSymbol for this type
continue;
}
const cached = this.getSymbol(handle);
if (cached) {
result[i] = cached;
}
else {
result[i] = null; // pending: resolved from the batched response below
pendingTypeIds.push(types[i].id);
}
}
if (pendingTypeIds.length > 0) {
// The response array can contain nulls at indices with no result (e.g. no aliasSymbol
// for that type); the generated SymbolResponse[] result type doesn't express this.
const data = this.client.apiRequest(method, {
snapshot: this.snapshotId,
project: projectId,
types: pendingTypeIds,
}) as (SymbolResponse | null)[];
let j = 0;
for (let i = 0; i < result.length; i++) {
if (result[i] === null) {
const d = data[j++];
result[i] = d ? this.getOrCreateSymbol(d) : undefined;
}
}
if (j !== data.length) {
throw new Error(`${method} response was not fully consumed: used ${j} of ${data.length} results`);
}
}
return result as (Symbol | undefined)[];
}
}

class ProjectObjectRegistry {
Expand Down Expand Up @@ -697,10 +743,61 @@ class ProjectObjectRegistry {
else return typesData.map(data => this.getOrCreateType(data));
}

fetchTypeArraysForTypes(
types: readonly Type[],
method: TypeArraysPropertyMethod,
handles: readonly (readonly number[])[],
): readonly Type[][] {
const result: (Type[] | null)[] = new Array(types.length);
const pendingTypeIds: number[] = [];
for (let i = 0; i < types.length; i++) {
const typeHandles = handles[i];
const cached = new Array<Type>(typeHandles.length);
let allCached = true;
for (let j = 0; j < typeHandles.length; j++) {
const c = this.getType(typeHandles[j]);
if (!c) {
allCached = false;
break;
}
cached[j] = c;
}
if (allCached) {
result[i] = cached;
}
else {
result[i] = null; // pending: resolved from the batched response below
pendingTypeIds.push(types[i].id);
}
}
if (pendingTypeIds.length > 0) {
const data = this.client.apiRequest(method, {
snapshot: this.snapshotId,
project: this.project.id,
types: pendingTypeIds,
});
let j = 0;
for (let i = 0; i < result.length; i++) {
if (result[i] === null) {
const d = data[j++];
result[i] = d ? d.map(x => this.getOrCreateType(x)) : [];
}
}
if (j !== data.length) {
throw new Error(`${method} response was not fully consumed: used ${j} of ${data.length} results`);
}
}
return result as Type[][];
}

fetchSymbols(source: Symbol | Signature | Type, method: SymbolsPropertyMethod, handles?: readonly number[]): readonly Symbol[] {
return this.snapshotRegistry.fetchSymbols(source, method, handles, this.project.id);
}

fetchSymbolsForTypes(types: readonly Type[], method: SymbolsPropertyMethod, handles: readonly (number | undefined)[]): (Symbol | undefined)[] {
return this.snapshotRegistry.fetchSymbolsForTypes(types, method, handles, this.project.id);
}

// getBaseTypes is a checker-level endpoint keyed by `type` (not `objectId`),
// so it cannot go through fetchTypes. This helper reuses that server method.
fetchBaseTypes(source: Type): readonly Type[] {
Expand Down Expand Up @@ -1953,6 +2050,36 @@ export class Checker {
return signature.id === (this.getWellKnownSignatures()).unknown;
}

getSymbolOfType(type: Type): Symbol | undefined;
getSymbolOfType(types: readonly Type[]): (Symbol | undefined)[];
getSymbolOfType(typeOrTypes: Type | readonly Type[]): Symbol | (Symbol | undefined)[] | undefined {
if (Array.isArray(typeOrTypes)) {
const types = typeOrTypes as readonly TypeObject[];
return this.objectRegistry.fetchSymbolsForTypes(types, "getSymbolsOfTypes", types.map(t => t.symbol));
}
return (typeOrTypes as Type).getSymbol();
}

getAliasSymbolOfType(type: Type): Symbol | undefined;
getAliasSymbolOfType(types: readonly Type[]): (Symbol | undefined)[];
getAliasSymbolOfType(typeOrTypes: Type | readonly Type[]): Symbol | (Symbol | undefined)[] | undefined {
if (Array.isArray(typeOrTypes)) {
const types = typeOrTypes as readonly TypeObject[];
return this.objectRegistry.fetchSymbolsForTypes(types, "getAliasSymbolsOfTypes", types.map(t => t.aliasSymbol));
}
return (typeOrTypes as Type).getAliasSymbol();
}

getAliasTypeArgumentsOfType(type: Type): readonly Type[];
getAliasTypeArgumentsOfType(types: readonly Type[]): readonly Type[][];
getAliasTypeArgumentsOfType(typeOrTypes: Type | readonly Type[]): readonly Type[] | readonly Type[][] {
if (Array.isArray(typeOrTypes)) {
const types = typeOrTypes as readonly TypeObject[];
return this.objectRegistry.fetchTypeArraysForTypes(types, "getAliasTypeArgumentsOfTypes", types.map(t => t.aliasTypeArguments));
}
return (typeOrTypes as Type).getAliasTypeArguments();
}

getExportsOfModule(symbol: Symbol): readonly Symbol[] {
const data = this.client.apiRequest("getExportsOfModule", {
snapshot: this.snapshotId,
Expand Down
Loading