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
16 changes: 16 additions & 0 deletions packages/typescript/src/api/async/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1801,6 +1801,22 @@ export class Checker {
return type.getIndexInfos();
}

async getIndexInfoOfType(type: Type, keyType: Type): Promise<IndexInfo | undefined> {
const data = await this.client.apiRequest("getIndexInfoOfType", {
snapshot: this.snapshotId,
project: this.project.id,
type: type.id,
keyType: keyType.id,
});
if (!data) return undefined;
return {
keyType: this.objectRegistry.getOrCreateType(data.keyType),
valueType: this.objectRegistry.getOrCreateType(data.valueType),
isReadonly: data.isReadonly ?? false,
declaration: data.declaration ? new NodeHandle<IndexSignatureDeclaration>(data.declaration, this.project) : undefined,
};
}

/**
* Get the constraint of a type parameter (the `T` in `<U extends T>`), or
* undefined if it has none.
Expand Down
9 changes: 9 additions & 0 deletions packages/typescript/src/api/proto.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export interface APIMethodInfo {
getApparentType: APIMethod<GetTypePropertyParams, TypeResponse>;
getReducedType: APIMethod<GetTypePropertyParams, TypeResponse>;
getPropertyOfType: APIMethod<GetPropertyOfTypeParams, SymbolResponse | null>;
getIndexInfoOfType: APIMethod<GetIndexInfoOfTypeParams, IndexInfoResponse | null>;
getIndexInfosOfType: APIMethod<CheckerTypeParams, IndexInfoResponse[] | null>;
getConstraintOfTypeParameter: APIMethod<GetTypePropertyParams, TypeResponse | null>;
getDefaultFromTypeParameter: APIMethod<GetTypePropertyParams, TypeResponse | null>;
Expand Down Expand Up @@ -644,6 +645,14 @@ export interface GetPropertyOfTypeParams {
name: string;
}

/** GetIndexInfoOfTypeParams are parameters for getIndexInfoOfType. */
export interface GetIndexInfoOfTypeParams {
snapshot: number;
project: string;
type: number;
keyType: number;
}

/** IndexInfoResponse represents a single index signature. */
export interface IndexInfoResponse {
keyType: TypeResponse;
Expand Down
16 changes: 16 additions & 0 deletions packages/typescript/src/api/sync/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1809,6 +1809,22 @@ export class Checker {
return type.getIndexInfos();
}

getIndexInfoOfType(type: Type, keyType: Type): IndexInfo | undefined {
const data = this.client.apiRequest("getIndexInfoOfType", {
snapshot: this.snapshotId,
project: this.project.id,
type: type.id,
keyType: keyType.id,
});
if (!data) return undefined;
return {
keyType: this.objectRegistry.getOrCreateType(data.keyType),
valueType: this.objectRegistry.getOrCreateType(data.valueType),
isReadonly: data.isReadonly ?? false,
declaration: data.declaration ? new NodeHandle<IndexSignatureDeclaration>(data.declaration, this.project) : undefined,
};
}

/**
* Get the constraint of a type parameter (the `T` in `<U extends T>`), or
* undefined if it has none.
Expand Down
84 changes: 84 additions & 0 deletions packages/typescript/test/async/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3914,6 +3914,90 @@ export declare const m: ReadonlyMap;
});
});

describe("Checker - getIndexInfoOfType", () => {
test("returns the index info for a matching key type", async () => {
const src = `
export interface StringMap {
[key: string]: number;
}
export declare const m: StringMap;
`;
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("m: StringMap"));
assert.ok(symbol);
const type = await project.checker.getTypeOfSymbol(symbol);
const stringType = await project.checker.getStringType();
const info = await project.checker.getIndexInfoOfType(type, stringType);
assert.ok(info);
assert.ok(info.keyType.flags & TypeFlags.String, `Expected string key, got flags ${info.keyType.flags}`);
assert.ok(info.valueType.flags & TypeFlags.Number, `Expected number value, got flags ${info.valueType.flags}`);
assert.equal(info.isReadonly, false);
}
finally {
await api.close();
}
});

test("returns undefined when the type has no matching index", async () => {
const src = `
export interface Person {
name: string;
}
export declare const p: Person;
`;
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("p: Person"));
assert.ok(symbol);
const type = await project.checker.getTypeOfSymbol(symbol);
const stringType = await project.checker.getStringType();
const info = await project.checker.getIndexInfoOfType(type, stringType);
assert.equal(info, undefined);
}
finally {
await api.close();
}
});

test("reports isReadonly for a readonly index signature", async () => {
const src = `
export interface ReadonlyMap {
readonly [key: string]: number;
}
export declare const m: ReadonlyMap;
`;
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("m: ReadonlyMap"));
assert.ok(symbol);
const type = await project.checker.getTypeOfSymbol(symbol);
const stringType = await project.checker.getStringType();
const info = await project.checker.getIndexInfoOfType(type, stringType);
assert.ok(info);
assert.equal(info.isReadonly, true);
}
finally {
await api.close();
}
});
});

describe("Checker - getConstraintOfTypeParameter", () => {
test("returns constraint of a type parameter", async () => {
const api = spawnAPI({
Expand Down
84 changes: 84 additions & 0 deletions packages/typescript/test/sync/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3922,6 +3922,90 @@ export declare const m: ReadonlyMap;
});
});

describe("Checker - getIndexInfoOfType", () => {
test("returns the index info for a matching key type", () => {
const src = `
export interface StringMap {
[key: string]: number;
}
export declare const m: StringMap;
`;
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("m: StringMap"));
assert.ok(symbol);
const type = project.checker.getTypeOfSymbol(symbol);
const stringType = project.checker.getStringType();
const info = project.checker.getIndexInfoOfType(type, stringType);
assert.ok(info);
assert.ok(info.keyType.flags & TypeFlags.String, `Expected string key, got flags ${info.keyType.flags}`);
assert.ok(info.valueType.flags & TypeFlags.Number, `Expected number value, got flags ${info.valueType.flags}`);
assert.equal(info.isReadonly, false);
}
finally {
api.close();
}
});

test("returns undefined when the type has no matching index", () => {
const src = `
export interface Person {
name: string;
}
export declare const p: Person;
`;
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("p: Person"));
assert.ok(symbol);
const type = project.checker.getTypeOfSymbol(symbol);
const stringType = project.checker.getStringType();
const info = project.checker.getIndexInfoOfType(type, stringType);
assert.equal(info, undefined);
}
finally {
api.close();
}
});

test("reports isReadonly for a readonly index signature", () => {
const src = `
export interface ReadonlyMap {
readonly [key: string]: number;
}
export declare const m: ReadonlyMap;
`;
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("m: ReadonlyMap"));
assert.ok(symbol);
const type = project.checker.getTypeOfSymbol(symbol);
const stringType = project.checker.getStringType();
const info = project.checker.getIndexInfoOfType(type, stringType);
assert.ok(info);
assert.equal(info.isReadonly, true);
}
finally {
api.close();
}
});
});

describe("Checker - getConstraintOfTypeParameter", () => {
test("returns constraint of a type parameter", () => {
const api = spawnAPI({
Expand Down
10 changes: 10 additions & 0 deletions tsc/internal/api/proto.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ const (
MethodGetApparentType Method = "getApparentType"
MethodGetReducedType Method = "getReducedType"
MethodGetPropertyOfType Method = "getPropertyOfType"
MethodGetIndexInfoOfType Method = "getIndexInfoOfType"
MethodGetIndexInfosOfType Method = "getIndexInfosOfType"
MethodGetConstraintOfTypeParameter Method = "getConstraintOfTypeParameter"
MethodGetDefaultFromTypeParameter Method = "getDefaultFromTypeParameter"
Expand Down Expand Up @@ -490,6 +491,7 @@ var unmarshalers = map[Method]func([]byte) (any, error){
MethodGetApparentType: unmarshallerFor[GetTypePropertyParams],
MethodGetReducedType: unmarshallerFor[GetTypePropertyParams],
MethodGetPropertyOfType: unmarshallerFor[GetPropertyOfTypeParams],
MethodGetIndexInfoOfType: unmarshallerFor[GetIndexInfoOfTypeParams],
MethodGetIndexInfosOfType: unmarshallerFor[CheckerTypeParams],
MethodGetConstraintOfTypeParameter: unmarshallerFor[GetTypePropertyParams],
MethodGetBaseConstraintOfType: unmarshallerFor[CheckerTypeParams],
Expand Down Expand Up @@ -1339,6 +1341,14 @@ type GetPropertyOfTypeParams struct {
Name string `json:"name"`
}

// GetIndexInfoOfTypeParams are parameters for getIndexInfoOfType.
type GetIndexInfoOfTypeParams struct {
Snapshot SnapshotID `json:"snapshot"`
Project ProjectID `json:"project"`
Type TypeID `json:"type"`
KeyType TypeID `json:"keyType"`
}

// GetMemberInModuleExportsParams are parameters for getMemberInModuleExports.
type GetMemberInModuleExportsParams struct {
Snapshot SnapshotID `json:"snapshot"`
Expand Down
37 changes: 37 additions & 0 deletions tsc/internal/api/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,8 @@ func (s *Session) HandleRequest(ctx context.Context, method string, params json.
return s.handleGetReducedType(ctx, parsed.(*GetTypePropertyParams))
case string(MethodGetPropertyOfType):
return s.handleGetPropertyOfType(ctx, parsed.(*GetPropertyOfTypeParams))
case string(MethodGetIndexInfoOfType):
return s.handleGetIndexInfoOfType(ctx, parsed.(*GetIndexInfoOfTypeParams))
case string(MethodGetIndexInfosOfType):
return s.handleGetIndexInfosOfType(ctx, parsed.(*CheckerTypeParams))
case string(MethodGetConstraintOfTypeParameter):
Expand Down Expand Up @@ -3114,6 +3116,41 @@ func (s *Session) handleGetIndexInfosOfType(ctx context.Context, params *Checker
return results, nil
}

// handleGetIndexInfoOfType returns the index info for a key type, or nil if none.
// @gen-proto-nullable
func (s *Session) handleGetIndexInfoOfType(ctx context.Context, params *GetIndexInfoOfTypeParams) (*IndexInfoResponse, 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
}

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

info := setup.checker.GetIndexInfoOfType(t, keyType)
if info == nil {
return nil, nil
}

result := &IndexInfoResponse{
KeyType: *setup.newTypeResponse(info.KeyType()),
ValueType: *setup.newTypeResponse(info.ValueType()),
IsReadonly: info.IsReadonly(),
}
if info.Declaration() != nil {
result.Declaration = setup.sd.nodeHandleFrom(info.Declaration())
}
return result, nil
}

// handleGetConstraintOfTypeParameter returns the constraint of a type parameter.
// @gen-proto-nullable
func (s *Session) handleGetConstraintOfTypeParameter(ctx context.Context, params *GetTypePropertyParams) (*TypeResponse, error) {
Expand Down