diff --git a/lib/6to5/transformation/helpers/build-conditional-assignment-operator-transformer.js b/lib/6to5/transformation/helpers/build-conditional-assignment-operator-transformer.js new file mode 100644 index 0000000000..a3bad77c73 --- /dev/null +++ b/lib/6to5/transformation/helpers/build-conditional-assignment-operator-transformer.js @@ -0,0 +1,75 @@ +var t = require("../../types"); + +module.exports = function (opts) { + var getObjRef = function (node, nodes, file, scope) { + var obj = node.object; + var temp = scope.generateUidBasedOnNode(obj, file); + nodes.push(t.variableDeclaration("var", [ + t.variableDeclarator(temp, obj) + ])); + return temp; + }; + + var getPropRef = function (node, nodes, file, scope) { + var prop = node.property; + var key = t.toComputedKey(node, prop); + if (t.isLiteral(key)) return key; + + var temp = scope.generateUidBasedOnNode(prop, file); + nodes.push(t.variableDeclaration("var", [ + t.variableDeclarator(temp, prop) + ])); + return temp; + }; + + var buildAbsoluteRef = function (node, nodes, file, scope) { + if (t.isIdentifier(node)) return node; + + var obj = getObjRef(node, nodes, file, scope); + var prop = getPropRef(node, nodes, file, scope); + + var computed = node.computed || t.isLiteral(prop); + return t.memberExpression(obj, prop, computed); + }; + + var buildAssignment = function (left, right) { + return t.assignmentExpression("=", left, right); + }; + + var exports = {}; + + exports.ExpressionStatement = function (node, parent, scope, context, file) { + var expr = node.expression; + if (!opts.is(expr)) return; + + var nodes = []; + var left = buildAbsoluteRef(expr.left, nodes, file, scope); + + nodes.push(t.ifStatement( + opts.build(left, file), + t.expressionStatement(buildAssignment(left, expr.right)) + )); + + return nodes; + }; + + exports.AssignmentExpression = function (node, parent, scope, context, file) { + if (t.isExpressionStatement(parent)) return; + if (!opts.is(node)) return; + + var nodes = []; + var left = buildAbsoluteRef(node.left, nodes, file, scope); + + nodes.push(t.logicalExpression( + "&&", + opts.build(left, file), + buildAssignment(left, node.right) + )); + + nodes.push(left); + + return t.toSequenceExpression(nodes, scope); + }; + + return exports; +}; diff --git a/lib/6to5/transformation/helpers/explode-unary.js b/lib/6to5/transformation/helpers/explode-unary.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/6to5/transformation/transformers/playground-mallet-operator.js b/lib/6to5/transformation/transformers/playground-mallet-operator.js index 3e8fc83bf5..16333c82e5 100644 --- a/lib/6to5/transformation/transformers/playground-mallet-operator.js +++ b/lib/6to5/transformation/transformers/playground-mallet-operator.js @@ -1,86 +1,18 @@ var t = require("../../types"); -var isMallet = function (node) { - var is = t.isAssignmentExpression(node) && node.operator === "||="; - if (is) { - var left = node.left; - if (!t.isMemberExpression(left) && !t.isIdentifier(left)) { - throw new Error("Expected type MemeberExpression or Identifier"); +module.exports = require("../helpers/build-conditional-assignment-operator-transformer")({ + is: function (node) { + var is = t.isAssignmentExpression(node) && node.operator === "||="; + if (is) { + var left = node.left; + if (!t.isMemberExpression(left) && !t.isIdentifier(left)) { + throw new Error("Expected type MemeberExpression or Identifier"); + } + return true; } - return true; + }, + + build: function (node) { + return t.unaryExpression("!", node, true); } -}; - -var getObjRef = function (node, nodes, file, scope) { - var obj = node.object; - if (t.isIdentifier(obj)) return obj; - - var temp = scope.generateUidBasedOnNode(obj, file); - nodes.push(t.variableDeclaration("var", [ - t.variableDeclarator(temp, obj) - ])); - return temp; -}; - -var getPropRef = function (node, nodes, file, scope) { - var prop = node.property; - var key = t.toComputedKey(node, prop); - if (t.isLiteral(key)) return key; - - var temp = scope.generateUidBasedOnNode(prop, file); - nodes.push(t.variableDeclaration("var", [ - t.variableDeclarator(temp, prop) - ])); - return temp; -}; - -var buildAbsoluteRef = function (node, nodes, file, scope) { - if (t.isIdentifier(node)) return node; - - var obj = getObjRef(node, nodes, file, scope); - var prop = getPropRef(node, nodes, file, scope); - - var computed = node.computed || t.isLiteral(prop); - return t.memberExpression(obj, prop, computed); -}; - -var buildIsFalsey = function (node) { - return t.unaryExpression("!", node, true); -}; - -var buildAssignment = function (left, right) { - return t.assignmentExpression("=", left, right); -}; - -exports.ExpressionStatement = function (node, parent, file, scope) { - var expr = node.expression; - if (!isMallet(expr)) return; - - var nodes = []; - var left = buildAbsoluteRef(expr.left, nodes, file, scope); - - nodes.push(t.ifStatement( - buildIsFalsey(left), - t.expressionStatement(buildAssignment(left, expr.right)) - )); - - return nodes; -}; - -exports.AssignmentExpression = function (node, parent, file, scope) { - if (t.isExpressionStatement(parent)) return; - if (!isMallet(node)) return; - - var nodes = []; - var left = buildAbsoluteRef(node.left, nodes, file, scope); - - nodes.push(t.logicalExpression( - "&&", - buildIsFalsey(left), - buildAssignment(left, node.right) - )); - - nodes.push(left); - - return t.toSequenceExpression(nodes, scope); -}; +}); diff --git a/lib/6to5/transformation/transformers/playground-memoization-operator.js b/lib/6to5/transformation/transformers/playground-memoization-operator.js index 1bd6d004ee..06c5f32405 100644 --- a/lib/6to5/transformation/transformers/playground-memoization-operator.js +++ b/lib/6to5/transformation/transformers/playground-memoization-operator.js @@ -1,88 +1,20 @@ var t = require("../../types"); -var isMemo = function (node) { - var is = t.isAssignmentExpression(node) && node.operator === "?="; - if (is) t.assertMemberExpression(node.left); - return is; -}; +module.exports = require("../helpers/build-conditional-assignment-operator-transformer")({ + is: function (node) { + var is = t.isAssignmentExpression(node) && node.operator === "?="; + if (is) t.assertMemberExpression(node.left); + return is; + }, -var getPropRef = function (nodes, member, file, scope) { - var prop = member.property; - var key = t.toComputedKey(member, member.property); - if (t.isLiteral(key)) { - return key; - } else { - var temp = scope.generateUidBasedOnNode(prop, file); - nodes.push(t.variableDeclaration("var", [ - t.variableDeclarator(temp, prop) - ])); - return temp; + build: function (node, file) { + return t.unaryExpression( + "!", + t.callExpression( + t.memberExpression(file.addHelper("has-own"), t.identifier("call")), + [node.object, node.property] + ), + true + ); } -}; - -var getObjRef = function (nodes, obj, file, scope) { - var temp = scope.generateUidBasedOnNode(obj, file); - nodes.push(t.variableDeclaration("var", [ - t.variableDeclarator(temp, obj) - ])); - return temp; -}; - -var buildHasOwn = function (obj, prop, file) { - return t.unaryExpression( - "!", - t.callExpression( - t.memberExpression(file.addHelper("has-own"), t.identifier("call")), - [obj, prop] - ), - true - ); -}; - -var buildAbsoluteRef = function (left, obj, prop) { - var computed = left.computed || t.isLiteral(prop); - return t.memberExpression(obj, prop, computed); -}; - -var buildAssignment = function (expr, obj, prop) { - return t.assignmentExpression("=", buildAbsoluteRef(expr.left, obj, prop), expr.right); -}; - -exports.ExpressionStatement = function (node, parent, scope, context, file) { - var expr = node.expression; - if (!isMemo(expr)) return; - - var nodes = []; - - var left = expr.left; - var obj = getObjRef(nodes, left.object, file, scope); - var prop = getPropRef(nodes, left, file, scope); - - nodes.push(t.ifStatement( - buildHasOwn(obj, prop, file), - t.expressionStatement(buildAssignment(expr, obj, prop)) - )); - - return nodes; -}; - -exports.AssignmentExpression = function (node, parent, scope, context, file) { - if (t.isExpressionStatement(parent)) return; - if (!isMemo(node)) return; - - var nodes = []; - - var left = node.left; - var obj = getObjRef(nodes, left.object, file, scope); - var prop = getPropRef(nodes, left, file, scope); - - nodes.push(t.logicalExpression( - "&&", - buildHasOwn(obj, prop, file), - buildAssignment(node, obj, prop) - )); - - nodes.push(buildAbsoluteRef(left, obj, prop)); - - return t.toSequenceExpression(nodes, scope); -}; +}); diff --git a/test/fixtures/transformation/playground/mallet-operator/actual.js b/test/fixtures/transformation/playground/mallet-operator/actual.js deleted file mode 100644 index 0c287674e4..0000000000 --- a/test/fixtures/transformation/playground/mallet-operator/actual.js +++ /dev/null @@ -1,17 +0,0 @@ -obj ||= {}; - -obj.x ||= 2; - -console.log(obj.x ||= 2); - -obj.x.x ||= 2; - -console.log(obj.x.x ||= 2); - -obj[x()] ||= 2; - -console.log(obj[x()] ||= 2); - -obj[y()][x()] ||= 2; - -console.log(obj[y()][x()] ||= 2); diff --git a/test/fixtures/transformation/playground/mallet-operator/exec.js b/test/fixtures/transformation/playground/mallet-operator/exec.js index e82d33882e..b784b844bd 100644 --- a/test/fixtures/transformation/playground/mallet-operator/exec.js +++ b/test/fixtures/transformation/playground/mallet-operator/exec.js @@ -88,3 +88,19 @@ assert.equal(obj, 2); obj = 0; assert.equal(obj ||= 2 , 2); + +var calls = 0; +var q = { q: 3 }; +var o = { + get p() { + calls++; + return q; + } +}; + +o.p.q ||= 2; +assert.equal(1, calls); +o.p.f ||= 2; +assert.equal(2, calls); +assert.equal(3, o.p.q); +assert.equal(2, o.p.f); diff --git a/test/fixtures/transformation/playground/mallet-operator/expected.js b/test/fixtures/transformation/playground/mallet-operator/expected.js deleted file mode 100644 index 28b7439356..0000000000 --- a/test/fixtures/transformation/playground/mallet-operator/expected.js +++ /dev/null @@ -1,29 +0,0 @@ -"use strict"; - -var _obj$x2, _x2, _obj$y2, _x4; -if (!obj) obj = {}; -if (!obj.x) obj.x = 2; - - -console.log((!obj.x && (obj.x = 2), obj.x)); - -var _obj$x = obj.x; -if (!_obj$x.x) _obj$x.x = 2; - - -console.log((_obj$x2 = obj.x, !_obj$x2.x && (_obj$x2.x = 2), _obj$x2.x)); - -var _x = x(); - -if (!obj[_x]) obj[_x] = 2; - - -console.log((_x2 = x(), !obj[_x2] && (obj[_x2] = 2), obj[_x2])); - -var _obj$y = obj[y()]; -var _x3 = x(); - -if (!_obj$y[_x3]) _obj$y[_x3] = 2; - - -console.log((_obj$y2 = obj[y()], _x4 = x(), !_obj$y2[_x4] && (_obj$y2[_x4] = 2), _obj$y2[_x4])); diff --git a/test/fixtures/transformation/playground/memoization-assignment-operator/actual.js b/test/fixtures/transformation/playground/memoization-assignment-operator/actual.js deleted file mode 100644 index b2d1521e2c..0000000000 --- a/test/fixtures/transformation/playground/memoization-assignment-operator/actual.js +++ /dev/null @@ -1,13 +0,0 @@ -var obj = {}; - -obj.x ?= 2; - -console.log(obj.x ?= 2); - -obj[x()] ?= 2; - -console.log(obj[x()] ?= 2); - -obj[y()][x()] ?= 2; - -console.log(obj[y()][x()] ?= 2); diff --git a/test/fixtures/transformation/playground/memoization-assignment-operator/exec.js b/test/fixtures/transformation/playground/memoization-assignment-operator/exec.js index 3e528c641d..716aca45a7 100644 --- a/test/fixtures/transformation/playground/memoization-assignment-operator/exec.js +++ b/test/fixtures/transformation/playground/memoization-assignment-operator/exec.js @@ -18,3 +18,19 @@ assert.equal(obj.x, undefined); obj = { x: undefined } assert.equal(obj.x ?= 2, undefined); + +var calls = 0; +var q = { q: 3 }; +var o = { + get p() { + calls++; + return q; + } +}; + +o.p.q ?= 2; +assert.equal(1, calls); +o.p.f ?= 2; +assert.equal(2, calls); +assert.equal(3, o.p.q); +assert.equal(2, o.p.f); diff --git a/test/fixtures/transformation/playground/memoization-assignment-operator/expected.js b/test/fixtures/transformation/playground/memoization-assignment-operator/expected.js deleted file mode 100644 index fbb64ac4f8..0000000000 --- a/test/fixtures/transformation/playground/memoization-assignment-operator/expected.js +++ /dev/null @@ -1,27 +0,0 @@ -"use strict"; - -var _obj2, _obj4, _x2, _obj$y2, _x4; -var _hasOwn = Object.prototype.hasOwnProperty; -var obj = {}; - -var _obj = obj; -if (!_hasOwn.call(_obj, "x")) _obj.x = 2; - - -console.log((_obj2 = obj, !_hasOwn.call(_obj2, "x") && (_obj2.x = 2), _obj2.x)); - -var _obj3 = obj; -var _x = x(); - -if (!_hasOwn.call(_obj3, _x)) _obj3[_x] = 2; - - -console.log((_obj4 = obj, _x2 = x(), !_hasOwn.call(_obj4, _x2) && (_obj4[_x2] = 2), _obj4[_x2])); - -var _obj$y = obj[y()]; -var _x3 = x(); - -if (!_hasOwn.call(_obj$y, _x3)) _obj$y[_x3] = 2; - - -console.log((_obj$y2 = obj[y()], _x4 = x(), !_hasOwn.call(_obj$y2, _x4) && (_obj$y2[_x4] = 2), _obj$y2[_x4]));