diff --git a/packages/babel-types/src/clone/cloneNode.ts b/packages/babel-types/src/clone/cloneNode.ts index 0e6506a0c752..1a5631cd7d7a 100644 --- a/packages/babel-types/src/clone/cloneNode.ts +++ b/packages/babel-types/src/clone/cloneNode.ts @@ -3,6 +3,7 @@ import type * as t from ".."; import { isFile, isIdentifier } from "../validators/generated"; const has = Function.call.bind(Object.prototype.hasOwnProperty); +const commentsCache: Map = new Map(); // This function will never be called for comments, only for real nodes. function cloneIfNode(obj, deep, withoutLoc) { @@ -32,6 +33,9 @@ export default function cloneNode( ): T { if (!node) return node; + const isTop = !commentsCache.get("inCloning"); + if (isTop) commentsCache.set("inCloning", true); + const { type } = node; const newNode: any = { type: node.type }; @@ -49,6 +53,7 @@ export default function cloneNode( : node.typeAnnotation; } } else if (!has(NODE_FIELDS, type)) { + if (isTop) commentsCache.clear(); throw new Error(`Unknown node type: "${type}"`); } else { for (const field of Object.keys(NODE_FIELDS[type])) { @@ -99,6 +104,8 @@ export default function cloneNode( }; } + if (isTop) commentsCache.clear(); + return newNode; } @@ -110,10 +117,19 @@ function maybeCloneComments( if (!comments || !deep) { return comments; } - return comments.map(({ type, value, loc }) => { + return comments.map(comment => { + const cache = commentsCache.get(comment); + if (cache) return cache; + + const { type, value, loc } = comment; + + const ret = { type, value, loc } as T; if (withoutLoc) { - return { type, value, loc: null } as T; + ret.loc = null; } - return { type, value, loc } as T; + + commentsCache.set(comment, ret); + + return ret; }); } diff --git a/packages/babel-types/test/cloning.js b/packages/babel-types/test/cloning.js index 9ab9151d1842..b9d60d9dd9db 100644 --- a/packages/babel-types/test/cloning.js +++ b/packages/babel-types/test/cloning.js @@ -1,5 +1,6 @@ import * as t from "../lib/index.js"; import { parse } from "@babel/parser"; +import { CodeGenerator } from "@babel/generator"; describe("cloneNode", function () { it("should handle undefined", function () { @@ -151,4 +152,24 @@ describe("cloneNode", function () { expect(cloned.declarations[0].id.innerComments[0].loc).toBe(null); expect(cloned.declarations[0].id.trailingComments[0].loc).toBe(null); }); + + it("should same code after deep cloning", function () { + let code = `//test1 + /*test2*/var/*test3*/ a = 1/*test4*/;//test5 + //test6 + var b; + `; + code = new CodeGenerator(parse(code), { retainLines: true }).generate() + .code; + + const ast = t.cloneNode( + parse(code), + /* deep */ true, + /* withoutLoc */ false, + ); + const newCode = new CodeGenerator(ast, { retainLines: true }).generate() + .code; + + expect(code).toBe(newCode); + }); });