in apps/api-extractor/src/analyzer/AstSymbolTable.ts [343:475]
private _analyzeChildTree(node: ts.Node, governingAstDeclaration: AstDeclaration): void {
switch (node.kind) {
case ts.SyntaxKind.JSDocComment: // Skip JSDoc comments - TS considers @param tags TypeReference nodes
return;
// Is this a reference to another AstSymbol?
case ts.SyntaxKind.TypeReference: // general type references
case ts.SyntaxKind.ExpressionWithTypeArguments: // special case for e.g. the "extends" keyword
case ts.SyntaxKind.ComputedPropertyName: // used for EcmaScript "symbols", e.g. "[toPrimitive]".
case ts.SyntaxKind.TypeQuery: // represents for "typeof X" as a type
{
// Sometimes the type reference will involve multiple identifiers, e.g. "a.b.C".
// In this case, we only need to worry about importing the first identifier,
// so do a depth-first search for it:
const identifierNode: ts.Identifier | undefined = TypeScriptHelpers.findFirstChildNode(
node,
ts.SyntaxKind.Identifier
);
if (identifierNode) {
let referencedAstEntity: AstEntity | undefined = this._entitiesByNode.get(identifierNode);
if (!referencedAstEntity) {
const symbol: ts.Symbol | undefined = this._typeChecker.getSymbolAtLocation(identifierNode);
if (!symbol) {
throw new Error('Symbol not found for identifier: ' + identifierNode.getText());
}
// Normally we expect getSymbolAtLocation() to take us to a declaration within the same source
// file, or else to an explicit "import" statement within the same source file. But in certain
// situations (e.g. a global variable) the symbol will refer to a declaration in some other
// source file. We'll call that case a "displaced symbol".
//
// For more info, see this discussion:
// https://github.com/microsoft/rushstack/issues/1765#issuecomment-595559849
let displacedSymbol: boolean = true;
for (const declaration of symbol.declarations || []) {
if (declaration.getSourceFile() === identifierNode.getSourceFile()) {
displacedSymbol = false;
break;
}
}
if (displacedSymbol) {
if (this._globalVariableAnalyzer.hasGlobalName(identifierNode.text)) {
// If the displaced symbol is a global variable, then API Extractor simply ignores it.
// Ambient declarations typically describe the runtime environment (provided by an API consumer),
// so we don't bother analyzing them as an API contract. (There are probably some packages
// that include interesting global variables in their API, but API Extractor doesn't support
// that yet; it would be a feature request.)
if (this._messageRouter.showDiagnostics) {
if (!this._alreadyWarnedGlobalNames.has(identifierNode.text)) {
this._alreadyWarnedGlobalNames.add(identifierNode.text);
this._messageRouter.logDiagnostic(
`Ignoring reference to global variable "${identifierNode.text}"` +
` in ` +
SourceFileLocationFormatter.formatDeclaration(identifierNode)
);
}
}
} else {
// If you encounter this, please report a bug with a repro. We're interested to know
// how it can occur.
throw new InternalError(`Unable to follow symbol for "${identifierNode.text}"`);
}
} else {
referencedAstEntity = this._exportAnalyzer.fetchReferencedAstEntity(
symbol,
governingAstDeclaration.astSymbol.isExternal
);
this._entitiesByNode.set(identifierNode, referencedAstEntity);
}
}
if (referencedAstEntity) {
governingAstDeclaration._notifyReferencedAstEntity(referencedAstEntity);
}
}
}
break;
// Is this the identifier for the governingAstDeclaration?
case ts.SyntaxKind.Identifier:
{
const identifierNode: ts.Identifier = node as ts.Identifier;
if (!this._entitiesByNode.has(identifierNode)) {
const symbol: ts.Symbol | undefined = this._typeChecker.getSymbolAtLocation(identifierNode);
let referencedAstEntity: AstEntity | undefined = undefined;
if (symbol === governingAstDeclaration.astSymbol.followedSymbol) {
referencedAstEntity = this._fetchEntityForNode(identifierNode, governingAstDeclaration);
}
this._entitiesByNode.set(identifierNode, referencedAstEntity);
}
}
break;
case ts.SyntaxKind.ImportType:
{
const importTypeNode: ts.ImportTypeNode = node as ts.ImportTypeNode;
let referencedAstEntity: AstEntity | undefined = this._entitiesByNode.get(importTypeNode);
if (!this._entitiesByNode.has(importTypeNode)) {
referencedAstEntity = this._fetchEntityForNode(importTypeNode, governingAstDeclaration);
if (!referencedAstEntity) {
// This should never happen
throw new Error('Failed to fetch entity for import() type node: ' + importTypeNode.getText());
}
this._entitiesByNode.set(importTypeNode, referencedAstEntity);
}
if (referencedAstEntity) {
governingAstDeclaration._notifyReferencedAstEntity(referencedAstEntity);
}
}
break;
}
// Is this node declaring a new AstSymbol?
const newGoverningAstDeclaration: AstDeclaration | undefined = this._fetchAstDeclaration(
node,
governingAstDeclaration.astSymbol.isExternal
);
for (const childNode of node.getChildren()) {
this._analyzeChildTree(childNode, newGoverningAstDeclaration || governingAstDeclaration);
}
}