551 lines
29 KiB
JavaScript
551 lines
29 KiB
JavaScript
|
"use strict";
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
|
||
|
// See LICENSE in the project root for license information.
|
||
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||
|
if (k2 === undefined) k2 = k;
|
||
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
||
|
}
|
||
|
Object.defineProperty(o, k2, desc);
|
||
|
}) : (function(o, m, k, k2) {
|
||
|
if (k2 === undefined) k2 = k;
|
||
|
o[k2] = m[k];
|
||
|
}));
|
||
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||
|
}) : function(o, v) {
|
||
|
o["default"] = v;
|
||
|
});
|
||
|
var __importStar = (this && this.__importStar) || function (mod) {
|
||
|
if (mod && mod.__esModule) return mod;
|
||
|
var result = {};
|
||
|
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||
|
__setModuleDefault(result, mod);
|
||
|
return result;
|
||
|
};
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
exports.AstSymbolTable = void 0;
|
||
|
/* eslint-disable no-bitwise */ // for ts.SymbolFlags
|
||
|
const ts = __importStar(require("typescript"));
|
||
|
const node_core_library_1 = require("@rushstack/node-core-library");
|
||
|
const AstDeclaration_1 = require("./AstDeclaration");
|
||
|
const TypeScriptHelpers_1 = require("./TypeScriptHelpers");
|
||
|
const AstSymbol_1 = require("./AstSymbol");
|
||
|
const PackageMetadataManager_1 = require("./PackageMetadataManager");
|
||
|
const ExportAnalyzer_1 = require("./ExportAnalyzer");
|
||
|
const AstNamespaceImport_1 = require("./AstNamespaceImport");
|
||
|
const TypeScriptInternals_1 = require("./TypeScriptInternals");
|
||
|
const SyntaxHelpers_1 = require("./SyntaxHelpers");
|
||
|
const SourceFileLocationFormatter_1 = require("./SourceFileLocationFormatter");
|
||
|
/**
|
||
|
* AstSymbolTable is the workhorse that builds AstSymbol and AstDeclaration objects.
|
||
|
* It maintains a cache of already constructed objects. AstSymbolTable constructs
|
||
|
* AstModule objects, but otherwise the state that it maintains is agnostic of
|
||
|
* any particular entry point. (For example, it does not track whether a given AstSymbol
|
||
|
* is "exported" or not.)
|
||
|
*
|
||
|
* Internally, AstSymbolTable relies on ExportAnalyzer to crawl import statements and determine where symbols
|
||
|
* are declared (i.e. the AstImport information needed to import them).
|
||
|
*/
|
||
|
class AstSymbolTable {
|
||
|
constructor(program, typeChecker, packageJsonLookup, bundledPackageNames, messageRouter) {
|
||
|
/**
|
||
|
* A mapping from ts.Symbol --> AstSymbol
|
||
|
* NOTE: The AstSymbol.followedSymbol will always be a lookup key, but additional keys
|
||
|
* are possible.
|
||
|
*
|
||
|
* After following type aliases, we use this map to look up the corresponding AstSymbol.
|
||
|
*/
|
||
|
this._astSymbolsBySymbol = new Map();
|
||
|
/**
|
||
|
* A mapping from ts.Declaration --> AstDeclaration
|
||
|
*/
|
||
|
this._astDeclarationsByDeclaration = new Map();
|
||
|
// Note that this is a mapping from specific AST nodes that we analyzed, based on the underlying symbol
|
||
|
// for that node.
|
||
|
this._entitiesByNode = new Map();
|
||
|
this._program = program;
|
||
|
this._typeChecker = typeChecker;
|
||
|
this._messageRouter = messageRouter;
|
||
|
this._globalVariableAnalyzer = TypeScriptInternals_1.TypeScriptInternals.getGlobalVariableAnalyzer(program);
|
||
|
this._packageMetadataManager = new PackageMetadataManager_1.PackageMetadataManager(packageJsonLookup, messageRouter);
|
||
|
this._exportAnalyzer = new ExportAnalyzer_1.ExportAnalyzer(this._program, this._typeChecker, bundledPackageNames, {
|
||
|
analyze: this.analyze.bind(this),
|
||
|
fetchAstSymbol: this._fetchAstSymbol.bind(this)
|
||
|
});
|
||
|
this._alreadyWarnedGlobalNames = new Set();
|
||
|
}
|
||
|
/**
|
||
|
* Used to analyze an entry point that belongs to the working package.
|
||
|
*/
|
||
|
fetchAstModuleFromWorkingPackage(sourceFile) {
|
||
|
return this._exportAnalyzer.fetchAstModuleFromSourceFile(sourceFile, undefined, false);
|
||
|
}
|
||
|
/**
|
||
|
* This crawls the specified entry point and collects the full set of exported AstSymbols.
|
||
|
*/
|
||
|
fetchAstModuleExportInfo(astModule) {
|
||
|
return this._exportAnalyzer.fetchAstModuleExportInfo(astModule);
|
||
|
}
|
||
|
/**
|
||
|
* Attempts to retrieve an export by name from the specified `AstModule`.
|
||
|
* Returns undefined if no match was found.
|
||
|
*/
|
||
|
tryGetExportOfAstModule(exportName, astModule) {
|
||
|
return this._exportAnalyzer.tryGetExportOfAstModule(exportName, astModule);
|
||
|
}
|
||
|
/**
|
||
|
* Ensures that AstSymbol.analyzed is true for the provided symbol. The operation
|
||
|
* starts from the root symbol and then fills out all children of all declarations, and
|
||
|
* also calculates AstDeclaration.referencedAstSymbols for all declarations.
|
||
|
* If the symbol is not imported, any non-imported references are also analyzed.
|
||
|
*
|
||
|
* @remarks
|
||
|
* This is an expensive operation, so we only perform it for top-level exports of an
|
||
|
* the AstModule. For example, if some code references a nested class inside
|
||
|
* a namespace from another library, we do not analyze any of that class's siblings
|
||
|
* or members. (We do always construct its parents however, since AstDefinition.parent
|
||
|
* is immutable, and needed e.g. to calculate release tag inheritance.)
|
||
|
*/
|
||
|
analyze(astEntity) {
|
||
|
if (astEntity instanceof AstSymbol_1.AstSymbol) {
|
||
|
return this._analyzeAstSymbol(astEntity);
|
||
|
}
|
||
|
if (astEntity instanceof AstNamespaceImport_1.AstNamespaceImport) {
|
||
|
return this._analyzeAstNamespaceImport(astEntity);
|
||
|
}
|
||
|
}
|
||
|
/**
|
||
|
* For a given astDeclaration, this efficiently finds the child corresponding to the
|
||
|
* specified ts.Node. It is assumed that AstDeclaration.isSupportedSyntaxKind() would return true for
|
||
|
* that node type, and that the node is an immediate child of the provided AstDeclaration.
|
||
|
*/
|
||
|
// NOTE: This could be a method of AstSymbol if it had a backpointer to its AstSymbolTable.
|
||
|
getChildAstDeclarationByNode(node, parentAstDeclaration) {
|
||
|
if (!parentAstDeclaration.astSymbol.analyzed) {
|
||
|
throw new Error('getChildDeclarationByNode() cannot be used for an AstSymbol that was not analyzed');
|
||
|
}
|
||
|
const childAstDeclaration = this._astDeclarationsByDeclaration.get(node);
|
||
|
if (!childAstDeclaration) {
|
||
|
throw new Error('Child declaration not found for the specified node');
|
||
|
}
|
||
|
if (childAstDeclaration.parent !== parentAstDeclaration) {
|
||
|
throw new node_core_library_1.InternalError('The found child is not attached to the parent AstDeclaration');
|
||
|
}
|
||
|
return childAstDeclaration;
|
||
|
}
|
||
|
/**
|
||
|
* For a given ts.Identifier that is part of an AstSymbol that we analyzed, return the AstEntity that
|
||
|
* it refers to. Returns undefined if it doesn't refer to anything interesting.
|
||
|
* @remarks
|
||
|
* Throws an Error if the ts.Identifier is not part of node tree that was analyzed.
|
||
|
*/
|
||
|
tryGetEntityForNode(identifier) {
|
||
|
if (!this._entitiesByNode.has(identifier)) {
|
||
|
throw new node_core_library_1.InternalError('tryGetEntityForIdentifier() called for an identifier that was not analyzed');
|
||
|
}
|
||
|
return this._entitiesByNode.get(identifier);
|
||
|
}
|
||
|
/**
|
||
|
* Builds an AstSymbol.localName for a given ts.Symbol. In the current implementation, the localName is
|
||
|
* a TypeScript-like expression that may be a string literal or ECMAScript symbol expression.
|
||
|
*
|
||
|
* ```ts
|
||
|
* class X {
|
||
|
* // localName="identifier"
|
||
|
* public identifier: number = 1;
|
||
|
* // localName="\"identifier\""
|
||
|
* public "quoted string!": number = 2;
|
||
|
* // localName="[MyNamespace.MySymbol]"
|
||
|
* public [MyNamespace.MySymbol]: number = 3;
|
||
|
* }
|
||
|
* ```
|
||
|
*/
|
||
|
static getLocalNameForSymbol(symbol) {
|
||
|
// TypeScript binds well-known ECMAScript symbols like "[Symbol.iterator]" as "__@iterator".
|
||
|
// Decode it back into "[Symbol.iterator]".
|
||
|
const wellKnownSymbolName = TypeScriptHelpers_1.TypeScriptHelpers.tryDecodeWellKnownSymbolName(symbol.escapedName);
|
||
|
if (wellKnownSymbolName) {
|
||
|
return wellKnownSymbolName;
|
||
|
}
|
||
|
const isUniqueSymbol = TypeScriptHelpers_1.TypeScriptHelpers.isUniqueSymbolName(symbol.escapedName);
|
||
|
// We will try to obtain the name from a declaration; otherwise we'll fall back to the symbol name.
|
||
|
let unquotedName = symbol.name;
|
||
|
for (const declaration of symbol.declarations || []) {
|
||
|
// Handle cases such as "export default class X { }" where the symbol name is "default"
|
||
|
// but the local name is "X".
|
||
|
const localSymbol = TypeScriptInternals_1.TypeScriptInternals.tryGetLocalSymbol(declaration);
|
||
|
if (localSymbol) {
|
||
|
unquotedName = localSymbol.name;
|
||
|
}
|
||
|
// If it is a non-well-known symbol, then return the late-bound name. For example, "X.Y.z" in this example:
|
||
|
//
|
||
|
// namespace X {
|
||
|
// export namespace Y {
|
||
|
// export const z: unique symbol = Symbol("z");
|
||
|
// }
|
||
|
// }
|
||
|
//
|
||
|
// class C {
|
||
|
// public [X.Y.z](): void { }
|
||
|
// }
|
||
|
//
|
||
|
if (isUniqueSymbol) {
|
||
|
const declarationName = ts.getNameOfDeclaration(declaration);
|
||
|
if (declarationName && ts.isComputedPropertyName(declarationName)) {
|
||
|
const lateBoundName = TypeScriptHelpers_1.TypeScriptHelpers.tryGetLateBoundName(declarationName);
|
||
|
if (lateBoundName) {
|
||
|
// Here the string may contain an expression such as "[X.Y.z]". Names starting with "[" are always
|
||
|
// expressions. If a string literal contains those characters, the code below will JSON.stringify() it
|
||
|
// to avoid a collision.
|
||
|
return lateBoundName;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Otherwise that name may come from a quoted string or pseudonym like `__constructor`.
|
||
|
// If the string is not a safe identifier, then we must add quotes.
|
||
|
// Note that if it was quoted but did not need to be quoted, here we will remove the quotes.
|
||
|
if (!SyntaxHelpers_1.SyntaxHelpers.isSafeUnquotedMemberIdentifier(unquotedName)) {
|
||
|
// For API Extractor's purposes, a canonical form is more appropriate than trying to reflect whatever
|
||
|
// appeared in the source code. The code is not even guaranteed to be consistent, for example:
|
||
|
//
|
||
|
// class X {
|
||
|
// public "f1"(x: string): void;
|
||
|
// public f1(x: boolean): void;
|
||
|
// public 'f1'(x: string | boolean): void { }
|
||
|
// }
|
||
|
return JSON.stringify(unquotedName);
|
||
|
}
|
||
|
return unquotedName;
|
||
|
}
|
||
|
_analyzeAstNamespaceImport(astNamespaceImport) {
|
||
|
if (astNamespaceImport.analyzed) {
|
||
|
return;
|
||
|
}
|
||
|
// mark before actual analyzing, to handle module cyclic reexport
|
||
|
astNamespaceImport.analyzed = true;
|
||
|
const exportedLocalEntities = this.fetchAstModuleExportInfo(astNamespaceImport.astModule).exportedLocalEntities;
|
||
|
for (const exportedEntity of exportedLocalEntities.values()) {
|
||
|
this.analyze(exportedEntity);
|
||
|
}
|
||
|
}
|
||
|
_analyzeAstSymbol(astSymbol) {
|
||
|
if (astSymbol.analyzed) {
|
||
|
return;
|
||
|
}
|
||
|
if (astSymbol.nominalAnalysis) {
|
||
|
// We don't analyze nominal symbols
|
||
|
astSymbol._notifyAnalyzed();
|
||
|
return;
|
||
|
}
|
||
|
// Start at the root of the tree
|
||
|
const rootAstSymbol = astSymbol.rootAstSymbol;
|
||
|
// Calculate the full child tree for each definition
|
||
|
for (const astDeclaration of rootAstSymbol.astDeclarations) {
|
||
|
this._analyzeChildTree(astDeclaration.declaration, astDeclaration);
|
||
|
}
|
||
|
rootAstSymbol._notifyAnalyzed();
|
||
|
if (!astSymbol.isExternal) {
|
||
|
// If this symbol is non-external (i.e. it belongs to the working package), then we also analyze any
|
||
|
// referencedAstSymbols that are non-external. For example, this ensures that forgotten exports
|
||
|
// get analyzed.
|
||
|
rootAstSymbol.forEachDeclarationRecursive((astDeclaration) => {
|
||
|
for (const referencedAstEntity of astDeclaration.referencedAstEntities) {
|
||
|
// Walk up to the root of the tree, looking for any imports along the way
|
||
|
if (referencedAstEntity instanceof AstSymbol_1.AstSymbol) {
|
||
|
if (!referencedAstEntity.isExternal) {
|
||
|
this._analyzeAstSymbol(referencedAstEntity);
|
||
|
}
|
||
|
}
|
||
|
if (referencedAstEntity instanceof AstNamespaceImport_1.AstNamespaceImport) {
|
||
|
if (!referencedAstEntity.astModule.isExternal) {
|
||
|
this._analyzeAstNamespaceImport(referencedAstEntity);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
/**
|
||
|
* Used by analyze to recursively analyze the entire child tree.
|
||
|
*/
|
||
|
_analyzeChildTree(node, governingAstDeclaration) {
|
||
|
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 = TypeScriptHelpers_1.TypeScriptHelpers.findFirstChildNode(node, ts.SyntaxKind.Identifier);
|
||
|
if (identifierNode) {
|
||
|
let referencedAstEntity = this._entitiesByNode.get(identifierNode);
|
||
|
if (!referencedAstEntity) {
|
||
|
const symbol = 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 = 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_1.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 node_core_library_1.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 = node;
|
||
|
if (!this._entitiesByNode.has(identifierNode)) {
|
||
|
const symbol = this._typeChecker.getSymbolAtLocation(identifierNode);
|
||
|
let referencedAstEntity = undefined;
|
||
|
if (symbol === governingAstDeclaration.astSymbol.followedSymbol) {
|
||
|
referencedAstEntity = this._fetchEntityForNode(identifierNode, governingAstDeclaration);
|
||
|
}
|
||
|
this._entitiesByNode.set(identifierNode, referencedAstEntity);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case ts.SyntaxKind.ImportType:
|
||
|
{
|
||
|
const importTypeNode = node;
|
||
|
let referencedAstEntity = 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 = this._fetchAstDeclaration(node, governingAstDeclaration.astSymbol.isExternal);
|
||
|
for (const childNode of node.getChildren()) {
|
||
|
this._analyzeChildTree(childNode, newGoverningAstDeclaration || governingAstDeclaration);
|
||
|
}
|
||
|
}
|
||
|
_fetchEntityForNode(node, governingAstDeclaration) {
|
||
|
let referencedAstEntity = this._entitiesByNode.get(node);
|
||
|
if (!referencedAstEntity) {
|
||
|
if (node.kind === ts.SyntaxKind.ImportType) {
|
||
|
referencedAstEntity = this._exportAnalyzer.fetchReferencedAstEntityFromImportTypeNode(node, governingAstDeclaration.astSymbol.isExternal);
|
||
|
}
|
||
|
else {
|
||
|
const symbol = this._typeChecker.getSymbolAtLocation(node);
|
||
|
if (!symbol) {
|
||
|
throw new Error('Symbol not found for identifier: ' + node.getText());
|
||
|
}
|
||
|
referencedAstEntity = this._exportAnalyzer.fetchReferencedAstEntity(symbol, governingAstDeclaration.astSymbol.isExternal);
|
||
|
}
|
||
|
this._entitiesByNode.set(node, referencedAstEntity);
|
||
|
}
|
||
|
return referencedAstEntity;
|
||
|
}
|
||
|
_fetchAstDeclaration(node, isExternal) {
|
||
|
if (!AstDeclaration_1.AstDeclaration.isSupportedSyntaxKind(node.kind)) {
|
||
|
return undefined;
|
||
|
}
|
||
|
const symbol = TypeScriptHelpers_1.TypeScriptHelpers.getSymbolForDeclaration(node, this._typeChecker);
|
||
|
if (!symbol) {
|
||
|
throw new node_core_library_1.InternalError('Unable to find symbol for node');
|
||
|
}
|
||
|
const astSymbol = this._fetchAstSymbol({
|
||
|
followedSymbol: symbol,
|
||
|
isExternal: isExternal,
|
||
|
includeNominalAnalysis: true,
|
||
|
addIfMissing: true
|
||
|
});
|
||
|
if (!astSymbol) {
|
||
|
return undefined;
|
||
|
}
|
||
|
const astDeclaration = this._astDeclarationsByDeclaration.get(node);
|
||
|
if (!astDeclaration) {
|
||
|
throw new node_core_library_1.InternalError('Unable to find constructed AstDeclaration');
|
||
|
}
|
||
|
return astDeclaration;
|
||
|
}
|
||
|
_fetchAstSymbol(options) {
|
||
|
const followedSymbol = options.followedSymbol;
|
||
|
// Filter out symbols representing constructs that we don't care about
|
||
|
const arbitraryDeclaration = TypeScriptHelpers_1.TypeScriptHelpers.tryGetADeclaration(followedSymbol);
|
||
|
if (!arbitraryDeclaration) {
|
||
|
return undefined;
|
||
|
}
|
||
|
if (followedSymbol.flags &
|
||
|
(ts.SymbolFlags.TypeParameter | ts.SymbolFlags.TypeLiteral | ts.SymbolFlags.Transient)) {
|
||
|
if (!TypeScriptInternals_1.TypeScriptInternals.isLateBoundSymbol(followedSymbol)) {
|
||
|
return undefined;
|
||
|
}
|
||
|
}
|
||
|
// API Extractor doesn't analyze ambient declarations at all
|
||
|
if (TypeScriptHelpers_1.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_1.TypeScriptHelpers.isFollowableAlias(followedSymbol, this._typeChecker)) {
|
||
|
// We expect the caller to have already followed any aliases
|
||
|
throw new node_core_library_1.InternalError('AstSymbolTable._fetchAstSymbol() cannot be called with a symbol alias');
|
||
|
}
|
||
|
let astSymbol = this._astSymbolsBySymbol.get(followedSymbol);
|
||
|
if (!astSymbol) {
|
||
|
// None of the above lookups worked, so create a new entry...
|
||
|
let nominalAnalysis = 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 = arbitraryDeclaration.getSourceFile().fileName;
|
||
|
if (!this._packageMetadataManager.isAedocSupportedFor(followedSymbolSourceFileName)) {
|
||
|
nominalAnalysis = true;
|
||
|
if (!options.includeNominalAnalysis) {
|
||
|
return undefined;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
let parentAstSymbol = undefined;
|
||
|
if (!nominalAnalysis) {
|
||
|
for (const declaration of followedSymbol.declarations || []) {
|
||
|
if (!AstDeclaration_1.AstDeclaration.isSupportedSyntaxKind(declaration.kind)) {
|
||
|
throw new node_core_library_1.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:
|
||
|
if (arbitraryDeclaration) {
|
||
|
const arbitraryParentDeclaration = this._tryFindFirstAstDeclarationParent(arbitraryDeclaration);
|
||
|
if (arbitraryParentDeclaration) {
|
||
|
const parentSymbol = TypeScriptHelpers_1.TypeScriptHelpers.getSymbolForDeclaration(arbitraryParentDeclaration, this._typeChecker);
|
||
|
parentAstSymbol = this._fetchAstSymbol({
|
||
|
followedSymbol: parentSymbol,
|
||
|
isExternal: options.isExternal,
|
||
|
includeNominalAnalysis: false,
|
||
|
addIfMissing: true
|
||
|
});
|
||
|
if (!parentAstSymbol) {
|
||
|
throw new node_core_library_1.InternalError('Unable to construct a parent AstSymbol for ' + followedSymbol.name);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
const localName = options.localName || AstSymbolTable.getLocalNameForSymbol(followedSymbol);
|
||
|
astSymbol = new AstSymbol_1.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 = undefined;
|
||
|
if (parentAstSymbol) {
|
||
|
const parentDeclaration = this._tryFindFirstAstDeclarationParent(declaration);
|
||
|
if (!parentDeclaration) {
|
||
|
throw new node_core_library_1.InternalError('Missing parent declaration');
|
||
|
}
|
||
|
parentAstDeclaration = this._astDeclarationsByDeclaration.get(parentDeclaration);
|
||
|
if (!parentAstDeclaration) {
|
||
|
throw new node_core_library_1.InternalError('Missing parent AstDeclaration');
|
||
|
}
|
||
|
}
|
||
|
const astDeclaration = new AstDeclaration_1.AstDeclaration({
|
||
|
declaration,
|
||
|
astSymbol,
|
||
|
parent: parentAstDeclaration
|
||
|
});
|
||
|
this._astDeclarationsByDeclaration.set(declaration, astDeclaration);
|
||
|
}
|
||
|
}
|
||
|
if (options.isExternal !== astSymbol.isExternal) {
|
||
|
throw new node_core_library_1.InternalError(`Cannot assign isExternal=${options.isExternal} for` +
|
||
|
` the symbol ${astSymbol.localName} because it was previously registered` +
|
||
|
` with isExternal=${astSymbol.isExternal}`);
|
||
|
}
|
||
|
return astSymbol;
|
||
|
}
|
||
|
/**
|
||
|
* Returns the first parent satisfying isAstDeclaration(), or undefined if none is found.
|
||
|
*/
|
||
|
_tryFindFirstAstDeclarationParent(node) {
|
||
|
let currentNode = node.parent;
|
||
|
while (currentNode) {
|
||
|
if (AstDeclaration_1.AstDeclaration.isSupportedSyntaxKind(currentNode.kind)) {
|
||
|
return currentNode;
|
||
|
}
|
||
|
currentNode = currentNode.parent;
|
||
|
}
|
||
|
return undefined;
|
||
|
}
|
||
|
}
|
||
|
exports.AstSymbolTable = AstSymbolTable;
|
||
|
//# sourceMappingURL=AstSymbolTable.js.map
|