mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-10-28 16:36:04 +08:00
[api] Add various merged symbol checker methods (#64598)
This commit is contained in:
1 parent
0ad3777160
commit
d2f9dd68cf
10 files changed
+400
-35
No files matched your search
@@ -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"
|
||||
|
||||
@@ -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)[];
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 */)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user