UNPKG

dts-bundle-generator

Version:
802 lines 64.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.generateDtsBundle = void 0; const ts = require("typescript"); const compile_dts_1 = require("./compile-dts"); const types_usage_evaluator_1 = require("./types-usage-evaluator"); const typescript_1 = require("./helpers/typescript"); const module_info_1 = require("./module-info"); const generate_output_1 = require("./generate-output"); const logger_1 = require("./logger"); const collisions_resolver_1 = require("./collisions-resolver"); function generateDtsBundle(entries, options = {}) { (0, logger_1.normalLog)('Compiling input files...'); const { program, rootFilesRemapping } = (0, compile_dts_1.compileDts)(entries.map((entry) => entry.filePath), options.preferredConfigPath, options.followSymlinks); const typeChecker = program.getTypeChecker(); const typeRoots = ts.getEffectiveTypeRoots(program.getCompilerOptions(), {}); const sourceFiles = program.getSourceFiles().filter((file) => { return !program.isSourceFileDefaultLibrary(file); }); const typesUsageEvaluator = new types_usage_evaluator_1.TypesUsageEvaluator(sourceFiles, typeChecker); // eslint-disable-next-line complexity return entries.map((entryConfig) => { (0, logger_1.normalLog)(`Processing ${entryConfig.filePath}`); const newRootFilePath = rootFilesRemapping.get(entryConfig.filePath); if (newRootFilePath === undefined) { throw new Error(`Cannot remap root source file ${entryConfig.filePath}`); } const rootSourceFile = (0, typescript_1.getRootSourceFile)(program, newRootFilePath); const rootSourceFileSymbol = typeChecker.getSymbolAtLocation(rootSourceFile); if (rootSourceFileSymbol === undefined) { throw new Error(`Symbol for root source file ${newRootFilePath} not found`); } const librariesOptions = entryConfig.libraries || {}; const criteria = { allowedTypesLibraries: librariesOptions.allowedTypesLibraries, importedLibraries: librariesOptions.importedLibraries, inlinedLibraries: librariesOptions.inlinedLibraries || [], typeRoots, }; const rootFileExports = (0, typescript_1.getExportsForSourceFile)(typeChecker, rootSourceFileSymbol); const rootFileExportSymbols = rootFileExports.map((exp) => exp.symbol); const collectionResult = { typesReferences: new Set(), imports: new Map(), statements: [], renamedExports: new Map(), wrappedNamespaces: new Map(), }; const outputOptions = entryConfig.output || {}; const inlineDeclareGlobals = Boolean(outputOptions.inlineDeclareGlobals); const inlineDeclareExternals = Boolean(outputOptions.inlineDeclareExternals); const collisionsResolver = new collisions_resolver_1.CollisionsResolver(typeChecker); function updateResultForAnyModule(statements, currentModule) { // contains a set of modules that were visited already // can be used to prevent infinite recursion in updating results in re-exports const visitedModules = new Set(); function updateResultForExternalExport(exportAssignment) { // if we have `export =` or `export * from` somewhere so we can decide that every declaration of exported symbol in this way // is "part of the exported module" and we need to update result according every member of each declaration // but treat they as current module (we do not need to update module info) for (const declaration of (0, typescript_1.getDeclarationsForExportedValues)(exportAssignment, typeChecker)) { if (ts.isVariableDeclaration(declaration)) { // variables will be processed separately anyway so no need to process them again here continue; } let exportedDeclarations = []; if (ts.isExportDeclaration(exportAssignment) && ts.isSourceFile(declaration)) { const referencedModule = (0, module_info_1.getReferencedModuleInfo)(exportAssignment, criteria, typeChecker); if (referencedModule !== null) { if (visitedModules.has(referencedModule.fileName)) { continue; } visitedModules.add(referencedModule.fileName); } exportedDeclarations = declaration.statements; } else if (ts.isModuleDeclaration(declaration)) { if (declaration.body !== undefined && ts.isModuleBlock(declaration.body)) { const referencedModule = (0, module_info_1.getReferencedModuleInfo)(declaration, criteria, typeChecker); if (referencedModule !== null) { if (visitedModules.has(referencedModule.fileName)) { continue; } visitedModules.add(referencedModule.fileName); } exportedDeclarations = declaration.body.statements; } } else { exportedDeclarations = [declaration]; } updateResultImpl(exportedDeclarations); } } // eslint-disable-next-line complexity function updateResultImpl(statementsToProcess) { for (const statement of statementsToProcess) { // we should skip import statements if (statement.kind === ts.SyntaxKind.ImportDeclaration || statement.kind === ts.SyntaxKind.ImportEqualsDeclaration) { continue; } if ((0, typescript_1.isDeclareModule)(statement)) { updateResultForModuleDeclaration(statement, currentModule); // if a statement is `declare module "module" {}` then don't process it below // as it is handled already in `updateResultForModuleDeclaration` // but if it is `declare module Module {}` then it can be used in types and imports // so in this case it needs to be checked for "usages" below if (ts.isStringLiteral(statement.name)) { continue; } } if (currentModule.type === 3 /* ModuleType.ShouldBeUsedForModulesOnly */) { continue; } if ((0, typescript_1.isDeclareGlobalStatement)(statement) && inlineDeclareGlobals && currentModule.type === 0 /* ModuleType.ShouldBeInlined */) { collectionResult.statements.push(statement); continue; } if (ts.isExportDeclaration(statement)) { if (currentModule.type === 0 /* ModuleType.ShouldBeInlined */) { continue; } // `export * from` if (statement.exportClause === undefined) { updateResultForExternalExport(statement); continue; } // `export { val }` if (ts.isNamedExports(statement.exportClause) && currentModule.type === 1 /* ModuleType.ShouldBeImported */) { updateImportsForStatement(statement); continue; } } if (ts.isExportAssignment(statement) && statement.isExportEquals && currentModule.type !== 0 /* ModuleType.ShouldBeInlined */) { updateResultForExternalExport(statement); continue; } if (!isNodeUsed(statement)) { continue; } switch (currentModule.type) { case 2 /* ModuleType.ShouldBeReferencedAsTypes */: // while a node might be "used" somewhere via transitive nodes // we need to add types reference only if a node is treated as "should be imported" // because otherwise we might have lots of false-positive references forEachNodeThatShouldBeImported(statement, () => addTypesReference(currentModule.typesLibraryName)); break; case 1 /* ModuleType.ShouldBeImported */: updateImportsForStatement(statement); break; case 0 /* ModuleType.ShouldBeInlined */: if (ts.isVariableStatement(statement)) { for (const variableDeclaration of statement.declarationList.declarations) { if (ts.isIdentifier(variableDeclaration.name)) { collisionsResolver.addTopLevelIdentifier(variableDeclaration.name); continue; } for (const element of variableDeclaration.name.elements) { if (!ts.isOmittedExpression(element) && ts.isIdentifier(element.name)) { collisionsResolver.addTopLevelIdentifier(element.name); } } } } else if ((0, typescript_1.isNodeNamedDeclaration)(statement)) { const statementName = (0, typescript_1.getNodeName)(statement); if (statementName !== undefined) { collisionsResolver.addTopLevelIdentifier(statementName); } } collectionResult.statements.push(statement); break; } } } updateResultImpl(statements); } function isReferencedModuleImportable(statement) { return (0, module_info_1.getReferencedModuleInfo)(statement, criteria, typeChecker)?.type === 1 /* ModuleType.ShouldBeImported */; } function handleExportDeclarationFromRootModule(exportDeclaration) { function handleExportStarStatement(exportStarStatement, visitedSymbols = new Set()) { if (exportStarStatement.moduleSpecifier === undefined || exportStarStatement.exportClause !== undefined) { throw new Error(`Invalid export-star declaration statement provided, ${exportStarStatement.getText()}`); } const importModuleSpecifier = (0, typescript_1.getImportModuleName)(exportStarStatement); if (importModuleSpecifier === null) { return; } const referencedModuleInfo = (0, module_info_1.getReferencedModuleInfo)(exportStarStatement, criteria, typeChecker); if (referencedModuleInfo === null) { return; } switch (referencedModuleInfo.type) { case 0 /* ModuleType.ShouldBeInlined */: { // `export * from './inlined-module'` const referencedModuleSymbol = (0, typescript_1.getNodeOwnSymbol)(exportStarStatement.moduleSpecifier, typeChecker); const referencedSourceFileExportStarSymbol = referencedModuleSymbol.exports?.get(ts.InternalSymbolName.ExportStar); if (referencedSourceFileExportStarSymbol !== undefined) { if (visitedSymbols.has(referencedSourceFileExportStarSymbol)) { return; } visitedSymbols.add(referencedSourceFileExportStarSymbol); // we need to go recursive for all `export * from` statements and add all that are from imported modules for (const exportDecl of (0, typescript_1.getSymbolExportStarDeclarations)(referencedSourceFileExportStarSymbol)) { handleExportStarStatement(exportDecl, visitedSymbols); } } break; } case 1 /* ModuleType.ShouldBeImported */: { // `export * from 'importable-package'` collectionResult.statements.push(exportStarStatement); break; } } } /** * This function returns an export-star object that exports given {@link nodeSymbol} symbol. * If an exporting export declaration object is not from an importable module then `null` is returned. * Also if the symbol is exported explicitly (i.e. via `export { Name }` or specifying `export` keyword next to the node) then `null` is returned as well. */ function findExportingExportStarExportFromImportableModule(referencedModuleSymbol, nodeSymbol) { function findResultRecursively(referencedModuleSym, exportedNodeSym, visitedSymbols) { // prevent infinite recursion if (visitedSymbols.has(referencedModuleSym)) { return null; } visitedSymbols.add(referencedModuleSym); // `export * from` exports always have less priority over explicit exports so it should go last const exportStarExport = referencedModuleSym.exports?.get(ts.InternalSymbolName.ExportStar); if (exportStarExport === undefined) { return null; } for (const exportStarDeclaration of (0, typescript_1.getDeclarationsForSymbol)(exportStarExport).filter(ts.isExportDeclaration)) { if (exportStarDeclaration.moduleSpecifier === undefined) { // this seems impossible, but to make the compiler/types happy continue; } const exportStarModuleSymbol = (0, typescript_1.getNodeOwnSymbol)(exportStarDeclaration.moduleSpecifier, typeChecker); if (exportStarModuleSymbol.exports === undefined) { continue; } if (isReferencedModuleImportable(exportStarDeclaration)) { // for "importable" modules we don't need to go deeper or even check "explicit" exports // as it doesn't matter how its done internally and we care about "public" interface only // so we can just check whether it exports a symbol or not (irregardless of how it is exported exactly internally) const referencedModuleExports = typeChecker.getExportsOfModule(exportStarModuleSymbol); const exportedNodeSymbol = referencedModuleExports.find((exp) => (0, typescript_1.getActualSymbol)(exp, typeChecker) === nodeSymbol); if (exportedNodeSymbol !== undefined) { return { exportStarDeclaration, exportedNodeSymbol }; } continue; } const result = findResultRecursively(exportStarModuleSymbol, exportedNodeSym, visitedSymbols); if (result !== null) { return result; } } return null; } if (referencedModuleSymbol.exports === undefined) { throw new Error(`No exports found for "${referencedModuleSymbol.getName()}" symbol`); } const hasExplicitExportOfSymbol = Array.from(referencedModuleSymbol.exports.values()).some((exp) => { if (exp.escapedName === ts.InternalSymbolName.ExportStar) { return false; } return (0, typescript_1.getActualSymbol)(exp, typeChecker) === nodeSymbol; }); if (hasExplicitExportOfSymbol) { // symbol is exported explicitly ¯\_(ツ)_/¯ return null; } return findResultRecursively(referencedModuleSymbol, nodeSymbol, new Set()); } // `export * from 'module'` if (exportDeclaration.exportClause === undefined) { handleExportStarStatement(exportDeclaration); return; } if (exportDeclaration.exportClause !== undefined && ts.isNamedExports(exportDeclaration.exportClause)) { // `export { val, val2 }` if (exportDeclaration.moduleSpecifier === undefined) { for (const exportElement of exportDeclaration.exportClause.elements) { const exportElementSymbol = (0, typescript_1.getImportExportReferencedSymbol)(exportElement, typeChecker); const namespaceImportFromImportableModule = (0, typescript_1.getDeclarationsForSymbol)(exportElementSymbol).find((importDecl) => { // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion return ts.isNamespaceImport(importDecl) && isReferencedModuleImportable(importDecl.parent.parent); }); if (namespaceImportFromImportableModule !== undefined) { // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion const importModuleSpecifier = (0, typescript_1.getImportModuleName)(namespaceImportFromImportableModule.parent.parent); if (importModuleSpecifier === null) { throw new Error(`Cannot get import module name from '${namespaceImportFromImportableModule.parent.parent.getText()}'`); } addNsImport(getImportItem(importModuleSpecifier), namespaceImportFromImportableModule.name); } } return; } // `export { val, val2 } from 'module'` if (exportDeclaration.moduleSpecifier !== undefined) { const referencedModuleSymbol = (0, typescript_1.getNodeOwnSymbol)(exportDeclaration.moduleSpecifier, typeChecker); // in this case we want to find all elements that we re-exported via `export * from` exports as they aren't handled elsewhere for (const exportElement of exportDeclaration.exportClause.elements) { const exportedNodeSymbol = (0, typescript_1.getActualSymbol)((0, typescript_1.getImportExportReferencedSymbol)(exportElement, typeChecker), typeChecker); const exportingExportStarResult = findExportingExportStarExportFromImportableModule(referencedModuleSymbol, exportedNodeSymbol); if (exportingExportStarResult === null) { continue; } const importModuleSpecifier = (0, typescript_1.getImportModuleName)(exportingExportStarResult.exportStarDeclaration); if (importModuleSpecifier === null) { throw new Error(`Cannot get import module name from '${exportingExportStarResult.exportStarDeclaration.getText()}'`); } // technically we could use named imports and then add re-exports // but this solution affects names scope (re-exports don't affect it) // and also it is slightly complicated to find a name declaration (identifier) that needs to be imported // so it feels better to go this way, but happy to change in the future if there would be any issues addReExport(getImportItem(importModuleSpecifier), exportingExportStarResult.exportedNodeSymbol.getName(), exportElement.name.text); } return; } } } function updateResultForRootModule(statements, currentModule) { updateResultForAnyModule(statements, currentModule); // add skipped by `updateResult` exports for (const statement of statements) { if (ts.isExportDeclaration(statement)) { handleExportDeclarationFromRootModule(statement); continue; } if (ts.isExportAssignment(statement)) { // `"export ="` or `export default 123` or `export default "str"` if (statement.isExportEquals || !ts.isIdentifier(statement.expression)) { collectionResult.statements.push(statement); } continue; } } } function updateResultForModuleDeclaration(moduleDecl, currentModule) { if (moduleDecl.body === undefined || !ts.isModuleBlock(moduleDecl.body)) { return; } const referencedModuleInfo = (0, module_info_1.getModuleLikeModuleInfo)(moduleDecl, criteria, typeChecker); if (referencedModuleInfo === null) { return; } // if we have declaration of external module inside internal one if (!currentModule.isExternal && referencedModuleInfo.isExternal) { // if it's allowed - we need to just add it to result without any processing if (inlineDeclareExternals) { collectionResult.statements.push(moduleDecl); } return; } updateResultForAnyModule(moduleDecl.body.statements, referencedModuleInfo); } function addTypesReference(library) { if (!collectionResult.typesReferences.has(library)) { (0, logger_1.normalLog)(`Library "${library}" will be added via reference directive`); collectionResult.typesReferences.add(library); } } function forEachNodeThatShouldBeImported(statement, callback) { const statementsToImport = ts.isVariableStatement(statement) ? statement.declarationList.declarations : ts.isExportDeclaration(statement) && statement.exportClause !== undefined ? ts.isNamespaceExport(statement.exportClause) ? [statement.exportClause] : statement.exportClause.elements : [statement]; for (const statementToImport of statementsToImport) { if (shouldNodeBeImported(statementToImport)) { callback(statementToImport); } } } function updateImportsForStatement(statement) { forEachNodeThatShouldBeImported(statement, (statementToImport) => { addImport(statementToImport); // if we're going to add import of any statement in the bundle // we should check whether the library of that statement // could be referenced via triple-slash reference-types directive // because the project which will use bundled declaration file // can have `types: []` in the tsconfig and it'll fail // this is especially related to the types packages // which declares different modules in their declarations // e.g. @types/node has declaration for "packages" events, fs, path and so on const sourceFile = statementToImport.getSourceFile(); const moduleInfo = (0, module_info_1.getFileModuleInfo)(sourceFile.fileName, criteria); if (moduleInfo.type === 2 /* ModuleType.ShouldBeReferencedAsTypes */) { addTypesReference(moduleInfo.typesLibraryName); } }); } function getDeclarationUsagesSourceFiles(declaration) { return new Set(getExportedSymbolsUsingStatement(declaration) .map((symbol) => (0, typescript_1.getDeclarationsForSymbol)(symbol)) .reduce((acc, val) => acc.concat(val), []) .map(typescript_1.getClosestModuleLikeNode)); } function getImportItem(importModuleSpecifier) { let importItem = collectionResult.imports.get(importModuleSpecifier); if (importItem === undefined) { importItem = { defaultImports: new Set(), namedImports: new Map(), nsImport: null, requireImports: new Set(), reExports: new Map(), }; collectionResult.imports.set(importModuleSpecifier, importItem); } return importItem; } function addRequireImport(importItem, preferredLocalName) { importItem.requireImports.add(collisionsResolver.addTopLevelIdentifier(preferredLocalName)); } function addNamedImport(importItem, preferredLocalName, importedIdentifier) { const newLocalName = collisionsResolver.addTopLevelIdentifier(preferredLocalName); const importedName = importedIdentifier.text; importItem.namedImports.set(newLocalName, importedName); } function addReExport(importItem, moduleExportedName, reExportedName) { // re-exports don't affect local names scope so we don't need to register them in collisions resolver importItem.reExports.set(reExportedName, moduleExportedName); } function addNsImport(importItem, preferredLocalName) { if (importItem.nsImport === null) { importItem.nsImport = collisionsResolver.addTopLevelIdentifier(preferredLocalName); } } function addDefaultImport(importItem, preferredLocalName) { importItem.defaultImports.add(collisionsResolver.addTopLevelIdentifier(preferredLocalName)); } function addImport(statement) { forEachImportOfStatement(statement, (imp, referencedModuleInfo, importModuleSpecifier) => { // if a referenced module should be inlined we can just ignore it if (referencedModuleInfo.type !== 1 /* ModuleType.ShouldBeImported */) { return; } const importItem = getImportItem(importModuleSpecifier); if (ts.isImportEqualsDeclaration(imp)) { // import x = require("mod"); addRequireImport(importItem, imp.name); return; } if (ts.isExportSpecifier(imp)) { // export { El1, El2 as ExportedName } from 'module'; addNamedImport(importItem, imp.name, imp.propertyName || imp.name); return; } if (ts.isNamespaceExport(imp)) { // export * as name from 'module'; addNsImport(importItem, imp.name); return; } if (ts.isImportClause(imp) && imp.name !== undefined) { // import name from 'module'; addDefaultImport(importItem, imp.name); return; } if (ts.isImportSpecifier(imp)) { // import { El1, El2 as ImportedName } from 'module'; addNamedImport(importItem, imp.name, imp.propertyName || imp.name); return; } if (ts.isNamespaceImport(imp)) { // import * as name from 'module'; addNsImport(importItem, imp.name); return; } }); } function forEachImportOfStatement(statement, callback) { if (!ts.isSourceFile(statement) && statement.name === undefined) { throw new Error(`Import/usage unnamed declaration: ${statement.getText()}`); } getDeclarationUsagesSourceFiles(statement).forEach((sourceFile) => { if ((0, module_info_1.getModuleLikeModuleInfo)(sourceFile, criteria, typeChecker).type !== 0 /* ModuleType.ShouldBeInlined */) { // we should ignore source files that aren't inlined return; } const sourceFileStatements = ts.isSourceFile(sourceFile) ? sourceFile.statements : sourceFile.body !== undefined && ts.isModuleBlock(sourceFile.body) ? sourceFile.body.statements : []; // eslint-disable-next-line complexity sourceFileStatements.forEach((st) => { if (!ts.isImportEqualsDeclaration(st) && !ts.isImportDeclaration(st) && !ts.isExportDeclaration(st)) { return; } const importModuleSpecifier = (0, typescript_1.getImportModuleName)(st); if (importModuleSpecifier === null) { return; } const referencedModuleInfo = (0, module_info_1.getReferencedModuleInfo)(st, criteria, typeChecker); // if a referenced module should be inlined we can just ignore it if (referencedModuleInfo === null) { return; } if (ts.isImportEqualsDeclaration(st)) { if (areDeclarationSame(statement, st)) { callback(st, referencedModuleInfo, importModuleSpecifier); } return; } if (ts.isExportDeclaration(st) && st.exportClause !== undefined) { if (ts.isNamedExports(st.exportClause)) { // export { El1, El2 as ExportedName } from 'module'; st.exportClause.elements .filter(areDeclarationSame.bind(null, statement)) .forEach((specifier) => { callback(specifier, referencedModuleInfo, importModuleSpecifier); }); } else { // export * as name from 'module'; if (isNodeUsed(st.exportClause)) { callback(st.exportClause, referencedModuleInfo, importModuleSpecifier); } } } else if (ts.isImportDeclaration(st) && st.importClause !== undefined) { if (st.importClause.name !== undefined && areDeclarationSame(statement, st.importClause)) { // import name from 'module'; callback(st.importClause, referencedModuleInfo, importModuleSpecifier); } if (st.importClause.namedBindings !== undefined) { if (ts.isNamedImports(st.importClause.namedBindings)) { // import { El1, El2 as ImportedName } from 'module'; st.importClause.namedBindings.elements .filter(areDeclarationSame.bind(null, statement)) .forEach((specifier) => { callback(specifier, referencedModuleInfo, importModuleSpecifier); }); } else { // import * as name from 'module'; if (isNodeUsed(st.importClause)) { callback(st.importClause.namedBindings, referencedModuleInfo, importModuleSpecifier); } } } } }); }); } function getInlinedSymbolsUsingSymbol(symbol, predicate) { return Array.from(typesUsageEvaluator.getSymbolsUsingSymbol(symbol) ?? []).filter((usedInSymbol) => { if (!predicate(usedInSymbol)) { return false; } return (0, typescript_1.getDeclarationsForSymbol)(usedInSymbol).some((decl) => { const closestModuleLike = (0, typescript_1.getClosestSourceFileLikeNode)(decl); const moduleInfo = (0, module_info_1.getModuleLikeModuleInfo)(closestModuleLike, criteria, typeChecker); return moduleInfo.type === 0 /* ModuleType.ShouldBeInlined */; }); }); } function isSymbolUsedByInlinedSymbols(symbol, predicate, visitedSymbols = new Set()) { if (visitedSymbols.has(symbol)) { return false; } visitedSymbols.add(symbol); return Array.from(typesUsageEvaluator.getSymbolsUsingSymbol(symbol) ?? []).some((usedInSymbol) => { if (!predicate(usedInSymbol)) { return isSymbolUsedByInlinedSymbols(usedInSymbol, predicate, visitedSymbols); } const usedByThisSymbol = (0, typescript_1.getDeclarationsForSymbol)(usedInSymbol).some((decl) => { const closestModuleLike = (0, typescript_1.getClosestSourceFileLikeNode)(decl); const moduleInfo = (0, module_info_1.getModuleLikeModuleInfo)(closestModuleLike, criteria, typeChecker); return moduleInfo.type === 0 /* ModuleType.ShouldBeInlined */; }); if (usedByThisSymbol) { return true; } return isSymbolUsedByInlinedSymbols(usedInSymbol, predicate, visitedSymbols); }); } function isSymbolUsedByRootFileExports(symbol) { return rootFileExportSymbols.some((rootSymbol) => typesUsageEvaluator.isSymbolUsedBySymbol(symbol, rootSymbol)); } function isSymbolForGlobalDeclaration(symbol) { return symbol.escapedName === ts.InternalSymbolName.Global; } function isSymbolForDeclareModuleDeclaration(symbol) { return (0, typescript_1.getDeclarationsForSymbol)(symbol).some(typescript_1.isDeclareModule); } // eslint-disable-next-line complexity function isNodeUsed(node) { if ((0, typescript_1.isNodeNamedDeclaration)(node) || ts.isSourceFile(node)) { const nodeSymbol = (0, typescript_1.getNodeSymbol)(node, typeChecker); if (nodeSymbol === null) { return false; } // note we don't need a function similar to `isSymbolUsedByGlobalSymbols` or `isSymbolUsedByGlobalSymbols` // because `TypesUsageEvaluator.isSymbolUsedBySymbol` already handles recursive checks const nodeUsedByDirectExports = isSymbolUsedByRootFileExports(nodeSymbol); if (nodeUsedByDirectExports) { return true; } if (inlineDeclareGlobals && isSymbolUsedByInlinedSymbols(nodeSymbol, isSymbolForGlobalDeclaration)) { return true; } if (inlineDeclareExternals && isSymbolUsedByInlinedSymbols(nodeSymbol, isSymbolForDeclareModuleDeclaration)) { return true; } return false; } if (ts.isVariableStatement(node)) { return node.declarationList.declarations.some((declaration) => { if (ts.isObjectBindingPattern(declaration.name) || ts.isArrayBindingPattern(declaration.name)) { return declaration.name.elements.some(isNodeUsed); } return isNodeUsed(declaration); }); } if (ts.isExportDeclaration(node) && node.exportClause !== undefined && ts.isNamespaceExport(node.exportClause)) { return isNodeUsed(node.exportClause); } if (ts.isImportClause(node) && node.namedBindings !== undefined) { return isNodeUsed(node.namedBindings); } return false; } function shouldNodeBeImported(node) { const nodeSymbol = (0, typescript_1.getNodeSymbol)(node, typeChecker); if (nodeSymbol === null) { return false; } return shouldSymbolBeImported(nodeSymbol); } function shouldSymbolBeImported(nodeSymbol) { const isSymbolDeclaredInDefaultLibrary = (0, typescript_1.getDeclarationsForSymbol)(nodeSymbol).some((declaration) => program.isSourceFileDefaultLibrary(declaration.getSourceFile())); if (isSymbolDeclaredInDefaultLibrary) { // we shouldn't import a node declared in the default library (such dom, es2015) // yeah, actually we should check that node is declared only in the default lib // but it seems we can check that at least one declaration is from default lib // to treat the node as un-importable // because we can't re-export declared somewhere else node with declaration merging // also, if some lib file will not be added to the project // for example like it is described in the react declaration file (e.g. React Native) // then here we still have a bug with "importing global declaration from a package" // (see https://github.com/timocov/dts-bundle-generator/issues/71) // but I don't think it is a big problem for now // and it's possible that it will be fixed in https://github.com/timocov/dts-bundle-generator/issues/59 return false; } const symbolsDeclarations = (0, typescript_1.getDeclarationsForSymbol)(nodeSymbol); // if all declarations of the symbol are in modules that should be inlined then this symbol must be inlined, not imported const shouldSymbolBeInlined = symbolsDeclarations.every((decl) => (0, module_info_1.getModuleLikeModuleInfo)((0, typescript_1.getClosestSourceFileLikeNode)(decl), criteria, typeChecker).type === 0 /* ModuleType.ShouldBeInlined */); if (shouldSymbolBeInlined) { return false; } return getExportedSymbolsUsingSymbol(nodeSymbol).length !== 0; } function getExportedSymbolsUsingStatement(node) { const nodeSymbol = (0, typescript_1.getNodeSymbol)(node, typeChecker); if (nodeSymbol === null) { return []; } return getExportedSymbolsUsingSymbol(nodeSymbol); } function getExportedSymbolsUsingSymbol(nodeSymbol) { const symbolsUsingNode = typesUsageEvaluator.getSymbolsUsingSymbol(nodeSymbol); if (symbolsUsingNode === null) { throw new Error(`Something went wrong - getSymbolsUsingSymbol returned null but expected to be a set of symbols (symbol=${nodeSymbol.name})`); } return [ ...(rootFileExportSymbols.includes(nodeSymbol) ? [nodeSymbol] : []), // symbols which are used in types directly ...getInlinedSymbolsUsingSymbol(nodeSymbol, isSymbolUsedByRootFileExports), // symbols which are used in global types i.e. in `declare global`s ...(inlineDeclareGlobals ? getInlinedSymbolsUsingSymbol(nodeSymbol, isSymbolForGlobalDeclaration) : []), // symbols which are used in "declare module" types ...(inlineDeclareExternals ? getInlinedSymbolsUsingSymbol(nodeSymbol, isSymbolForDeclareModuleDeclaration) : []), ]; } function areDeclarationSame(left, right) { const leftSymbols = (0, typescript_1.splitTransientSymbol)((0, typescript_1.getNodeSymbol)(left, typeChecker), typeChecker); const rightSymbols = (0, typescript_1.splitTransientSymbol)((0, typescript_1.getNodeSymbol)(right, typeChecker), typeChecker); for (const leftSymbol of leftSymbols) { if (rightSymbols.has(leftSymbol)) { return true; } } return false; } function createNamespaceForExports(exports, namespaceSymbol) { function addSymbolToNamespaceExports(namespaceExports, symbol) { // if a symbol isn't used by the namespace symbol it might mean that it shouldn't be included into the bundle because of tree-shaking // in this case we shouldn't even try to add such symbol to the namespace if (!typesUsageEvaluator.isSymbolUsedBySymbol(symbol, namespaceSymbol)) { return; } const symbolKnownNames = collisionsResolver.namesForSymbol(symbol); if (symbolKnownNames.size === 0) { throw new Error(`Cannot get local names for symbol '${symbol.getName()}' while generating namespaced export`); } namespaceExports.set(symbol.getName(), Array.from(symbolKnownNames)[0]); } function handleNamespacedImportOrExport(namespacedImportOrExport, namespaceExports, symbol) { if (namespacedImportOrExport.moduleSpecifier === undefined) { return; } if (isReferencedModuleImportable(namespacedImportOrExport)) { // in case of an external export statement we should copy it as is // here we assume that a namespace import will be added in other places // so here we can just add re-export addSymbolToNamespaceExports(namespaceExports, symbol); return; } const referencedSourceFileSymbol = (0, typescript_1.getNodeOwnSymbol)(namespacedImportOrExport.moduleSpecifier, typeChecker); if (referencedSourceFileSymbol.exports === undefined) { return; } if (ts.isImportDeclaration(namespacedImportOrExport) && referencedSourceFileSymbol.exports.has(ts.InternalSymbolName.ExportEquals)) { // in case of handling `import * as Ns` statements with `export =` export in a module we need to ignore it // as that import will be renamed later return; } const localNamespaceName = createNamespaceForExports(referencedSourceFileSymbol.exports, symbol); if (localNamespaceName !== null) { namespaceExports.set(symbol.getName(), localNamespaceName); } } function processExportSymbol(namespaceExports, symbol) { if (symbol.escapedName === ts.InternalSymbolName.ExportStar) { // this means that an export contains `export * from 'module'` statement for (const exportStarDeclaration of (0, typescript_1.getSymbolExportStarDeclarations)(symbol)) { if (exportStarDeclaration.moduleSpecifier === undefined) { throw new Error(`Export star declaration does not have a module specifier '${exportStarDeclaration.getText()}'`); } if (isReferencedModuleImportable(exportStarDeclaration)) { // in case of re-exporting from other modules directly we should import everything and re-export manually // but it is not supported yet so lets just fail for now throw new Error(`Having a re-export from an importable module as a part of namespaced export is not supported yet.`); } const referencedSourceFileSymbol = (0, typescript_1.getNodeOwnSymbol)(exportStarDeclaration.moduleSpecifier, typeChecker); referencedSourceFileSymbol.exports?.forEach(processExportSymbol.bind(null, namespaceExports)); } return; } symbol.declarations?.forEach((decl) => { if (ts.isNamespaceExport(decl) && decl.parent.moduleSpecifier !== undefined) { handleNamespacedImportOrExport(decl.parent, namespaceExports, symbol); return; } if (ts.isExportSpecifier(decl)) { const exportElementSymbol = (0, typescript_1.getImportExportReferencedSymbol)(decl, typeChecker); const namespaceImport = (0, typescript_1.getDeclarationsForSymbol)(exportElementSymbol).find(ts.isNamespaceImport); if (namespaceImport !== undefined) { // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion handleNamespacedImportOrExport(namespaceImport.parent.parent, namespaceExports, symbol); } return; } }); addSymbolToNamespaceExports(namespaceExports, symbol); } // eslint-disable-next-line complexity function getIdentifierOfNamespaceImportFromInlinedModule(nsSymbol) { // handling namespaced re-exports/imports // e.g. `export * as NS from './local-module';` or `import * as NS from './local-module'; export { NS }` for (const decl of (0, typescript_1.getDeclarationsForSymbol)(nsSymbol)) { if (!ts.isNamespaceExport(decl) && !ts.isExportSpecifier(decl) && !ts.isNamespaceImport(decl)) { continue; } // if it is namespace export then it should be from a inlined module (e.g. `export * as NS from './local-module';`) if (ts.isNamespaceExport(decl) && !isReferencedModuleImportable(decl.parent)) { return decl.name; } // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion if (ts.isNamespaceImport(decl) && !isReferencedModuleImportable(decl.parent.parent)) { return decl.name; } if (ts.isExportSpecifier(decl)) { // if it is export specifier then it should exporting a local symbol i.e. without a module specifier (e.g. `export { NS };` or `export { NS as NewNsName };`) if (decl.parent.parent.moduleSpecifier !== undefined) { // this means that namespace symbol is created somewhere else in the import/export chain if (isReferencedModuleImportable(decl.parent.parent)) { continue; } // in case of a chain of imports/exports we need to keep searching recursively if (getIdentifierOfNamespaceImportFromInlinedModule((0, typescript_1.getImportExportReferencedSymbol)(decl, typeChecker))) { return decl.name; } } // but also that local symbol should be a namespace imported from inlined module // i.e. `import * as NS from './local-module'` const result = (0, typescript_1.getDeclarationsForSymbol)((0, typescript_1.getImportExportReferencedSymbol)(decl, typeChecker)).some((importDecl) => { if (ts.isNamespaceImport(importDecl)) { // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion return !isReferencedModuleImportable(importDecl.parent.parent); } if (ts.isImportSpecifier(importDecl)) { // this means that namespace symbol is created somewhere else