dts-bundle-generator
Version:
DTS Bundle Generator
492 lines (491 loc) • 22.9 kB
JavaScript
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;
;