UNPKG

@microsoft/api-extractor

Version:

Validate, document, and review the exported API for a TypeScript library

256 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. Object.defineProperty(exports, "__esModule", { value: true }); /* tslint:disable:no-bitwise */ const ts = require("typescript"); const TypeScriptHelpers_1 = require("../../utils/TypeScriptHelpers"); const AstImport_1 = require("./AstImport"); /** * This is a helper class for DtsRollupGenerator and AstSymbolTable. * Its main role is to provide an expanded version of TypeScriptHelpers.followAliases() * that supports tracking of imports from eternal packages. */ class SymbolAnalyzer { /** * This function determines which ts.Node kinds will generate an AstDeclaration. * These correspond to the definitions that we can add AEDoc to. */ static isAstDeclaration(kind) { // (alphabetical order) switch (kind) { case ts.SyntaxKind.ClassDeclaration: case ts.SyntaxKind.Constructor: // Example: new(x: number); case ts.SyntaxKind.ConstructSignature: // Example: new(x: number); case ts.SyntaxKind.EnumDeclaration: case ts.SyntaxKind.EnumMember: case ts.SyntaxKind.FunctionDeclaration: case ts.SyntaxKind.IndexSignature: // Example: [key: string]: string case ts.SyntaxKind.InterfaceDeclaration: case ts.SyntaxKind.MethodDeclaration: case ts.SyntaxKind.MethodSignature: // ModuleDeclaration is used for both "module" and "namespace" declarations case ts.SyntaxKind.ModuleDeclaration: case ts.SyntaxKind.PropertyDeclaration: case ts.SyntaxKind.PropertySignature: case ts.SyntaxKind.TypeAliasDeclaration: case ts.SyntaxKind.VariableDeclaration: return true; } return false; } /** * For the given symbol, follow imports and type alias to find the symbol that represents * the original definition. */ static followAliases(symbol, typeChecker) { let current = symbol; // We will try to obtain the name from a declaration; otherwise we'll fall back to the symbol name let declarationName = undefined; while (true) { for (const declaration of current.declarations || []) { const declarationNameIdentifier = ts.getNameOfDeclaration(declaration); if (declarationNameIdentifier && ts.isIdentifier(declarationNameIdentifier)) { declarationName = declarationNameIdentifier.getText().trim(); } // 2. Check for any signs that this was imported from an external package let result; result = SymbolAnalyzer._followAliasesForExportDeclaration(declaration, current, typeChecker); if (result) { return result; } result = SymbolAnalyzer._followAliasesForImportDeclaration(declaration, current, typeChecker); if (result) { return result; } } if (!(current.flags & ts.SymbolFlags.Alias)) { break; } const currentAlias = TypeScriptHelpers_1.TypeScriptHelpers.getImmediateAliasedSymbol(current, typeChecker); // Stop if we reach the end of the chain if (!currentAlias || currentAlias === current) { break; } current = currentAlias; } // Is this an ambient declaration? let isAmbient = true; if (current.declarations) { // Test 1: Are we inside the sinister "declare global {" construct? let insideDeclareGlobal = false; const highestModuleDeclaration = TypeScriptHelpers_1.TypeScriptHelpers.findHighestParent(current.declarations[0], ts.SyntaxKind.ModuleDeclaration); if (highestModuleDeclaration) { if (highestModuleDeclaration.name.getText().trim() === 'global') { insideDeclareGlobal = 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). if (!insideDeclareGlobal) { const sourceFileNode = TypeScriptHelpers_1.TypeScriptHelpers.findFirstParent(current.declarations[0], ts.SyntaxKind.SourceFile); if (sourceFileNode && !!typeChecker.getSymbolAtLocation(sourceFileNode)) { isAmbient = false; } } } return { followedSymbol: current, localName: declarationName || current.name, astImport: undefined, isAmbient: isAmbient }; } /** * Helper function for _followAliases(), for handling ts.ExportDeclaration patterns */ static _followAliasesForExportDeclaration(declaration, symbol, typeChecker) { const exportDeclaration = TypeScriptHelpers_1.TypeScriptHelpers.findFirstParent(declaration, ts.SyntaxKind.ExportDeclaration); if (exportDeclaration) { let exportName; if (declaration.kind === ts.SyntaxKind.ExportSpecifier) { // EXAMPLE: // "export { A } from './file-a';" // // ExportDeclaration: // ExportKeyword: pre=[export] sep=[ ] // NamedExports: // FirstPunctuation: pre=[{] sep=[ ] // SyntaxList: // ExportSpecifier: <------------- declaration // Identifier: pre=[A] sep=[ ] // CloseBraceToken: pre=[}] sep=[ ] // FromKeyword: pre=[from] sep=[ ] // StringLiteral: pre=['./file-a'] // SemicolonToken: pre=[;] // Example: " ExportName as RenamedName" const exportSpecifier = declaration; exportName = (exportSpecifier.propertyName || exportSpecifier.name).getText().trim(); } else { throw new Error('Unimplemented export declaration kind: ' + declaration.getText()); } if (exportDeclaration.moduleSpecifier) { // Examples: // " '@microsoft/sp-lodash-subset'" // " "lodash/has"" const modulePath = SymbolAnalyzer._getPackagePathFromModuleSpecifier(exportDeclaration.moduleSpecifier); if (modulePath) { return { followedSymbol: TypeScriptHelpers_1.TypeScriptHelpers.followAliases(symbol, typeChecker), localName: exportName, astImport: new AstImport_1.AstImport({ modulePath, exportName }), isAmbient: false }; } } } return undefined; } /** * Helper function for _followAliases(), for handling ts.ImportDeclaration patterns */ static _followAliasesForImportDeclaration(declaration, symbol, typeChecker) { const importDeclaration = TypeScriptHelpers_1.TypeScriptHelpers.findFirstParent(declaration, ts.SyntaxKind.ImportDeclaration); if (importDeclaration) { let exportName; if (declaration.kind === ts.SyntaxKind.ImportSpecifier) { // EXAMPLE: // "import { A, B } from 'the-lib';" // // ImportDeclaration: // ImportKeyword: pre=[import] sep=[ ] // ImportClause: // NamedImports: // FirstPunctuation: pre=[{] sep=[ ] // SyntaxList: // ImportSpecifier: <------------- declaration // Identifier: pre=[A] // CommaToken: pre=[,] sep=[ ] // ImportSpecifier: // Identifier: pre=[B] sep=[ ] // CloseBraceToken: pre=[}] sep=[ ] // FromKeyword: pre=[from] sep=[ ] // StringLiteral: pre=['the-lib'] // SemicolonToken: pre=[;] // Example: " ExportName as RenamedName" const importSpecifier = declaration; exportName = (importSpecifier.propertyName || importSpecifier.name).getText().trim(); } else if (declaration.kind === ts.SyntaxKind.NamespaceImport) { // EXAMPLE: // "import * as theLib from 'the-lib';" // // ImportDeclaration: // ImportKeyword: pre=[import] sep=[ ] // ImportClause: // NamespaceImport: <------------- declaration // AsteriskToken: pre=[*] sep=[ ] // AsKeyword: pre=[as] sep=[ ] // Identifier: pre=[theLib] sep=[ ] // FromKeyword: pre=[from] sep=[ ] // StringLiteral: pre=['the-lib'] // SemicolonToken: pre=[;] exportName = '*'; } else if (declaration.kind === ts.SyntaxKind.ImportClause) { // EXAMPLE: // "import A, { B } from './A';" // // ImportDeclaration: // ImportKeyword: pre=[import] sep=[ ] // ImportClause: <------------- declaration (referring to A) // Identifier: pre=[A] // CommaToken: pre=[,] sep=[ ] // NamedImports: // FirstPunctuation: pre=[{] sep=[ ] // SyntaxList: // ImportSpecifier: // Identifier: pre=[B] sep=[ ] // CloseBraceToken: pre=[}] sep=[ ] // FromKeyword: pre=[from] sep=[ ] // StringLiteral: pre=['./A'] // SemicolonToken: pre=[;] exportName = 'default'; } else { throw new Error('Unimplemented import declaration kind: ' + declaration.getText()); } if (importDeclaration.moduleSpecifier) { // Examples: // " '@microsoft/sp-lodash-subset'" // " "lodash/has"" const modulePath = SymbolAnalyzer._getPackagePathFromModuleSpecifier(importDeclaration.moduleSpecifier); if (modulePath) { return { followedSymbol: TypeScriptHelpers_1.TypeScriptHelpers.followAliases(symbol, typeChecker), localName: symbol.name, astImport: new AstImport_1.AstImport({ modulePath, exportName }), isAmbient: false }; } } } return undefined; } static _getPackagePathFromModuleSpecifier(moduleSpecifier) { // Examples: // " '@microsoft/sp-lodash-subset'" // " "lodash/has"" // " './MyClass'" const moduleSpecifierText = moduleSpecifier.getFullText(); // Remove quotes/whitespace const path = moduleSpecifierText .replace(/^\s*['"]/, '') .replace(/['"]\s*$/, ''); // Does it start with something like "./" or "../"? // If not, then assume it's an import from an external package if (!/^\.\.?\//.test(path)) { return path; } return undefined; } } exports.SymbolAnalyzer = SymbolAnalyzer; //# sourceMappingURL=SymbolAnalyzer.js.map