@microsoft/api-extractor
Version:
Analyze the exported API for a TypeScript library and generate reviews, documentation, and .d.ts rollups
329 lines • 17.5 kB
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 () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.DeclarationReferenceGenerator = void 0;
/* eslint-disable no-bitwise */
const ts = __importStar(require("typescript"));
const DeclarationReference_1 = require("@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference");
const node_core_library_1 = require("@rushstack/node-core-library");
const TypeScriptHelpers_1 = require("../analyzer/TypeScriptHelpers");
const TypeScriptInternals_1 = require("../analyzer/TypeScriptInternals");
const AstNamespaceImport_1 = require("../analyzer/AstNamespaceImport");
class DeclarationReferenceGenerator {
constructor(collector) {
this._collector = collector;
}
/**
* Gets the UID for a TypeScript Identifier that references a type.
*/
getDeclarationReferenceForIdentifier(node) {
const symbol = this._collector.typeChecker.getSymbolAtLocation(node);
if (symbol !== undefined) {
const isExpression = DeclarationReferenceGenerator._isInExpressionContext(node);
return (this.getDeclarationReferenceForSymbol(symbol, isExpression ? ts.SymbolFlags.Value : ts.SymbolFlags.Type) ||
this.getDeclarationReferenceForSymbol(symbol, isExpression ? ts.SymbolFlags.Type : ts.SymbolFlags.Value) ||
this.getDeclarationReferenceForSymbol(symbol, ts.SymbolFlags.Namespace));
}
}
/**
* Gets the DeclarationReference for a TypeScript Symbol for a given meaning.
*/
getDeclarationReferenceForSymbol(symbol, meaning) {
return this._symbolToDeclarationReference(symbol, meaning, /*includeModuleSymbols*/ false);
}
static _isInExpressionContext(node) {
switch (node.parent.kind) {
case ts.SyntaxKind.TypeQuery:
case ts.SyntaxKind.ComputedPropertyName:
return true;
case ts.SyntaxKind.QualifiedName:
return DeclarationReferenceGenerator._isInExpressionContext(node.parent);
default:
return false;
}
}
static _isExternalModuleSymbol(symbol) {
return (!!(symbol.flags & ts.SymbolFlags.ValueModule) &&
symbol.valueDeclaration !== undefined &&
ts.isSourceFile(symbol.valueDeclaration));
}
static _isSameSymbol(left, right) {
return (left === right ||
!!(left &&
left.valueDeclaration &&
right.valueDeclaration &&
left.valueDeclaration === right.valueDeclaration));
}
_getNavigationToSymbol(symbol) {
const declaration = TypeScriptHelpers_1.TypeScriptHelpers.tryGetADeclaration(symbol);
const sourceFile = declaration === null || declaration === void 0 ? void 0 : declaration.getSourceFile();
const parent = TypeScriptInternals_1.TypeScriptInternals.getSymbolParent(symbol);
// If it's global or from an external library, then use either Members or Exports. It's not possible for
// global symbols or external library symbols to be Locals.
const isGlobal = !!sourceFile && !ts.isExternalModule(sourceFile);
const isFromExternalLibrary = !!sourceFile && this._collector.program.isSourceFileFromExternalLibrary(sourceFile);
if (isGlobal || isFromExternalLibrary) {
if (parent &&
parent.members &&
DeclarationReferenceGenerator._isSameSymbol(parent.members.get(symbol.escapedName), symbol)) {
return DeclarationReference_1.Navigation.Members;
}
return DeclarationReference_1.Navigation.Exports;
}
// Otherwise, this symbol is from the current package. If we've found an associated consumable
// `CollectorEntity`, then use Exports. We use `consumable` here instead of `exported` because
// if the symbol is exported from a non-consumable `AstNamespaceImport`, we don't want to use
// Exports. We should use Locals instead.
const entity = this._collector.tryGetEntityForSymbol(symbol);
if (entity === null || entity === void 0 ? void 0 : entity.consumable) {
return DeclarationReference_1.Navigation.Exports;
}
// If its parent symbol is not a source file, then use either Exports or Members. If the parent symbol
// is a source file, but it wasn't exported from the package entry point (in the check above), then the
// symbol is a local, so fall through below.
if (parent && !DeclarationReferenceGenerator._isExternalModuleSymbol(parent)) {
if (parent.members &&
DeclarationReferenceGenerator._isSameSymbol(parent.members.get(symbol.escapedName), symbol)) {
return DeclarationReference_1.Navigation.Members;
}
return DeclarationReference_1.Navigation.Exports;
}
// Otherwise, we have a local symbol, so use a Locals navigation. These are either:
//
// 1. Symbols that are exported from a file module but not the package entry point.
// 2. Symbols that are not exported from their parent module.
return DeclarationReference_1.Navigation.Locals;
}
static _getMeaningOfSymbol(symbol, meaning) {
if (symbol.flags & meaning & ts.SymbolFlags.Class) {
return DeclarationReference_1.Meaning.Class;
}
if (symbol.flags & meaning & ts.SymbolFlags.Enum) {
return DeclarationReference_1.Meaning.Enum;
}
if (symbol.flags & meaning & ts.SymbolFlags.Interface) {
return DeclarationReference_1.Meaning.Interface;
}
if (symbol.flags & meaning & ts.SymbolFlags.TypeAlias) {
return DeclarationReference_1.Meaning.TypeAlias;
}
if (symbol.flags & meaning & ts.SymbolFlags.Function) {
return DeclarationReference_1.Meaning.Function;
}
if (symbol.flags & meaning & ts.SymbolFlags.Variable) {
return DeclarationReference_1.Meaning.Variable;
}
if (symbol.flags & meaning & ts.SymbolFlags.Module) {
return DeclarationReference_1.Meaning.Namespace;
}
if (symbol.flags & meaning & ts.SymbolFlags.ClassMember) {
return DeclarationReference_1.Meaning.Member;
}
if (symbol.flags & meaning & ts.SymbolFlags.Constructor) {
return DeclarationReference_1.Meaning.Constructor;
}
if (symbol.flags & meaning & ts.SymbolFlags.EnumMember) {
return DeclarationReference_1.Meaning.Member;
}
if (symbol.flags & meaning & ts.SymbolFlags.Signature) {
if (symbol.escapedName === ts.InternalSymbolName.Call) {
return DeclarationReference_1.Meaning.CallSignature;
}
if (symbol.escapedName === ts.InternalSymbolName.New) {
return DeclarationReference_1.Meaning.ConstructSignature;
}
if (symbol.escapedName === ts.InternalSymbolName.Index) {
return DeclarationReference_1.Meaning.IndexSignature;
}
}
if (symbol.flags & meaning & ts.SymbolFlags.TypeParameter) {
// This should have already been handled in `getDeclarationReferenceOfSymbol`.
throw new node_core_library_1.InternalError('Not supported.');
}
return undefined;
}
_symbolToDeclarationReference(symbol, meaning, includeModuleSymbols) {
const declaration = TypeScriptHelpers_1.TypeScriptHelpers.tryGetADeclaration(symbol);
const sourceFile = declaration === null || declaration === void 0 ? void 0 : declaration.getSourceFile();
let followedSymbol = symbol;
if (followedSymbol.flags & ts.SymbolFlags.ExportValue) {
followedSymbol = this._collector.typeChecker.getExportSymbolOfSymbol(followedSymbol);
}
if (followedSymbol.flags & ts.SymbolFlags.Alias) {
followedSymbol = this._collector.typeChecker.getAliasedSymbol(followedSymbol);
// Without this logic, we end up following the symbol `ns` in `import * as ns from './file'` to
// the actual file `file.ts`. We don't want to do this, so revert to the original symbol.
if (followedSymbol.flags & ts.SymbolFlags.ValueModule) {
followedSymbol = symbol;
}
}
if (DeclarationReferenceGenerator._isExternalModuleSymbol(followedSymbol)) {
if (!includeModuleSymbols) {
return undefined;
}
return new DeclarationReference_1.DeclarationReference(this._sourceFileToModuleSource(sourceFile));
}
// Do not generate a declaration reference for a type parameter.
if (followedSymbol.flags & ts.SymbolFlags.TypeParameter) {
return undefined;
}
let parentRef = this._getParentReference(followedSymbol);
if (!parentRef) {
return undefined;
}
let localName = followedSymbol.name;
const entity = this._collector.tryGetEntityForSymbol(followedSymbol);
if (entity === null || entity === void 0 ? void 0 : entity.nameForEmit) {
localName = entity.nameForEmit;
}
if (followedSymbol.escapedName === ts.InternalSymbolName.Constructor) {
localName = 'constructor';
}
else {
const wellKnownName = TypeScriptHelpers_1.TypeScriptHelpers.tryDecodeWellKnownSymbolName(followedSymbol.escapedName);
if (wellKnownName) {
// TypeScript binds well-known ECMAScript symbols like 'Symbol.iterator' as '__@iterator'.
// This converts a string like '__@iterator' into the property name '[Symbol.iterator]'.
localName = wellKnownName;
}
else if (TypeScriptHelpers_1.TypeScriptHelpers.isUniqueSymbolName(followedSymbol.escapedName)) {
for (const decl of followedSymbol.declarations || []) {
const declName = ts.getNameOfDeclaration(decl);
if (declName && ts.isComputedPropertyName(declName)) {
const lateName = TypeScriptHelpers_1.TypeScriptHelpers.tryGetLateBoundName(declName);
if (lateName !== undefined) {
localName = lateName;
break;
}
}
}
}
}
const navigation = this._getNavigationToSymbol(followedSymbol);
// If the symbol is a global, ensure the source is global.
if (sourceFile && !ts.isExternalModule(sourceFile) && parentRef.source !== DeclarationReference_1.GlobalSource.instance) {
parentRef = new DeclarationReference_1.DeclarationReference(DeclarationReference_1.GlobalSource.instance);
}
return parentRef
.addNavigationStep(navigation, localName)
.withMeaning(DeclarationReferenceGenerator._getMeaningOfSymbol(followedSymbol, meaning));
}
_getParentReference(symbol) {
var _a;
const declaration = TypeScriptHelpers_1.TypeScriptHelpers.tryGetADeclaration(symbol);
const sourceFile = declaration === null || declaration === void 0 ? void 0 : declaration.getSourceFile();
// Note that it's possible for a symbol to be exported from an entry point as well as one or more
// namespaces. In that case, it's not clear what to choose as its parent. Today's logic is neither
// perfect nor particularly stable to API items being renamed and shuffled around.
const entity = this._collector.tryGetEntityForSymbol(symbol);
if (entity) {
if (entity.exportedFromEntryPoint) {
return new DeclarationReference_1.DeclarationReference(this._sourceFileToModuleSource(sourceFile));
}
const firstExportingConsumableParent = entity.getFirstExportingConsumableParent();
if (firstExportingConsumableParent &&
firstExportingConsumableParent.astEntity instanceof AstNamespaceImport_1.AstNamespaceImport) {
const parentSymbol = TypeScriptInternals_1.TypeScriptInternals.tryGetSymbolForDeclaration(firstExportingConsumableParent.astEntity.declaration, this._collector.typeChecker);
if (parentSymbol) {
return this._symbolToDeclarationReference(parentSymbol, parentSymbol.flags,
/*includeModuleSymbols*/ true);
}
}
}
// Next, try to find a parent symbol via the symbol tree.
const parentSymbol = TypeScriptInternals_1.TypeScriptInternals.getSymbolParent(symbol);
if (parentSymbol) {
return this._symbolToDeclarationReference(parentSymbol, parentSymbol.flags,
/*includeModuleSymbols*/ true);
}
// If that doesn't work, try to find a parent symbol via the node tree. As far as we can tell,
// this logic is only needed for local symbols within namespaces. For example:
//
// ```
// export namespace n {
// type SomeType = number;
// export function someFunction(): SomeType { return 5; }
// }
// ```
//
// In the example above, `SomeType` doesn't have a parent symbol per the TS internal API above,
// but its reference still needs to be qualified with the parent reference for `n`.
const grandParent = (_a = declaration === null || declaration === void 0 ? void 0 : declaration.parent) === null || _a === void 0 ? void 0 : _a.parent;
if (grandParent && ts.isModuleDeclaration(grandParent)) {
const grandParentSymbol = TypeScriptInternals_1.TypeScriptInternals.tryGetSymbolForDeclaration(grandParent, this._collector.typeChecker);
if (grandParentSymbol) {
return this._symbolToDeclarationReference(grandParentSymbol, grandParentSymbol.flags,
/*includeModuleSymbols*/ true);
}
}
// At this point, we have a local symbol in a module.
if (sourceFile && ts.isExternalModule(sourceFile)) {
return new DeclarationReference_1.DeclarationReference(this._sourceFileToModuleSource(sourceFile));
}
else {
return new DeclarationReference_1.DeclarationReference(DeclarationReference_1.GlobalSource.instance);
}
}
_getPackageName(sourceFile) {
if (this._collector.program.isSourceFileFromExternalLibrary(sourceFile)) {
const packageJson = this._collector.packageJsonLookup.tryLoadNodePackageJsonFor(sourceFile.fileName);
if (packageJson && packageJson.name) {
return packageJson.name;
}
return DeclarationReferenceGenerator.unknownReference;
}
return this._collector.workingPackage.name;
}
_sourceFileToModuleSource(sourceFile) {
if (sourceFile && ts.isExternalModule(sourceFile)) {
const packageName = this._getPackageName(sourceFile);
if (this._collector.bundledPackageNames.has(packageName)) {
// The api-extractor.json config file has a "bundledPackages" setting, which causes imports from
// certain NPM packages to be treated as part of the working project. In this case, we need to
// substitute the working package name.
return new DeclarationReference_1.ModuleSource(this._collector.workingPackage.name);
}
else {
return new DeclarationReference_1.ModuleSource(packageName);
}
}
return DeclarationReference_1.GlobalSource.instance;
}
}
exports.DeclarationReferenceGenerator = DeclarationReferenceGenerator;
DeclarationReferenceGenerator.unknownReference = '?';
//# sourceMappingURL=DeclarationReferenceGenerator.js.map