'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var path = require('node:path'); var ts = require('typescript'); var node_module = require('node:module'); var MagicString = require('magic-string'); var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null; function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path); function resolveDefaultOptions(options) { return { ...options, compilerOptions: options.compilerOptions ?? {}, respectExternal: options.respectExternal ?? false, }; } const DTS_EXTENSIONS = /\.d\.(c|m)?tsx?$/; const dts = ".d.ts"; const formatHost = { getCurrentDirectory: () => ts.sys.getCurrentDirectory(), getNewLine: () => ts.sys.newLine, getCanonicalFileName: ts.sys.useCaseSensitiveFileNames ? (f) => f : (f) => f.toLowerCase(), }; const DEFAULT_OPTIONS = { // Ensure ".d.ts" modules are generated declaration: true, // Skip ".js" generation noEmit: false, emitDeclarationOnly: true, // Skip code generation when error occurs noEmitOnError: true, // Avoid extra work checkJs: false, declarationMap: false, skipLibCheck: true, // Ensure TS2742 errors are visible preserveSymlinks: true, // Ensure we can parse the latest code target: ts.ScriptTarget.ESNext, }; const configByPath = new Map(); const logCache = (...args) => (process.env.DTS_LOG_CACHE ? console.log("[cache]", ...args) : null); /** * Caches the config for every path between two given paths. * * It starts from the first path and walks up the directory tree until it reaches the second path. */ function cacheConfig([fromPath, toPath], config) { logCache(fromPath); configByPath.set(fromPath, config); while (fromPath !== toPath && // make sure we're not stuck in an infinite loop fromPath !== path__namespace.dirname(fromPath)) { fromPath = path__namespace.dirname(fromPath); logCache("up", fromPath); if (configByPath.has(fromPath)) return logCache("has", fromPath); configByPath.set(fromPath, config); } } function getCompilerOptions(input, overrideOptions, overrideConfigPath) { const compilerOptions = { ...DEFAULT_OPTIONS, ...overrideOptions }; let dirName = path__namespace.dirname(input); let dtsFiles = []; // if a custom config is provided we'll use that as the cache key since it will always be used const cacheKey = overrideConfigPath || dirName; if (!configByPath.has(cacheKey)) { logCache("miss", cacheKey); const configPath = overrideConfigPath ? path__namespace.resolve(process.cwd(), overrideConfigPath) : ts.findConfigFile(dirName, ts.sys.fileExists); if (!configPath) { return { dtsFiles, dirName, compilerOptions }; } const inputDirName = dirName; dirName = path__namespace.dirname(configPath); const { config, error } = ts.readConfigFile(configPath, ts.sys.readFile); if (error) { console.error(ts.formatDiagnostic(error, formatHost)); return { dtsFiles, dirName, compilerOptions }; } logCache("tsconfig", config); const configContents = ts.parseJsonConfigFileContent(config, ts.sys, dirName); if (overrideConfigPath) { // if a custom config is provided, we always only use that one cacheConfig([overrideConfigPath, overrideConfigPath], configContents); } else { // cache the config for all directories between input and resolved config path cacheConfig([inputDirName, dirName], configContents); } } else { logCache("HIT", cacheKey); } const { fileNames, projectReferences = [], options, errors } = configByPath.get(cacheKey); let forceReturn = false; for (const key of Object.keys(options.paths ?? {})) { const reg = new RegExp(key .replace('.', '\\.') .replace(/\//g, '\\/') .replace(/\*/g, ".*")); if (reg.test(input)) { forceReturn = true; break; } } if ({ ...options, ...compilerOptions, }.allowJs && input.endsWith('.js')) { forceReturn = true; } const maybeInputs = [input]; if (!/\/index\.[cm]?tsx?$/.test(input)) { maybeInputs.push(input + '/index.ts', input + '/index.cts', input + '/index.mts', input + '/index.tsx'); } if (!/\/index\.[cm]?jsx?$/.test(input)) { maybeInputs.push(input + '/index.js', input + '/index.cjs', input + '/index.mjs', input + '/index.jsx'); } if (!/\/index\.d\.ts$/.test(input)) { maybeInputs.push(input + '/index.d.ts'); } if (!/\.[cm]tsx?$/.test(input)) { maybeInputs.push(input + '.ts', input + '.tsx', input + '.cts', input + '.ctsx', input + '.mts', input + '.mtsx'); } if (!/\.[cm]jsx?$/.test(input)) { maybeInputs.push(input + '.js', input + '.jsx', input + '.cjs', input + '.cjsx', input + '.mjs', input + '.mjsx'); } if (!/\.d\.ts$/.test(input)) { maybeInputs.push(input + '.d.ts'); } let isInclude = fileNames.findIndex((name) => maybeInputs.includes(name)) !== -1; if (isInclude || forceReturn) { dtsFiles = fileNames.filter((name) => DTS_EXTENSIONS.test(name)); if (errors.length) { console.error(ts.formatDiagnostics(errors, formatHost)); return { dtsFiles, dirName, compilerOptions }; } return { dtsFiles, dirName, projectReferences, compilerOptions: { ...options, ...compilerOptions, }, }; } for (const ref of projectReferences) { try { return getCompilerOptions(input, overrideOptions, ref.path); } catch (e) { } } throw new Error(`Module ${input} is not included in the tsconfig project: ${cacheKey}`); } function createProgram$1(fileName, overrideOptions, tsconfig, overrideProjectReferences) { const { dtsFiles, projectReferences, compilerOptions } = getCompilerOptions(fileName, overrideOptions, tsconfig); return ts.createProgram({ rootNames: [fileName].concat(Array.from(dtsFiles)), options: compilerOptions, host: ts.createCompilerHost(compilerOptions, true), projectReferences: projectReferences, }); } function createPrograms(input, overrideOptions, tsconfig) { const programs = []; const dtsFiles = new Set(); let inputs = []; let dirName = ""; let compilerOptions = {}; let projectReferences = []; for (let main of input) { if (DTS_EXTENSIONS.test(main)) { continue; } main = path__namespace.resolve(main); const options = getCompilerOptions(main, overrideOptions, tsconfig); options.dtsFiles.forEach(dtsFiles.add, dtsFiles); if (!inputs.length) { inputs.push(main); ({ dirName, compilerOptions, projectReferences } = options); continue; } if (options.dirName === dirName) { inputs.push(main); } else { const program = ts.createProgram({ rootNames: inputs.concat(Array.from(dtsFiles)), options: compilerOptions, host: ts.createCompilerHost(compilerOptions, true), projectReferences, }); programs.push(program); inputs = [main]; ({ dirName, compilerOptions } = options); } } if (inputs.length) { const host = ts.createCompilerHost(compilerOptions, true); const program = ts.createProgram({ rootNames: inputs.concat(Array.from(dtsFiles)), options: compilerOptions, host, projectReferences, }); programs.push(program); } return programs; } function getCodeFrame() { let codeFrameColumns = undefined; try { ({ codeFrameColumns } = require("@babel/code-frame")); return codeFrameColumns; } catch { try { const esmRequire = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('rollup-plugin-dts.cjs', document.baseURI).href))); ({ codeFrameColumns } = esmRequire("@babel/code-frame")); return codeFrameColumns; } catch { } } return undefined; } function getLocation(node) { const sourceFile = node.getSourceFile(); const start = sourceFile.getLineAndCharacterOfPosition(node.getStart()); const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd()); return { start: { line: start.line + 1, column: start.character + 1 }, end: { line: end.line + 1, column: end.character + 1 }, }; } function frameNode(node) { const codeFrame = getCodeFrame(); const sourceFile = node.getSourceFile(); const code = sourceFile.getFullText(); const location = getLocation(node); if (codeFrame) { return ("\n" + codeFrame(code, location, { highlightCode: true, })); } else { return `\n${location.start.line}:${location.start.column}: \`${node.getFullText().trim()}\``; } } class UnsupportedSyntaxError extends Error { constructor(node, message = "Syntax not yet supported") { super(`${message}\n${frameNode(node)}`); } } class NamespaceFixer { constructor(sourceFile) { this.sourceFile = sourceFile; } findNamespaces() { const namespaces = []; const items = {}; for (const node of this.sourceFile.statements) { const location = { start: node.getStart(), end: node.getEnd(), }; // Well, this is a big hack: // For some global `namespace` and `module` declarations, we generate // some fake IIFE code, so rollup can correctly scan its scope. // However, rollup will then insert bogus semicolons, // these `EmptyStatement`s, which are a syntax error and we want to // remove them. Well, we do that here… if (ts.isEmptyStatement(node)) { namespaces.unshift({ name: "", exports: [], location, }); continue; } // When generating multiple chunks, rollup links those via import // statements, obviously. But rollup uses full filenames with typescript extension, // which typescript does not like. So make sure to change those to javascript extension here. // `.d.ts` -> `.js` // `.d.cts` -> `.cjs` // `.d.mts` -> `.mjs` if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) { const { text } = node.moduleSpecifier; if (text.startsWith(".") && (text.endsWith(".d.ts") || text.endsWith(".d.cts") || text.endsWith(".d.mts"))) { const start = node.moduleSpecifier.getStart() + 1; // +1 to account for the quote const end = node.moduleSpecifier.getEnd() - 1; // -1 to account for the quote namespaces.unshift({ name: "", exports: [], location: { start, end, }, textBeforeCodeAfter: text .replace(/\.d\.ts$/, ".js") .replace(/\.d\.cts$/, ".cjs") .replace(/\.d\.mts$/, ".mjs"), }); } } // Remove redundant `{ Foo as Foo }` exports from a namespace which we // added in pre-processing to fix up broken renaming if (ts.isModuleDeclaration(node) && node.body && ts.isModuleBlock(node.body)) { for (const stmt of node.body.statements) { if (ts.isExportDeclaration(stmt) && stmt.exportClause) { if (ts.isNamespaceExport(stmt.exportClause)) { continue; } for (const decl of stmt.exportClause.elements) { if (decl.propertyName && decl.propertyName.getText() == decl.name.getText()) { namespaces.unshift({ name: "", exports: [], location: { start: decl.propertyName.getEnd(), end: decl.name.getEnd(), }, }); } } } } } if (ts.isClassDeclaration(node)) { items[node.name.getText()] = { type: "class", generics: node.typeParameters }; } else if (ts.isFunctionDeclaration(node)) { // a function has generics, but these don’t need to be specified explicitly, // since functions are treated as values. items[node.name.getText()] = { type: "function" }; } else if (ts.isInterfaceDeclaration(node)) { items[node.name.getText()] = { type: "interface", generics: node.typeParameters }; } else if (ts.isTypeAliasDeclaration(node)) { items[node.name.getText()] = { type: "type", generics: node.typeParameters }; } else if (ts.isModuleDeclaration(node) && ts.isIdentifier(node.name)) { items[node.name.getText()] = { type: "namespace" }; } else if (ts.isEnumDeclaration(node)) { items[node.name.getText()] = { type: "enum" }; } if (!ts.isVariableStatement(node)) { continue; } const { declarations } = node.declarationList; if (declarations.length !== 1) { continue; } const decl = declarations[0]; const name = decl.name.getText(); if (!decl.initializer || !ts.isCallExpression(decl.initializer)) { items[name] = { type: "var" }; continue; } const obj = decl.initializer.arguments[0]; if (!decl.initializer.expression.getFullText().includes("/*#__PURE__*/Object.freeze") || !ts.isObjectLiteralExpression(obj)) { continue; } const exports = []; for (const prop of obj.properties) { if (!ts.isPropertyAssignment(prop) || !(ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)) || (prop.name.text !== "__proto__" && !ts.isIdentifier(prop.initializer))) { throw new UnsupportedSyntaxError(prop, "Expected a property assignment"); } if (prop.name.text === "__proto__") { continue; } exports.push({ exportedName: prop.name.text, localName: prop.initializer.getText(), }); } // sort in reverse order, since we will do string manipulation namespaces.unshift({ name, exports, location, }); } return { namespaces, itemTypes: items }; } fix() { let code = this.sourceFile.getFullText(); const { namespaces, itemTypes } = this.findNamespaces(); for (const ns of namespaces) { const codeAfter = code.slice(ns.location.end); code = code.slice(0, ns.location.start); for (const { exportedName, localName } of ns.exports) { if (exportedName === localName) { const { type, generics } = itemTypes[localName] || {}; if (type === "interface" || type === "type") { // an interface is just a type const typeParams = renderTypeParams(generics); code += `type ${ns.name}_${exportedName}${typeParams.in} = ${localName}${typeParams.out};\n`; } else if (type === "enum" || type === "class") { // enums and classes are both types and values const typeParams = renderTypeParams(generics); code += `type ${ns.name}_${exportedName}${typeParams.in} = ${localName}${typeParams.out};\n`; code += `declare const ${ns.name}_${exportedName}: typeof ${localName};\n`; } else if (type === "namespace") { // namespaces may contain both types and values code += `import ${ns.name}_${exportedName} = ${localName};\n`; } else { // functions and vars are just values code += `declare const ${ns.name}_${exportedName}: typeof ${localName};\n`; } } } if (ns.name) { code += `declare namespace ${ns.name} {\n`; code += ` export {\n`; for (const { exportedName, localName } of ns.exports) { if (exportedName === localName) { code += ` ${ns.name}_${exportedName} as ${exportedName},\n`; } else { code += ` ${localName} as ${exportedName},\n`; } } code += ` };\n`; code += `}`; } code += ns.textBeforeCodeAfter ?? ""; code += codeAfter; } return code; } } function renderTypeParams(typeParameters) { if (!typeParameters || !typeParameters.length) { return { in: "", out: "" }; } return { in: `<${typeParameters.map((param) => param.getText()).join(", ")}>`, out: `<${typeParameters.map((param) => param.name.getText()).join(", ")}>`, }; } let IDs = 1; /** * Create a new `Program` for the given `node`: */ function createProgram(node) { return withStartEnd({ type: "Program", sourceType: "module", body: [], }, { start: node.getFullStart(), end: node.getEnd() }); } /** * Creates a reference to `id`: * `_ = ${id}` */ function createReference(id) { const ident = { type: "Identifier", name: String(IDs++), }; return { ident, expr: { type: "AssignmentPattern", left: ident, right: id, }, }; } function createIdentifier(node) { return withStartEnd({ type: "Identifier", name: node.getText(), }, node); } /** * Create a new Scope which is always included * `(function (_ = MARKER) {})()` */ function createIIFE(range) { const fn = withStartEnd({ type: "FunctionExpression", id: null, params: [], body: { type: "BlockStatement", body: [] }, }, range); const iife = withStartEnd({ type: "ExpressionStatement", expression: { type: "CallExpression", callee: { type: "Identifier", name: String(IDs++) }, arguments: [fn], optional: false, }, }, range); return { fn, iife }; } /** * Create a dummy ReturnStatement with an ArrayExpression: * `return [];` */ function createReturn() { const expr = { type: "ArrayExpression", elements: [], }; return { expr, stmt: { type: "ReturnStatement", argument: expr, }, }; } /** * Create a new Declaration and Scope for `id`: * `function ${id}(_ = MARKER) {}` */ function createDeclaration(id, range) { return withStartEnd({ type: "FunctionDeclaration", id: withStartEnd({ type: "Identifier", name: ts.idText(id), }, id), params: [], body: { type: "BlockStatement", body: [] }, }, range); } function convertExpression(node) { if (ts.isLiteralExpression(node)) { return { type: "Literal", value: node.text }; } if (ts.isPropertyAccessExpression(node)) { if (ts.isPrivateIdentifier(node.name)) { throw new UnsupportedSyntaxError(node.name); } return withStartEnd({ type: "MemberExpression", computed: false, optional: false, object: convertExpression(node.expression), property: convertExpression(node.name), }, { start: node.expression.getStart(), end: node.name.getEnd(), }); } if (ts.isIdentifier(node)) { return createIdentifier(node); } else if (node.kind == ts.SyntaxKind.NullKeyword) { return { type: "Literal", value: null }; } else { throw new UnsupportedSyntaxError(node); } } function withStartEnd(esNode, nodeOrRange) { const range = "start" in nodeOrRange ? nodeOrRange : { start: nodeOrRange.getStart(), end: nodeOrRange.getEnd() }; return Object.assign(esNode, range); } function matchesModifier(node, flags) { const nodeFlags = ts.getCombinedModifierFlags(node); return (nodeFlags & flags) === flags; } /** * The pre-process step has the following goals: * - [x] Fixes the "modifiers", removing any `export` modifier and adding any * missing `declare` modifier. * - [x] Splitting compound `VariableStatement` into its parts. * - [x] Moving declarations for the same "name" to be next to each other. * - [x] Removing any triple-slash directives and recording them. * - [x] Create a synthetic name for any nameless "export default". * - [x] Resolve inline `import()` statements and generate top-level imports for * them. * - [x] Generate a separate `export {}` statement for any item which had its * modifiers rewritten. * - [ ] Duplicate the identifiers of a namespace `export`, so that renaming does * not break it */ function preProcess({ sourceFile }) { const code = new MagicString(sourceFile.getFullText()); /** All the names that are declared in the `SourceFile`. */ const declaredNames = new Set(); /** All the names that are exported. */ const exportedNames = new Set(); /** The name of the default export. */ let defaultExport = ""; /** Inlined exports from `fileId` -> . */ const inlineImports = new Map(); /** The ranges that each name covers, for re-ordering. */ const nameRanges = new Map(); /** * Pass 1: * * - Remove statements that we can’t handle. * - Collect a `Set` of all the declared names. * - Collect a `Set` of all the exported names. * - Maybe collect the name of the default export if present. * - Fix the modifiers of all the items. * - Collect the ranges of each named statement. * - Duplicate the identifiers of a namespace `export`, so that renaming does * not break it */ for (const node of sourceFile.statements) { if (ts.isEmptyStatement(node)) { code.remove(node.getStart(), node.getEnd()); continue; } if (ts.isEnumDeclaration(node) || ts.isFunctionDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isClassDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isModuleDeclaration(node)) { // collect the declared name if (node.name) { const name = node.name.getText(); declaredNames.add(name); // collect the exported name, maybe as `default`. if (matchesModifier(node, ts.ModifierFlags.ExportDefault)) { defaultExport = name; } else if (matchesModifier(node, ts.ModifierFlags.Export)) { exportedNames.add(name); } if (!(node.flags & ts.NodeFlags.GlobalAugmentation)) { pushNamedNode(name, [getStart(node), getEnd(node)]); } } // duplicate exports of namespaces if (ts.isModuleDeclaration(node)) { duplicateExports(code, node); } fixModifiers(code, node); } else if (ts.isVariableStatement(node)) { const { declarations } = node.declarationList; // collect all the names, also check if they are exported const isExport = matchesModifier(node, ts.ModifierFlags.Export); for (const decl of node.declarationList.declarations) { if (ts.isIdentifier(decl.name)) { const name = decl.name.getText(); declaredNames.add(name); if (isExport) { exportedNames.add(name); } } } fixModifiers(code, node); // collect the ranges for re-ordering if (declarations.length === 1) { const decl = declarations[0]; if (ts.isIdentifier(decl.name)) { pushNamedNode(decl.name.getText(), [getStart(node), getEnd(node)]); } } else { // we do reordering after splitting const decls = declarations.slice(); const first = decls.shift(); pushNamedNode(first.name.getText(), [getStart(node), first.getEnd()]); for (const decl of decls) { if (ts.isIdentifier(decl.name)) { pushNamedNode(decl.name.getText(), [decl.getFullStart(), decl.getEnd()]); } } } // split the variable declaration into different statements const { flags } = node.declarationList; const type = flags & ts.NodeFlags.Let ? "let" : flags & ts.NodeFlags.Const ? "const" : "var"; const prefix = `declare ${type} `; const list = node.declarationList .getChildren() .find((c) => c.kind === ts.SyntaxKind.SyntaxList) .getChildren(); let commaPos = 0; for (const node of list) { if (node.kind === ts.SyntaxKind.CommaToken) { commaPos = node.getStart(); code.remove(commaPos, node.getEnd()); } else if (commaPos) { code.appendLeft(commaPos, ";\n"); const start = node.getFullStart(); const slice = code.slice(start, node.getStart()); const whitespace = slice.length - slice.trimStart().length; if (whitespace) { code.overwrite(start, start + whitespace, prefix); } else { code.appendLeft(start, prefix); } } } } } /** * Pass 2: * * Now that we have a Set of all the declared names, we can use that to * generate and de-conflict names for the following steps: * * - Resolve all the inline imports. * - Give any name-less `default export` a name. */ for (const node of sourceFile.statements) { // recursively check inline imports checkInlineImport(node); if (!matchesModifier(node, ts.ModifierFlags.ExportDefault)) { continue; } // only function and class can be default exported, and be missing a name if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)) { if (node.name) { continue; } if (!defaultExport) { defaultExport = uniqName("export_default"); } const children = node.getChildren(); const idx = children.findIndex((node) => node.kind === ts.SyntaxKind.ClassKeyword || node.kind === ts.SyntaxKind.FunctionKeyword); const token = children[idx]; const nextToken = children[idx + 1]; const isPunctuation = nextToken.kind >= ts.SyntaxKind.FirstPunctuation && nextToken.kind <= ts.SyntaxKind.LastPunctuation; if (isPunctuation) { const addSpace = code.slice(token.getEnd(), nextToken.getStart()) != " "; code.appendLeft(nextToken.getStart(), `${addSpace ? " " : ""}${defaultExport}`); } else { code.appendRight(token.getEnd(), ` ${defaultExport}`); } } } // and re-order all the name ranges to be contiguous for (const ranges of nameRanges.values()) { // we have to move all the nodes in front of the *last* one, which is a bit // unintuitive but is a workaround for: // https://github.com/Rich-Harris/magic-string/issues/180 const last = ranges.pop(); const start = last[0]; for (const node of ranges) { code.move(node[0], node[1], start); } } // render all the inline imports, and all the exports if (defaultExport) { code.append(`\nexport default ${defaultExport};\n`); } if (exportedNames.size) { code.append(`\nexport { ${[...exportedNames].join(", ")} };\n`); } for (const [fileId, importName] of inlineImports.entries()) { code.prepend(`import * as ${importName} from "${fileId}";\n`); } const lineStarts = sourceFile.getLineStarts(); // and collect/remove all the typeReferenceDirectives const typeReferences = new Set(); for (const ref of sourceFile.typeReferenceDirectives) { typeReferences.add(ref.fileName); const { line } = sourceFile.getLineAndCharacterOfPosition(ref.pos); const start = lineStarts[line]; let end = sourceFile.getLineEndOfPosition(ref.pos); if (code.slice(end, end + 1) === "\n") { end += 1; } code.remove(start, end); } // and collect/remove all the fileReferenceDirectives const fileReferences = new Set(); for (const ref of sourceFile.referencedFiles) { fileReferences.add(ref.fileName); const { line } = sourceFile.getLineAndCharacterOfPosition(ref.pos); const start = lineStarts[line]; let end = sourceFile.getLineEndOfPosition(ref.pos); if (code.slice(end, end + 1) === "\n") { end += 1; } code.remove(start, end); } return { code, typeReferences, fileReferences, }; function checkInlineImport(node) { ts.forEachChild(node, checkInlineImport); if (ts.isImportTypeNode(node)) { if (!ts.isLiteralTypeNode(node.argument) || !ts.isStringLiteral(node.argument.literal)) { throw new UnsupportedSyntaxError(node, "inline imports should have a literal argument"); } const fileId = node.argument.literal.text; const children = node.getChildren(); const start = children.find((t) => t.kind === ts.SyntaxKind.ImportKeyword).getStart(); let end = node.getEnd(); const token = children.find((t) => t.kind === ts.SyntaxKind.DotToken || t.kind === ts.SyntaxKind.LessThanToken); if (token) { end = token.getStart(); } const importName = createNamespaceImport(fileId); code.overwrite(start, end, importName); } } function createNamespaceImport(fileId) { let importName = inlineImports.get(fileId); if (!importName) { importName = uniqName(fileId.replace(/[^a-zA-Z0-9_$]/g, () => "_")); inlineImports.set(fileId, importName); } return importName; } function uniqName(hint) { let name = hint; while (declaredNames.has(name)) { name = `_${name}`; } declaredNames.add(name); return name; } function pushNamedNode(name, range) { let nodes = nameRanges.get(name); if (!nodes) { nodes = [range]; nameRanges.set(name, nodes); } else { const last = nodes[nodes.length - 1]; if (last[1] === range[0]) { last[1] = range[1]; } else { nodes.push(range); } } } } function fixModifiers(code, node) { // remove the `export` and `default` modifier, add a `declare` if its missing. if (!ts.canHaveModifiers(node)) { return; } let hasDeclare = false; const needsDeclare = ts.isEnumDeclaration(node) || ts.isClassDeclaration(node) || ts.isFunctionDeclaration(node) || ts.isModuleDeclaration(node) || ts.isVariableStatement(node); for (const mod of node.modifiers ?? []) { switch (mod.kind) { case ts.SyntaxKind.ExportKeyword: // fall through case ts.SyntaxKind.DefaultKeyword: // TODO: be careful about that `+ 1` code.remove(mod.getStart(), mod.getEnd() + 1); break; case ts.SyntaxKind.DeclareKeyword: hasDeclare = true; } } if (needsDeclare && !hasDeclare) { code.appendRight(node.getStart(), "declare "); } } function duplicateExports(code, module) { if (!module.body || !ts.isModuleBlock(module.body)) { return; } for (const node of module.body.statements) { if (ts.isExportDeclaration(node) && node.exportClause) { if (ts.isNamespaceExport(node.exportClause)) { continue; } for (const decl of node.exportClause.elements) { if (!decl.propertyName) { code.appendLeft(decl.name.getEnd(), ` as ${decl.name.getText()}`); } } } } } function getStart(node) { const start = node.getFullStart(); return start + (newlineAt(node, start) ? 1 : 0); } function getEnd(node) { const end = node.getEnd(); return end + (newlineAt(node, end) ? 1 : 0); } function newlineAt(node, idx) { return node.getSourceFile().getFullText()[idx] === "\n"; } const IGNORE_TYPENODES = new Set([ ts.SyntaxKind.LiteralType, ts.SyntaxKind.VoidKeyword, ts.SyntaxKind.UnknownKeyword, ts.SyntaxKind.AnyKeyword, ts.SyntaxKind.BooleanKeyword, ts.SyntaxKind.NumberKeyword, ts.SyntaxKind.StringKeyword, ts.SyntaxKind.ObjectKeyword, ts.SyntaxKind.NullKeyword, ts.SyntaxKind.UndefinedKeyword, ts.SyntaxKind.SymbolKeyword, ts.SyntaxKind.NeverKeyword, ts.SyntaxKind.ThisKeyword, ts.SyntaxKind.ThisType, ts.SyntaxKind.BigIntKeyword, ]); class DeclarationScope { constructor({ id, range }) { /** * As we walk the AST, we need to keep track of type variable bindings that * shadow the outer identifiers. To achieve this, we keep a stack of scopes, * represented as Sets of variable names. */ this.scopes = []; if (id) { this.declaration = createDeclaration(id, range); } else { const { iife, fn } = createIIFE(range); this.iife = iife; this.declaration = fn; } const ret = createReturn(); this.declaration.body.body.push(ret.stmt); this.returnExpr = ret.expr; } pushScope() { this.scopes.push(new Set()); } popScope(n = 1) { for (let i = 0; i < n; i++) { this.scopes.pop(); } } pushTypeVariable(id) { const name = id.getText(); this.scopes[this.scopes.length - 1]?.add(name); } pushReference(id) { let name; // We convert references from TS AST to ESTree // to hand them off to rollup. // This means we have to check the left-most identifier inside our scope // tree and avoid to create the reference in that case if (id.type === "Identifier") { name = id.name; } else if (id.type === "MemberExpression") { if (id.object.type === "Identifier") { name = id.object.name; } } if (name) { for (const scope of this.scopes) { if (scope.has(name)) { return; } } } // `this` is a reserved keyword that retrains meaning in certain Type-only contexts, including classes if (name === "this") return; const { ident, expr } = createReference(id); this.declaration.params.push(expr); this.returnExpr.elements.push(ident); } pushIdentifierReference(id) { this.pushReference(createIdentifier(id)); } convertEntityName(node) { if (ts.isIdentifier(node)) { return createIdentifier(node); } return withStartEnd({ type: "MemberExpression", computed: false, optional: false, object: this.convertEntityName(node.left), property: createIdentifier(node.right), }, node); } convertPropertyAccess(node) { // hm, we only care about property access expressions here… if (!ts.isIdentifier(node.expression) && !ts.isPropertyAccessExpression(node.expression)) { throw new UnsupportedSyntaxError(node.expression); } if (ts.isPrivateIdentifier(node.name)) { throw new UnsupportedSyntaxError(node.name); } const object = ts.isIdentifier(node.expression) ? createIdentifier(node.expression) : this.convertPropertyAccess(node.expression); return withStartEnd({ type: "MemberExpression", computed: false, optional: false, object, property: createIdentifier(node.name), }, node); } convertComputedPropertyName(node) { if (!node.name || !ts.isComputedPropertyName(node.name)) { return; } const { expression } = node.name; if (ts.isLiteralExpression(expression) || ts.isPrefixUnaryExpression(expression)) { return; } if (ts.isIdentifier(expression)) { return this.pushReference(createIdentifier(expression)); } if (ts.isPropertyAccessExpression(expression)) { return this.pushReference(this.convertPropertyAccess(expression)); } throw new UnsupportedSyntaxError(expression); } convertParametersAndType(node) { this.convertComputedPropertyName(node); const typeVariables = this.convertTypeParameters(node.typeParameters); for (const param of node.parameters) { this.convertTypeNode(param.type); } this.convertTypeNode(node.type); this.popScope(typeVariables); } convertHeritageClauses(node) { for (const heritage of node.heritageClauses || []) { for (const type of heritage.types) { this.pushReference(convertExpression(type.expression)); this.convertTypeArguments(type); } } } convertTypeArguments(node) { if (!node.typeArguments) { return; } for (const arg of node.typeArguments) { this.convertTypeNode(arg); } } convertMembers(members) { for (const node of members) { if (ts.isPropertyDeclaration(node) || ts.isPropertySignature(node) || ts.isIndexSignatureDeclaration(node)) { if (ts.isPropertyDeclaration(node) && node.initializer && ts.isPropertyAccessExpression(node.initializer)) { this.pushReference(this.convertPropertyAccess(node.initializer)); } this.convertComputedPropertyName(node); this.convertTypeNode(node.type); continue; } if (ts.isMethodDeclaration(node) || ts.isMethodSignature(node) || ts.isConstructorDeclaration(node) || ts.isConstructSignatureDeclaration(node) || ts.isCallSignatureDeclaration(node) || ts.isGetAccessorDeclaration(node) || ts.isSetAccessorDeclaration(node)) { this.convertParametersAndType(node); } else { throw new UnsupportedSyntaxError(node); } } } convertTypeParameters(params) { if (!params) { return 0; } for (const node of params) { this.convertTypeNode(node.constraint); this.convertTypeNode(node.default); this.pushScope(); this.pushTypeVariable(node.name); } return params.length; } convertTypeNode(node) { if (!node) { return; } if (IGNORE_TYPENODES.has(node.kind)) { return; } if (ts.isTypeReferenceNode(node)) { this.pushReference(this.convertEntityName(node.typeName)); this.convertTypeArguments(node); return; } if (ts.isTypeLiteralNode(node)) { this.convertMembers(node.members); return; } if (ts.isArrayTypeNode(node)) { this.convertTypeNode(node.elementType); return; } if (ts.isTupleTypeNode(node)) { for (const type of node.elements) { this.convertTypeNode(type); } return; } if (ts.isNamedTupleMember(node) || ts.isParenthesizedTypeNode(node) || ts.isTypeOperatorNode(node) || ts.isTypePredicateNode(node)) { this.convertTypeNode(node.type); return; } if (ts.isUnionTypeNode(node) || ts.isIntersectionTypeNode(node)) { for (const type of node.types) { this.convertTypeNode(type); } return; } if (ts.isMappedTypeNode(node)) { const { typeParameter, type, nameType } = node; this.convertTypeNode(typeParameter.constraint); this.pushScope(); this.pushTypeVariable(typeParameter.name); this.convertTypeNode(type); if (nameType) { this.convertTypeNode(nameType); } this.popScope(); return; } if (ts.isConditionalTypeNode(node)) { this.convertTypeNode(node.checkType); this.pushScope(); this.convertTypeNode(node.extendsType); this.convertTypeNode(node.trueType); this.convertTypeNode(node.falseType); this.popScope(); return; } if (ts.isIndexedAccessTypeNode(node)) { this.convertTypeNode(node.objectType); this.convertTypeNode(node.indexType); return; } if (ts.isFunctionOrConstructorTypeNode(node)) { this.convertParametersAndType(node); return; } if (ts.isTypeQueryNode(node)) { this.pushReference(this.convertEntityName(node.exprName)); return; } if (ts.isRestTypeNode(node)) { this.convertTypeNode(node.type); return; } if (ts.isOptionalTypeNode(node)) { this.convertTypeNode(node.type); return; } if (ts.isTemplateLiteralTypeNode(node)) { for (const span of node.templateSpans) { this.convertTypeNode(span.type); } return; } if (ts.isInferTypeNode(node)) { const { typeParameter } = node; this.convertTypeNode(typeParameter.constraint); this.pushTypeVariable(typeParameter.name); return; } else { throw new UnsupportedSyntaxError(node); } } convertNamespace(node, relaxedModuleBlock = false) { this.pushScope(); if (relaxedModuleBlock && node.body && ts.isModuleDeclaration(node.body)) { this.convertNamespace(node.body, true); return; } if (!node.body || !ts.isModuleBlock(node.body)) { throw new UnsupportedSyntaxError(node, `namespace must have a "ModuleBlock" body.`); } const { statements } = node.body; // first, hoist all the declarations for correct shadowing for (const stmt of statements) { if (ts.isEnumDeclaration(stmt) || ts.isFunctionDeclaration(stmt) || ts.isClassDeclaration(stmt) || ts.isInterfaceDeclaration(stmt) || ts.isTypeAliasDeclaration(stmt) || ts.isModuleDeclaration(stmt)) { if (stmt.name && ts.isIdentifier(stmt.name)) { this.pushTypeVariable(stmt.name); } else { throw new UnsupportedSyntaxError(stmt, "non-Identifier name not supported"); } continue; } if (ts.isVariableStatement(stmt)) { for (const decl of stmt.declarationList.declarations) { if (ts.isIdentifier(decl.name)) { this.pushTypeVariable(decl.name); } else { throw new UnsupportedSyntaxError(decl, "non-Identifier name not supported"); } } continue; } if (ts.isExportDeclaration(stmt)) ; else { throw new UnsupportedSyntaxError(stmt, "namespace child (hoisting) not supported yet"); } } // and then walk all the children like normal… for (const stmt of statements) { if (ts.isVariableStatement(stmt)) { for (const decl of stmt.declarationList.declarations) { if (decl.type) { this.convertTypeNode(decl.type); } } continue; } if (ts.isFunctionDeclaration(stmt)) { this.convertParametersAndType(stmt); continue; } if (ts.isInterfaceDeclaration(stmt) || ts.isClassDeclaration(stmt)) { const typeVariables = this.convertTypeParameters(stmt.typeParameters); this.convertHeritageClauses(stmt); this.convertMembers(stmt.members); this.popScope(typeVariables); continue; } if (ts.isTypeAliasDeclaration(stmt)) { const typeVariables = this.convertTypeParameters(stmt.typeParameters); this.convertTypeNode(stmt.type); this.popScope(typeVariables); continue; } if (ts.isModuleDeclaration(stmt)) { this.convertNamespace(stmt, relaxedModuleBlock); continue; } if (ts.isEnumDeclaration(stmt)) { // noop continue; } if (ts.isExportDeclaration(stmt)) { if (stmt.exportClause) { if (ts.isNamespaceExport(stmt.exportClause)) { throw new UnsupportedSyntaxError(stmt.exportClause); } for (const decl of stmt.exportClause.elements) { const id = decl.propertyName || decl.name; this.pushIdentifierReference(id); } } } else { throw new UnsupportedSyntaxError(stmt, "namespace child (walking) not supported yet"); } } this.popScope(); } } function convert({ sourceFile }) { const transformer = new Transformer(sourceFile); return transformer.transform(); } class Transformer { constructor(sourceFile) { this.sourceFile = sourceFile; this.declarations = new Map(); this.ast = createProgram(sourceFile); for (const stmt of sourceFile.statements) { this.convertStatement(stmt); } } transform() { return { ast: this.ast, }; } pushStatement(node) { this.ast.body.push(node); } createDeclaration(node, id) { const range = { start: node.getFullStart(), end: node.getEnd() }; if (!id) { const scope = new DeclarationScope({ range }); this.pushStatement(scope.iife); return scope; } const name = id.getText(); // We have re-ordered and grouped declarations in `reorderStatements`, // so we can assume same-name statements are next to each other, so we just // bump the `end` range. const scope = new DeclarationScope({ id, range }); const existingScope = this.declarations.get(name); if (existingScope) { existingScope.pushIdentifierReference(id); existingScope.declaration.end = range.end; // we possibly have other declarations, such as an ExportDeclaration in // between, which should also be updated to the correct start/end. const selfIdx = this.ast.body.findIndex((node) => node == existingScope.declaration); for (let i = selfIdx + 1; i < this.ast.body.length; i++) { const decl = this.ast.body[i]; decl.start = decl.end = range.end; } } else { this.pushStatement(scope.declaration); this.declarations.set(name, scope); } return existingScope || scope; } convertStatement(node) { if (ts.isEnumDeclaration(node)) { return this.convertEnumDeclaration(node); } if (ts.isFunctionDeclaration(node)) { return this.convertFunctionDeclaration(node); } if (ts.isInterfaceDeclaration(node) || ts.isClassDeclaration(node)) { return this.convertClassOrInterfaceDeclaration(node); } if (ts.isTypeAliasDeclaration(node)) { return this.convertTypeAliasDeclaration(node); } if (ts.isVariableStatement(node)) { return this.convertVariableStatement(node); } if (ts.isExportDeclaration(node) || ts.isExportAssignment(node)) { return this.convertExportDeclaration(node); } if (ts.isModuleDeclaration(node)) { return this.convertNamespaceDeclaration(node); } if (node.kind === ts.SyntaxKind.NamespaceExportDeclaration) { // just ignore `export as namespace FOO` statements… return this.removeStatement(node); } if (ts.isImportDeclaration(node) || ts.isImportEqualsDeclaration(node)) { return this.convertImportDeclaration(node); } else { throw new UnsupportedSyntaxError(node); } } removeStatement(node) { this.pushStatement(withStartEnd({ type: "ExpressionStatement", expression: { type: "Literal", value: "pls remove me" }, }, node)); } convertNamespaceDeclaration(node) { // we want to keep `declare global` augmentations, and we want to // pull in all the things referenced inside. // so for this case, we need to figure out some way so that rollup does // the right thing and not rename these… const isGlobalAugmentation = node.flags & ts.NodeFlags.GlobalAugmentation; if (isGlobalAugmentation || !ts.isIdentifier(node.name)) { const scope = this.createDeclaration(node); scope.convertNamespace(node, true); return; } const scope = this.createDeclaration(node, node.name); scope.pushIdentifierReference(node.name); scope.convertNamespace(node); } convertEnumDeclaration(node) { const scope = this.createDeclaration(node, node.name); scope.pushIdentifierReference(node.name); } convertFunctionDeclaration(node) { if (!node.name) { throw new UnsupportedSyntaxError(node, "FunctionDeclaration should have a name"); } const scope = this.createDeclaration(node, node.name); scope.pushIdentifierReference(node.name); scope.convertParametersAndType(node); } convertClassOrInterfaceDeclaration(node) { if (!node.name) { throw new UnsupportedSyntaxError(node, "ClassDeclaration / InterfaceDeclaration should have a name"); } const scope = this.createDeclaration(node, node.name); const typeVariables = scope.convertTypeParameters(node.typeParameters); scope.convertHeritageClauses(node); scope.convertMembers(node.members); scope.popScope(typeVariables); } convertTypeAliasDeclaration(node) { const scope = this.createDeclaration(node, node.name); const typeVariables = scope.convertTypeParameters(node.typeParameters); scope.convertTypeNode(node.type); scope.popScope(typeVariables); } convertVariableStatement(node) { const { declarations } = node.declarationList; if (declarations.length !== 1) { throw new UnsupportedSyntaxError(node, "VariableStatement with more than one declaration not yet supported"); } for (const decl of declarations) { if (!ts.isIdentifier(decl.name)) { throw new UnsupportedSyntaxError(node, "VariableDeclaration must have a name"); } const scope = this.createDeclaration(node, decl.name); scope.convertTypeNode(decl.type); } } convertExportDeclaration(node) { if (ts.isExportAssignment(node)) { this.pushStatement(withStartEnd({ type: "ExportDefaultDeclaration", declaration: convertExpression(node.expression), }, node)); return; } const source = node.moduleSpecifier ? convertExpression(node.moduleSpecifier) : undefined; if (!node.exportClause) { // export * from './other' this.pushStatement(withStartEnd({ type: "ExportAllDeclaration", source, exported: null, }, node)); } else if (ts.isNamespaceExport(node.exportClause)) { // export * as name from './other' this.pushStatement(withStartEnd({ type: "ExportAllDeclaration", source, exported: createIdentifier(node.exportClause.name), }, node)); } else { // export { name } from './other' const specifiers = []; for (const elem of node.exportClause.elements) { specifiers.push(this.convertExportSpecifier(elem)); } this.pushStatement(withStartEnd({ type: "ExportNamedDeclaration", declaration: null, specifiers, source, }, node)); } } convertImportDeclaration(node) { if (ts.isImportEqualsDeclaration(node)) { if (ts.isEntityName(node.moduleReference)) { const scope = this.createDeclaration(node, node.name); scope.pushReference(scope.convertEntityName(node.moduleReference)); return; } // assume its like `import default` if (!ts.isExternalModuleReference(node.moduleReference)) { throw new UnsupportedSyntaxError(node, "ImportEquals should have a literal source."); } this.pushStatement(withStartEnd({ type: "ImportDeclaration", specifiers: [ { type: "ImportDefaultSpecifier", local: createIdentifier(node.name), }, ], source: convertExpression(node.moduleReference.expression), }, node)); return; } const source = convertExpression(node.moduleSpecifier); const specifiers = node.importClause && node.importClause.namedBindings ? this.convertNamedImportBindings(node.importClause.namedBindings) : []; if (node.importClause && node.importClause.name) { specifiers.push({ type: "ImportDefaultSpecifier", local: createIdentifier(node.importClause.name), }); } this.pushStatement(withStartEnd({ type: "ImportDeclaration", specifiers, source, }, node)); } convertNamedImportBindings(node) { if (ts.isNamedImports(node)) { return node.elements.map((el) => { const local = createIdentifier(el.name); const imported = el.propertyName ? createIdentifier(el.propertyName) : local; return { type: "ImportSpecifier", local, imported, }; }); } return [ { type: "ImportNamespaceSpecifier", local: createIdentifier(node.name), }, ]; } convertExportSpecifier(node) { const exported = createIdentifier(node.name); return { type: "ExportSpecifier", exported: exported, local: node.propertyName ? createIdentifier(node.propertyName) : exported, }; } } class ExportsFixer { constructor(source) { this.source = source; this.DEBUG = !!process.env.DTS_EXPORTS_FIXER_DEBUG; } fix() { const exports = this.findExports(); exports.sort((a, b) => a.location.start - b.location.start); return this.getCodeParts(exports).join(""); } findExports() { const { rawExports, values, types } = this.getExportsAndLocals(); return rawExports.map((rawExport) => { const elements = rawExport.elements.map((e) => { const exportedName = e.name.text; const localName = e.propertyName?.text ?? e.name.text; const kind = types.some((node) => node.getText() === localName) && !values.some((node) => node.getText() === localName) ? "type" : "value"; this.DEBUG && console.log(`export ${localName} as ${exportedName} is a ${kind}`); return { exportedName, localName, kind, }; }); return { location: { start: rawExport.getStart(), end: rawExport.getEnd(), }, exports: elements, }; }); } getExportsAndLocals(statements = this.source.statements) { const rawExports = []; const values = []; const types = []; const recurseInto = (subStatements) => { const { rawExports: subExports, values: subValues, types: subTypes } = this.getExportsAndLocals(subStatements); rawExports.push(...subExports); values.push(...subValues); types.push(...subTypes); }; for (const statement of statements) { this.DEBUG && console.log(statement.getText(), statement.kind); if (ts.isImportDeclaration(statement)) { continue; } if (ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement)) { this.DEBUG && console.log(`${statement.name.getFullText()} is a type`); types.push(statement.name); continue; } if (ts.isEnumDeclaration(statement) || ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement) || ts.isVariableStatement(statement)) { if (ts.isVariableStatement(statement)) { for (const declaration of statement.declarationList.declarations) { if (ts.isIdentifier(declaration.name)) { this.DEBUG && console.log(`${declaration.name.getFullText()} is a value (from var statement)`); values.push(declaration.name); } } } else { if (statement.name) { this.DEBUG && console.log(`${statement.name.getFullText()} is a value (from declaration)`); values.push(statement.name); } } continue; } if (ts.isModuleBlock(statement)) { const subStatements = statement.statements; recurseInto(subStatements); continue; } if (ts.isModuleDeclaration(statement)) { if (statement.name && ts.isIdentifier(statement.name)) { this.DEBUG && console.log(`${statement.name.getFullText()} is a value (from module declaration)`); values.push(statement.name); } recurseInto(statement.getChildren()); continue; } if (ts.isExportDeclaration(statement)) { if (statement.moduleSpecifier) { continue; } if (statement.isTypeOnly) { // no fixup neccessary continue; } const exportClause = statement.exportClause; if (!exportClause || !ts.isNamedExports(exportClause)) { continue; } rawExports.push(exportClause); continue; } this.DEBUG && console.log("unhandled statement", statement.getFullText(), statement.kind); } return { rawExports, values, types }; } createNamedExport(exportSpec, elideType = false) { return `${!elideType && exportSpec.kind === "type" ? "type " : ""}${exportSpec.localName}${exportSpec.localName === exportSpec.exportedName ? "" : ` as ${exportSpec.exportedName}`}`; } getCodeParts(exports) { let cursor = 0; const code = this.source.getFullText(); const parts = []; for (const exportDeclaration of exports) { const head = code.slice(cursor, exportDeclaration.location.start); if (head.length > 0) { parts.push(head); } parts.push(this.getExportStatement(exportDeclaration)); cursor = exportDeclaration.location.end; } if (cursor < code.length) { parts.push(code.slice(cursor)); } return parts; } getExportStatement(exportDeclaration) { const isTypeOnly = exportDeclaration.exports.every((e) => e.kind === "type") && exportDeclaration.exports.length > 0; return `${isTypeOnly ? "type " : ""}{ ${exportDeclaration.exports.map((exp) => this.createNamedExport(exp, isTypeOnly)).join(", ")} }`; } } function parse(fileName, code) { return ts.createSourceFile(fileName, code, ts.ScriptTarget.Latest, true); } /** * This is the *transform* part of `rollup-plugin-dts`. * * It sets a few input and output options, and otherwise is the core part of the * plugin responsible for bundling `.d.ts` files. * * That itself is a multi-step process: * * 1. The plugin has a preprocessing step that moves code around and cleans it * up a bit, so that later steps can work with it easier. See `preprocess.ts`. * 2. It then converts the TypeScript AST into a ESTree-like AST that rollup * understands. See `Transformer.ts`. * 3. After rollup is finished, the plugin will postprocess the output in a * `renderChunk` hook. As rollup usually outputs javascript, it can output * some code that is invalid in the context of a `.d.ts` file. In particular, * the postprocess convert any javascript code that was created for namespace * exports into TypeScript namespaces. See `NamespaceFixer.ts`. */ const transform = () => { const allTypeReferences = new Map(); const allFileReferences = new Map(); return { name: "dts-transform", options({ onLog, ...options }) { return { ...options, onLog(level, log, defaultHandler) { if (level === "warn" && log.code === "CIRCULAR_DEPENDENCY") { return; } if (onLog) { onLog(level, log, defaultHandler); } else { defaultHandler(level, log); } }, treeshake: { moduleSideEffects: "no-external", propertyReadSideEffects: true, unknownGlobalSideEffects: false, }, }; }, outputOptions(options) { return { ...options, chunkFileNames: options.chunkFileNames || "[name]-[hash].d.ts", entryFileNames: options.entryFileNames || "[name].d.ts", format: "es", exports: "named", compact: false, freeze: true, interop: "esModule", generatedCode: Object.assign({ symbols: false }, options.generatedCode), strict: false, }; }, transform(code, fileName) { let sourceFile = parse(fileName, code); const preprocessed = preProcess({ sourceFile }); // `sourceFile.fileName` here uses forward slashes allTypeReferences.set(sourceFile.fileName, preprocessed.typeReferences); allFileReferences.set(sourceFile.fileName, preprocessed.fileReferences); code = preprocessed.code.toString(); sourceFile = parse(fileName, code); const converted = convert({ sourceFile }); if (process.env.DTS_DUMP_AST) { console.log(fileName); console.log(code); console.log(JSON.stringify(converted.ast.body, undefined, 2)); } return { code, ast: converted.ast, map: preprocessed.code.generateMap() }; }, renderChunk(inputCode, chunk, options) { const source = parse(chunk.fileName, inputCode); const fixer = new NamespaceFixer(source); const typeReferences = new Set(); const fileReferences = new Set(); for (const fileName of Object.keys(chunk.modules)) { for (const ref of allTypeReferences.get(fileName.split("\\").join("/")) || []) { typeReferences.add(ref); } for (const ref of allFileReferences.get(fileName.split("\\").join("/")) || []) { if (ref.startsWith(".")) { // Need absolute path of the target file here const absolutePathToOriginal = path__namespace.join(path__namespace.dirname(fileName), ref); const chunkFolder = (options.file && path__namespace.dirname(options.file)) || (chunk.facadeModuleId && path__namespace.dirname(chunk.facadeModuleId)) || "."; let targetRelPath = path__namespace.relative(chunkFolder, absolutePathToOriginal).split("\\").join("/"); if (targetRelPath[0] !== ".") { targetRelPath = "./" + targetRelPath; } fileReferences.add(targetRelPath); } else { fileReferences.add(ref); } } } let code = writeBlock(Array.from(fileReferences, (ref) => `/// `)); code += writeBlock(Array.from(typeReferences, (ref) => `/// `)); code += fixer.fix(); if (!code) { code += "\nexport { }"; } const exportsFixer = new ExportsFixer(parse(chunk.fileName, code)); return { code: exportsFixer.fix(), map: { mappings: "" } }; }, }; }; function writeBlock(codes) { if (codes.length) { return codes.join("\n") + "\n"; } return ""; } const TS_EXTENSIONS = /\.([cm]ts|[tj]sx?)$/; const refPrograms = new Map(); function getModule({ entries, programs, resolvedOptions: { compilerOptions, tsconfig } }, fileName, code) { // Create any `ts.SourceFile` objects on-demand for ".d.ts" modules, // but only when there are zero ".ts" entry points. if (!programs.length && DTS_EXTENSIONS.test(fileName)) { return { code }; } const isEntry = entries.includes(fileName); // Rollup doesn't tell you the entry point of each module in the bundle, // so we need to ask every TypeScript program for the given filename. let existingProgram = programs.find((p) => { // Entry points may be in the other entry source files, but it can't emit from them. // So we should find the program about the entry point which is the root files. if (isEntry) { return p.getRootFileNames().includes(fileName); } else { const sourceFile = p.getSourceFile(fileName); if (sourceFile && p.isSourceFileFromExternalLibrary(sourceFile)) { return false; } return !!sourceFile; } }); existingProgram?.getResolvedProjectReferences()?.forEach(ref => { if (ref === undefined || ref.commandLine === undefined) return; const { commandLine, sourceFile: tsconfigSourceFile } = ref; let program = refPrograms.get(tsconfigSourceFile.fileName); if (!program) { program = ts.createProgram({ rootNames: commandLine.fileNames, options: commandLine.options, host: ts.createCompilerHost(commandLine.options, true), projectReferences: commandLine.projectReferences, }); } refPrograms.set(tsconfigSourceFile.fileName, program); const sourceFile = program.getSourceFile(fileName); if (!sourceFile || sourceFile && program.isSourceFileFromExternalLibrary(sourceFile)) { return; } existingProgram = program; }); if (existingProgram) { // we know this exists b/c of the .filter above, so this non-null assertion is safe const source = existingProgram.getSourceFile(fileName); return { code: source?.getFullText(), source, program: existingProgram, }; } else if (ts.sys.fileExists(fileName)) { const newProgram = createProgram$1(fileName, compilerOptions, tsconfig); programs.push(newProgram); // we created hte program from this fileName, so the source file must exist :P const source = newProgram.getSourceFile(fileName); return { code: source?.getFullText(), source, program: newProgram, }; } else { // the file isn't part of an existing program and doesn't exist on disk return null; } } const plugin = (options = {}) => { const transformPlugin = transform(); const ctx = { entries: [], programs: [], resolvedOptions: resolveDefaultOptions(options) }; return { name: "dts", // pass outputOptions & renderChunk hooks to the inner transform plugin outputOptions: transformPlugin.outputOptions, renderChunk: transformPlugin.renderChunk, options(options) { let { input = [] } = options; if (!Array.isArray(input)) { input = typeof input === "string" ? [input] : Object.values(input); } else if (input.length > 1) { // when dealing with multiple unnamed inputs, transform the inputs into // an explicit object, which strips the file extension options.input = {}; for (const filename of input) { let name = filename.replace(/((\.d)?\.(c|m)?(t|j)sx?)$/, ""); if (path__namespace.isAbsolute(filename)) { name = path__namespace.basename(name); } else { name = path__namespace.normalize(name); } options.input[name] = filename; } } ctx.programs = createPrograms(Object.values(input), ctx.resolvedOptions.compilerOptions, ctx.resolvedOptions.tsconfig); return transformPlugin.options.call(this, options); }, transform(code, id) { if (!TS_EXTENSIONS.test(id)) { return null; } const watchFiles = (module) => { if (module.program) { const sourceDirectory = path__namespace.dirname(id); const sourceFilesInProgram = module.program .getSourceFiles() .map((sourceFile) => sourceFile.fileName) .filter((fileName) => fileName.startsWith(sourceDirectory)); sourceFilesInProgram.forEach(this.addWatchFile); } }; const handleDtsFile = () => { const module = getModule(ctx, id, code); if (module) { watchFiles(module); return transformPlugin.transform.call(this, module.code, id); } return null; }; const treatTsAsDts = () => { const declarationId = id.replace(TS_EXTENSIONS, dts); const module = getModule(ctx, declarationId, code); if (module) { watchFiles(module); return transformPlugin.transform.call(this, module.code, declarationId); } return null; }; const generateDtsFromTs = () => { const module = getModule(ctx, id, code); if (!module || !module.source || !module.program) return null; watchFiles(module); const declarationId = id.replace(TS_EXTENSIONS, dts); let generated; const { emitSkipped, diagnostics } = module.program.emit(module.source, (_, declarationText) => { generated = transformPlugin.transform.call(this, declarationText, declarationId); }, undefined, // cancellationToken true, // emitOnlyDtsFiles undefined, // customTransformers // @ts-ignore This is a private API for workers, should be safe to use as TypeScript Playground has used it for a long time. true); if (emitSkipped) { const errors = diagnostics.filter((diag) => diag.category === ts.DiagnosticCategory.Error); if (errors.length) { console.error(ts.formatDiagnostics(errors, formatHost)); this.error("Failed to compile. Check the logs above."); } } return generated; }; // if it's a .d.ts file, handle it as-is if (DTS_EXTENSIONS.test(id)) return handleDtsFile(); // first attempt to treat .ts files as .d.ts files, and otherwise use the typescript compiler to generate the declarations return treatTsAsDts() ?? generateDtsFromTs(); }, resolveId: { order: 'post', handler(source, importer) { if (!importer) { // store the entry point, because we need to know which program to add the file ctx.entries.push(path__namespace.resolve(source)); return; } // normalize directory separators to forward slashes, as apparently typescript expects that? importer = importer.split("\\").join("/"); let resolvedCompilerOptions = ctx.resolvedOptions.compilerOptions; if (ctx.resolvedOptions.tsconfig) { // Here we have a chicken and egg problem. // `source` would be resolved by `ts.nodeModuleNameResolver` a few lines below, but // `ts.nodeModuleNameResolver` requires `compilerOptions` which we have to resolve here, // since we have a custom `tsconfig.json`. // So, we use Node's resolver algorithm so we can see where the request is coming from so we // can load the custom `tsconfig.json` from the correct path. const resolvedSource = source.startsWith(".") ? path__namespace.resolve(path__namespace.dirname(importer), source) : source; resolvedCompilerOptions = getCompilerOptions(resolvedSource, ctx.resolvedOptions.compilerOptions, ctx.resolvedOptions.tsconfig).compilerOptions; } // resolve this via typescript const { resolvedModule } = ts.resolveModuleName(source, importer, resolvedCompilerOptions, ts.sys); if (!resolvedModule) { return; } if (!ctx.resolvedOptions.respectExternal && resolvedModule.isExternalLibraryImport) { // here, we define everything that comes from `node_modules` as `external`. return { id: source, external: true }; } else { // using `path.resolve` here converts paths back to the system specific separators return { id: path__namespace.resolve(resolvedModule.resolvedFileName) }; } } }, }; }; exports.default = plugin; exports.dts = plugin;