diff --git a/packages/babel-plugin-transform-typescript/src/namespace.ts b/packages/babel-plugin-transform-typescript/src/namespace.ts index 2a24f156deac..186da7c3c77c 100644 --- a/packages/babel-plugin-transform-typescript/src/namespace.ts +++ b/packages/babel-plugin-transform-typescript/src/namespace.ts @@ -1,4 +1,5 @@ import { template, types as t } from "@babel/core"; +import type { NodePath } from "@babel/traverse"; export default function transpileNamespace(path, t, allowNamespaces) { if (path.node.declare || path.node.id.type === "StringLiteral") { @@ -102,11 +103,34 @@ function handleVariableDeclaration( return [node, t.expressionStatement(t.sequenceExpression(assignments))]; } -function handleNested(path, t, node, parentExport?) { +function buildNestedAmbiendModuleError(path: NodePath, node: t.Node) { + throw path.hub.buildError( + node, + "Ambient modules cannot be nested in other modules or namespaces.", + Error, + ); +} + +function handleNested( + path: NodePath, + t: typeof import("@babel/types"), + node: t.TSModuleDeclaration, + parentExport?, +) { const names = new Set(); const realName = node.id; + t.assertIdentifier(realName); + const name = path.scope.generateUid(realName.name); - const namespaceTopLevel = node.body.body; + + const namespaceTopLevel: t.Statement[] = t.isTSModuleBlock(node.body) + ? node.body.body + : // We handle `namespace X.Y {}` as if it was + // namespace X { + // export namespace Y {} + // } + [t.exportNamedDeclaration(node.body)]; + for (let i = 0; i < namespaceTopLevel.length; i++) { const subNode = namespaceTopLevel[i]; @@ -114,6 +138,10 @@ function handleNested(path, t, node, parentExport?) { // declarations require further transformation. switch (subNode.type) { case "TSModuleDeclaration": { + if (!t.isIdentifier(subNode.id)) { + throw buildNestedAmbiendModuleError(path, subNode); + } + const transformed = handleNested(path, t, subNode); const moduleName = subNode.id.name; if (names.has(moduleName)) { @@ -181,6 +209,10 @@ function handleNested(path, t, node, parentExport?) { break; } case "TSModuleDeclaration": { + if (!t.isIdentifier(subNode.declaration.id)) { + throw buildNestedAmbiendModuleError(path, subNode.declaration); + } + const transformed = handleNested( path, t, @@ -204,7 +236,7 @@ function handleNested(path, t, node, parentExport?) { } // {} - let fallthroughValue = t.objectExpression([]); + let fallthroughValue: t.Expression = t.objectExpression([]); if (parentExport) { const memberExpr = t.memberExpression(parentExport, realName); diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/ambient-module-nested-exported/input.ts b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/ambient-module-nested-exported/input.ts new file mode 100644 index 000000000000..58db7e4f66d9 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/ambient-module-nested-exported/input.ts @@ -0,0 +1,3 @@ +namespace X { + export module "m" {} +} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/ambient-module-nested-exported/options.json b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/ambient-module-nested-exported/options.json new file mode 100644 index 000000000000..653571301113 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/ambient-module-nested-exported/options.json @@ -0,0 +1,3 @@ +{ + "throws": "Ambient modules cannot be nested in other modules or namespaces." +} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/ambient-module-nested/input.ts b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/ambient-module-nested/input.ts new file mode 100644 index 000000000000..35d3d38cb1f6 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/ambient-module-nested/input.ts @@ -0,0 +1,3 @@ +namespace X { + module "m" {} +} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/ambient-module-nested/options.json b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/ambient-module-nested/options.json new file mode 100644 index 000000000000..653571301113 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/ambient-module-nested/options.json @@ -0,0 +1,3 @@ +{ + "throws": "Ambient modules cannot be nested in other modules or namespaces." +} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested-shorthand/input.ts b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested-shorthand/input.ts new file mode 100644 index 000000000000..8a39ba3d5e72 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested-shorthand/input.ts @@ -0,0 +1,7 @@ +namespace X.Y { + export const Z = 1; +} + +namespace proj.data.util.api { + export const X = 1; +} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested-shorthand/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested-shorthand/output.mjs new file mode 100644 index 000000000000..8c99a195f160 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested-shorthand/output.mjs @@ -0,0 +1,27 @@ +let X; + +(function (_X) { + let Y; + + (function (_Y) { + const Z = _Y.Z = 1; + })(Y || (Y = _X.Y || (_X.Y = {}))); +})(X || (X = {})); + +let proj; + +(function (_proj) { + let data; + + (function (_data) { + let util; + + (function (_util) { + let api; + + (function (_api) { + const X = _api.X = 1; + })(api || (api = _util.api || (_util.api = {}))); + })(util || (util = _data.util || (_data.util = {}))); + })(data || (data = _proj.data || (_proj.data = {}))); +})(proj || (proj = {}));