UNPKG

dts-bundle-generator

Version:
232 lines (231 loc) 11.7 kB
"use strict"; 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); }); }