From 616640a128a4c6e31b1936dd4fa8dcbe0a088077 Mon Sep 17 00:00:00 2001 From: Justin Ridgewell Date: Fri, 16 Jan 2015 01:14:28 -0500 Subject: [PATCH] Playground Proposal: Mallet operator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mallet operator is similar to the current memoization operator, except it can be used outside of just objects. In Ruby, it’s almost the same as `a = a || b`. Note that only `nil` and `false` are falsey in Ruby. I’ve defined it as `== null`, though that could be changed to any JS falsey value. --- lib/6to5/transformation/transform.js | 1 + .../playground-mallet-operator.js | 86 +++++++++++++++++++ .../playground/mallet-operator/actual.js | 17 ++++ .../playground/mallet-operator/exec.js | 56 ++++++++++++ .../playground/mallet-operator/expected.js | 29 +++++++ 5 files changed, 189 insertions(+) create mode 100644 lib/6to5/transformation/transformers/playground-mallet-operator.js create mode 100644 test/fixtures/transformation/playground/mallet-operator/actual.js create mode 100644 test/fixtures/transformation/playground/mallet-operator/exec.js create mode 100644 test/fixtures/transformation/playground/mallet-operator/expected.js diff --git a/lib/6to5/transformation/transform.js b/lib/6to5/transformation/transform.js index 351fe6e7d8..485ed26959 100644 --- a/lib/6to5/transformation/transform.js +++ b/lib/6to5/transformation/transform.js @@ -46,6 +46,7 @@ _.each({ specSetters: require("./transformers/spec-setters"), // playground + malletOperator: require("./transformers/playground-mallet-operator"), methodBinding: require("./transformers/playground-method-binding"), memoizationOperator: require("./transformers/playground-memoization-operator"), objectGetterMemoization: require("./transformers/playground-object-getter-memoization"), diff --git a/lib/6to5/transformation/transformers/playground-mallet-operator.js b/lib/6to5/transformation/transformers/playground-mallet-operator.js new file mode 100644 index 0000000000..c0e92bddf3 --- /dev/null +++ b/lib/6to5/transformation/transformers/playground-mallet-operator.js @@ -0,0 +1,86 @@ +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"); + } + return 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 buildIsNull = function (node) { + return t.binaryExpression("==", node, t.literal(null)); +}; + +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( + buildIsNull(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( + "&&", + buildIsNull(left), + buildAssignment(left, node.right) + )); + + nodes.push(left); + + 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 new file mode 100644 index 0000000000..0c287674e4 --- /dev/null +++ b/test/fixtures/transformation/playground/mallet-operator/actual.js @@ -0,0 +1,17 @@ +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 new file mode 100644 index 0000000000..3fccbc42a2 --- /dev/null +++ b/test/fixtures/transformation/playground/mallet-operator/exec.js @@ -0,0 +1,56 @@ +var obj = {}; +obj.x ||= 2; +assert.equal(obj.x, 2); + +obj = {}; +assert.equal(obj.x ||= 2, 2); + +obj = { x: 1 }; +obj.x ||= 2; +assert.equal(obj.x, 1); + +obj = { x: 1 }; +assert.equal(obj.x ||= 2, 1); + +obj = { x: undefined } +obj.x ||= 2; +assert.equal(obj.x, 2); + +obj = { x: undefined } +assert.equal(obj.x ||= 2, 2); + +obj = { x: null } +obj.x ||= 2; +assert.equal(obj.x, 2); + +obj = { x: null } +assert.equal(obj.x ||= 2, 2); + +obj = { x: false } +obj.x ||= 2; +assert.equal(obj.x, false); + +obj = { x: false } +assert.equal(obj.x ||= 2, false); + +obj = undefined; +obj ||= 2; +assert.equal(obj, 2); + +obj = undefined; +assert.equal(obj ||= 2 , 2); + +obj = 1; +obj ||= 2; +assert.equal(obj, 1); + +obj = 1; +assert.equal(obj ||= 2 , 1); + +obj = null; +obj ||= 2; +assert.equal(obj, 2); + +obj = null; +assert.equal(obj ||= 2 , 2); + diff --git a/test/fixtures/transformation/playground/mallet-operator/expected.js b/test/fixtures/transformation/playground/mallet-operator/expected.js new file mode 100644 index 0000000000..efa81dfea9 --- /dev/null +++ b/test/fixtures/transformation/playground/mallet-operator/expected.js @@ -0,0 +1,29 @@ +"use strict"; + +var _obj$x2, _x2, _obj$y2, _x4; +if (obj == null) obj = {}; +if (obj.x == null) obj.x = 2; + + +console.log((obj.x == null && (obj.x = 2), obj.x)); + +var _obj$x = obj.x; +if (_obj$x.x == null) _obj$x.x = 2; + + +console.log((_obj$x2 = obj.x, _obj$x2.x == null && (_obj$x2.x = 2), _obj$x2.x)); + +var _x = x(); + +if (obj[_x] == null) obj[_x] = 2; + + +console.log((_x2 = x(), obj[_x2] == null && (obj[_x2] = 2), obj[_x2])); + +var _obj$y = obj[y()]; +var _x3 = x(); + +if (_obj$y[_x3] == null) _obj$y[_x3] = 2; + + +console.log((_obj$y2 = obj[y()], _x4 = x(), _obj$y2[_x4] == null && (_obj$y2[_x4] = 2), _obj$y2[_x4]));