UNPKG

dts-bundle-generator

Version:
492 lines (491 loc) 22.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.resolveGlobalName = exports.getDeclarationsForExportedValues = exports.getSymbolExportStarDeclarations = exports.getImportExportReferencedSymbol = exports.getImportModuleName = exports.resolveReferencedModule = exports.getClosestSourceFileLikeNode = exports.getClosestModuleLikeNode = exports.getNodeSymbol = exports.getNodeOwnSymbol = exports.getRootSourceFile = exports.getModifiers = exports.recreateRootLevelNodeWithModifiers = exports.modifiersMapToArray = exports.modifiersToMap = exports.getExportsForStatement = exports.resolveIdentifier = exports.getExportsForSourceFile = exports.getDeclarationsForSymbol = exports.isDeclareGlobalStatement = exports.isDeclareModule = exports.isAmbientModule = exports.splitTransientSymbol = exports.getDeclarationNameSymbol = exports.getActualSymbol = exports.getNodeName = exports.hasNodeModifier = exports.isNodeNamedDeclaration = void 0; const ts = require("typescript"); const logger_1 = require("../logger"); const namedDeclarationKinds = [ ts.SyntaxKind.InterfaceDeclaration, ts.SyntaxKind.ClassDeclaration, ts.SyntaxKind.EnumDeclaration, ts.SyntaxKind.TypeAliasDeclaration, ts.SyntaxKind.ModuleDeclaration, ts.SyntaxKind.FunctionDeclaration, ts.SyntaxKind.VariableDeclaration, ts.SyntaxKind.PropertySignature, ts.SyntaxKind.NamespaceExport, ts.SyntaxKind.NamespaceImport, ts.SyntaxKind.ExportSpecifier, ts.SyntaxKind.BindingElement, ]; function isNodeNamedDeclaration(node) { return namedDeclarationKinds.indexOf(node.kind) !== -1; } exports.isNodeNamedDeclaration = isNodeNamedDeclaration; function hasNodeModifier(node, modifier) { const modifiers = getModifiers(node); return Boolean(modifiers && modifiers.some((nodeModifier) => nodeModifier.kind === modifier)); } exports.hasNodeModifier = hasNodeModifier; function getNodeName(node) { const nodeName = node.name; if (nodeName === undefined) { const modifiers = getModifiers(node); const defaultModifier = modifiers?.find((mod) => mod.kind === ts.SyntaxKind.DefaultKeyword); if (defaultModifier !== undefined) { return defaultModifier; } } return nodeName; } exports.getNodeName = getNodeName; function getActualSymbol(symbol, typeChecker) { if (symbol.flags & ts.SymbolFlags.Alias) { symbol = typeChecker.getAliasedSymbol(symbol); } return typeChecker.getMergedSymbol(symbol); } exports.getActualSymbol = getActualSymbol; function getDeclarationNameSymbol(name, typeChecker) { const symbol = typeChecker.getSymbolAtLocation(name); if (symbol === undefined) { return null; } return getActualSymbol(symbol, typeChecker); } exports.getDeclarationNameSymbol = getDeclarationNameSymbol; function splitTransientSymbol(symbol, typeChecker) { // actually I think we even don't need to operate/use "Transient" symbols anywhere // it's kind of aliased symbol, but just merged // but it's hard to refractor everything to use array of symbols instead of just symbol // so let's fix it for some places if ((symbol.flags & ts.SymbolFlags.Transient) === 0) { return new Set([symbol]); } // "Transient" symbol is kinda "merged" symbol // I don't really know is this way to "split" is correct // but it seems that it works for now ¯\_(ツ)_/¯ const declarations = getDeclarationsForSymbol(symbol); const result = new Set(); for (const declaration of declarations) { if (!isNodeNamedDeclaration(declaration) || declaration.name === undefined) { continue; } const sym = typeChecker.getSymbolAtLocation(declaration.name); if (sym === undefined) { continue; } result.add(getActualSymbol(sym, typeChecker)); } return result; } exports.splitTransientSymbol = splitTransientSymbol; /** * @see https://github.com/Microsoft/TypeScript/blob/f7c4fefeb62416c311077a699cc15beb211c25c9/src/compiler/utilities.ts#L626-L628 */ function isGlobalScopeAugmentation(module) { return Boolean(module.flags & ts.NodeFlags.GlobalAugmentation); } /** * Returns whether node is ambient module declaration (declare module "name" or declare global) * @see https://github.com/Microsoft/TypeScript/blob/f7c4fefeb62416c311077a699cc15beb211c25c9/src/compiler/utilities.ts#L588-L590 */ function isAmbientModule(node) { return ts.isModuleDeclaration(node) && (node.name.kind === ts.SyntaxKind.StringLiteral || isGlobalScopeAugmentation(node)); } exports.isAmbientModule = isAmbientModule; /** * Returns whether node is `declare module` ModuleDeclaration (not `declare global` or `namespace`) */ function isDeclareModule(node) { // `declare module ""`, `declare global` and `namespace {}` are ModuleDeclaration // but here we need to check only `declare module` statements return ts.isModuleDeclaration(node) && !(node.flags & ts.NodeFlags.Namespace) && !isGlobalScopeAugmentation(node); } exports.isDeclareModule = isDeclareModule; /** * Returns whether statement is `declare global` ModuleDeclaration */ function isDeclareGlobalStatement(statement) { return ts.isModuleDeclaration(statement) && isGlobalScopeAugmentation(statement); } exports.isDeclareGlobalStatement = isDeclareGlobalStatement; function getDeclarationsForSymbol(symbol) { const result = []; if (symbol.declarations !== undefined) { result.push(...symbol.declarations); } if (symbol.valueDeclaration !== undefined) { // push valueDeclaration might be already in declarations array // so let's check first to avoid duplication nodes if (!result.includes(symbol.valueDeclaration)) { result.push(symbol.valueDeclaration); } } return result; } exports.getDeclarationsForSymbol = getDeclarationsForSymbol; function getExportsForSourceFile(typeChecker, sourceFileSymbol) { if (sourceFileSymbol.exports !== undefined) { const commonJsExport = sourceFileSymbol.exports.get(ts.InternalSymbolName.ExportEquals); if (commonJsExport !== undefined) { const symbol = getActualSymbol(commonJsExport, typeChecker); return [ { symbol, originalSymbol: commonJsExport, type: 0 /* ExportType.CommonJS */, exportedName: '', }, ]; } } const result = typeChecker .getExportsOfModule(sourceFileSymbol) .map((symbol) => ({ symbol, originalSymbol: symbol, exportedName: symbol.name, type: 1 /* ExportType.ES6Named */ })); if (sourceFileSymbol.exports !== undefined) { const defaultExportSymbol = sourceFileSymbol.exports.get(ts.InternalSymbolName.Default); if (defaultExportSymbol !== undefined) { const defaultExport = result.find((exp) => exp.symbol === defaultExportSymbol); if (defaultExport !== undefined) { defaultExport.type = 2 /* ExportType.ES6Default */; } else { // it seems that default export is always returned by getExportsOfModule // but let's add it to be sure add if there is no such export result.push({ symbol: defaultExportSymbol, originalSymbol: defaultExportSymbol, type: 2 /* ExportType.ES6Default */, exportedName: 'default', }); } } } const symbolsMergingResolvedExports = []; result.forEach((exp) => { exp.symbol = getActualSymbol(exp.symbol, typeChecker); const symbolsDeclarations = getDeclarationsForSymbol(exp.symbol); const importSpecifierDeclaration = symbolsDeclarations.find(ts.isImportSpecifier); if (symbolsDeclarations.length > 1 && importSpecifierDeclaration !== undefined) { // most likely this export is part of the symbol merging situation // where one of the declarations is the imported value but the other is declared locally // in this case we need to add an extra export to the exports list to make sure that it is marked as "exported" // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion const referencedModule = resolveReferencedModule(importSpecifierDeclaration.parent.parent.parent, typeChecker); if (referencedModule !== null) { const referencedModuleSymbol = getNodeSymbol(referencedModule, typeChecker); if (referencedModuleSymbol !== null) { const importedName = (importSpecifierDeclaration.propertyName ?? importSpecifierDeclaration.name).getText(); const exportedItemSymbol = typeChecker.getExportsOfModule(referencedModuleSymbol).find((exportSymbol) => exportSymbol.getName() === importedName); if (exportedItemSymbol !== undefined) { symbolsMergingResolvedExports.push({ ...exp, symbol: getActualSymbol(exportedItemSymbol, typeChecker), }); } } } } }); return [...result, ...symbolsMergingResolvedExports]; } exports.getExportsForSourceFile = getExportsForSourceFile; function resolveIdentifier(typeChecker, identifier) { const symbol = getDeclarationNameSymbol(identifier, typeChecker); if (symbol === null) { return undefined; } return resolveDeclarationByIdentifierSymbol(symbol); } exports.resolveIdentifier = resolveIdentifier; function resolveDeclarationByIdentifierSymbol(identifierSymbol) { const declarations = getDeclarationsForSymbol(identifierSymbol); if (declarations.length === 0) { return undefined; } const decl = declarations[0]; if (!isNodeNamedDeclaration(decl)) { return undefined; } return decl; } function getExportsForStatement(exportedSymbols, typeChecker, statement) { if (ts.isVariableStatement(statement)) { if (statement.declarationList.declarations.length === 0) { return []; } const firstDeclarationExports = getExportsForName(exportedSymbols, typeChecker, statement.declarationList.declarations[0].name); const allDeclarationsHaveSameExportType = statement.declarationList.declarations.every((variableDecl) => { // all declaration should have the same export type // TODO: for now it's not supported to have different type of exports return getExportsForName(exportedSymbols, typeChecker, variableDecl.name)[0]?.type === firstDeclarationExports[0]?.type; }); if (!allDeclarationsHaveSameExportType) { // log warn? return []; } return firstDeclarationExports; } const nodeName = getNodeName(statement); if (nodeName === undefined) { return []; } return getExportsForName(exportedSymbols, typeChecker, nodeName); } exports.getExportsForStatement = getExportsForStatement; function getExportsForName(exportedSymbols, typeChecker, name) { if (ts.isArrayBindingPattern(name) || ts.isObjectBindingPattern(name)) { // TODO: binding patterns in variable declarations are not supported for now // see https://github.com/microsoft/TypeScript/issues/30598 also return []; } const declarationSymbol = typeChecker.getSymbolAtLocation(name); return exportedSymbols.filter((rootExport) => rootExport.symbol === declarationSymbol); } const modifiersPriority = { [ts.SyntaxKind.ExportKeyword]: 4, [ts.SyntaxKind.DefaultKeyword]: 3, [ts.SyntaxKind.DeclareKeyword]: 2, [ts.SyntaxKind.AsyncKeyword]: 1, [ts.SyntaxKind.ConstKeyword]: 1, }; function modifiersToMap(modifiers) { modifiers = modifiers || []; return modifiers.reduce((result, modifier) => { result[modifier.kind] = true; return result; }, // eslint-disable-next-line @typescript-eslint/consistent-type-assertions {}); } exports.modifiersToMap = modifiersToMap; function modifiersMapToArray(modifiersMap) { return Object.entries(modifiersMap) .filter(([kind, include]) => include) .map(([kind]) => { // we don't care about decorators here as it is not possible to have them in declaration files return ts.factory.createModifier(Number(kind)); }) .sort((a, b) => { // note `|| 0` is here as a fallback in the case if the compiler adds a new modifier // but the tool isn't updated yet const aValue = modifiersPriority[a.kind] || 0; const bValue = modifiersPriority[b.kind] || 0; return bValue - aValue; }); } exports.modifiersMapToArray = modifiersMapToArray; function recreateRootLevelNodeWithModifiers(node, modifiersMap, newName, keepComments = true) { const newNode = recreateRootLevelNodeWithModifiersImpl(node, modifiersMap, newName); if (keepComments) { ts.setCommentRange(newNode, ts.getCommentRange(node)); } return newNode; } exports.recreateRootLevelNodeWithModifiers = recreateRootLevelNodeWithModifiers; // eslint-disable-next-line complexity function recreateRootLevelNodeWithModifiersImpl(node, modifiersMap, newName) { const modifiers = modifiersMapToArray(modifiersMap); if (ts.isArrowFunction(node)) { return ts.factory.createArrowFunction(modifiers, node.typeParameters, node.parameters, node.type, node.equalsGreaterThanToken, node.body); } if (ts.isClassDeclaration(node)) { return ts.factory.createClassDeclaration(modifiers, newName || node.name, node.typeParameters, node.heritageClauses, node.members); } if (ts.isClassExpression(node)) { return ts.factory.createClassExpression(modifiers, newName || node.name, node.typeParameters, node.heritageClauses, node.members); } if (ts.isEnumDeclaration(node)) { return ts.factory.createEnumDeclaration(modifiers, newName || node.name, node.members); } if (ts.isExportAssignment(node)) { return ts.factory.createExportAssignment(modifiers, node.isExportEquals, node.expression); } if (ts.isExportDeclaration(node)) { return ts.factory.createExportDeclaration(modifiers, node.isTypeOnly, node.exportClause, node.moduleSpecifier, // eslint-disable-next-line deprecation/deprecation node.attributes || node.assertClause); } if (ts.isFunctionDeclaration(node)) { return ts.factory.createFunctionDeclaration(modifiers, node.asteriskToken, newName || node.name, node.typeParameters, node.parameters, node.type, node.body); } if (ts.isFunctionExpression(node)) { return ts.factory.createFunctionExpression(modifiers, node.asteriskToken, newName || node.name, node.typeParameters, node.parameters, node.type, node.body); } if (ts.isImportDeclaration(node)) { return ts.factory.createImportDeclaration(modifiers, node.importClause, node.moduleSpecifier, // eslint-disable-next-line deprecation/deprecation node.attributes || node.assertClause); } if (ts.isImportEqualsDeclaration(node)) { return ts.factory.createImportEqualsDeclaration(modifiers, node.isTypeOnly, newName || node.name, node.moduleReference); } if (ts.isInterfaceDeclaration(node)) { return ts.factory.createInterfaceDeclaration(modifiers, newName || node.name, node.typeParameters, node.heritageClauses, node.members); } if (ts.isModuleDeclaration(node)) { return ts.factory.createModuleDeclaration(modifiers, node.name, node.body, node.flags); } if (ts.isTypeAliasDeclaration(node)) { return ts.factory.createTypeAliasDeclaration(modifiers, newName || node.name, node.typeParameters, node.type); } if (ts.isVariableStatement(node)) { return ts.factory.createVariableStatement(modifiers, node.declarationList); } throw new Error(`Unknown top-level node kind (with modifiers): ${ts.SyntaxKind[node.kind]}. If you're seeing this error, please report a bug on https://github.com/timocov/dts-bundle-generator/issues`); } function getModifiers(node) { if (!ts.canHaveModifiers(node)) { return undefined; } return ts.getModifiers(node); } exports.getModifiers = getModifiers; function getRootSourceFile(program, rootFileName) { if (program.getRootFileNames().indexOf(rootFileName) === -1) { throw new Error(`There is no such root file ${rootFileName}`); } const sourceFile = program.getSourceFile(rootFileName); if (sourceFile === undefined) { throw new Error(`Cannot get source file for root file ${rootFileName}`); } return sourceFile; } exports.getRootSourceFile = getRootSourceFile; function getNodeOwnSymbol(node, typeChecker) { const nodeSymbol = typeChecker.getSymbolAtLocation(node); if (nodeSymbol === undefined) { throw new Error(`Cannot find symbol for node "${node.getText()}" in "${node.parent.getText()}" from "${node.getSourceFile().fileName}"`); } return nodeSymbol; } exports.getNodeOwnSymbol = getNodeOwnSymbol; function getNodeSymbol(node, typeChecker) { if (ts.isSourceFile(node)) { const fileSymbol = typeChecker.getSymbolAtLocation(node); // a source file might not have a symbol in case of no exports in that file if (fileSymbol === undefined) { return null; } return getActualSymbol(fileSymbol, typeChecker); } const nodeName = getNodeName(node); if (nodeName === undefined) { return null; } return getDeclarationNameSymbol(nodeName, typeChecker); } exports.getNodeSymbol = getNodeSymbol; function getClosestModuleLikeNode(node) { // we need to find a module block and return its module declaration // we don't need to handle empty modules/modules with jsdoc/etc while (!ts.isModuleBlock(node) && !ts.isSourceFile(node)) { node = node.parent; } return ts.isSourceFile(node) ? node : node.parent; } exports.getClosestModuleLikeNode = getClosestModuleLikeNode; function getClosestSourceFileLikeNode(node) { // we need to find a module block and return its module declaration // we don't need to handle empty modules/modules with jsdoc/etc while (!(ts.isModuleBlock(node) && ts.isStringLiteral(node.parent.name)) && !ts.isSourceFile(node)) { node = node.parent; } return ts.isSourceFile(node) ? node : node.parent; } exports.getClosestSourceFileLikeNode = getClosestSourceFileLikeNode; function resolveReferencedModule(node, typeChecker) { let moduleName; if (ts.isExportDeclaration(node) || ts.isImportDeclaration(node)) { moduleName = node.moduleSpecifier; } else if (ts.isModuleDeclaration(node)) { moduleName = node.name; } else if (ts.isImportEqualsDeclaration(node)) { if (ts.isExternalModuleReference(node.moduleReference)) { moduleName = node.moduleReference.expression; } } else if (ts.isLiteralTypeNode(node.argument) && ts.isStringLiteral(node.argument.literal)) { moduleName = node.argument.literal; } if (moduleName === undefined) { return null; } const moduleSymbol = typeChecker.getSymbolAtLocation(moduleName); if (moduleSymbol === undefined) { return null; } const symbol = getActualSymbol(moduleSymbol, typeChecker); if (symbol.valueDeclaration === undefined) { return null; } return ts.isSourceFile(symbol.valueDeclaration) || ts.isModuleDeclaration(symbol.valueDeclaration) ? symbol.valueDeclaration : null; } exports.resolveReferencedModule = resolveReferencedModule; function getImportModuleName(imp) { if (ts.isImportDeclaration(imp)) { return imp.importClause === undefined ? null : imp.moduleSpecifier.text; } if (ts.isExportDeclaration(imp)) { return imp.moduleSpecifier === undefined ? null : imp.moduleSpecifier.text; } if (ts.isExternalModuleReference(imp.moduleReference)) { if (!ts.isStringLiteral(imp.moduleReference.expression)) { (0, logger_1.warnLog)(`Cannot handle non string-literal-like import expression: ${imp.moduleReference.expression.getText()}`); return null; } return imp.moduleReference.expression.text; } return null; } exports.getImportModuleName = getImportModuleName; /** * Returns a symbol that an {@link importExportSpecifier} node references to. * * For example, for given `export { Value }` it returns a declaration of `Value` whatever it is (import statement, interface declaration, etc). */ function getImportExportReferencedSymbol(importExportSpecifier, typeChecker) { return importExportSpecifier.propertyName !== undefined // eslint-disable-next-line @typescript-eslint/no-non-null-assertion ? typeChecker.getSymbolAtLocation(importExportSpecifier.propertyName) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion : typeChecker.getImmediateAliasedSymbol(typeChecker.getSymbolAtLocation(importExportSpecifier.name)); } exports.getImportExportReferencedSymbol = getImportExportReferencedSymbol; function getSymbolExportStarDeclarations(symbol) { if (symbol.escapedName !== ts.InternalSymbolName.ExportStar) { throw new Error(`Only ExportStar symbol can have export star declaration, but got ${symbol.escapedName}`); } // this means that an export contains `export * from 'module'` statement return getDeclarationsForSymbol(symbol).filter((decl) => ts.isExportDeclaration(decl) && decl.moduleSpecifier !== undefined); } exports.getSymbolExportStarDeclarations = getSymbolExportStarDeclarations; function getDeclarationsForExportedValues(exp, typeChecker) { const nodeForSymbol = ts.isExportAssignment(exp) ? exp.expression : exp.moduleSpecifier; if (nodeForSymbol === undefined) { return []; } const symbolForExpression = typeChecker.getSymbolAtLocation(nodeForSymbol); if (symbolForExpression === undefined) { return []; } const symbol = getActualSymbol(symbolForExpression, typeChecker); return getDeclarationsForSymbol(symbol); } exports.getDeclarationsForExportedValues = getDeclarationsForExportedValues; function resolveGlobalName(typeChecker, name) { // this value isn't available in all typescript versions so lets assign its value here instead const tsSymbolFlagsAll = /* ts.SymbolFlags.All */ -1; // see https://github.com/microsoft/TypeScript/pull/56932 return typeChecker.resolveName(name, undefined, tsSymbolFlagsAll, false); } exports.resolveGlobalName = resolveGlobalName;