diff --git a/djedi-react/babel-plugin.js b/djedi-react/babel-plugin.js index f39602fb..f63f4af9 100644 --- a/djedi-react/babel-plugin.js +++ b/djedi-react/babel-plugin.js @@ -65,8 +65,10 @@ module.exports = function djediBabelPlugin({ types: t }) { // Move the uri into a variable. const uriId = path.scope.generateUidIdentifier(URI_VAR_NAME); - program.push({ id: uriId, init: t.stringLiteral(uri) }); - uriAttr.get("value").replaceWith(t.jSXExpressionContainer(uriId)); + program.push({ id: t.cloneNode(uriId), init: t.stringLiteral(uri) }); + uriAttr + .get("value") + .replaceWith(t.jSXExpressionContainer(t.cloneNode(uriId))); let defaultId = undefined; @@ -74,10 +76,10 @@ module.exports = function djediBabelPlugin({ types: t }) { if (defaultValue != null) { defaultId = path.scope.generateUidIdentifier(DEFAULT_VAR_NAME); program.push({ - id: defaultId, + id: t.cloneNode(defaultId), init: t.stringLiteral(dedent(defaultValue)), }); - child.replaceWith(t.jSXExpressionContainer(defaultId)); + child.replaceWith(t.jSXExpressionContainer(t.cloneNode(defaultId))); for (const otherChild of path.get("children")) { if (otherChild !== child) { otherChild.remove(); @@ -217,7 +219,7 @@ function getDefaultValue(valuePath, t) { function makeClientImport(clientId, t) { return t.importDeclaration( - [t.importSpecifier(clientId, t.identifier(CLIENT_NAME))], + [t.importSpecifier(t.cloneNode(clientId), t.identifier(CLIENT_NAME))], t.stringLiteral(MODULE_NAME) ); } @@ -225,13 +227,18 @@ function makeClientImport(clientId, t) { function makeReportCall(clientId, uriId, defaultId, t) { return t.expressionStatement( t.callExpression( - t.memberExpression(clientId, t.identifier(CLIENT_METHOD_NAME)), + t.memberExpression( + t.cloneNode(clientId), + t.identifier(CLIENT_METHOD_NAME) + ), [ t.objectExpression([ - t.objectProperty(t.identifier(NODE_URI_KEY), uriId), + t.objectProperty(t.identifier(NODE_URI_KEY), t.cloneNode(uriId)), t.objectProperty( t.identifier(NODE_VALUE_KEY), - defaultId == null ? t.identifier("undefined") : defaultId + defaultId == null + ? t.identifier("undefined") + : t.cloneNode(defaultId) ), ]), ] diff --git a/djedi-react/package-lock.json b/djedi-react/package-lock.json index 3c7aedfc..a6d6e049 100644 --- a/djedi-react/package-lock.json +++ b/djedi-react/package-lock.json @@ -2796,6 +2796,12 @@ "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", "dev": true }, + "babel-check-duplicated-nodes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/babel-check-duplicated-nodes/-/babel-check-duplicated-nodes-1.0.0.tgz", + "integrity": "sha512-luUr6B28RzichAHdhCaGY6z53sm4+PAxzSedNlhZ9LtdW9txpR3G2Y5983iOnBosky88V08LeaUiDB/NR7vWvQ==", + "dev": true + }, "babel-core": { "version": "7.0.0-bridge.0", "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", @@ -5118,7 +5124,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -5161,7 +5168,8 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", @@ -5172,7 +5180,8 @@ "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -5289,7 +5298,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -5301,6 +5311,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5323,12 +5334,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -5347,6 +5360,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -5427,7 +5441,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -5439,6 +5454,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5524,7 +5540,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -5560,6 +5577,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5579,6 +5597,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5622,12 +5641,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, diff --git a/djedi-react/package.json b/djedi-react/package.json index 42250971..535fcf12 100644 --- a/djedi-react/package.json +++ b/djedi-react/package.json @@ -43,6 +43,7 @@ "@babel/plugin-syntax-jsx": "7.2.0", "@babel/preset-env": "7.4.5", "@babel/preset-react": "7.0.0", + "babel-check-duplicated-nodes": "^1.0.0", "babel-eslint": "10.0.1", "babel-jest": "24.8.0", "coffeescript": "1.8.0", diff --git a/djedi-react/test/babel-plugin.test.js b/djedi-react/test/babel-plugin.test.js index d18fee4d..22b18b81 100644 --- a/djedi-react/test/babel-plugin.test.js +++ b/djedi-react/test/babel-plugin.test.js @@ -1,5 +1,6 @@ import * as babel from "@babel/core"; import jsx from "@babel/plugin-syntax-jsx"; +import checkDuplicatedNodes from "babel-check-duplicated-nodes"; import dedent from "dedent-js"; import fs from "fs"; @@ -24,6 +25,14 @@ function transform(code) { }).code; } +function transformToAST(code) { + return babel.transform(code, { + plugins: [jsx, babelPlugin], + ast: true, + code: false, + }).ast; +} + test("it works", () => { // The fixture is pretty long, so it makes sense not using an inline snapshot. const code = fs.readFileSync(require.resolve("./fixtures/nodes.js"), "utf8"); @@ -66,6 +75,18 @@ _djedi.reportPrefetchableNode({ `); }); +test("ensure no duplicated AST nodes are found", () => { + const code = dedent` + <> + value1 + value2 + + `; + expect(() => checkDuplicatedNodes(babel, transformToAST(code))).not.toThrow( + Error + ); +}); + describe("it throws helpful errors", () => { test("duplicate uri prop", () => { expect(() => transform(''))