dts-bundle-generator
Version:
DTS Bundle Generator
232 lines (231 loc) • 11.7 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateOutput = void 0;
const ts = require("typescript");
const package_version_1 = require("./helpers/package-version");
const typescript_1 = require("./helpers/typescript");
function generateOutput(params, options = {}) {
const resultOutputParts = [];
if (!options.noBanner) {
resultOutputParts.push(`// Generated by dts-bundle-generator v${(0, package_version_1.packageVersion)()}`);
}
if (params.typesReferences.size !== 0) {
const header = generateReferenceTypesDirective(Array.from(params.typesReferences));
resultOutputParts.push(header);
}
if (params.imports.size !== 0) {
// we need to have sorted imports of libraries to have more "stable" output
const sortedEntries = Array.from(params.imports.entries()).sort((firstEntry, secondEntry) => {
return firstEntry[0].localeCompare(secondEntry[0]);
});
const importsArray = [];
for (const [libraryName, libraryImports] of sortedEntries) {
importsArray.push(...generateImports(libraryName, libraryImports));
}
if (importsArray.length !== 0) {
resultOutputParts.push(importsArray.join('\n'));
}
}
const statements = params.statements.map((statement) => getStatementText(statement, Boolean(options.sortStatements), params));
if (options.sortStatements) {
statements.sort(compareStatementText);
}
if (statements.length !== 0) {
resultOutputParts.push(statementsTextToString(statements));
}
if (params.wrappedNamespaces.size !== 0) {
resultOutputParts.push(Array.from(params.wrappedNamespaces.entries())
.map(([namespaceName, exportedNames]) => {
return `declare namespace ${namespaceName} {\n\texport { ${Array.from(exportedNames.entries())
.map(([exportedName, localName]) => renamedExportValue(exportedName, localName))
.sort()
.join(', ')} };\n}`;
})
.join('\n'));
}
if (params.renamedExports.size !== 0) {
resultOutputParts.push(`export {\n\t${Array.from(params.renamedExports.entries())
.map(([exportedName, localName]) => renamedExportValue(exportedName, localName))
.sort()
.join(',\n\t')},\n};`);
}
if (options.umdModuleName !== undefined) {
resultOutputParts.push(`export as namespace ${options.umdModuleName};`);
}
// this is used to prevent importing non-exported nodes
// see https://stackoverflow.com/questions/52583603/intentional-that-export-shuts-off-automatic-export-of-all-symbols-from-a-ty
resultOutputParts.push(`export {};\n`);
return resultOutputParts.join('\n\n');
}
exports.generateOutput = generateOutput;
function statementsTextToString(statements) {
const statementsText = statements.map(statement => statement.text).join('\n');
return spacesToTabs(prettifyStatementsText(statementsText));
}
function renamedExportValue(exportedName, localName) {
return exportedName !== localName ? `${localName} as ${exportedName}` : exportedName;
}
function renamedImportValue(importedName, localName) {
return importedName !== localName ? `${importedName} as ${localName}` : importedName;
}
function prettifyStatementsText(statementsText) {
const sourceFile = ts.createSourceFile('output.d.ts', statementsText, ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
const printer = ts.createPrinter({
newLine: ts.NewLineKind.LineFeed,
removeComments: false,
});
return printer.printFile(sourceFile).trim();
}
function compareStatementText(a, b) {
if (a.sortingValue > b.sortingValue) {
return 1;
}
else if (a.sortingValue < b.sortingValue) {
return -1;
}
return 0;
}
function recreateEntityName(node, helpers) {
const resolvedName = helpers.resolveIdentifierName(node);
if (resolvedName !== null && resolvedName !== node.getText()) {
const identifiers = resolvedName.split('.');
let result = ts.factory.createIdentifier(identifiers[0]);
for (let index = 1; index < identifiers.length; index += 1) {
result = ts.factory.createQualifiedName(result, ts.factory.createIdentifier(identifiers[index]));
}
return result;
}
return node;
}
function getStatementText(statement, includeSortingValue, helpers) {
const { shouldHaveExportKeyword, shouldHaveJSDoc } = helpers.getStatementSettings(statement);
// re-export statements do not contribute to top-level names scope so we don't need to resolve their identifiers
const needResolveIdentifiers = !ts.isExportDeclaration(statement) || statement.moduleSpecifier === undefined;
const printer = ts.createPrinter({
newLine: ts.NewLineKind.LineFeed,
removeComments: false,
}, {
// eslint-disable-next-line complexity
substituteNode: (hint, node) => {
if (node.parent === undefined) {
return node;
}
if (needResolveIdentifiers) {
if (ts.isPropertyAccessExpression(node)) {
const resolvedName = helpers.resolveIdentifierName(node);
if (resolvedName !== null && resolvedName !== node.getText()) {
const identifiers = resolvedName.split('.');
let result = ts.factory.createIdentifier(identifiers[0]);
for (let index = 1; index < identifiers.length; index += 1) {
result = ts.factory.createPropertyAccessExpression(result, ts.factory.createIdentifier(identifiers[index]));
}
return result;
}
return node;
}
if (ts.isIdentifier(node) || ts.isQualifiedName(node)) {
// QualifiedName and PropertyAccessExpression are handled separately
if (ts.isIdentifier(node) && (ts.isQualifiedName(node.parent) || ts.isPropertyAccessExpression(node.parent))) {
return node;
}
if (ts.isIdentifier(node) && ts.isImportTypeNode(node.parent) && node.parent.qualifier === node) {
// identifiers in dynamic imports should be ignored as they don't use local scope
return node;
}
return recreateEntityName(node, helpers);
}
}
// `import('module').Qualifier` or `typeof import('module').Qualifier`
if (ts.isImportTypeNode(node) && node.qualifier !== undefined && helpers.needStripImportFromImportTypeNode(node)) {
const newQualifier = recreateEntityName(node.qualifier, helpers);
if (node.isTypeOf) {
return ts.factory.createTypeQueryNode(newQualifier);
}
return ts.factory.createTypeReferenceNode(newQualifier, node.typeArguments);
}
if (node !== statement) {
return node;
}
const modifiersMap = (0, typescript_1.modifiersToMap)((0, typescript_1.getModifiers)(node));
if (ts.isEnumDeclaration(node)
&& modifiersMap[ts.SyntaxKind.ConstKeyword]
&& helpers.needStripConstFromConstEnum(node)) {
modifiersMap[ts.SyntaxKind.ConstKeyword] = false;
}
const nodeName = (0, typescript_1.getNodeName)(node);
const resolvedStatementName = nodeName !== undefined ? helpers.resolveIdentifierName(nodeName) || undefined : undefined;
// strip the `default` keyword from node regardless
if (modifiersMap[ts.SyntaxKind.DefaultKeyword]) {
modifiersMap[ts.SyntaxKind.DefaultKeyword] = false;
if (ts.isClassDeclaration(node)) {
// for classes we need to replace `default` with `declare` instead otherwise it will produce an invalid syntax
modifiersMap[ts.SyntaxKind.DeclareKeyword] = true;
}
}
if (!shouldHaveExportKeyword) {
modifiersMap[ts.SyntaxKind.ExportKeyword] = false;
}
else {
modifiersMap[ts.SyntaxKind.ExportKeyword] = true;
}
// for some reason TypeScript allows to not write `declare` keyword for ClassDeclaration, FunctionDeclaration and VariableDeclaration
// if it already has `export` keyword - so we need to add it
// to avoid TS1046: Top-level declarations in .d.ts files must start with either a 'declare' or 'export' modifier.
if (!modifiersMap[ts.SyntaxKind.ExportKeyword] &&
(ts.isClassDeclaration(node)
|| ts.isFunctionDeclaration(node)
|| ts.isVariableStatement(node)
|| ts.isEnumDeclaration(node)
|| ts.isModuleDeclaration(node))) {
modifiersMap[ts.SyntaxKind.DeclareKeyword] = true;
}
return (0, typescript_1.recreateRootLevelNodeWithModifiers)(node, modifiersMap, resolvedStatementName, shouldHaveJSDoc);
},
});
const statementText = printer.printNode(ts.EmitHint.Unspecified, statement, statement.getSourceFile()).trim();
let sortingValue = '';
if (includeSortingValue) {
// it looks like there is no way to get node's text without a comment at the same time as printing it
// so to get the actual node text we have to parse it again
// hopefully it shouldn't take too long (we don't need to do type check, just parse the AST)
// also let's do it opt-in so if someone isn't using node sorting it won't affect them
const tempSourceFile = ts.createSourceFile('temp.d.ts', statementText, ts.ScriptTarget.ESNext);
// we certainly know that there should be 1 statement at the root level (the printed statement)
sortingValue = tempSourceFile.getChildren()[0].getText();
}
return { text: statementText, sortingValue };
}
function generateImports(libraryName, imports) {
const fromEnding = `from '${libraryName}';`;
const result = [];
// sort to make output more "stable"
if (imports.nsImport !== null) {
result.push(`import * as ${imports.nsImport} ${fromEnding}`);
}
Array.from(imports.requireImports).sort().forEach((importName) => result.push(`import ${importName} = require('${libraryName}');`));
Array.from(imports.defaultImports).sort().forEach((importName) => result.push(`import ${importName} ${fromEnding}`));
if (imports.namedImports.size !== 0) {
result.push(`import { ${Array.from(imports.namedImports.entries())
.map(([localName, importedName]) => renamedImportValue(importedName, localName))
.sort()
.join(', ')} } ${fromEnding}`);
}
if (imports.reExports.size !== 0) {
result.push(`export { ${Array.from(imports.reExports.entries())
.map(([localName, importedName]) => renamedImportValue(importedName, localName))
.sort()
.join(', ')} } ${fromEnding}`);
}
return result;
}
function generateReferenceTypesDirective(libraries) {
return libraries.sort().map((library) => {
return `/// <reference types="${library}" />`;
}).join('\n');
}
function spacesToTabs(text) {
// eslint-disable-next-line no-regex-spaces
return text.replace(/^( )+/gm, (substring) => {
return '\t'.repeat(substring.length / 4);
});
}
;