[api] Add various merged symbol checker methods (#64598)

This commit is contained in:
Andrew Branch authored and GitHub committed 2026-10-02 21:28:37 +00:00
1 parent 0ad3777160
commit d2f9dd68cf
10 files changed
+400 -35

No files matched your search

+44
View File
@@ -2231,6 +2231,50 @@ export class Checker {
this.objectRegistry.clear();
}
/** Get the merged symbol, or the original symbol if it has not been merged. */
async getMergedSymbol(symbol: Symbol): Promise<Symbol> {
const data = await this.client.apiRequest("getMergedSymbol", {
snapshot: this.snapshotId,
project: this.project.id,
symbol: symbol.reference,
});
return this.objectRegistry.getOrCreateSymbol(data);
}
/**
* Get a node's merged symbol. For a declaration, prefer
* {@link Checker.getSymbolOfDeclaration} for a non-nullable result.
*/
async getSymbolOfNode(node: Node): Promise<Symbol | undefined> {
const data = await this.client.apiRequest("getSymbolOfNode", {
snapshot: this.snapshotId,
project: this.project.id,
location: getNodeId(node),
});
return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined;
}
/** Get a declaration's merged symbol. */
async getSymbolOfDeclaration(node: Declaration): Promise<Symbol> {
const data = await this.client.apiRequest("getSymbolOfDeclarationForChecker", {
snapshot: this.snapshotId,
project: this.project.id,
location: getNodeId(node),
});
// Declarations normally have symbols, but preserve unexpected null results at runtime.
return (data && this.objectRegistry.getOrCreateSymbol(data))!;
}
/** Get a symbol's merged parent, if any. */
async getParentOfSymbol(symbol: Symbol): Promise<Symbol | undefined> {
const data = await this.client.apiRequest("getParentOfSymbolForChecker", {
snapshot: this.snapshotId,
project: this.project.id,
symbol: symbol.reference,
});
return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined;
}
getSymbolAtLocation(node: Node): Promise<Symbol | undefined>;
getSymbolAtLocation(nodes: readonly Node[]): Promise<(Symbol | undefined)[]>;
async getSymbolAtLocation(nodeOrNodes: Node | readonly Node[]): Promise<Symbol | (Symbol | undefined)[] | undefined> {
@@ -145,6 +145,10 @@ export interface APIMethodInfo {
getAliasedSymbol: APIMethod<CheckerSymbolParams, SymbolResponse>;
getImmediateAliasedSymbol: APIMethod<CheckerSymbolParams, SymbolResponse | null>;
getTargetSymbol: APIMethod<CheckerSymbolParams, SymbolResponse>;
getMergedSymbol: APIMethod<CheckerSymbolParams, SymbolResponse>;
getSymbolOfNode: APIMethod<CheckerNodeParams, SymbolResponse | null>;
getSymbolOfDeclarationForChecker: APIMethod<CheckerNodeParams, SymbolResponse | null>;
getParentOfSymbolForChecker: APIMethod<CheckerSymbolParams, SymbolResponse | null>;
getExportSymbolOfSymbolForChecker: APIMethod<CheckerSymbolParams, SymbolResponse>;
getFullyQualifiedName: APIMethod<CheckerSymbolParams, string>;
getExportsOfModule: APIMethod<CheckerSymbolParams, SymbolResponse[] | null>;
@@ -1193,6 +1197,7 @@ export interface BatchRequest {
| "getLocalTypeParametersOfType"
| "getMemberInModuleExports"
| "getMembersOfSymbol"
| "getMergedSymbol"
| "getModeForResolutionAtIndex"
| "getModeForUsageLocation"
| "getNameTypeOfMappedType"
@@ -1207,6 +1212,7 @@ export interface BatchRequest {
| "getParameterType"
| "getParametersOfSignature"
| "getParentOfSymbol"
| "getParentOfSymbolForChecker"
| "getProgramDiagnostics"
| "getPropertiesOfType"
| "getPropertyOfType"
@@ -1234,6 +1240,8 @@ export interface BatchRequest {
| "getSymbolAtLocation"
| "getSymbolAtPosition"
| "getSymbolOfDeclaration"
| "getSymbolOfDeclarationForChecker"
| "getSymbolOfNode"
| "getSymbolOfSourceFile"
| "getSymbolOfType"
| "getSymbolsAtLocations"
@@ -1371,6 +1379,7 @@ export interface BatchResponse {
| "getLocalTypeParametersOfType"
| "getMemberInModuleExports"
| "getMembersOfSymbol"
| "getMergedSymbol"
| "getModeForResolutionAtIndex"
| "getModeForUsageLocation"
| "getNameTypeOfMappedType"
@@ -1385,6 +1394,7 @@ export interface BatchResponse {
| "getParameterType"
| "getParametersOfSignature"
| "getParentOfSymbol"
| "getParentOfSymbolForChecker"
| "getProgramDiagnostics"
| "getPropertiesOfType"
| "getPropertyOfType"
@@ -1412,6 +1422,8 @@ export interface BatchResponse {
| "getSymbolAtLocation"
| "getSymbolAtPosition"
| "getSymbolOfDeclaration"
| "getSymbolOfDeclarationForChecker"
| "getSymbolOfNode"
| "getSymbolOfSourceFile"
| "getSymbolOfType"
| "getSymbolsAtLocations"
+117
View File
@@ -4068,6 +4068,123 @@ export class Checker {
this.objectRegistry.clear();
}
/** Get the merged symbol, or the original symbol if it has not been merged. */
get getMergedSymbol(): {
(symbol: Symbol): Symbol;
gen(symbol: Symbol): Generator<ProtocolRequest, Symbol, ProtocolResponse["result"]>;
} {
const owner = this;
return cacheGeneratorMethod(
owner,
"getMergedSymbol",
function (symbol: Symbol): Symbol {
const data = owner.client.apiRequest("getMergedSymbol", {
snapshot: owner.snapshotId,
project: owner.project.id,
symbol: symbol.reference,
});
return owner.objectRegistry.getOrCreateSymbol(data);
},
function* (symbol: Symbol): Generator<ProtocolRequest, Symbol, ProtocolResponse["result"]> {
const data = yield* apiRequest("getMergedSymbol", {
snapshot: owner.snapshotId,
project: owner.project.id,
symbol: symbol.reference,
});
return owner.objectRegistry.getOrCreateSymbol(data);
},
);
}
/**
* Get a node's merged symbol. For a declaration, prefer
* {@link Checker.getSymbolOfDeclaration} for a non-nullable result.
*/
get getSymbolOfNode(): {
(node: Node): Symbol | undefined;
gen(node: Node): Generator<ProtocolRequest, Symbol | undefined, ProtocolResponse["result"]>;
} {
const owner = this;
return cacheGeneratorMethod(
owner,
"getSymbolOfNode",
function (node: Node): Symbol | undefined {
const data = owner.client.apiRequest("getSymbolOfNode", {
snapshot: owner.snapshotId,
project: owner.project.id,
location: getNodeId(node),
});
return data ? owner.objectRegistry.getOrCreateSymbol(data) : undefined;
},
function* (node: Node): Generator<ProtocolRequest, Symbol | undefined, ProtocolResponse["result"]> {
const data = yield* apiRequest("getSymbolOfNode", {
snapshot: owner.snapshotId,
project: owner.project.id,
location: getNodeId(node),
});
return data ? owner.objectRegistry.getOrCreateSymbol(data) : undefined;
},
);
}
/** Get a declaration's merged symbol. */
get getSymbolOfDeclaration(): {
(node: Declaration): Symbol;
gen(node: Declaration): Generator<ProtocolRequest, Symbol, ProtocolResponse["result"]>;
} {
const owner = this;
return cacheGeneratorMethod(
owner,
"getSymbolOfDeclaration",
function (node: Declaration): Symbol {
const data = owner.client.apiRequest("getSymbolOfDeclarationForChecker", {
snapshot: owner.snapshotId,
project: owner.project.id,
location: getNodeId(node),
});
// Declarations normally have symbols, but preserve unexpected null results at runtime.
return (data && owner.objectRegistry.getOrCreateSymbol(data))!;
},
function* (node: Declaration): Generator<ProtocolRequest, Symbol, ProtocolResponse["result"]> {
const data = yield* apiRequest("getSymbolOfDeclarationForChecker", {
snapshot: owner.snapshotId,
project: owner.project.id,
location: getNodeId(node),
});
// Declarations normally have symbols, but preserve unexpected null results at runtime.
return (data && owner.objectRegistry.getOrCreateSymbol(data))!;
},
);
}
/** Get a symbol's merged parent, if any. */
get getParentOfSymbol(): {
(symbol: Symbol): Symbol | undefined;
gen(symbol: Symbol): Generator<ProtocolRequest, Symbol | undefined, ProtocolResponse["result"]>;
} {
const owner = this;
return cacheGeneratorMethod(
owner,
"getParentOfSymbol",
function (symbol: Symbol): Symbol | undefined {
const data = owner.client.apiRequest("getParentOfSymbolForChecker", {
snapshot: owner.snapshotId,
project: owner.project.id,
symbol: symbol.reference,
});
return data ? owner.objectRegistry.getOrCreateSymbol(data) : undefined;
},
function* (symbol: Symbol): Generator<ProtocolRequest, Symbol | undefined, ProtocolResponse["result"]> {
const data = yield* apiRequest("getParentOfSymbolForChecker", {
snapshot: owner.snapshotId,
project: owner.project.id,
symbol: symbol.reference,
});
return data ? owner.objectRegistry.getOrCreateSymbol(data) : undefined;
},
);
}
get getSymbolAtLocation(): {
(node: Node): Symbol | undefined;
(nodes: readonly Node[]): (Symbol | undefined)[];
+66 -14
View File
@@ -21,6 +21,7 @@ import {
isNamedImports,
isObjectLiteralExpression,
isPropertyAssignment,
isPropertyDeclaration,
isReturnStatement,
isShorthandPropertyAssignment,
isStringLiteral,
@@ -505,7 +506,7 @@ describe("API", { concurrency }, () => {
});
test("remote declarations lazily fetch and cache binder symbols", async () => {
const sourceText = "function present() {}\nimport {} from './missing';";
const sourceText = "function present() {}";
await using api = spawnAPI({
"/symbols.ts": sourceText,
});
@@ -514,9 +515,7 @@ describe("API", { concurrency }, () => {
const sourceFile = await project.program.getSourceFile("/symbols.ts");
assert.ok(sourceFile);
const declaration = sourceFile.statements[0];
const withoutSymbol = sourceFile.statements[1];
assert.ok(isFunctionDeclaration(declaration));
assert.ok(isImportDeclaration(withoutSymbol));
const checkerSymbol = await project.checker.getSymbolAtPosition("/symbols.ts", sourceText.indexOf("present")); // @sync: const checkerSymbol = project.checker.getSymbolAtPosition("/symbols.ts", sourceText.indexOf("present"));
assert.ok(checkerSymbol);
@@ -537,11 +536,6 @@ describe("API", { concurrency }, () => {
assert.strictEqual(await getSymbol(declaration), first); // @sync: assert.strictEqual(getSymbol(declaration), first);
assert.equal(symbolRequests, 1);
symbolRequests = 0;
await assert.rejects(getSymbol(withoutSymbol), /has no binder symbol/); // @sync: assert.throws(() => getSymbol(withoutSymbol), /has no binder symbol/);
await assert.rejects(api.getSymbol(withoutSymbol), /has no binder symbol/); // @sync: assert.throws(() => api.getSymbol(withoutSymbol), /has no binder symbol/);
assert.equal(symbolRequests, 2);
await snapshot.dispose();
});
@@ -563,7 +557,7 @@ describe("API", { concurrency }, () => {
test("source files own separate declaration result and request caches", async () => {
await using api = spawnAPI();
await using lease = await api.createSourceFile("/symbols.ts", "function present() {}\nimport {} from './missing';");
await using lease = await api.createSourceFile("/symbols.ts", "function present() {}");
const file = lease.sourceFile;
assert.ok(file instanceof RemoteSourceFile);
const cache = file.symbolCache;
@@ -578,11 +572,6 @@ describe("API", { concurrency }, () => {
assert.strictEqual(cache.symbolsByDeclarationNodeIndex.get(index), symbol);
assert.strictEqual(cache.symbolsById.get(symbol.reference.id), symbol);
assert.equal(cache.declarationSymbolRequests.has(index), false);
const withoutSymbol = cast(file.statements[1], isImportDeclaration);
const absentIndex = parseNodeHandle(getNodeId(withoutSymbol)).index;
await assert.rejects(getSymbol(withoutSymbol), /has no binder symbol/); // @sync: assert.throws(() => getSymbol(withoutSymbol), /has no binder symbol/);
assert.equal(cache.symbolsByDeclarationNodeIndex.has(absentIndex), false);
assert.equal(cache.declarationSymbolRequests.has(absentIndex), false);
api.clearSourceFileCache();
assert.strictEqual(file.symbolCache, cache);
});
@@ -3953,6 +3942,69 @@ export const value = 1;
assert.notStrictEqual(second, first);
});
test("checker symbol methods merge declarations and parents in their project", async () => {
await using api = spawnAPI({
"/tsconfig.json": JSON.stringify({ files: ["/src/a.ts", "/src/b.ts"] }),
"/single/tsconfig.json": JSON.stringify({ files: ["/src/a.ts"] }),
"/src/a.ts": "namespace Merged { export const a = 1; }",
"/src/b.ts": "namespace Merged { export const b = 1; }",
});
const snapshot = await api.createSnapshot({ openProjects: ["/tsconfig.json", "/single/tsconfig.json"] });
const project = snapshot.getConfiguredProject("/tsconfig.json")!;
const singleProject = snapshot.getConfiguredProject("/single/tsconfig.json")!;
const fileA = await project.program.getSourceFile("/src/a.ts");
const fileB = await project.program.getSourceFile("/src/b.ts");
assert.ok(fileA && fileB);
const declarationA = cast(fileA.statements[0], isModuleDeclaration);
const declarationB = cast(fileB.statements[0], isModuleDeclaration);
const rawA = await getSymbol(declarationA);
const rawB = await getSymbol(declarationB);
assert.notStrictEqual(rawA, rawB);
const merged = await project.checker.getSymbolOfDeclaration(declarationA);
assert.strictEqual(await project.checker.getSymbolOfDeclaration(declarationB), merged);
assert.strictEqual(await project.checker.getSymbolOfNode(declarationA), merged);
assert.strictEqual(await project.checker.getMergedSymbol(rawA), merged);
assert.strictEqual(await project.checker.getMergedSymbol(rawB), merged);
assert.strictEqual(await project.checker.getMergedSymbol(merged), merged);
assert.strictEqual(await singleProject.checker.getMergedSymbol(rawA), rawA);
assert.strictEqual(await singleProject.checker.getSymbolOfDeclaration(declarationA), rawA);
assert.equal(await project.checker.getSymbolOfNode(declarationA.name), undefined);
assert.equal(await project.checker.getSymbolOfNode(fileA), undefined);
assert.equal(await project.checker.getParentOfSymbol(merged), undefined);
const member = (await rawA.getExports()).get("a" as __String);
assert.ok(member);
assert.strictEqual(await member.getParent(), rawA);
assert.strictEqual(await project.checker.getParentOfSymbol(member), merged);
assert.strictEqual(await singleProject.checker.getParentOfSymbol(member), rawA);
});
test("checker symbol methods late-bind computed members", async () => {
await using api = spawnAPI({
"/src/computed.ts": "declare const key: unique symbol;\nclass Container { [key] = 1; ordinary = 2; }\nimport {} from './missing';",
});
const snapshot = await api.createSnapshot({ openFiles: ["/src/computed.ts"] });
const project = snapshot.getProjects()[0];
const file = await project.program.getSourceFile("/src/computed.ts");
assert.ok(file);
const declaration = cast(file.statements[1], isClassDeclaration);
const memberDeclaration = cast(declaration.members[0], isPropertyDeclaration);
const raw = await getSymbol(memberDeclaration);
const lateBound = await project.checker.getSymbolOfDeclaration(memberDeclaration);
assert.notStrictEqual(lateBound, raw);
assert.strictEqual(await project.checker.getSymbolOfNode(memberDeclaration), lateBound);
const container = await project.checker.getSymbolOfDeclaration(declaration);
assert.strictEqual(await project.checker.getParentOfSymbol(lateBound), container);
const type = await project.checker.getDeclaredTypeOfSymbol(container);
const property = (await type.getProperties()).find(symbol => symbol.escapedName === lateBound.escapedName);
assert.ok(property);
assert.strictEqual(await project.checker.getTargetSymbol(property), lateBound);
const ordinary = cast(declaration.members[1], isPropertyDeclaration);
assert.strictEqual(await project.checker.getSymbolOfDeclaration(ordinary), await getSymbol(ordinary));
assert.equal(await project.checker.getSymbolOfNode(file.statements[2]), undefined);
});
test("snapshot-owned symbols resolve cached file-owned parents", async () => {
const source = `interface Box<T> { value: T; }\ndeclare const box: Box<string>;\nbox;`;
await using api = spawnAPI({
@@ -1642,6 +1642,11 @@ describe("API - generator batching", { concurrency: areTestsFiltered() }, () =>
parityCase("Program", "getJavaScriptEmit", program.getJavaScriptEmit, assertDeepEquivalent, ["/src/index.ts"]),
parityCase("Program", "getDeclarationEmit", program.getDeclarationEmit, assertDeepEquivalent, ["/src/index.ts"]),
parityCase("Checker", "getMergedSymbol", checker.getMergedSymbol, assertSymbolsEquivalent, interfaceSymbol),
parityCase("Checker", "getSymbolOfNode", checker.getSymbolOfNode, assertOptionalSymbolsEquivalent, interfaceDeclaration),
parityCase("Checker", "getSymbolOfNode", checker.getSymbolOfNode, assertOptionalSymbolsEquivalent, interfaceDeclaration.name),
parityCase("Checker", "getSymbolOfDeclaration", checker.getSymbolOfDeclaration, assertSymbolsEquivalent, interfaceDeclaration),
parityCase("Checker", "getParentOfSymbol", checker.getParentOfSymbol, assertOptionalSymbolsEquivalent, unimportedSymbol),
parityCase("Checker", "getSymbolAtLocation", selectGeneratorMethod<[node: Node], Symbol | undefined>(checker.getSymbolAtLocation), assertOptionalSymbolsEquivalent, importedDerived),
parityCase("Checker", "getSymbolAtLocation", checker.getSymbolAtLocation, assertOptionalSymbolArraysEquivalent, [importedDerived, combineDeclaration.name!]),
parityCase("Checker", "getSymbolAtPosition", selectGeneratorMethod<[file: string, position: number], Symbol | undefined>(checker.getSymbolAtPosition), assertOptionalSymbolsEquivalent, "/src/index.ts", importedDerived.pos),
+66 -14
View File
@@ -29,6 +29,7 @@ import {
isNamedImports,
isObjectLiteralExpression,
isPropertyAssignment,
isPropertyDeclaration,
isReturnStatement,
isShorthandPropertyAssignment,
isStringLiteral,
@@ -493,7 +494,7 @@ describe("API", { concurrency }, () => {
});
test("remote declarations lazily fetch and cache binder symbols", () => {
const sourceText = "function present() {}\nimport {} from './missing';";
const sourceText = "function present() {}";
using api = spawnAPI({
"/symbols.ts": sourceText,
});
@@ -502,9 +503,7 @@ describe("API", { concurrency }, () => {
const sourceFile = project.program.getSourceFile("/symbols.ts");
assert.ok(sourceFile);
const declaration = sourceFile.statements[0];
const withoutSymbol = sourceFile.statements[1];
assert.ok(isFunctionDeclaration(declaration));
assert.ok(isImportDeclaration(withoutSymbol));
const checkerSymbol = project.checker.getSymbolAtPosition("/symbols.ts", sourceText.indexOf("present"));
assert.ok(checkerSymbol);
@@ -526,11 +525,6 @@ describe("API", { concurrency }, () => {
assert.strictEqual(getSymbol(declaration), first);
assert.equal(symbolRequests, 1);
symbolRequests = 0;
assert.throws(() => getSymbol(withoutSymbol), /has no binder symbol/);
assert.throws(() => api.getSymbol(withoutSymbol), /has no binder symbol/);
assert.equal(symbolRequests, 2);
snapshot.dispose();
});
@@ -552,7 +546,7 @@ describe("API", { concurrency }, () => {
test("source files own separate declaration result and request caches", () => {
using api = spawnAPI();
using lease = api.createSourceFile("/symbols.ts", "function present() {}\nimport {} from './missing';");
using lease = api.createSourceFile("/symbols.ts", "function present() {}");
const file = lease.sourceFile;
assert.ok(file instanceof RemoteSourceFile);
const cache = file.symbolCache;
@@ -565,11 +559,6 @@ describe("API", { concurrency }, () => {
assert.strictEqual(cache.symbolsByDeclarationNodeIndex.get(index), symbol);
assert.strictEqual(cache.symbolsById.get(symbol.reference.id), symbol);
assert.equal(cache.declarationSymbolRequests.has(index), false);
const withoutSymbol = cast(file.statements[1], isImportDeclaration);
const absentIndex = parseNodeHandle(getNodeId(withoutSymbol)).index;
assert.throws(() => getSymbol(withoutSymbol), /has no binder symbol/);
assert.equal(cache.symbolsByDeclarationNodeIndex.has(absentIndex), false);
assert.equal(cache.declarationSymbolRequests.has(absentIndex), false);
api.clearSourceFileCache();
assert.strictEqual(file.symbolCache, cache);
});
@@ -3778,6 +3767,69 @@ export const value = 1;
assert.notStrictEqual(second, first);
});
test("checker symbol methods merge declarations and parents in their project", () => {
using api = spawnAPI({
"/tsconfig.json": JSON.stringify({ files: ["/src/a.ts", "/src/b.ts"] }),
"/single/tsconfig.json": JSON.stringify({ files: ["/src/a.ts"] }),
"/src/a.ts": "namespace Merged { export const a = 1; }",
"/src/b.ts": "namespace Merged { export const b = 1; }",
});
const snapshot = api.createSnapshot({ openProjects: ["/tsconfig.json", "/single/tsconfig.json"] });
const project = snapshot.getConfiguredProject("/tsconfig.json")!;
const singleProject = snapshot.getConfiguredProject("/single/tsconfig.json")!;
const fileA = project.program.getSourceFile("/src/a.ts");
const fileB = project.program.getSourceFile("/src/b.ts");
assert.ok(fileA && fileB);
const declarationA = cast(fileA.statements[0], isModuleDeclaration);
const declarationB = cast(fileB.statements[0], isModuleDeclaration);
const rawA = getSymbol(declarationA);
const rawB = getSymbol(declarationB);
assert.notStrictEqual(rawA, rawB);
const merged = project.checker.getSymbolOfDeclaration(declarationA);
assert.strictEqual(project.checker.getSymbolOfDeclaration(declarationB), merged);
assert.strictEqual(project.checker.getSymbolOfNode(declarationA), merged);
assert.strictEqual(project.checker.getMergedSymbol(rawA), merged);
assert.strictEqual(project.checker.getMergedSymbol(rawB), merged);
assert.strictEqual(project.checker.getMergedSymbol(merged), merged);
assert.strictEqual(singleProject.checker.getMergedSymbol(rawA), rawA);
assert.strictEqual(singleProject.checker.getSymbolOfDeclaration(declarationA), rawA);
assert.equal(project.checker.getSymbolOfNode(declarationA.name), undefined);
assert.equal(project.checker.getSymbolOfNode(fileA), undefined);
assert.equal(project.checker.getParentOfSymbol(merged), undefined);
const member = (rawA.getExports()).get("a" as __String);
assert.ok(member);
assert.strictEqual(member.getParent(), rawA);
assert.strictEqual(project.checker.getParentOfSymbol(member), merged);
assert.strictEqual(singleProject.checker.getParentOfSymbol(member), rawA);
});
test("checker symbol methods late-bind computed members", () => {
using api = spawnAPI({
"/src/computed.ts": "declare const key: unique symbol;\nclass Container { [key] = 1; ordinary = 2; }\nimport {} from './missing';",
});
const snapshot = api.createSnapshot({ openFiles: ["/src/computed.ts"] });
const project = snapshot.getProjects()[0];
const file = project.program.getSourceFile("/src/computed.ts");
assert.ok(file);
const declaration = cast(file.statements[1], isClassDeclaration);
const memberDeclaration = cast(declaration.members[0], isPropertyDeclaration);
const raw = getSymbol(memberDeclaration);
const lateBound = project.checker.getSymbolOfDeclaration(memberDeclaration);
assert.notStrictEqual(lateBound, raw);
assert.strictEqual(project.checker.getSymbolOfNode(memberDeclaration), lateBound);
const container = project.checker.getSymbolOfDeclaration(declaration);
assert.strictEqual(project.checker.getParentOfSymbol(lateBound), container);
const type = project.checker.getDeclaredTypeOfSymbol(container);
const property = (type.getProperties()).find(symbol => symbol.escapedName === lateBound.escapedName);
assert.ok(property);
assert.strictEqual(project.checker.getTargetSymbol(property), lateBound);
const ordinary = cast(declaration.members[1], isPropertyDeclaration);
assert.strictEqual(project.checker.getSymbolOfDeclaration(ordinary), getSymbol(ordinary));
assert.equal(project.checker.getSymbolOfNode(file.statements[2]), undefined);
});
test("snapshot-owned symbols resolve cached file-owned parents", () => {
const source = `interface Box<T> { value: T; }\ndeclare const box: Box<string>;\nbox;`;
using api = spawnAPI({
+8
View File
@@ -205,6 +205,10 @@ const (
MethodGetAliasedSymbol Method = "getAliasedSymbol"
MethodGetImmediateAliasedSymbol Method = "getImmediateAliasedSymbol"
MethodGetTargetSymbol Method = "getTargetSymbol"
MethodGetMergedSymbol Method = "getMergedSymbol"
MethodGetSymbolOfNode Method = "getSymbolOfNode"
MethodGetSymbolOfDeclarationForChecker Method = "getSymbolOfDeclarationForChecker"
MethodGetParentOfSymbolForChecker Method = "getParentOfSymbolForChecker"
MethodGetExportSymbolOfSymbolForChecker Method = "getExportSymbolOfSymbolForChecker"
MethodGetFullyQualifiedName Method = "getFullyQualifiedName"
MethodGetExportsOfModule Method = "getExportsOfModule"
@@ -709,6 +713,10 @@ var unmarshalers = map[Method]func([]byte) (any, error){
MethodGetAliasedSymbol: unmarshallerFor[CheckerSymbolParams],
MethodGetImmediateAliasedSymbol: unmarshallerFor[CheckerSymbolParams],
MethodGetTargetSymbol: unmarshallerFor[CheckerSymbolParams],
MethodGetMergedSymbol: unmarshallerFor[CheckerSymbolParams],
MethodGetSymbolOfNode: unmarshallerFor[CheckerNodeParams],
MethodGetSymbolOfDeclarationForChecker: unmarshallerFor[CheckerNodeParams],
MethodGetParentOfSymbolForChecker: unmarshallerFor[CheckerSymbolParams],
MethodGetExportSymbolOfSymbolForChecker: unmarshallerFor[CheckerSymbolParams],
MethodGetFullyQualifiedName: unmarshallerFor[CheckerSymbolParams],
MethodGetExportsOfModule: unmarshallerFor[CheckerSymbolParams],
+70
View File
@@ -1155,6 +1155,14 @@ func (s *Session) HandleRequest(ctx context.Context, method string, params json.
return s.handleGetImmediateAliasedSymbol(ctx, parsed.(*CheckerSymbolParams))
case string(MethodGetTargetSymbol):
return s.handleMethodGetTargetSymbol(ctx, parsed.(*CheckerSymbolParams))
case string(MethodGetMergedSymbol):
return s.handleGetMergedSymbol(ctx, parsed.(*CheckerSymbolParams))
case string(MethodGetSymbolOfNode):
return s.handleGetSymbolOfNode(ctx, parsed.(*CheckerNodeParams))
case string(MethodGetSymbolOfDeclarationForChecker):
return s.handleGetSymbolOfDeclarationForChecker(ctx, parsed.(*CheckerNodeParams))
case string(MethodGetParentOfSymbolForChecker):
return s.handleGetParentOfSymbolForChecker(ctx, parsed.(*CheckerSymbolParams))
case string(MethodGetExportSymbolOfSymbolForChecker):
return s.handleGetExportSymbolOfSymbolForChecker(ctx, parsed.(*CheckerSymbolParams))
case string(MethodGetFullyQualifiedName):
@@ -4654,6 +4662,68 @@ func (s *Session) handleGetExportSpecifierLocalTargetSymbol(ctx context.Context,
return setup.newSymbolResponse(symbol), nil
}
func (s *Session) handleGetMergedSymbol(ctx context.Context, params *CheckerSymbolParams) (*SymbolResponse, error) {
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
if err != nil {
return nil, err
}
defer setup.done()
symbol, err := setup.resolveSymbolHandle(params.Symbol)
if err != nil {
return nil, err
}
return setup.newSymbolResponse(setup.checker.GetMergedSymbol(symbol)), nil
}
// @gen-proto-nullable
func (s *Session) handleGetSymbolOfNode(ctx context.Context, params *CheckerNodeParams) (*SymbolResponse, error) {
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
if err != nil {
return nil, err
}
defer setup.done()
node, err := setup.sd.resolveNodeHandle(setup.program, params.Location)
if err != nil {
return nil, err
}
return setup.newSymbolResponse(setup.checker.GetSymbolOfNode(node)), nil
}
// @gen-proto-nullable
func (s *Session) handleGetSymbolOfDeclarationForChecker(ctx context.Context, params *CheckerNodeParams) (*SymbolResponse, error) {
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
if err != nil {
return nil, err
}
defer setup.done()
node, err := setup.sd.resolveNodeHandle(setup.program, params.Location)
if err != nil {
return nil, err
}
return setup.newSymbolResponse(setup.checker.GetSymbolOfDeclaration(node)), nil
}
// @gen-proto-nullable
func (s *Session) handleGetParentOfSymbolForChecker(ctx context.Context, params *CheckerSymbolParams) (*SymbolResponse, error) {
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
if err != nil {
return nil, err
}
defer setup.done()
symbol, err := setup.resolveSymbolHandle(params.Symbol)
if err != nil {
return nil, err
}
return setup.newSymbolResponse(setup.checker.GetParentOfSymbol(symbol)), nil
}
// handleGetAliasedSymbol resolves an alias symbol to its target.
func (s *Session) handleGetAliasedSymbol(ctx context.Context, params *CheckerSymbolParams) (*SymbolResponse, error) {
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
@@ -190,13 +190,6 @@ func TestCreateSourceFile(t *testing.T) {
assert.Equal(t, present.Name, "present")
assert.Equal(t, present.Reference.Kind, SymbolOwnerKindFile)
absent, err := session.handleGetSymbolOfDeclaration(&GetSymbolOfDeclarationParams{
File: descriptor,
Index: table.GetIndex(sourceFile.Statements.Nodes[1]),
})
assert.ErrorContains(t, err, "has no binder symbol")
assert.Assert(t, absent == nil)
_, err = session.handleGetSymbolOfDeclaration(&GetSymbolOfDeclarationParams{
File: descriptor,
Index: 0,
+12
View File
@@ -104,6 +104,18 @@ func (c *Checker) GetMergedSymbol(symbol *ast.Symbol) *ast.Symbol {
return c.getMergedSymbol(symbol)
}
func (c *Checker) GetSymbolOfNode(node *ast.Node) *ast.Symbol {
return c.getSymbolOfNode(node)
}
func (c *Checker) GetSymbolOfDeclaration(node *ast.Node) *ast.Symbol {
return c.getSymbolOfDeclaration(node)
}
func (c *Checker) GetParentOfSymbol(symbol *ast.Symbol) *ast.Symbol {
return c.getParentOfSymbol(symbol)
}
func (c *Checker) TryFindAmbientModule(moduleName string) *ast.Symbol {
return c.tryFindAmbientModule(moduleName, true /* withAugmentations */)
}