diff --git a/packages/babel-core/src/tools/build-external-helpers.js b/packages/babel-core/src/tools/build-external-helpers.js index 8649d1802d..2b6eba92a1 100644 --- a/packages/babel-core/src/tools/build-external-helpers.js +++ b/packages/babel-core/src/tools/build-external-helpers.js @@ -69,7 +69,7 @@ function buildModule(whitelist) { t.exportNamedDeclaration( null, Object.keys(refs).map(name => { - return t.exportSpecifier(t.clone(refs[name]), t.identifier(name)); + return t.exportSpecifier(t.cloneNode(refs[name]), t.identifier(name)); }), ), ); diff --git a/packages/babel-helper-module-imports/src/import-builder.js b/packages/babel-helper-module-imports/src/import-builder.js index fd2b040b1f..95b2c9a383 100644 --- a/packages/babel-helper-module-imports/src/import-builder.js +++ b/packages/babel-helper-module-imports/src/import-builder.js @@ -50,7 +50,7 @@ export default class ImportBuilder { assert(statement.type === "ImportDeclaration"); assert(statement.specifiers.length === 0); statement.specifiers = [t.importNamespaceSpecifier(name)]; - this._resultName = t.clone(name); + this._resultName = t.cloneNode(name); return this; } default(name) { @@ -59,7 +59,7 @@ export default class ImportBuilder { assert(statement.type === "ImportDeclaration"); assert(statement.specifiers.length === 0); statement.specifiers = [t.importDefaultSpecifier(name)]; - this._resultName = t.clone(name); + this._resultName = t.cloneNode(name); return this; } named(name, importName) { @@ -70,7 +70,7 @@ export default class ImportBuilder { assert(statement.type === "ImportDeclaration"); assert(statement.specifiers.length === 0); statement.specifiers = [t.importSpecifier(name, t.identifier(importName))]; - this._resultName = t.clone(name); + this._resultName = t.cloneNode(name); return this; } @@ -86,7 +86,7 @@ export default class ImportBuilder { "var", [t.variableDeclarator(name, statement.expression)], ); - this._resultName = t.clone(name); + this._resultName = t.cloneNode(name); return this; } diff --git a/packages/babel-helper-module-transforms/src/index.js b/packages/babel-helper-module-transforms/src/index.js index 0f4d160f30..ce07b94e4a 100644 --- a/packages/babel-helper-module-transforms/src/index.js +++ b/packages/babel-helper-module-transforms/src/index.js @@ -128,7 +128,7 @@ export function buildNamespaceInitStatements( statements.push( template.statement`var NAME = SOURCE;`({ NAME: localName, - SOURCE: t.cloneDeep(srcNamespace), + SOURCE: t.cloneNode(srcNamespace), }), ); } @@ -150,14 +150,14 @@ export function buildNamespaceInitStatements( : template.statement`EXPORTS.NAME = NAMESPACE;`)({ EXPORTS: metadata.exportName, NAME: exportName, - NAMESPACE: t.cloneDeep(srcNamespace), + NAMESPACE: t.cloneNode(srcNamespace), }), ); } if (sourceMetadata.reexportAll) { const statement = buildNamespaceReexport( metadata, - t.cloneDeep(srcNamespace), + t.cloneNode(srcNamespace), loose, ); statement.loc = sourceMetadata.reexportAll.loc; @@ -191,7 +191,7 @@ const buildReexportsFromMeta = (meta, metadata, loose) => { templateForCurrentMode({ EXPORTS: meta.exportName, EXPORT_NAME: exportName, - NAMESPACE: t.cloneDeep(namespace), + NAMESPACE: t.cloneNode(namespace), IMPORT_NAME: importName, }), ); diff --git a/packages/babel-helper-transform-fixture-test-runner/src/index.js b/packages/babel-helper-transform-fixture-test-runner/src/index.js index 884bf5f8c7..40652a0f8c 100644 --- a/packages/babel-helper-transform-fixture-test-runner/src/index.js +++ b/packages/babel-helper-transform-fixture-test-runner/src/index.js @@ -309,7 +309,7 @@ function checkDuplicatedNodes(ast) { if (isByRegenerator(node)) return; if (nodes.has(node)) { throw new Error( - "Do not reuse nodes. Use `t.clone` or `t.cloneDeep` to copy them.\n" + + "Do not reuse nodes. Use `t.cloneNode` to copy them.\n" + JSON.stringify(node, hidePrivateProperties, 2) + "\nParent:\n" + JSON.stringify(parents.get(node), hidePrivateProperties, 2), diff --git a/packages/babel-helpers/src/index.js b/packages/babel-helpers/src/index.js index c44a794f3d..960393353e 100644 --- a/packages/babel-helpers/src/index.js +++ b/packages/babel-helpers/src/index.js @@ -215,7 +215,7 @@ function permuteHelperAST(file, metadata, id, localBindings, getDependency) { for (const path of imps) path.remove(); for (const path of impsBindingRefs) { - const node = t.cloneDeep(dependenciesRefs[path.node.name]); + const node = t.cloneNode(dependenciesRefs[path.node.name]); path.replaceWith(node); } diff --git a/packages/babel-plugin-proposal-class-properties/src/index.js b/packages/babel-plugin-proposal-class-properties/src/index.js index 909dfbdd7f..83822ca3a2 100644 --- a/packages/babel-plugin-proposal-class-properties/src/index.js +++ b/packages/babel-plugin-proposal-class-properties/src/index.js @@ -128,7 +128,7 @@ export default function(api, options) { t.variableDeclarator(ident, computedNode.key), ]), ); - computedNode.key = t.clone(ident); + computedNode.key = t.cloneNode(ident); } } diff --git a/packages/babel-plugin-proposal-function-bind/src/index.js b/packages/babel-plugin-proposal-function-bind/src/index.js index 86f9efbf6b..ca9939d039 100644 --- a/packages/babel-plugin-proposal-function-bind/src/index.js +++ b/packages/babel-plugin-proposal-function-bind/src/index.js @@ -17,7 +17,7 @@ export default function() { function inferBindContext(bind, scope) { const staticContext = getStaticContext(bind, scope); - if (staticContext) return t.cloneDeep(staticContext); + if (staticContext) return t.cloneNode(staticContext); const tempId = getTempId(scope); if (bind.object) { diff --git a/packages/babel-plugin-proposal-nullish-coalescing-operator/src/index.js b/packages/babel-plugin-proposal-nullish-coalescing-operator/src/index.js index a6303f1ffe..b9469081af 100644 --- a/packages/babel-plugin-proposal-nullish-coalescing-operator/src/index.js +++ b/packages/babel-plugin-proposal-nullish-coalescing-operator/src/index.js @@ -15,7 +15,11 @@ export default function(api, { loose = false }) { const ref = scope.generateUidIdentifierBasedOnNode(node.left); scope.push({ id: ref }); - const assignment = t.assignmentExpression("=", t.clone(ref), node.left); + const assignment = t.assignmentExpression( + "=", + t.cloneNode(ref), + node.left, + ); path.replaceWith( t.conditionalExpression( @@ -28,11 +32,11 @@ export default function(api, { loose = false }) { t.binaryExpression("!==", assignment, t.nullLiteral()), t.binaryExpression( "!==", - t.clone(ref), + t.cloneNode(ref), scope.buildUndefinedNode(), ), ), - t.clone(ref), + t.cloneNode(ref), node.right, ), ); diff --git a/packages/babel-plugin-proposal-object-rest-spread/src/index.js b/packages/babel-plugin-proposal-object-rest-spread/src/index.js index 7e35ea3220..60d7aa2679 100644 --- a/packages/babel-plugin-proposal-object-rest-spread/src/index.js +++ b/packages/babel-plugin-proposal-object-rest-spread/src/index.js @@ -74,7 +74,7 @@ export default function(api, opts) { const props = path.get("properties"); const last = props[props.length - 1]; t.assertRestElement(last.node); - const restElement = t.clone(last.node); + const restElement = t.cloneNode(last.node); last.remove(); const impureComputedPropertyDeclarators = replaceImpureComputedKeys(path); @@ -291,7 +291,7 @@ export default function(api, opts) { ); } - const nodeWithoutSpread = t.clone(path.node); + const nodeWithoutSpread = t.cloneNode(path.node); nodeWithoutSpread.right = ref; nodes.push(t.expressionStatement(nodeWithoutSpread)); nodes.push( diff --git a/packages/babel-plugin-proposal-optional-chaining/src/index.js b/packages/babel-plugin-proposal-optional-chaining/src/index.js index 4509bd6149..8779ae2743 100644 --- a/packages/babel-plugin-proposal-optional-chaining/src/index.js +++ b/packages/babel-plugin-proposal-optional-chaining/src/index.js @@ -73,13 +73,13 @@ export default function(api, options) { replacementPath.replaceWith( t.conditionalExpression( loose - ? t.binaryExpression("==", t.clone(check), t.nullLiteral()) + ? t.binaryExpression("==", t.cloneNode(check), t.nullLiteral()) : t.logicalExpression( "||", - t.binaryExpression("===", t.clone(check), t.nullLiteral()), + t.binaryExpression("===", t.cloneNode(check), t.nullLiteral()), t.binaryExpression( "===", - t.clone(ref), + t.cloneNode(ref), scope.buildUndefinedNode(), ), ), diff --git a/packages/babel-plugin-transform-async-to-generator/src/index.js b/packages/babel-plugin-transform-async-to-generator/src/index.js index a6014df863..abb0b11311 100644 --- a/packages/babel-plugin-transform-async-to-generator/src/index.js +++ b/packages/babel-plugin-transform-async-to-generator/src/index.js @@ -13,7 +13,7 @@ export default function(api, options) { let wrapAsync = state.methodWrapper; if (wrapAsync) { - wrapAsync = t.cloneDeep(wrapAsync); + wrapAsync = t.cloneNode(wrapAsync); } else { wrapAsync = state.methodWrapper = addNamed(path, method, module); } diff --git a/packages/babel-plugin-transform-block-scoping/src/index.js b/packages/babel-plugin-transform-block-scoping/src/index.js index 372a3dfcc2..e3a0bc211d 100644 --- a/packages/babel-plugin-transform-block-scoping/src/index.js +++ b/packages/babel-plugin-transform-block-scoping/src/index.js @@ -525,7 +525,7 @@ class BlockScoping { // turn outsideLetReferences into an array const args = values(outsideRefs); - const params = args.map(id => t.clone(id)); + const params = args.map(id => t.cloneNode(id)); const isSwitch = this.blockPath.isSwitchStatement(); diff --git a/packages/babel-plugin-transform-destructuring/src/index.js b/packages/babel-plugin-transform-destructuring/src/index.js index e858c9bbf2..1713bc3a8e 100644 --- a/packages/babel-plugin-transform-destructuring/src/index.js +++ b/packages/babel-plugin-transform-destructuring/src/index.js @@ -221,7 +221,7 @@ export default function(api, options) { if (t.isRestElement(prop)) { this.pushObjectRest(pattern, objRef, prop, i); } else { - this.pushObjectProperty(prop, t.clone(objRef)); + this.pushObjectProperty(prop, t.cloneNode(objRef)); } } } diff --git a/packages/babel-plugin-transform-for-of/src/index.js b/packages/babel-plugin-transform-for-of/src/index.js index 4e3cc698d3..ad85e7def1 100644 --- a/packages/babel-plugin-transform-for-of/src/index.js +++ b/packages/babel-plugin-transform-for-of/src/index.js @@ -25,7 +25,7 @@ export default function(api, options) { array = right; } - const item = t.memberExpression(array, t.clone(i), true); + const item = t.memberExpression(array, t.cloneNode(i), true); let assignment; if (t.isVariableDeclaration(left)) { assignment = left; @@ -44,10 +44,10 @@ export default function(api, options) { t.variableDeclaration("let", inits), t.binaryExpression( "<", - t.clone(i), - t.memberExpression(t.clone(array), t.identifier("length")), + t.cloneNode(i), + t.memberExpression(t.cloneNode(array), t.identifier("length")), ), - t.updateExpression("++", t.clone(i)), + t.updateExpression("++", t.cloneNode(i)), block, ), ); diff --git a/packages/babel-plugin-transform-runtime/src/index.js b/packages/babel-plugin-transform-runtime/src/index.js index 3a8a96063d..8b1787c6d8 100644 --- a/packages/babel-plugin-transform-runtime/src/index.js +++ b/packages/babel-plugin-transform-runtime/src/index.js @@ -63,7 +63,7 @@ export default function(api, options) { let cached = cache.get(key); if (cached) { - cached = t.cloneDeep(cached); + cached = t.cloneNode(cached); } else { cached = addDefault(file.path, source, { importedInterop: "uncompiled", diff --git a/packages/babel-plugin-transform-spread/src/index.js b/packages/babel-plugin-transform-spread/src/index.js index 6c23a02595..01a877c774 100644 --- a/packages/babel-plugin-transform-spread/src/index.js +++ b/packages/babel-plugin-transform-spread/src/index.js @@ -107,7 +107,7 @@ export default function(api, options) { callee.object = t.assignmentExpression("=", temp, callee.object); contextLiteral = temp; } else { - contextLiteral = t.cloneDeep(callee.object); + contextLiteral = t.cloneNode(callee.object); } t.appendToMemberExpression(callee, t.identifier("apply")); } else { diff --git a/packages/babel-plugin-transform-template-literals/src/index.js b/packages/babel-plugin-transform-template-literals/src/index.js index afd0b21e3e..f910003fab 100644 --- a/packages/babel-plugin-transform-template-literals/src/index.js +++ b/packages/babel-plugin-transform-template-literals/src/index.js @@ -72,7 +72,7 @@ export default function(api, options) { let templateObject = this.templates.get(name); if (templateObject) { - templateObject = t.clone(templateObject); + templateObject = t.cloneNode(templateObject); } else { const programPath = path.find(p => p.isProgram()); templateObject = programPath.scope.generateUidIdentifier( diff --git a/packages/babel-template/src/populate.js b/packages/babel-template/src/populate.js index 9f8a25f145..90be31b8e4 100644 --- a/packages/babel-template/src/populate.js +++ b/packages/babel-template/src/populate.js @@ -8,7 +8,7 @@ export default function populatePlaceholders( metadata: Metadata, replacements: TemplateReplacements, ): BabelNodeFile { - const ast = t.cloneDeep(metadata.ast); + const ast = t.cloneNode(metadata.ast); if (replacements) { metadata.placeholders.forEach(placeholder => { @@ -57,9 +57,9 @@ function applyReplacement( // once to avoid injecting the same node multiple times. if (placeholder.isDuplicate) { if (Array.isArray(replacement)) { - replacement = replacement.map(node => t.cloneDeep(node)); + replacement = replacement.map(node => t.cloneNode(node)); } else if (typeof replacement === "object") { - replacement = t.cloneDeep(replacement); + replacement = t.cloneNode(replacement); } } diff --git a/packages/babel-types/src/clone/clone.js b/packages/babel-types/src/clone/clone.js index c3b1537890..5a742a1785 100644 --- a/packages/babel-types/src/clone/clone.js +++ b/packages/babel-types/src/clone/clone.js @@ -1,16 +1,12 @@ // @flow +import cloneNode from "./cloneNode"; + /** - * Create a shallow clone of a `node` excluding `_private` properties. + * Create a shallow clone of a `node`, including only + * properties belonging to the node. + * @deprecated Use t.cloneNode instead. */ export default function clone(node: T): T { - if (!node) return node; - const newNode = (({}: any): T); - - Object.keys(node).forEach(key => { - if (key[0] === "_") return; - newNode[key] = node[key]; - }); - - return newNode; + return cloneNode(node, /* deep */ false); } diff --git a/packages/babel-types/src/clone/cloneDeep.js b/packages/babel-types/src/clone/cloneDeep.js index 25a5252dc5..c0e31db8ce 100644 --- a/packages/babel-types/src/clone/cloneDeep.js +++ b/packages/babel-types/src/clone/cloneDeep.js @@ -1,28 +1,12 @@ // @flow +import cloneNode from "./cloneNode"; + /** * Create a deep clone of a `node` and all of it's child nodes - * excluding `_private` properties. + * including only properties belonging to the node. + * @deprecated Use t.cloneNode instead. */ export default function cloneDeep(node: T): T { - if (!node) return node; - const newNode = (({}: any): T); - - Object.keys(node).forEach(key => { - if (key[0] === "_") return; - - let val = node[key]; - - if (val) { - if (val.type) { - val = cloneDeep(val); - } else if (Array.isArray(val)) { - val = val.map(cloneDeep); - } - } - - newNode[key] = val; - }); - - return newNode; + return cloneNode(node); } diff --git a/packages/babel-types/src/clone/cloneNode.js b/packages/babel-types/src/clone/cloneNode.js new file mode 100644 index 0000000000..1fc1896fde --- /dev/null +++ b/packages/babel-types/src/clone/cloneNode.js @@ -0,0 +1,69 @@ +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); + } + + return obj; +} + +function cloneIfNodeOrArray(obj, deep) { + if (Array.isArray(obj)) { + return obj.map(node => cloneIfNode(node, deep)); + } + return cloneIfNode(obj, deep); +} + +/** + * Create a clone of a `node` including only properties belonging to the node. + * If the second parameter is `false`, cloneNode performs a shallow clone. + */ +export default function cloneNode(node: T, deep: boolean = true): T { + if (!node) return node; + + const { type } = node; + const newNode = (({ type }: any): T); + + // Special-case identifiers since they are the most cloned nodes. + if (type === "Identifier") { + newNode.name = node.name; + } else if (!has(NODE_FIELDS, type)) { + throw new Error(`Unknown node type: "${type}"`); + } else { + for (const field of Object.keys(NODE_FIELDS[type])) { + if (has(node, field)) { + newNode[field] = deep + ? cloneIfNodeOrArray(node[field], true) + : node[field]; + } + } + } + + if (has(node, "loc")) { + newNode.loc = node.loc; + } + if (has(node, "leadingComments")) { + newNode.leadingComments = node.leadingComments; + } + if (has(node, "innerComments")) { + newNode.innerComments = node.innerCmments; + } + if (has(node, "trailingComments")) { + newNode.trailingComments = node.trailingComments; + } + if (has(node, "extra")) { + newNode.extra = Object.assign({}, node.extra); + } + + return newNode; +} diff --git a/packages/babel-types/src/converters/toKeyAlias.js b/packages/babel-types/src/converters/toKeyAlias.js index 29d781f1c8..71b4ef49d5 100644 --- a/packages/babel-types/src/converters/toKeyAlias.js +++ b/packages/babel-types/src/converters/toKeyAlias.js @@ -1,6 +1,6 @@ // @flow import { isIdentifier, isStringLiteral } from "../validators/generated"; -import cloneDeep from "../clone/cloneDeep"; +import cloneNode from "../clone/cloneNode"; import removePropertiesDeep from "../modifications/removePropertiesDeep"; export default function toKeyAlias( @@ -16,7 +16,7 @@ export default function toKeyAlias( } else if (isStringLiteral(key)) { alias = JSON.stringify(key.value); } else { - alias = JSON.stringify(removePropertiesDeep(cloneDeep(key))); + alias = JSON.stringify(removePropertiesDeep(cloneNode(key))); } if (node.computed) { diff --git a/packages/babel-types/src/index.js b/packages/babel-types/src/index.js index f4c280b9fa..56d36125a9 100644 --- a/packages/babel-types/src/index.js +++ b/packages/babel-types/src/index.js @@ -17,6 +17,7 @@ export { export * from "./builders/generated"; // clone +export { default as cloneNode } from "./clone/cloneNode"; export { default as clone } from "./clone/clone"; export { default as cloneDeep } from "./clone/cloneDeep"; export { default as cloneWithoutLoc } from "./clone/cloneWithoutLoc"; diff --git a/packages/babel-types/test/cloning.js b/packages/babel-types/test/cloning.js index 8303cff327..298f64fc84 100644 --- a/packages/babel-types/test/cloning.js +++ b/packages/babel-types/test/cloning.js @@ -2,68 +2,61 @@ import * as t from "../lib"; import assert from "assert"; import { parse } from "babylon"; -suite("cloning", function() { - suite("clone", function() { - it("should handle undefined", function() { - const node = undefined; - const cloned = t.clone(node); - assert(cloned === undefined); - }); - - it("should handle null", function() { - const node = null; - const cloned = t.clone(node); - assert(cloned === null); - }); - - it("should handle simple cases", function() { - const node = t.arrayExpression([null, t.identifier("a")]); - const cloned = t.clone(node); - assert(node !== cloned); - assert(t.isNodesEquivalent(node, cloned) === true); - }); +suite("cloneNode", function() { + it("should handle undefined", function() { + const node = undefined; + const cloned = t.cloneNode(node); + assert(cloned === undefined); }); - suite("cloneDeep", function() { - it("should handle undefined", function() { - const node = undefined; - const cloned = t.cloneDeep(node); - assert(cloned === undefined); - }); + it("should handle null", function() { + const node = null; + const cloned = t.cloneNode(node); + assert(cloned === null); + }); - it("should handle null", function() { - const node = null; - const cloned = t.cloneDeep(node); - assert(cloned === null); - }); + it("should handle simple cases", function() { + const node = t.identifier("a"); + const cloned = t.cloneNode(node); + assert(node !== cloned); + assert(t.isNodesEquivalent(node, cloned) === true); + }); - it("should handle simple cases", function() { - const node = t.arrayExpression([null, t.identifier("a")]); - const cloned = t.cloneDeep(node); - assert(node !== cloned); - assert(t.isNodesEquivalent(node, cloned) === true); - }); + it("should handle full programs", function() { + const file = parse("1 + 1"); + const cloned = t.cloneNode(file); + assert(file !== cloned); + assert( + file.program.body[0].expression.right !== + cloned.program.body[0].expression.right, + ); + assert( + file.program.body[0].expression.left !== + cloned.program.body[0].expression.left, + ); + assert(t.isNodesEquivalent(file, cloned) === true); + }); - it("should handle full programs", function() { - const node = parse("1 + 1"); - const cloned = t.cloneDeep(node); - assert(node !== cloned); - assert(t.isNodesEquivalent(node, cloned) === true); - }); + it("should handle complex programs", function() { + const program = "'use strict'; function lol() { wow();return 1; }"; + const node = parse(program); + const cloned = t.cloneNode(node); + assert(node !== cloned); + assert(t.isNodesEquivalent(node, cloned) === true); + }); - it("should handle complex programs", function() { - const program = "'use strict'; function lol() { wow();return 1; }"; - const node = parse(program); - const cloned = t.cloneDeep(node); - assert(node !== cloned); - assert(t.isNodesEquivalent(node, cloned) === true); - }); + it("should handle missing array element", function() { + const node = parse("[,0]"); + const cloned = t.cloneNode(node); + assert(node !== cloned); + assert(t.isNodesEquivalent(node, cloned) === true); + }); - it("should handle missing array element", function() { - const node = parse("[,0]"); - const cloned = t.cloneDeep(node); - assert(node !== cloned); - assert(t.isNodesEquivalent(node, cloned) === true); - }); + it("should support shallow cloning", function() { + const node = t.memberExpression(t.identifier("foo"), t.identifier("bar")); + const cloned = t.cloneNode(node, /* deep */ false); + assert.notStrictEqual(node, cloned); + assert.strictEqual(node.object, cloned.object); + assert.strictEqual(node.property, cloned.property); }); });