in apps/api-extractor/src/analyzer/AstSymbolTable.ts [538:694]
private _fetchAstSymbol(options: IFetchAstSymbolOptions): AstSymbol | undefined {
const followedSymbol: ts.Symbol = options.followedSymbol;
// Filter out symbols representing constructs that we don't care about
const arbitraryDeclaration: ts.Declaration | undefined =
TypeScriptHelpers.tryGetADeclaration(followedSymbol);
if (!arbitraryDeclaration) {
return undefined;
}
if (
followedSymbol.flags &
(ts.SymbolFlags.TypeParameter | ts.SymbolFlags.TypeLiteral | ts.SymbolFlags.Transient)
) {
if (!TypeScriptInternals.isLateBoundSymbol(followedSymbol)) {
return undefined;
}
}
// API Extractor doesn't analyze ambient declarations at all
if (TypeScriptHelpers.isAmbient(followedSymbol, this._typeChecker)) {
// We make a special exemption for ambient declarations that appear in a source file containing
// an "export=" declaration that allows them to be imported as non-ambient.
if (!this._exportAnalyzer.isImportableAmbientSourceFile(arbitraryDeclaration.getSourceFile())) {
return undefined;
}
}
// Make sure followedSymbol isn't an alias for something else
if (TypeScriptHelpers.isFollowableAlias(followedSymbol, this._typeChecker)) {
// We expect the caller to have already followed any aliases
throw new InternalError('AstSymbolTable._fetchAstSymbol() cannot be called with a symbol alias');
}
let astSymbol: AstSymbol | undefined = this._astSymbolsBySymbol.get(followedSymbol);
if (!astSymbol) {
// None of the above lookups worked, so create a new entry...
let nominalAnalysis: boolean = false;
if (options.isExternal) {
// If the file is from an external package that does not support AEDoc, normally we ignore it completely.
// But in some cases (e.g. checking star exports of an external package) we need an AstSymbol to
// represent it, but we don't need to analyze its sibling/children.
const followedSymbolSourceFileName: string = arbitraryDeclaration.getSourceFile().fileName;
if (!this._packageMetadataManager.isAedocSupportedFor(followedSymbolSourceFileName)) {
nominalAnalysis = true;
if (!options.includeNominalAnalysis) {
return undefined;
}
}
}
let parentAstSymbol: AstSymbol | undefined = undefined;
if (!nominalAnalysis) {
for (const declaration of followedSymbol.declarations || []) {
if (!AstDeclaration.isSupportedSyntaxKind(declaration.kind)) {
throw new InternalError(
`The "${followedSymbol.name}" symbol has a` +
` ts.SyntaxKind.${ts.SyntaxKind[declaration.kind]} declaration which is not (yet?)` +
` supported by API Extractor`
);
}
}
// We always fetch the entire chain of parents for each declaration.
// (Children/siblings are only analyzed on demand.)
// Key assumptions behind this squirrely logic:
//
// IF a given symbol has two declarations D1 and D2; AND
// If D1 has a parent P1, then
// - D2 will also have a parent P2; AND
// - P1 and P2's symbol will be the same
// - but P1 and P2 may be different (e.g. merged namespaces containing merged interfaces)
// Is there a parent AstSymbol? First we check to see if there is a parent declaration:
const arbitraryDeclaration: ts.Node | undefined =
TypeScriptHelpers.tryGetADeclaration(followedSymbol);
if (arbitraryDeclaration) {
const arbitraryParentDeclaration: ts.Node | undefined =
this._tryFindFirstAstDeclarationParent(arbitraryDeclaration);
if (arbitraryParentDeclaration) {
const parentSymbol: ts.Symbol = TypeScriptHelpers.getSymbolForDeclaration(
arbitraryParentDeclaration as ts.Declaration,
this._typeChecker
);
parentAstSymbol = this._fetchAstSymbol({
followedSymbol: parentSymbol,
isExternal: options.isExternal,
includeNominalAnalysis: false,
addIfMissing: true
});
if (!parentAstSymbol) {
throw new InternalError('Unable to construct a parent AstSymbol for ' + followedSymbol.name);
}
}
}
}
const localName: string | undefined =
options.localName || AstSymbolTable.getLocalNameForSymbol(followedSymbol);
astSymbol = new AstSymbol({
followedSymbol: followedSymbol,
localName: localName,
isExternal: options.isExternal,
nominalAnalysis: nominalAnalysis,
parentAstSymbol: parentAstSymbol,
rootAstSymbol: parentAstSymbol ? parentAstSymbol.rootAstSymbol : undefined
});
this._astSymbolsBySymbol.set(followedSymbol, astSymbol);
// Okay, now while creating the declarations we will wire them up to the
// their corresponding parent declarations
for (const declaration of followedSymbol.declarations || []) {
let parentAstDeclaration: AstDeclaration | undefined = undefined;
if (parentAstSymbol) {
const parentDeclaration: ts.Node | undefined = this._tryFindFirstAstDeclarationParent(declaration);
if (!parentDeclaration) {
throw new InternalError('Missing parent declaration');
}
parentAstDeclaration = this._astDeclarationsByDeclaration.get(parentDeclaration);
if (!parentAstDeclaration) {
throw new InternalError('Missing parent AstDeclaration');
}
}
const astDeclaration: AstDeclaration = new AstDeclaration({
declaration,
astSymbol,
parent: parentAstDeclaration
});
this._astDeclarationsByDeclaration.set(declaration, astDeclaration);
}
}
if (options.isExternal !== astSymbol.isExternal) {
throw new InternalError(
`Cannot assign isExternal=${options.isExternal} for` +
` the symbol ${astSymbol.localName} because it was previously registered` +
` with isExternal=${astSymbol.isExternal}`
);
}
return astSymbol;
}