diff --git a/packages/babel-types/scripts/generators/flow.js b/packages/babel-types/scripts/generators/flow.js index 6c81a28af7..ec664e7fe4 100644 --- a/packages/babel-types/scripts/generators/flow.js +++ b/packages/babel-types/scripts/generators/flow.js @@ -135,7 +135,8 @@ lines.push( // clone/ `declare function clone(n: T): T;`, `declare function cloneDeep(n: T): T;`, - `declare function cloneNode(n: T, deep?: boolean): T;`, + `declare function cloneDeepWithoutLoc(n: T): T;`, + `declare function cloneNode(n: T, deep?: boolean, withoutLoc?: boolean): T;`, `declare function cloneWithoutLoc(n: T): T;`, // comments/ diff --git a/packages/babel-types/scripts/generators/typescript.js b/packages/babel-types/scripts/generators/typescript.js index 1b2707aff0..298e22e9ee 100644 --- a/packages/babel-types/scripts/generators/typescript.js +++ b/packages/babel-types/scripts/generators/typescript.js @@ -154,7 +154,8 @@ lines.push( // clone/ `export function clone(n: T): T;`, `export function cloneDeep(n: T): T;`, - `export function cloneNode(n: T, deep?: boolean): T;`, + `export function cloneDeepWithoutLoc(n: T): T;`, + `export function cloneNode(n: T, deep?: boolean, withoutLoc?: boolean): T;`, `export function cloneWithoutLoc(n: T): T;`, // comments/ diff --git a/packages/babel-types/src/clone/cloneDeepWithoutLoc.js b/packages/babel-types/src/clone/cloneDeepWithoutLoc.js new file mode 100644 index 0000000000..80c1872308 --- /dev/null +++ b/packages/babel-types/src/clone/cloneDeepWithoutLoc.js @@ -0,0 +1,10 @@ +// @flow +import cloneNode from "./cloneNode"; +/** + * Create a deep clone of a `node` and all of it's child nodes + * including only properties belonging to the node. + * excluding `_private` and location properties. + */ +export default function cloneDeepWithoutLoc(node: T): T { + return cloneNode(node, /* deep */ true, /* withoutLoc */ true); +} diff --git a/packages/babel-types/src/clone/cloneNode.js b/packages/babel-types/src/clone/cloneNode.js index 72347d4a0d..9f71429a2c 100644 --- a/packages/babel-types/src/clone/cloneNode.js +++ b/packages/babel-types/src/clone/cloneNode.js @@ -2,33 +2,32 @@ import { NODE_FIELDS } from "../definitions"; const has = Function.call.bind(Object.prototype.hasOwnProperty); -function cloneIfNode(obj, deep) { - if ( - obj && - typeof obj.type === "string" && - // CommentLine and CommentBlock are used in File#comments, but they are - // not defined in babel-types - obj.type !== "CommentLine" && - obj.type !== "CommentBlock" - ) { - return cloneNode(obj, deep); +// This function will never be called for comments, only for real nodes. +function cloneIfNode(obj, deep, withoutLoc) { + if (obj && typeof obj.type === "string") { + return cloneNode(obj, deep, withoutLoc); } return obj; } -function cloneIfNodeOrArray(obj, deep) { +function cloneIfNodeOrArray(obj, deep, withoutLoc) { if (Array.isArray(obj)) { - return obj.map(node => cloneIfNode(node, deep)); + return obj.map(node => cloneIfNode(node, deep, withoutLoc)); } - return cloneIfNode(obj, deep); + return cloneIfNode(obj, deep, withoutLoc); } /** * Create a clone of a `node` including only properties belonging to the node. * If the second parameter is `false`, cloneNode performs a shallow clone. + * If the third parameter is true, the cloned nodes exclude location properties. */ -export default function cloneNode(node: T, deep: boolean = true): T { +export default function cloneNode( + node: T, + deep: boolean = true, + withoutLoc: boolean = false, +): T { if (!node) return node; const { type } = node; @@ -44,7 +43,7 @@ export default function cloneNode(node: T, deep: boolean = true): T { if (has(node, "typeAnnotation")) { newNode.typeAnnotation = deep - ? cloneIfNodeOrArray(node.typeAnnotation, true) + ? cloneIfNodeOrArray(node.typeAnnotation, true, withoutLoc) : node.typeAnnotation; } } else if (!has(NODE_FIELDS, type)) { @@ -52,24 +51,45 @@ export default function cloneNode(node: T, deep: boolean = true): T { } else { for (const field of Object.keys(NODE_FIELDS[type])) { if (has(node, field)) { - newNode[field] = deep - ? cloneIfNodeOrArray(node[field], true) - : node[field]; + if (deep) { + newNode[field] = + type === "File" && field === "comments" + ? maybeCloneComments(node.comments, deep, withoutLoc) + : cloneIfNodeOrArray(node[field], true, withoutLoc); + } else { + newNode[field] = node[field]; + } } } } if (has(node, "loc")) { - newNode.loc = node.loc; + if (withoutLoc) { + newNode.loc = null; + } else { + newNode.loc = node.loc; + } } if (has(node, "leadingComments")) { - newNode.leadingComments = node.leadingComments; + newNode.leadingComments = maybeCloneComments( + node.leadingComments, + deep, + withoutLoc, + ); } if (has(node, "innerComments")) { - newNode.innerComments = node.innerComments; + newNode.innerComments = maybeCloneComments( + node.innerComments, + deep, + withoutLoc, + ); } if (has(node, "trailingComments")) { - newNode.trailingComments = node.trailingComments; + newNode.trailingComments = maybeCloneComments( + node.trailingComments, + deep, + withoutLoc, + ); } if (has(node, "extra")) { newNode.extra = { @@ -79,3 +99,11 @@ export default function cloneNode(node: T, deep: boolean = true): T { return newNode; } + +function cloneCommentsWithoutLoc(comments: T[]): T { + return comments.map(({ type, value }) => ({ type, value, loc: null })); +} + +function maybeCloneComments(comments, deep, withoutLoc) { + return deep && withoutLoc ? cloneCommentsWithoutLoc(comments) : comments; +} diff --git a/packages/babel-types/src/clone/cloneWithoutLoc.js b/packages/babel-types/src/clone/cloneWithoutLoc.js index f495f94b8f..b5cfe2fff0 100644 --- a/packages/babel-types/src/clone/cloneWithoutLoc.js +++ b/packages/babel-types/src/clone/cloneWithoutLoc.js @@ -1,12 +1,8 @@ // @flow -import clone from "./clone"; - +import cloneNode from "./cloneNode"; /** * Create a shallow clone of a `node` excluding `_private` and location properties. */ export default function cloneWithoutLoc(node: T): T { - const newNode = clone(node); - newNode.loc = null; - - return newNode; + return cloneNode(node, /* deep */ false, /* withoutLoc */ true); } diff --git a/packages/babel-types/src/index.js b/packages/babel-types/src/index.js index 51fd0b9846..b0586a4f90 100644 --- a/packages/babel-types/src/index.js +++ b/packages/babel-types/src/index.js @@ -16,6 +16,7 @@ export * from "./builders/generated"; export { default as cloneNode } from "./clone/cloneNode"; export { default as clone } from "./clone/clone"; export { default as cloneDeep } from "./clone/cloneDeep"; +export { default as cloneDeepWithoutLoc } from "./clone/cloneDeepWithoutLoc"; export { default as cloneWithoutLoc } from "./clone/cloneWithoutLoc"; // comments diff --git a/packages/babel-types/test/cloning.js b/packages/babel-types/test/cloning.js index 83e7c70491..fecc1bd700 100644 --- a/packages/babel-types/test/cloning.js +++ b/packages/babel-types/test/cloning.js @@ -72,4 +72,72 @@ describe("cloneNode", function() { node.declarations[0].id.typeAnnotation, ); }); + + it("should support shallow cloning without loc", function() { + const node = t.variableDeclaration("let", [ + t.variableDeclarator({ + ...t.identifier("value"), + typeAnnotation: t.anyTypeAnnotation(), + }), + ]); + node.loc = {}; + const cloned = t.cloneNode(node, /* deep */ false, /* withoutLoc */ true); + expect(cloned.loc).toBeNull(); + }); + + it("should support deep cloning without loc", function() { + const node = t.variableDeclaration("let", [ + t.variableDeclarator({ + ...t.identifier("value"), + typeAnnotation: t.anyTypeAnnotation(), + }), + ]); + node.loc = {}; + node.declarations[0].id.loc = {}; + const cloned = t.cloneNode(node, /* deep */ true, /* withoutLoc */ true); + expect(cloned.loc).toBeNull(); + expect(cloned.declarations[0].id.loc).toBeNull(); + }); + + it("should support deep cloning for comments", function() { + const node = t.variableDeclaration("let", [ + t.variableDeclarator({ + ...t.identifier("value"), + leadingComments: [{ loc: {} }], + innerComments: [{ loc: {} }], + trailingComments: [{ loc: {} }], + }), + ]); + node.loc = {}; + node.declarations[0].id.loc = {}; + + const cloned = t.cloneNode(node, /* deep */ true, /* withoutLoc */ false); + expect(cloned.declarations[0].id.leadingComments[0].loc).toBe( + node.declarations[0].id.leadingComments[0].loc, + ); + expect(cloned.declarations[0].id.innerComments[0].loc).toBe( + node.declarations[0].id.innerComments[0].loc, + ); + expect(cloned.declarations[0].id.trailingComments[0].loc).toBe( + node.declarations[0].id.trailingComments[0].loc, + ); + }); + + it("should support deep cloning for comments without loc", function() { + const node = t.variableDeclaration("let", [ + t.variableDeclarator({ + ...t.identifier("value"), + leadingComments: [{ loc: {} }], + innerComments: [{ loc: {} }], + trailingComments: [{ loc: {} }], + }), + ]); + node.loc = {}; + node.declarations[0].id.loc = {}; + + const cloned = t.cloneNode(node, /* deep */ true, /* withoutLoc */ true); + expect(cloned.declarations[0].id.leadingComments[0].loc).toBe(null); + expect(cloned.declarations[0].id.innerComments[0].loc).toBe(null); + expect(cloned.declarations[0].id.trailingComments[0].loc).toBe(null); + }); });