Skip to content

Commit 5237ed4

Browse files
[api] Add .getTargetSymbol() method (#63945)
Signed-off-by: mrazauskas <tom@mrazauskas.de> Co-authored-by: Andrew Branch <andrewbranch@users.noreply.github.com> Co-authored-by: Andrew Branch <andrew@wheream.io>
1 parent 889659e commit 5237ed4

9 files changed

Lines changed: 142 additions & 1 deletion

File tree

packages/typescript/src/api/async/api.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1958,6 +1958,21 @@ export class Checker {
19581958
return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined;
19591959
}
19601960

1961+
/**
1962+
* Get the target symbol if instantiated, or the provided symbol otherwise.
1963+
*/
1964+
async getTargetSymbol(symbol: Symbol): Promise<Symbol> {
1965+
if (symbol.checkFlags & CheckFlags.Instantiated) {
1966+
const data = await this.client.apiRequest("getTargetSymbol", {
1967+
snapshot: this.snapshotId,
1968+
project: this.project.id,
1969+
symbol: symbol.id,
1970+
});
1971+
return this.objectRegistry.getOrCreateSymbol(data);
1972+
}
1973+
return symbol;
1974+
}
1975+
19611976
/**
19621977
* Fetch (once, then cache) the handle ids of the per-checker singleton
19631978
* symbols (unknown, undefined, arguments). These ids are stable for the life

packages/typescript/src/api/proto.generated.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export interface APIMethodInfo {
107107
getExportSpecifierLocalTargetSymbol: APIMethod<CheckerNodeParams, SymbolResponse | null>;
108108
getAliasedSymbol: APIMethod<CheckerSymbolParams, SymbolResponse>;
109109
getImmediateAliasedSymbol: APIMethod<CheckerSymbolParams, SymbolResponse | null>;
110+
getTargetSymbol: APIMethod<CheckerSymbolParams, SymbolResponse>;
110111
getFullyQualifiedName: APIMethod<CheckerSymbolParams, string>;
111112
getExportsOfModule: APIMethod<CheckerSymbolParams, SymbolResponse[] | null>;
112113
getMemberInModuleExports: APIMethod<GetMemberInModuleExportsParams, SymbolResponse | null>;
@@ -970,6 +971,7 @@ export interface BatchRequest {
970971
| "getSyntacticDiagnostics"
971972
| "getTargetOfSignature"
972973
| "getTargetOfType"
974+
| "getTargetSymbol"
973975
| "getThisParameterOfSignature"
974976
| "getTrueTypeOfConditionalType"
975977
| "getTypeArguments"
@@ -1114,6 +1116,7 @@ export interface BatchResponse {
11141116
| "getSyntacticDiagnostics"
11151117
| "getTargetOfSignature"
11161118
| "getTargetOfType"
1119+
| "getTargetSymbol"
11171120
| "getThisParameterOfSignature"
11181121
| "getTrueTypeOfConditionalType"
11191122
| "getTypeArguments"

packages/typescript/src/api/sync/api.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1960,6 +1960,21 @@ export class Checker {
19601960
return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined;
19611961
}
19621962

1963+
/**
1964+
* Get the target symbol if instantiated, or the provided symbol otherwise.
1965+
*/
1966+
getTargetSymbol(symbol: Symbol): Symbol {
1967+
if (symbol.checkFlags & CheckFlags.Instantiated) {
1968+
const data = this.client.apiRequest("getTargetSymbol", {
1969+
snapshot: this.snapshotId,
1970+
project: this.project.id,
1971+
symbol: symbol.id,
1972+
});
1973+
return this.objectRegistry.getOrCreateSymbol(data);
1974+
}
1975+
return symbol;
1976+
}
1977+
19631978
/**
19641979
* Fetch (once, then cache) the handle ids of the per-checker singleton
19651980
* symbols (unknown, undefined, arguments). These ids are stable for the life

packages/typescript/test/async/api.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,48 @@ describe("Checker - getImmediateAliasedSymbol", () => {
557557
});
558558
});
559559

560+
describe("Checker - getTargetSymbol", () => {
561+
test("gets the target symbol of instantiated symbol", async () => {
562+
const api = spawnAPI({
563+
"/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }),
564+
"/src/main.ts": `
565+
class Base<T> {
566+
private value!: T;
567+
}
568+
class Alpha extends Base<string> {}
569+
class Bravo extends Base<string> {}
570+
571+
declare function test<T>(): void;
572+
test<Alpha>();
573+
test<Bravo>();
574+
`,
575+
});
576+
try {
577+
const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" });
578+
const project = snapshot.getProject("/tsconfig.json")!;
579+
const sourceFile = await project.program.getSourceFile("/src/main.ts");
580+
assert.ok(sourceFile);
581+
const nodes: Array<Node> = [];
582+
sourceFile.forEachChild(node => {
583+
if (isExpressionStatement(node) && isCallExpression(node.expression) && node.expression.typeArguments) {
584+
nodes.push(node.expression.typeArguments[0]);
585+
}
586+
});
587+
const aType = await project.checker.getTypeAtLocation(nodes[0]);
588+
const bType = await project.checker.getTypeAtLocation(nodes[1]);
589+
const aProperty = (await project.checker.getPropertiesOfType(aType))[0];
590+
const bProperty = (await project.checker.getPropertiesOfType(bType))[0];
591+
assert.ok(aProperty);
592+
assert.ok(bProperty);
593+
assert.equal(aProperty === bProperty, false);
594+
assert.equal(await project.checker.getTargetSymbol(aProperty) === await project.checker.getTargetSymbol(bProperty), true);
595+
}
596+
finally {
597+
await api.close();
598+
}
599+
});
600+
});
601+
560602
describe("Snapshot", () => {
561603
test("updateSnapshot returns snapshot with projects", async () => {
562604
const api = spawnAPI();

packages/typescript/test/sync/api.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,48 @@ describe("Checker - getImmediateAliasedSymbol", () => {
473473
});
474474
});
475475

476+
describe("Checker - getTargetSymbol", () => {
477+
test("gets the target symbol of instantiated symbol", () => {
478+
const api = spawnAPI({
479+
"/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }),
480+
"/src/main.ts": `
481+
class Base<T> {
482+
private value!: T;
483+
}
484+
class Alpha extends Base<string> {}
485+
class Bravo extends Base<string> {}
486+
487+
declare function test<T>(): void;
488+
test<Alpha>();
489+
test<Bravo>();
490+
`,
491+
});
492+
try {
493+
const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" });
494+
const project = snapshot.getProject("/tsconfig.json")!;
495+
const sourceFile = project.program.getSourceFile("/src/main.ts");
496+
assert.ok(sourceFile);
497+
const nodes: Array<Node> = [];
498+
sourceFile.forEachChild(node => {
499+
if (isExpressionStatement(node) && isCallExpression(node.expression) && node.expression.typeArguments) {
500+
nodes.push(node.expression.typeArguments[0]);
501+
}
502+
});
503+
const aType = project.checker.getTypeAtLocation(nodes[0]);
504+
const bType = project.checker.getTypeAtLocation(nodes[1]);
505+
const aProperty = (project.checker.getPropertiesOfType(aType))[0];
506+
const bProperty = (project.checker.getPropertiesOfType(bType))[0];
507+
assert.ok(aProperty);
508+
assert.ok(bProperty);
509+
assert.equal(aProperty === bProperty, false);
510+
assert.equal(project.checker.getTargetSymbol(aProperty) === project.checker.getTargetSymbol(bProperty), true);
511+
}
512+
finally {
513+
api.close();
514+
}
515+
});
516+
});
517+
476518
describe("Snapshot", () => {
477519
test("updateSnapshot returns snapshot with projects", () => {
478520
const api = spawnAPI();

tsc/internal/api/proto.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ const (
168168
MethodGetExportSpecifierLocalTarget Method = "getExportSpecifierLocalTargetSymbol"
169169
MethodGetAliasedSymbol Method = "getAliasedSymbol"
170170
MethodGetImmediateAliasedSymbol Method = "getImmediateAliasedSymbol"
171+
MethodGetTargetSymbol Method = "getTargetSymbol"
171172
MethodGetFullyQualifiedName Method = "getFullyQualifiedName"
172173
MethodGetExportsOfModule Method = "getExportsOfModule"
173174
MethodGetMemberInModuleExports Method = "getMemberInModuleExports"
@@ -505,6 +506,7 @@ var unmarshalers = map[Method]func([]byte) (any, error){
505506
MethodGetExportSpecifierLocalTarget: unmarshallerFor[CheckerNodeParams],
506507
MethodGetAliasedSymbol: unmarshallerFor[CheckerSymbolParams],
507508
MethodGetImmediateAliasedSymbol: unmarshallerFor[CheckerSymbolParams],
509+
MethodGetTargetSymbol: unmarshallerFor[CheckerSymbolParams],
508510
MethodGetFullyQualifiedName: unmarshallerFor[CheckerSymbolParams],
509511
MethodGetExportsOfModule: unmarshallerFor[CheckerSymbolParams],
510512
MethodGetMemberInModuleExports: unmarshallerFor[GetMemberInModuleExportsParams],

tsc/internal/api/session.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,8 @@ func (s *Session) HandleRequest(ctx context.Context, method string, params json.
806806
return s.handleGetAliasedSymbol(ctx, parsed.(*CheckerSymbolParams))
807807
case string(MethodGetImmediateAliasedSymbol):
808808
return s.handleGetImmediateAliasedSymbol(ctx, parsed.(*CheckerSymbolParams))
809+
case string(MethodGetTargetSymbol):
810+
return s.handleMethodGetTargetSymbol(ctx, parsed.(*CheckerSymbolParams))
809811
case string(MethodGetFullyQualifiedName):
810812
return s.handleGetFullyQualifiedName(ctx, parsed.(*CheckerSymbolParams))
811813
case string(MethodGetExportsOfModule):
@@ -3346,6 +3348,23 @@ func (s *Session) handleGetImmediateAliasedSymbol(ctx context.Context, params *C
33463348
return setup.newSymbolResponse(aliased), nil
33473349
}
33483350

3351+
// handleGetTargetSymbol returns the target symbol if the symbol is instantiated,
3352+
// otherwise returns the provided symbol.
3353+
func (s *Session) handleMethodGetTargetSymbol(ctx context.Context, params *CheckerSymbolParams) (*SymbolResponse, error) {
3354+
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
3355+
if err != nil {
3356+
return nil, err
3357+
}
3358+
defer setup.done()
3359+
3360+
symbol, err := setup.resolveSymbolHandle(params.Symbol)
3361+
if err != nil {
3362+
return nil, err
3363+
}
3364+
3365+
return setup.newSymbolResponse(setup.checker.GetTargetSymbol(symbol)), nil
3366+
}
3367+
33493368
// handleGetExportsOfModule returns the resolved exports of a module symbol,
33503369
// including those introduced by `export *` and re-exports.
33513370
// @gen-proto-nullable

tsc/internal/checker/checker.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21798,7 +21798,6 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s
2179821798
func (c *Checker) getTargetSymbol(s *ast.Symbol) *ast.Symbol {
2179921799
// if symbol is instantiated its flags are not copied from the 'target'
2180021800
// so we'll need to get back original 'target' symbol to work with correct set of flags
21801-
// NOTE: cast to TransientSymbol should be safe because only TransientSymbols have CheckFlags.Instantiated
2180221801
if s != nil && s.CheckFlags&ast.CheckFlagsInstantiated != 0 {
2180321802
return c.valueSymbolLinks.Get(s).target
2180421803
}

tsc/internal/checker/exports.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ func (c *Checker) GetImmediateAliasedSymbol(symbol *ast.Symbol) *ast.Symbol {
113113
return c.getImmediateAliasedSymbol(symbol)
114114
}
115115

116+
func (c *Checker) GetTargetSymbol(symbol *ast.Symbol) *ast.Symbol {
117+
return c.getTargetSymbol(symbol)
118+
}
119+
116120
func (c *Checker) GetTypeOnlyAliasDeclaration(symbol *ast.Symbol) *ast.Node {
117121
return c.getTypeOnlyAliasDeclaration(symbol)
118122
}

0 commit comments

Comments
 (0)