From 53b596456e5e74c1d0f30d01320e296017df94e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Thu, 4 Feb 2021 17:23:56 -0500 Subject: [PATCH 1/4] refactor: extract handleVariableDeclaration --- .../src/namespace.js | 58 +++++++++++++------ 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/packages/babel-plugin-transform-typescript/src/namespace.js b/packages/babel-plugin-transform-typescript/src/namespace.js index be3b51c949cd..94df43e0a719 100644 --- a/packages/babel-plugin-transform-typescript/src/namespace.js +++ b/packages/babel-plugin-transform-typescript/src/namespace.js @@ -1,4 +1,4 @@ -import { template } from "@babel/core"; +import { template, types as t } from "@babel/core"; export default function transpileNamespace(path, t, allowNamespaces) { if (path.node.declare || path.node.id.type === "StringLiteral") { @@ -46,6 +46,36 @@ function getMemberExpression(t, name, itemName) { return t.memberExpression(t.identifier(name), t.identifier(itemName)); } +/** + * Convert export const foo = 1 to Namepsace.foo = 1; + * + * @param {t.VariableDeclaration} node given variable declaration, e.g. `const foo = 1` + * @param {string} name the generated unique namespace member name + * @param {*} hub An instance implements HubInterface defined in `@babel/traverse` that can throw a code frame error + */ +function handleVariableDeclaration( + node: t.VariableDeclaration, + name: string, + hub: any, +): t.Statement[] { + if (node.kind !== "const") { + throw hub.file.buildCodeFrameError( + node, + "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 declarator of node.declarations) { + declarator.init = t.assignmentExpression( + "=", + getMemberExpression(t, name, declarator.id.name), + declarator.init, + ); + } + return [node]; +} + function handleNested(path, t, node, parentExport) { const names = new Set(); const realName = node.id; @@ -111,24 +141,16 @@ function handleNested(path, t, node, parentExport) { ); 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; + case "VariableDeclaration": { + const nodes = handleVariableDeclaration( + subNode.declaration, + name, + path.hub, + ); + namespaceTopLevel.splice(i, nodes.length, ...nodes); + i += nodes.length - 1; break; + } case "TSModuleDeclaration": { const transformed = handleNested( path, From d724f23a73a2187c6ea58ac4d434a8bdd7be85d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Thu, 4 Feb 2021 18:55:10 -0500 Subject: [PATCH 2/4] fix: support general variable declarator under namespace --- .../src/namespace.js | 36 +++++++++++++++---- .../namespace/nested-destructuring/input.ts | 6 ++++ .../namespace/nested-destructuring/output.mjs | 24 +++++++++++++ 3 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested-destructuring/input.ts create mode 100644 packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested-destructuring/output.mjs diff --git a/packages/babel-plugin-transform-typescript/src/namespace.js b/packages/babel-plugin-transform-typescript/src/namespace.js index 94df43e0a719..b119984c0b9d 100644 --- a/packages/babel-plugin-transform-typescript/src/namespace.js +++ b/packages/babel-plugin-transform-typescript/src/namespace.js @@ -66,14 +66,36 @@ function handleVariableDeclaration( " https://babeljs.io/docs/en/babel-plugin-transform-typescript", ); } - for (const declarator of node.declarations) { - declarator.init = t.assignmentExpression( - "=", - getMemberExpression(t, name, declarator.id.name), - declarator.init, - ); + const { declarations } = node; + if (declarations.every(declarator => t.isIdentifier(declarator.id))) { + // `export const a = 1` transforms to `const a = N.a = 1`, the output + // is smaller than `const a = 1; N.a = a`; + for (const declarator of node.declarations) { + declarator.init = t.assignmentExpression( + "=", + getMemberExpression(t, name, declarator.id.name), + declarator.init, + ); + } + return [node]; } - return [node]; + // Now we have pattern in declarators + // `export const [a] = 1` transforms to `const [a] = 1; N.a = a` + const bindingIdentifiers = Object.values(t.getBindingIdentifiers(node)); + return [ + node, + t.expressionStatement( + t.sequenceExpression( + bindingIdentifiers.map(id => + t.assignmentExpression( + "=", + getMemberExpression(t, name, id.name), + t.cloneNode(id), + ), + ), + ), + ), + ]; } function handleNested(path, t, node, parentExport) { diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested-destructuring/input.ts b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested-destructuring/input.ts new file mode 100644 index 000000000000..3eaf06b7a860 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested-destructuring/input.ts @@ -0,0 +1,6 @@ +class C { } +namespace N { + export const { a } = C; + export const [ b ] = C; + export const [ { a: [{ b: c }] }] = A, { a: { b: { d = 1 } } = {}, ...e } = C; +} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested-destructuring/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested-destructuring/output.mjs new file mode 100644 index 000000000000..7ba530d74ec9 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested-destructuring/output.mjs @@ -0,0 +1,24 @@ +class C {} + +let N; + +(function (_N) { + const { + a + } = C; + _N.a = a; + const [{ + a: [{ + b: c + }] + }] = A, + { + a: { + b: { + d = 1 + } + } = {}, + ...e + } = C; + _N.e = e, _N.c = c, _N.d = d; +})(N || (N = {})); From 7ba47251ce7d9ba826d2ba147fcb197099ee43d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Thu, 4 Feb 2021 19:12:31 -0500 Subject: [PATCH 3/4] fix: support general declarator on export module id duplication check --- .../src/namespace.js | 10 +++++++--- .../namespace/namespace-nested-module/input.ts | 6 ++++++ .../namespace/namespace-nested-module/output.mjs | 13 +++++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 packages/babel-plugin-transform-typescript/test/fixtures/namespace/namespace-nested-module/input.ts create mode 100644 packages/babel-plugin-transform-typescript/test/fixtures/namespace/namespace-nested-module/output.mjs diff --git a/packages/babel-plugin-transform-typescript/src/namespace.js b/packages/babel-plugin-transform-typescript/src/namespace.js index b119984c0b9d..10ef00e91712 100644 --- a/packages/babel-plugin-transform-typescript/src/namespace.js +++ b/packages/babel-plugin-transform-typescript/src/namespace.js @@ -130,11 +130,15 @@ function handleNested(path, t, node, parentExport) { case "ClassDeclaration": names.add(subNode.id.name); continue; - case "VariableDeclaration": - for (const variable of subNode.declarations) { - names.add(variable.id.name); + case "VariableDeclaration": { + const bindingIdentifiers = Object.values( + t.getBindingIdentifiers(subNode), + ); + for (const id of bindingIdentifiers) { + names.add(id.name); } continue; + } default: // Neither named declaration nor export, continue to next item. continue; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/namespace-nested-module/input.ts b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/namespace-nested-module/input.ts new file mode 100644 index 000000000000..c5db9c8d7ecf --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/namespace-nested-module/input.ts @@ -0,0 +1,6 @@ +namespace N { + const M1 = {} + export module M1 {} + const { M2 } = {} + export module M2 {} +} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/namespace-nested-module/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/namespace-nested-module/output.mjs new file mode 100644 index 000000000000..7d9a95580e17 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/namespace-nested-module/output.mjs @@ -0,0 +1,13 @@ +let N; + +(function (_N) { + const M1 = {}; + + (function (_M) {})(M1 || (M1 = _N.M1 || (_N.M1 = {}))); + + const { + M2 + } = {}; + + (function (_M2) {})(M2 || (M2 = _N.M2 || (_N.M2 = {}))); +})(N || (N = {})); From 00797733825285387fc619ed0ba71539157fc65d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 5 Feb 2021 09:44:36 -0500 Subject: [PATCH 4/4] refactor: use for-in --- .../src/namespace.js | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/packages/babel-plugin-transform-typescript/src/namespace.js b/packages/babel-plugin-transform-typescript/src/namespace.js index 10ef00e91712..664326f4626c 100644 --- a/packages/babel-plugin-transform-typescript/src/namespace.js +++ b/packages/babel-plugin-transform-typescript/src/namespace.js @@ -81,21 +81,20 @@ function handleVariableDeclaration( } // Now we have pattern in declarators // `export const [a] = 1` transforms to `const [a] = 1; N.a = a` - const bindingIdentifiers = Object.values(t.getBindingIdentifiers(node)); - return [ - node, - t.expressionStatement( - t.sequenceExpression( - bindingIdentifiers.map(id => - t.assignmentExpression( - "=", - getMemberExpression(t, name, id.name), - t.cloneNode(id), - ), - ), + const bindingIdentifiers = t.getBindingIdentifiers(node); + const assignments = []; + // getBindingIdentifiers returns an object without prototype. + // eslint-disable-next-line guard-for-in + for (const idName in bindingIdentifiers) { + assignments.push( + t.assignmentExpression( + "=", + getMemberExpression(t, name, idName), + t.cloneNode(bindingIdentifiers[idName]), ), - ), - ]; + ); + } + return [node, t.expressionStatement(t.sequenceExpression(assignments))]; } function handleNested(path, t, node, parentExport) { @@ -131,11 +130,10 @@ function handleNested(path, t, node, parentExport) { names.add(subNode.id.name); continue; case "VariableDeclaration": { - const bindingIdentifiers = Object.values( - t.getBindingIdentifiers(subNode), - ); - for (const id of bindingIdentifiers) { - names.add(id.name); + // getBindingIdentifiers returns an object without prototype. + // eslint-disable-next-line guard-for-in + for (const name in t.getBindingIdentifiers(subNode)) { + names.add(name); } continue; }