UNPKG

@hqjs/babel-plugin-transform-typescript

Version:

Original babel plugin fork that supports parameters decorators and dependency injection

172 lines (160 loc) 5.22 kB
const { template } = require("@babel/core"); module.exports = function transpileNamespace(path, t, allowNamespaces) { if (path.node.declare || path.node.id.type === "StringLiteral") { path.remove(); return; } if (!allowNamespaces) { throw path.hub.file.buildCodeFrameError( path.node.id, "Namespace not marked type-only declare." + " Non-declarative namespaces are only supported experimentally in Babel." + " To enable and review caveats see:" + " https://babeljs.io/docs/en/babel-plugin-transform-typescript", ); } const name = path.node.id.name; const value = handleNested(path, t, t.cloneDeep(path.node)); const bound = path.scope.hasOwnBinding(name); if (path.parent.type === "ExportNamedDeclaration") { if (!bound) { path.parentPath.insertAfter(value); path.replaceWith(getDeclaration(t, name)); path.scope.registerDeclaration(path.parentPath); } else { path.parentPath.replaceWith(value); } } else if (bound) { path.replaceWith(value); } else { path.scope.registerDeclaration( path.replaceWithMultiple([getDeclaration(t, name), value])[0], ); } } function getDeclaration(t, name) { return t.variableDeclaration("let", [ t.variableDeclarator(t.identifier(name)), ]); } function getMemberExpression(t, name, itemName) { return t.memberExpression(t.identifier(name), t.identifier(itemName)); } function handleNested(path, t, node, parentExport) { const names = new Set(); const realName = node.id; const name = path.scope.generateUid(realName.name); const namespaceTopLevel = node.body.body; for (let i = 0; i < namespaceTopLevel.length; i++) { const subNode = namespaceTopLevel[i]; // The first switch is mainly to detect name usage. Only export // declarations require further transformation. switch (subNode.type) { case "TSModuleDeclaration": { const transformed = handleNested(path, t, subNode); const moduleName = subNode.id.name; if (names.has(moduleName)) { namespaceTopLevel[i] = transformed; } else { names.add(moduleName); namespaceTopLevel.splice( i++, 1, getDeclaration(t, moduleName), transformed, ); } continue; } case "TSEnumDeclaration": case "FunctionDeclaration": case "ClassDeclaration": names.add(subNode.id.name); continue; case "VariableDeclaration": for (const variable of subNode.declarations) { names.add(variable.id.name); } continue; default: // Neither named declaration nor export, continue to next item. continue; case "ExportNamedDeclaration": // Export declarations get parsed using the next switch. } // Transform the export declarations that occur inside of a namespace. switch (subNode.declaration.type) { case "TSEnumDeclaration": case "FunctionDeclaration": case "ClassDeclaration": { const itemName = subNode.declaration.id.name; names.add(itemName); namespaceTopLevel.splice( i++, 1, subNode.declaration, t.expressionStatement( t.assignmentExpression( "=", getMemberExpression(t, name, itemName), t.identifier(itemName), ), ), ); break; } case "VariableDeclaration": if (subNode.declaration.kind !== "const") { throw path.hub.file.buildCodeFrameError( subNode.declaration, "Namespaces exporting non-const are not supported by Babel." + " Change to const or see:" + " https://babeljs.io/docs/en/babel-plugin-transform-typescript", ); } for (const variable of subNode.declaration.declarations) { variable.init = t.assignmentExpression( "=", getMemberExpression(t, name, variable.id.name), variable.init, ); } namespaceTopLevel[i] = subNode.declaration; break; case "TSModuleDeclaration": { const transformed = handleNested( path, t, subNode.declaration, t.identifier(name), ); const moduleName = subNode.declaration.id.name; if (names.has(moduleName)) { namespaceTopLevel[i] = transformed; } else { names.add(moduleName); namespaceTopLevel.splice( i++, 1, getDeclaration(t, moduleName), transformed, ); } } } } // {} let fallthroughValue = t.objectExpression([]); if (parentExport) { const memberExpr = t.memberExpression(parentExport, realName); fallthroughValue = template.expression.ast` ${t.cloneNode(memberExpr)} || (${t.cloneNode(memberExpr)} = ${fallthroughValue}) `; } return template.statement.ast` (function (${t.identifier(name)}) { ${namespaceTopLevel} })(${realName} || (${t.cloneNode(realName)} = ${fallthroughValue})); `; }