UNPKG

@microsoft/api-extractor

Version:

Analyze the exported API for a TypeScript library and generate reviews, documentation, and .d.ts rollups

279 lines 12.3 kB
"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.TypeScriptHelpers = void 0; /* eslint-disable no-bitwise */ const ts = __importStar(require("typescript")); const SourceFileLocationFormatter_1 = require("./SourceFileLocationFormatter"); const TypeScriptInternals_1 = require("./TypeScriptInternals"); const node_core_library_1 = require("@rushstack/node-core-library"); class TypeScriptHelpers { /** * This traverses any symbol aliases to find the original place where an item was defined. * For example, suppose a class is defined as "export default class MyClass { }" * but exported from the package's index.ts like this: * * export { default as _MyClass } from './MyClass'; * * In this example, calling followAliases() on the _MyClass symbol will return the * original definition of MyClass, traversing any intermediary places where the * symbol was imported and re-exported. */ static followAliases(symbol, typeChecker) { let current = symbol; for (;;) { if (!(current.flags & ts.SymbolFlags.Alias)) { break; } const currentAlias = typeChecker.getAliasedSymbol(current); if (!currentAlias || currentAlias === current) { break; } current = currentAlias; } return current; } /** * Returns true if TypeScriptHelpers.followAliases() would return something different * from the input `symbol`. */ static isFollowableAlias(symbol, typeChecker) { if (!(symbol.flags & ts.SymbolFlags.Alias)) { return false; } const alias = typeChecker.getAliasedSymbol(symbol); if (!alias || alias === symbol) { return false; } return true; } /** * Certain virtual symbols do not have any declarations. For example, `ts.TypeChecker.getExportsOfModule()` can * sometimes return a "prototype" symbol for an object, even though there is no corresponding declaration in the * source code. API Extractor generally ignores such symbols. */ static tryGetADeclaration(symbol) { if (symbol.declarations && symbol.declarations.length > 0) { return symbol.declarations[0]; } return undefined; } /** * Returns true if the specified symbol is an ambient declaration. */ static isAmbient(symbol, typeChecker) { const followedSymbol = TypeScriptHelpers.followAliases(symbol, typeChecker); if (followedSymbol.declarations && followedSymbol.declarations.length > 0) { const firstDeclaration = followedSymbol.declarations[0]; // Test 1: Are we inside the sinister "declare global {" construct? const highestModuleDeclaration = TypeScriptHelpers.findHighestParent(firstDeclaration, ts.SyntaxKind.ModuleDeclaration); if (highestModuleDeclaration) { if (highestModuleDeclaration.name.getText().trim() === 'global') { return true; } } // Test 2: Otherwise, the main heuristic for ambient declarations is by looking at the // ts.SyntaxKind.SourceFile node to see whether it has a symbol or not (i.e. whether it // is acting as a module or not). const sourceFile = firstDeclaration.getSourceFile(); if (typeChecker.getSymbolAtLocation(sourceFile)) { return false; } } return true; } /** * Same semantics as tryGetSymbolForDeclaration(), but throws an exception if the symbol * cannot be found. */ static getSymbolForDeclaration(declaration, checker) { const symbol = TypeScriptInternals_1.TypeScriptInternals.tryGetSymbolForDeclaration(declaration, checker); if (!symbol) { throw new node_core_library_1.InternalError('Unable to determine semantic information for declaration:\n' + SourceFileLocationFormatter_1.SourceFileLocationFormatter.formatDeclaration(declaration)); } return symbol; } // Return name of the module, which could be like "./SomeLocalFile' or like 'external-package/entry/point' static getModuleSpecifier(nodeWithModuleSpecifier) { if (nodeWithModuleSpecifier.kind === ts.SyntaxKind.ImportType) { // As specified internally in typescript:/src/compiler/types.ts#ValidImportTypeNode if (nodeWithModuleSpecifier.argument.kind !== ts.SyntaxKind.LiteralType || nodeWithModuleSpecifier.argument.literal.kind !== ts.SyntaxKind.StringLiteral) { throw new node_core_library_1.InternalError(`Invalid ImportTypeNode: ${nodeWithModuleSpecifier.getText()}\n` + SourceFileLocationFormatter_1.SourceFileLocationFormatter.formatDeclaration(nodeWithModuleSpecifier)); } const literalTypeNode = nodeWithModuleSpecifier.argument; const stringLiteral = literalTypeNode.literal; return stringLiteral.text.trim(); } // Node is a declaration if (nodeWithModuleSpecifier.moduleSpecifier && ts.isStringLiteralLike(nodeWithModuleSpecifier.moduleSpecifier)) { return TypeScriptInternals_1.TypeScriptInternals.getTextOfIdentifierOrLiteral(nodeWithModuleSpecifier.moduleSpecifier); } return undefined; } /** * Returns an ancestor of "node", such that the ancestor, any intermediary nodes, * and the starting node match a list of expected kinds. Undefined is returned * if there aren't enough ancestors, or if the kinds are incorrect. * * For example, suppose child "C" has parents A --> B --> C. * * Calling _matchAncestor(C, [ExportSpecifier, NamedExports, ExportDeclaration]) * would return A only if A is of kind ExportSpecifier, B is of kind NamedExports, * and C is of kind ExportDeclaration. * * Calling _matchAncestor(C, [ExportDeclaration]) would return C. */ static matchAncestor(node, kindsToMatch) { // (slice(0) clones an array) const reversedParentKinds = kindsToMatch.slice(0).reverse(); let current = undefined; for (const parentKind of reversedParentKinds) { if (!current) { // The first time through, start with node current = node; } else { // Then walk the parents current = current.parent; } // If we ran out of items, or if the kind doesn't match, then fail if (!current || current.kind !== parentKind) { return undefined; } } // If we matched everything, then return the node that matched the last parentKinds item return current; } /** * Does a depth-first search of the children of the specified node. Returns the first child * with the specified kind, or undefined if there is no match. */ static findFirstChildNode(node, kindToMatch) { for (const child of node.getChildren()) { if (child.kind === kindToMatch) { return child; } const recursiveMatch = TypeScriptHelpers.findFirstChildNode(child, kindToMatch); if (recursiveMatch) { return recursiveMatch; } } return undefined; } /** * Returns the first parent node with the specified SyntaxKind, or undefined if there is no match. */ static findFirstParent(node, kindToMatch) { let current = node.parent; while (current) { if (current.kind === kindToMatch) { return current; } current = current.parent; } return undefined; } /** * Returns the highest parent node with the specified SyntaxKind, or undefined if there is no match. * @remarks * Whereas findFirstParent() returns the first match, findHighestParent() returns the last match. */ static findHighestParent(node, kindToMatch) { let current = node; let highest = undefined; for (;;) { current = TypeScriptHelpers.findFirstParent(current, kindToMatch); if (!current) { break; } highest = current; } return highest; } /** * Decodes the names that the compiler generates for a built-in ECMAScript symbol. * * @remarks * TypeScript binds well-known ECMAScript symbols like `[Symbol.iterator]` as `__@iterator`. * If `name` is of this form, then `tryGetWellKnownSymbolName()` converts it back into e.g. `[Symbol.iterator]`. * If the string does not start with `__@` then `undefined` is returned. */ static tryDecodeWellKnownSymbolName(name) { const match = TypeScriptHelpers._wellKnownSymbolNameRegExp.exec(name); if (match) { const identifier = match[1]; return `[Symbol.${identifier}]`; } return undefined; } /** * Returns whether the provided name was generated for a TypeScript `unique symbol`. */ static isUniqueSymbolName(name) { return TypeScriptHelpers._uniqueSymbolNameRegExp.test(name); } /** * Derives the string representation of a TypeScript late-bound symbol. */ static tryGetLateBoundName(declarationName) { // Create a node printer that ignores comments and indentation that we can use to convert // declarationName to a string. const printer = ts.createPrinter({ removeComments: true }, { onEmitNode(hint, node, emitCallback) { ts.setEmitFlags(declarationName, ts.EmitFlags.NoIndentation | ts.EmitFlags.SingleLine); emitCallback(hint, node); } }); const sourceFile = declarationName.getSourceFile(); const text = printer.printNode(ts.EmitHint.Unspecified, declarationName, sourceFile); // clean up any emit flags we've set on any nodes in the tree. ts.disposeEmitNodes(sourceFile); return text; } } exports.TypeScriptHelpers = TypeScriptHelpers; // Matches TypeScript's encoded names for well-known ECMAScript symbols like // "__@iterator" or "__@toStringTag". TypeScriptHelpers._wellKnownSymbolNameRegExp = /^__@(\w+)$/; // Matches TypeScript's encoded names for late-bound symbols derived from `unique symbol` declarations // which have the form of "__@<variableName>@<symbolId>", i.e. "__@someSymbol@12345". TypeScriptHelpers._uniqueSymbolNameRegExp = /^__@.*@\d+$/; //# sourceMappingURL=TypeScriptHelpers.js.map