dts-bundle-generator
Version:
DTS Bundle Generator
802 lines • 64.1 kB
JavaScript
"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