Skip to content

Commit 528e4be

Browse files
authored
Merge pull request #3566 from zelliott/namespace-references
[api-extractor] Fix incorrect declaration references for local symbols within namespaces
2 parents 8e5e982 + 3f978c2 commit 528e4be

File tree

23 files changed

+1474
-202
lines changed

23 files changed

+1474
-202
lines changed

apps/api-extractor/src/generators/DeclarationReferenceGenerator.ts

Lines changed: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -230,10 +230,8 @@ export class DeclarationReferenceGenerator {
230230
if (!includeModuleSymbols) {
231231
return undefined;
232232
}
233-
const sourceFile: ts.SourceFile | undefined =
234-
followedSymbol.declarations &&
235-
followedSymbol.declarations[0] &&
236-
followedSymbol.declarations[0].getSourceFile();
233+
const declaration: ts.Node | undefined = TypeScriptHelpers.tryGetADeclaration(symbol);
234+
const sourceFile: ts.SourceFile | undefined = declaration?.getSourceFile();
237235
return new DeclarationReference(this._sourceFileToModuleSource(sourceFile));
238236
}
239237

@@ -242,28 +240,8 @@ export class DeclarationReferenceGenerator {
242240
return undefined;
243241
}
244242

245-
const parent: ts.Symbol | undefined = TypeScriptInternals.getSymbolParent(followedSymbol);
246-
let parentRef: DeclarationReference | undefined;
247-
if (parent) {
248-
parentRef = this._symbolToDeclarationReference(
249-
parent,
250-
ts.SymbolFlags.Namespace,
251-
/*includeModuleSymbols*/ true
252-
);
253-
} else {
254-
// this may be a local symbol in a module...
255-
const sourceFile: ts.SourceFile | undefined =
256-
followedSymbol.declarations &&
257-
followedSymbol.declarations[0] &&
258-
followedSymbol.declarations[0].getSourceFile();
259-
if (sourceFile && ts.isExternalModule(sourceFile)) {
260-
parentRef = new DeclarationReference(this._sourceFileToModuleSource(sourceFile));
261-
} else {
262-
parentRef = new DeclarationReference(GlobalSource.instance);
263-
}
264-
}
265-
266-
if (parentRef === undefined) {
243+
let parentRef: DeclarationReference | undefined = this._getParentReference(followedSymbol);
244+
if (!parentRef) {
267245
return undefined;
268246
}
269247

@@ -306,6 +284,55 @@ export class DeclarationReferenceGenerator {
306284
.withMeaning(DeclarationReferenceGenerator._getMeaningOfSymbol(followedSymbol, meaning));
307285
}
308286

287+
private _getParentReference(symbol: ts.Symbol): DeclarationReference | undefined {
288+
const declaration: ts.Node | undefined = TypeScriptHelpers.tryGetADeclaration(symbol);
289+
290+
// First, try to find a parent symbol via the symbol tree.
291+
const parentSymbol: ts.Symbol | undefined = TypeScriptInternals.getSymbolParent(symbol);
292+
if (parentSymbol) {
293+
return this._symbolToDeclarationReference(
294+
parentSymbol,
295+
parentSymbol.flags,
296+
/*includeModuleSymbols*/ true
297+
);
298+
}
299+
300+
// If that doesn't work, try to find a parent symbol via the node tree. As far as we can tell,
301+
// this logic is only needed for local symbols within namespaces. For example:
302+
//
303+
// ```
304+
// export namespace n {
305+
// type SomeType = number;
306+
// export function someFunction(): SomeType { return 5; }
307+
// }
308+
// ```
309+
//
310+
// In the example above, `SomeType` doesn't have a parent symbol per the TS internal API above,
311+
// but its reference still needs to be qualified with the parent reference for `n`.
312+
const grandParent: ts.Node | undefined = declaration?.parent?.parent;
313+
if (grandParent && ts.isModuleDeclaration(grandParent)) {
314+
const grandParentSymbol: ts.Symbol | undefined = TypeScriptInternals.tryGetSymbolForDeclaration(
315+
grandParent,
316+
this._typeChecker
317+
);
318+
if (grandParentSymbol) {
319+
return this._symbolToDeclarationReference(
320+
grandParentSymbol,
321+
grandParentSymbol.flags,
322+
/*includeModuleSymbols*/ true
323+
);
324+
}
325+
}
326+
327+
// At this point, we have a local symbol in a module.
328+
const sourceFile: ts.SourceFile | undefined = declaration?.getSourceFile();
329+
if (sourceFile && ts.isExternalModule(sourceFile)) {
330+
return new DeclarationReference(this._sourceFileToModuleSource(sourceFile));
331+
} else {
332+
return new DeclarationReference(GlobalSource.instance);
333+
}
334+
}
335+
309336
private _getPackageName(sourceFile: ts.SourceFile): string {
310337
if (this._program.isSourceFileFromExternalLibrary(sourceFile)) {
311338
const packageJson: INodePackageJson | undefined = this._packageJsonLookup.tryLoadNodePackageJsonFor(

build-tests/api-extractor-scenarios/config/build-config.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@
4040
"namedDefaultImport",
4141
"preapproved",
4242
"readonlyDeclarations",
43+
"referenceTokens",
4344
"spanSorting",
45+
"typeLiterals",
4446
"typeOf",
4547
"typeOf2",
4648
"typeOf3",

0 commit comments

Comments
 (0)