Add cloneDeepWithoutLoc (#10680)

* feat: add cloneDeepWithoutLoc

* fix: sort functions alphabetically

* doc: add documentation for the withoutLoc parameter

* test: add a test for  shallow cloneWithoutLoc

* test: add loc object to node and fix test

* fix: set loc object on deeper node

* test: check loc on deeper node is null

* doc: adjust withoutLoc documentation

* fix: add withoutLoc param to deep clones

* fix: apply cloneIfNodeOrArray for leadingComments, innerComments and trailingComments

* test: add test for leadingComments, innerComments and trailingComments

* Cleanup PR

Co-authored-by: Nicolò Ribaudo <nicolo.ribaudo@gmail.com>
This commit is contained in:
Taym Haddadi 2020-03-16 23:07:01 +01:00 committed by GitHub
parent f232d4d109
commit 286aaeadd9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 135 additions and 30 deletions

View File

@ -135,7 +135,8 @@ lines.push(
// clone/
`declare function clone<T>(n: T): T;`,
`declare function cloneDeep<T>(n: T): T;`,
`declare function cloneNode<T>(n: T, deep?: boolean): T;`,
`declare function cloneDeepWithoutLoc<T>(n: T): T;`,
`declare function cloneNode<T>(n: T, deep?: boolean, withoutLoc?: boolean): T;`,
`declare function cloneWithoutLoc<T>(n: T): T;`,
// comments/

View File

@ -154,7 +154,8 @@ lines.push(
// clone/
`export function clone<T extends Node>(n: T): T;`,
`export function cloneDeep<T extends Node>(n: T): T;`,
`export function cloneNode<T extends Node>(n: T, deep?: boolean): T;`,
`export function cloneDeepWithoutLoc<T extends Node>(n: T): T;`,
`export function cloneNode<T extends Node>(n: T, deep?: boolean, withoutLoc?: boolean): T;`,
`export function cloneWithoutLoc<T extends Node>(n: T): T;`,
// comments/

View File

@ -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<T: Object>(node: T): T {
return cloneNode(node, /* deep */ true, /* withoutLoc */ true);
}

View File

@ -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<T: Object>(node: T, deep: boolean = true): T {
export default function cloneNode<T: Object>(
node: T,
deep: boolean = true,
withoutLoc: boolean = false,
): T {
if (!node) return node;
const { type } = node;
@ -44,7 +43,7 @@ export default function cloneNode<T: Object>(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<T: Object>(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<T: Object>(node: T, deep: boolean = true): T {
return newNode;
}
function cloneCommentsWithoutLoc<T: Object>(comments: T[]): T {
return comments.map(({ type, value }) => ({ type, value, loc: null }));
}
function maybeCloneComments(comments, deep, withoutLoc) {
return deep && withoutLoc ? cloneCommentsWithoutLoc(comments) : comments;
}

View File

@ -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<T: Object>(node: T): T {
const newNode = clone(node);
newNode.loc = null;
return newNode;
return cloneNode(node, /* deep */ false, /* withoutLoc */ true);
}

View File

@ -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

View File

@ -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);
});
});