From 800c350db69bf0b2ac45420400b9d9d74e3780d2 Mon Sep 17 00:00:00 2001 From: Sebastian McKenzie Date: Fri, 2 Jan 2015 00:36:28 +1100 Subject: [PATCH] use Object.defineProperty on computed properties - fixes #357 --- lib/6to5/file.js | 3 +- .../templates/define-property.js | 8 ++ .../templates/function-return-obj.js | 3 - .../object-define-properties-closure.js | 4 - .../templates/object-getter-memoization.js | 6 - .../es6-computed-property-names.js | 107 +++++++++++++----- .../argument/expected.js | 14 ++- .../assignment/expected.js | 14 ++- .../ignore-symbol/actual.js | 3 + .../ignore-symbol/expected.js | 8 ++ .../method/expected.js | 17 ++- .../mixed/expected.js | 28 +++-- .../multiple/expected.js | 21 +++- .../single/expected.js | 14 ++- .../this/expected.js | 15 ++- .../variable/expected.js | 14 ++- 16 files changed, 197 insertions(+), 82 deletions(-) create mode 100644 lib/6to5/transformation/templates/define-property.js delete mode 100644 lib/6to5/transformation/templates/function-return-obj.js delete mode 100644 lib/6to5/transformation/templates/object-define-properties-closure.js delete mode 100644 lib/6to5/transformation/templates/object-getter-memoization.js create mode 100644 test/fixtures/transformation/es6-computed-property-names/ignore-symbol/actual.js create mode 100644 test/fixtures/transformation/es6-computed-property-names/ignore-symbol/expected.js diff --git a/lib/6to5/file.js b/lib/6to5/file.js index 53550d43b3..87c09f5583 100644 --- a/lib/6to5/file.js +++ b/lib/6to5/file.js @@ -26,7 +26,8 @@ File.declarations = [ "sliced-to-array", "object-without-properties", "has-own", - "slice" + "slice", + "define-property" ]; File.normaliseOptions = function (opts) { diff --git a/lib/6to5/transformation/templates/define-property.js b/lib/6to5/transformation/templates/define-property.js new file mode 100644 index 0000000000..dca42eeabb --- /dev/null +++ b/lib/6to5/transformation/templates/define-property.js @@ -0,0 +1,8 @@ +(function (obj, key, value) { + return Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); +}); diff --git a/lib/6to5/transformation/templates/function-return-obj.js b/lib/6to5/transformation/templates/function-return-obj.js deleted file mode 100644 index 4f54b679b9..0000000000 --- a/lib/6to5/transformation/templates/function-return-obj.js +++ /dev/null @@ -1,3 +0,0 @@ -(function (KEY) { - return KEY; -})(OBJECT) diff --git a/lib/6to5/transformation/templates/object-define-properties-closure.js b/lib/6to5/transformation/templates/object-define-properties-closure.js deleted file mode 100644 index a7b90fbc8c..0000000000 --- a/lib/6to5/transformation/templates/object-define-properties-closure.js +++ /dev/null @@ -1,4 +0,0 @@ -(function (KEY) { - CONTENT; - return KEY; -})(OBJECT); diff --git a/lib/6to5/transformation/templates/object-getter-memoization.js b/lib/6to5/transformation/templates/object-getter-memoization.js deleted file mode 100644 index c15e24e9ad..0000000000 --- a/lib/6to5/transformation/templates/object-getter-memoization.js +++ /dev/null @@ -1,6 +0,0 @@ -Object.defineProperty(this, KEY, { - enumerable: true, - configurable: true, - writable: true, - value: VALUE -}) diff --git a/lib/6to5/transformation/transformers/es6-computed-property-names.js b/lib/6to5/transformation/transformers/es6-computed-property-names.js index 3b434abb14..9f9ce01b4d 100644 --- a/lib/6to5/transformation/transformers/es6-computed-property-names.js +++ b/lib/6to5/transformation/transformers/es6-computed-property-names.js @@ -4,44 +4,91 @@ var t = require("../../types"); exports.ObjectExpression = function (node, parent, file) { var hasComputed = false; - var computed = []; - - node.properties = node.properties.filter(function (prop) { - if (prop.computed) { - hasComputed = true; - computed.unshift(prop); - return false; - } else { - return true; - } - }); + for (var i in node.properties) { + hasComputed = t.isProperty(node.properties[i], { computed: true }); + if (hasComputed) break; + } if (!hasComputed) return; var objId = util.getUid(parent, file); - var container = util.template("function-return-obj", { - KEY: objId, - OBJECT: node - }); + var body = []; + var container = t.functionExpression(null, [], t.blockStatement(body)); + container._aliasFunction = true; - var containerCallee = container.callee; - var containerBody = containerCallee.body.body; + var props = node.properties; - containerCallee._aliasFunction = true; + // normalise key - for (var i in computed) { - var prop = computed[i]; - containerBody.unshift( - t.expressionStatement( - t.assignmentExpression( - "=", - t.memberExpression(objId, prop.key, true), - prop.value - ) - ) - ); + for (i in props) { + var prop = props[i]; + var key = prop.key; + + if (!prop.computed && t.isIdentifier(key)) { + prop.key = t.literal(key.name); + } } - return container; + // add all non-computed properties and `__proto__` properties to the initializer + + var initProps = []; + var broken = false; + + for (i in props) { + var prop = props[i]; + + if (prop.computed) { + broken = true; + } + + if (!broken || t.isLiteral(prop.key, { value: "__proto__" })) { + initProps.push(prop); + props[i] = null; + } + } + + // add a simple assignment for all Symbol member expressions due to symbol polyfill limitations + // otherwise use Object.defineProperty + + for (i in props) { + var prop = props[i]; + if (!prop) continue; + + var key = prop.key; + var bodyNode; + + if (prop.computed && t.isMemberExpression(key) && t.isIdentifier(key.object, { name: "Symbol" })) { + bodyNode = t.assignmentExpression( + "=", + t.memberExpression(objId, key, true), + prop.value + ); + } else { + bodyNode = t.callExpression(file.addDeclaration("define-property"), [objId, key, prop.value]); + } + + body.push(t.expressionStatement(bodyNode)); + } + + // only one node and it's a Object.defineProperty that returns the object + + if (body.length === 1) { + var first = body[0].expression; + + if (t.isCallExpression(first)) { + first.arguments[0] = t.objectExpression([]); + return first; + } + } + + // + + body.unshift(t.variableDeclaration("var", [ + t.variableDeclarator(objId, t.objectExpression(initProps)) + ])); + + body.push(t.returnStatement(objId)); + + return t.callExpression(container, []); }; diff --git a/test/fixtures/transformation/es6-computed-property-names/argument/expected.js b/test/fixtures/transformation/es6-computed-property-names/argument/expected.js index 32e48a5d4e..543b8d3166 100644 --- a/test/fixtures/transformation/es6-computed-property-names/argument/expected.js +++ b/test/fixtures/transformation/es6-computed-property-names/argument/expected.js @@ -1,6 +1,12 @@ "use strict"; -foo((function (_ref) { - _ref[bar] = "foobar"; - return _ref; -})({})); +var _defineProperty = function (obj, key, value) { + return Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); +}; + +foo(_defineProperty({}, bar, "foobar")); diff --git a/test/fixtures/transformation/es6-computed-property-names/assignment/expected.js b/test/fixtures/transformation/es6-computed-property-names/assignment/expected.js index 252b1e0d78..1ba34e86f5 100644 --- a/test/fixtures/transformation/es6-computed-property-names/assignment/expected.js +++ b/test/fixtures/transformation/es6-computed-property-names/assignment/expected.js @@ -1,6 +1,12 @@ "use strict"; -foo = (function (_foo) { - _foo[bar] = "foobar"; - return _foo; -})({}); +var _defineProperty = function (obj, key, value) { + return Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); +}; + +foo = _defineProperty({}, bar, "foobar"); diff --git a/test/fixtures/transformation/es6-computed-property-names/ignore-symbol/actual.js b/test/fixtures/transformation/es6-computed-property-names/ignore-symbol/actual.js new file mode 100644 index 0000000000..fce8293ed4 --- /dev/null +++ b/test/fixtures/transformation/es6-computed-property-names/ignore-symbol/actual.js @@ -0,0 +1,3 @@ +var foo = { + [Symbol.iterator]: "foobar" +}; diff --git a/test/fixtures/transformation/es6-computed-property-names/ignore-symbol/expected.js b/test/fixtures/transformation/es6-computed-property-names/ignore-symbol/expected.js new file mode 100644 index 0000000000..852e2b4e6c --- /dev/null +++ b/test/fixtures/transformation/es6-computed-property-names/ignore-symbol/expected.js @@ -0,0 +1,8 @@ +"use strict"; + +var foo = (function () { + var _foo = {}; + + _foo[Symbol.iterator] = "foobar"; + return _foo; +})(); diff --git a/test/fixtures/transformation/es6-computed-property-names/method/expected.js b/test/fixtures/transformation/es6-computed-property-names/method/expected.js index ce553158fa..a4c9782668 100644 --- a/test/fixtures/transformation/es6-computed-property-names/method/expected.js +++ b/test/fixtures/transformation/es6-computed-property-names/method/expected.js @@ -1,9 +1,14 @@ "use strict"; -var obj = (function (_obj) { - _obj[foobar] = function () { - return "foobar"; - }; +var _defineProperty = function (obj, key, value) { + return Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); +}; - return _obj; -})({}); +var obj = _defineProperty({}, foobar, function () { + return "foobar"; +}); diff --git a/test/fixtures/transformation/es6-computed-property-names/mixed/expected.js b/test/fixtures/transformation/es6-computed-property-names/mixed/expected.js index 4a28187475..afab9e7b3f 100644 --- a/test/fixtures/transformation/es6-computed-property-names/mixed/expected.js +++ b/test/fixtures/transformation/es6-computed-property-names/mixed/expected.js @@ -1,10 +1,24 @@ "use strict"; -var obj = (function (_obj) { - _obj["x" + foo] = "heh"; - _obj["y" + bar] = "noo"; +var _defineProperty = function (obj, key, value) { + return Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); +}; + +var obj = (function () { + var _obj = {}; + + _defineProperty(_obj, "x" + foo, "heh"); + + _defineProperty(_obj, "y" + bar, "noo"); + + _defineProperty(_obj, "foo", "foo"); + + _defineProperty(_obj, "bar", "bar"); + return _obj; -})({ - foo: "foo", - bar: "bar" -}); +})(); diff --git a/test/fixtures/transformation/es6-computed-property-names/multiple/expected.js b/test/fixtures/transformation/es6-computed-property-names/multiple/expected.js index 9c62aa4935..df5da87983 100644 --- a/test/fixtures/transformation/es6-computed-property-names/multiple/expected.js +++ b/test/fixtures/transformation/es6-computed-property-names/multiple/expected.js @@ -1,7 +1,20 @@ "use strict"; -var obj = (function (_obj) { - _obj["x" + foo] = "heh"; - _obj["y" + bar] = "noo"; +var _defineProperty = function (obj, key, value) { + return Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); +}; + +var obj = (function () { + var _obj = {}; + + _defineProperty(_obj, "x" + foo, "heh"); + + _defineProperty(_obj, "y" + bar, "noo"); + return _obj; -})({}); +})(); diff --git a/test/fixtures/transformation/es6-computed-property-names/single/expected.js b/test/fixtures/transformation/es6-computed-property-names/single/expected.js index d2b4c26e24..8b9c799cb7 100644 --- a/test/fixtures/transformation/es6-computed-property-names/single/expected.js +++ b/test/fixtures/transformation/es6-computed-property-names/single/expected.js @@ -1,6 +1,12 @@ "use strict"; -var obj = (function (_obj) { - _obj["x" + foo] = "heh"; - return _obj; -})({}); +var _defineProperty = function (obj, key, value) { + return Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); +}; + +var obj = _defineProperty({}, "x" + foo, "heh"); diff --git a/test/fixtures/transformation/es6-computed-property-names/this/expected.js b/test/fixtures/transformation/es6-computed-property-names/this/expected.js index 432f09b1df..3c3334e470 100644 --- a/test/fixtures/transformation/es6-computed-property-names/this/expected.js +++ b/test/fixtures/transformation/es6-computed-property-names/this/expected.js @@ -1,7 +1,12 @@ "use strict"; -var _this = this; -var obj = (function (_obj) { - _obj["x" + _this.foo] = "heh"; - return _obj; -})({}); +var _defineProperty = function (obj, key, value) { + return Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); +}; + +var obj = _defineProperty({}, "x" + this.foo, "heh"); diff --git a/test/fixtures/transformation/es6-computed-property-names/variable/expected.js b/test/fixtures/transformation/es6-computed-property-names/variable/expected.js index de5c25aae4..18819eecab 100644 --- a/test/fixtures/transformation/es6-computed-property-names/variable/expected.js +++ b/test/fixtures/transformation/es6-computed-property-names/variable/expected.js @@ -1,6 +1,12 @@ "use strict"; -var foo = (function (_foo) { - _foo[bar] = "foobar"; - return _foo; -})({}); +var _defineProperty = function (obj, key, value) { + return Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); +}; + +var foo = _defineProperty({}, bar, "foobar");