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(''))