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
9 changes: 9 additions & 0 deletions packages/typescript/src/api/async/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1822,6 +1822,15 @@ export class Checker {
return data ? this.objectRegistry.getOrCreateType(data) : undefined;
}

async getAwaitedType(type: Type): Promise<Type | undefined> {
const data = await this.client.apiRequest("getAwaitedType", {
snapshot: this.snapshotId,
project: this.project.id,
type: type.id,
});
return data ? this.objectRegistry.getOrCreateType(data) : undefined;
}

async getPropertyOfType(type: Type, name: string): Promise<Symbol | undefined> {
const data = await this.client.apiRequest("getPropertyOfType", {
snapshot: this.snapshotId,
Expand Down
1 change: 1 addition & 0 deletions packages/typescript/src/api/proto.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export interface APIMethodInfo {
getPropertiesOfType: APIMethod<CheckerTypeParams, SymbolResponse[] | null>;
getApparentPropertiesOfType: APIMethod<GetTypePropertyParams, SymbolResponse[]>;
getApparentType: APIMethod<GetTypePropertyParams, TypeResponse>;
getAwaitedType: APIMethod<CheckerTypeParams, TypeResponse | null>;
getReducedType: APIMethod<GetTypePropertyParams, TypeResponse>;
getPropertyOfType: APIMethod<GetPropertyOfTypeParams, SymbolResponse | null>;
getIndexInfosOfType: APIMethod<CheckerTypeParams, IndexInfoResponse[] | null>;
Expand Down
9 changes: 9 additions & 0 deletions packages/typescript/src/api/sync/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1830,6 +1830,15 @@ export class Checker {
return data ? this.objectRegistry.getOrCreateType(data) : undefined;
}

getAwaitedType(type: Type): Type | undefined {
const data = this.client.apiRequest("getAwaitedType", {
snapshot: this.snapshotId,
project: this.project.id,
type: type.id,
});
return data ? this.objectRegistry.getOrCreateType(data) : undefined;
}

getPropertyOfType(type: Type, name: string): Symbol | undefined {
const data = this.client.apiRequest("getPropertyOfType", {
snapshot: this.snapshotId,
Expand Down
85 changes: 85 additions & 0 deletions packages/typescript/test/async/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4112,6 +4112,91 @@ describe("Checker - getBaseConstraintOfType", () => {
});
});

describe("Checker - getAwaitedType", () => {
test("unwraps Promise<string> to string", async () => {
const src = `export const value: Promise<string> = Promise.resolve("x");`;
const api = spawnAPI({
"/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }),
"/src/main.ts": src,
});
try {
const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" });
const project = snapshot.getProject("/tsconfig.json")!;
const symbol = await project.checker.getSymbolAtPosition("/src/main.ts", src.indexOf("value"));
assert.ok(symbol);
const type = await project.checker.getTypeOfSymbol(symbol);
const awaited = await project.checker.getAwaitedType(type);
assert.ok(awaited);
assert.ok(awaited.flags & TypeFlags.String, `Expected string, got flags ${awaited.flags}`);
}
finally {
await api.close();
}
});

test("unwraps nested Promise<Promise<number>>", async () => {
const src = `export const value: Promise<Promise<number>> = Promise.resolve(Promise.resolve(1));`;
const api = spawnAPI({
"/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }),
"/src/main.ts": src,
});
try {
const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" });
const project = snapshot.getProject("/tsconfig.json")!;
const symbol = await project.checker.getSymbolAtPosition("/src/main.ts", src.indexOf("value"));
assert.ok(symbol);
const type = await project.checker.getTypeOfSymbol(symbol);
const awaited = await project.checker.getAwaitedType(type);
assert.ok(awaited);
assert.ok(awaited.flags & TypeFlags.Number, `Expected number, got flags ${awaited.flags}`);
}
finally {
await api.close();
}
});

test("returns the type itself when it is not thenable", async () => {
const src = `export const value: string = "x";`;
const api = spawnAPI({
"/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }),
"/src/main.ts": src,
});
try {
const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" });
const project = snapshot.getProject("/tsconfig.json")!;
const symbol = await project.checker.getSymbolAtPosition("/src/main.ts", src.indexOf("value"));
assert.ok(symbol);
const type = await project.checker.getTypeOfSymbol(symbol);
const awaited = await project.checker.getAwaitedType(type);
assert.ok(awaited);
assert.strictEqual(awaited, type);
}
finally {
await api.close();
}
});

test("returns undefined for a recursive thenable", async () => {
const src = `export type Loop = { then(resolve: (value: Loop) => void): void };`;
const api = spawnAPI({
"/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }),
"/src/main.ts": src,
});
try {
const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" });
const project = snapshot.getProject("/tsconfig.json")!;
const symbol = await project.checker.getSymbolAtPosition("/src/main.ts", src.indexOf("Loop"));
assert.ok(symbol);
const type = await project.checker.getDeclaredTypeOfSymbol(symbol);
const awaited = await project.checker.getAwaitedType(type);
assert.equal(awaited, undefined);
}
finally {
await api.close();
}
});
});

describe("Checker - getPropertyOfType", () => {
test("returns a named property symbol of a type", async () => {
const api = spawnAPI({
Expand Down
85 changes: 85 additions & 0 deletions packages/typescript/test/sync/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4120,6 +4120,91 @@ describe("Checker - getBaseConstraintOfType", () => {
});
});

describe("Checker - getAwaitedType", () => {
test("unwraps Promise<string> to string", () => {
const src = `export const value: Promise<string> = Promise.resolve("x");`;
const api = spawnAPI({
"/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }),
"/src/main.ts": src,
});
try {
const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" });
const project = snapshot.getProject("/tsconfig.json")!;
const symbol = project.checker.getSymbolAtPosition("/src/main.ts", src.indexOf("value"));
assert.ok(symbol);
const type = project.checker.getTypeOfSymbol(symbol);
const awaited = project.checker.getAwaitedType(type);
assert.ok(awaited);
assert.ok(awaited.flags & TypeFlags.String, `Expected string, got flags ${awaited.flags}`);
}
finally {
api.close();
}
});

test("unwraps nested Promise<Promise<number>>", () => {
const src = `export const value: Promise<Promise<number>> = Promise.resolve(Promise.resolve(1));`;
const api = spawnAPI({
"/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }),
"/src/main.ts": src,
});
try {
const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" });
const project = snapshot.getProject("/tsconfig.json")!;
const symbol = project.checker.getSymbolAtPosition("/src/main.ts", src.indexOf("value"));
assert.ok(symbol);
const type = project.checker.getTypeOfSymbol(symbol);
const awaited = project.checker.getAwaitedType(type);
assert.ok(awaited);
assert.ok(awaited.flags & TypeFlags.Number, `Expected number, got flags ${awaited.flags}`);
}
finally {
api.close();
}
});

test("returns the type itself when it is not thenable", () => {
const src = `export const value: string = "x";`;
const api = spawnAPI({
"/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }),
"/src/main.ts": src,
});
try {
const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" });
const project = snapshot.getProject("/tsconfig.json")!;
const symbol = project.checker.getSymbolAtPosition("/src/main.ts", src.indexOf("value"));
assert.ok(symbol);
const type = project.checker.getTypeOfSymbol(symbol);
const awaited = project.checker.getAwaitedType(type);
assert.ok(awaited);
assert.strictEqual(awaited, type);
}
finally {
api.close();
}
});

test("returns undefined for a recursive thenable", () => {
const src = `export type Loop = { then(resolve: (value: Loop) => void): void };`;
const api = spawnAPI({
"/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }),
"/src/main.ts": src,
});
try {
const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" });
const project = snapshot.getProject("/tsconfig.json")!;
const symbol = project.checker.getSymbolAtPosition("/src/main.ts", src.indexOf("Loop"));
assert.ok(symbol);
const type = project.checker.getDeclaredTypeOfSymbol(symbol);
const awaited = project.checker.getAwaitedType(type);
assert.equal(awaited, undefined);
}
finally {
api.close();
}
});
});

describe("Checker - getPropertyOfType", () => {
test("returns a named property symbol of a type", () => {
const api = spawnAPI({
Expand Down
2 changes: 2 additions & 0 deletions tsc/internal/api/proto.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ const (
MethodGetPropertiesOfType Method = "getPropertiesOfType"
MethodGetApparentPropertiesOfType Method = "getApparentPropertiesOfType"
MethodGetApparentType Method = "getApparentType"
MethodGetAwaitedType Method = "getAwaitedType"
MethodGetReducedType Method = "getReducedType"
MethodGetPropertyOfType Method = "getPropertyOfType"
MethodGetIndexInfosOfType Method = "getIndexInfosOfType"
Expand Down Expand Up @@ -488,6 +489,7 @@ var unmarshalers = map[Method]func([]byte) (any, error){
MethodGetPropertiesOfType: unmarshallerFor[CheckerTypeParams],
MethodGetApparentPropertiesOfType: unmarshallerFor[GetTypePropertyParams],
MethodGetApparentType: unmarshallerFor[GetTypePropertyParams],
MethodGetAwaitedType: unmarshallerFor[CheckerTypeParams],
MethodGetReducedType: unmarshallerFor[GetTypePropertyParams],
MethodGetPropertyOfType: unmarshallerFor[GetPropertyOfTypeParams],
MethodGetIndexInfosOfType: unmarshallerFor[CheckerTypeParams],
Expand Down
24 changes: 24 additions & 0 deletions tsc/internal/api/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,8 @@ func (s *Session) HandleRequest(ctx context.Context, method string, params json.
return s.handleGetApparentPropertiesOfType(ctx, parsed.(*GetTypePropertyParams))
case string(MethodGetApparentType):
return s.handleGetApparentType(ctx, parsed.(*GetTypePropertyParams))
case string(MethodGetAwaitedType):
return s.handleGetAwaitedType(ctx, parsed.(*CheckerTypeParams))
case string(MethodGetReducedType):
return s.handleGetReducedType(ctx, parsed.(*GetTypePropertyParams))
case string(MethodGetPropertyOfType):
Expand Down Expand Up @@ -3064,6 +3066,28 @@ func (s *Session) handleGetApparentType(ctx context.Context, params *GetTypeProp
return setup.newTypeResponse(setup.checker.GetApparentType(t)), nil
}

// handleGetAwaitedType returns the type of `await expr` for a type.
// @gen-proto-nullable
func (s *Session) handleGetAwaitedType(ctx context.Context, params *CheckerTypeParams) (*TypeResponse, error) {
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
if err != nil {
return nil, err
}
defer setup.done()

t, err := setup.resolveTypeHandle(params.Type)
if err != nil {
return nil, err
}

awaited := setup.checker.GetAwaitedType(t)
if awaited == nil {
return nil, nil
}

return setup.newTypeResponse(awaited), nil
}

// handleGetReducedType returns the reduced type of a type.
func (s *Session) handleGetReducedType(ctx context.Context, params *GetTypePropertyParams) (*TypeResponse, error) {
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
Expand Down
4 changes: 4 additions & 0 deletions tsc/internal/checker/exports.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,10 @@ func (c *Checker) GetApparentType(t *Type) *Type {
return c.getApparentType(t)
}

func (c *Checker) GetAwaitedType(t *Type) *Type {
return c.getAwaitedType(t)
}

func (c *Checker) GetReducedType(t *Type) *Type {
return c.getReducedType(t)
}
Expand Down