Fix object spread according to spec (#7034)

This commit is contained in:
Andrea Puddu 2018-02-17 02:06:17 +01:00 committed by Justin Ridgewell
parent e732ee0c5b
commit ee6dfd1580
22 changed files with 177 additions and 36 deletions

View File

@ -374,6 +374,26 @@ helpers.extends = defineHelper(`
}
`);
helpers.objectSpread = defineHelper(`
import defineProperty from "defineProperty";
export default function _objectSpread(target) {
for (var i = 1; i < arguments.length; i++) {
var source = (arguments[i] != null) ? arguments[i] : {};
var ownKeys = Object.keys(source);
if (typeof Object.getOwnPropertySymbols === 'function') {
ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
}));
}
ownKeys.forEach(function(key) {
defineProperty(target, key, source[key]);
});
}
return target;
}
`);
helpers.get = defineHelper(`
export default function _get(object, property, receiver) {
if (object === null) object = Function.prototype;

View File

@ -54,18 +54,34 @@ require("@babel/core").transform("code", {
## Options
By default, this plugin will produce spec compliant code. The Babel's `objectSpread` helper will be used.
### `loose`
`boolean`, defaults to `false`.
Enabling this option will use Babel's `extends` helper, which is basically the same as `Object.assign` (see `useBuiltIns` below to use it directly).
:warning: Please take in mind that even if they're almost equivalent, there's an important difference between spread and `Object.assign`: to summarize, **spread defines new properties, while `Object.assign()` sets them**, so using this mode might produce unexpected results in some case.
For detailed information please check out [Spread VS. Object.assign](http://2ality.com/2016/10/rest-spread-properties.html#spreading-objects-versus-objectassign) and [Assigning VS. defining properties](http://exploringjs.com/es6/ch_oop-besides-classes.html#sec_assigning-vs-defining-properties).
### `useBuiltIns`
`boolean`, defaults to `false`.
By default, this plugin uses Babel's `extends` helper which polyfills `Object.assign`. Enabling this option will use `Object.assign` directly.
Enabling this option will use `Object.assign` directly instead of the Babel's `extends` helper. Keep in mind that this flag only applies when `loose: true`.
##### Example
**.babelrc**
```json
{
"plugins": [
["@babel/plugin-proposal-object-rest-spread", { "useBuiltIns": true }]
["@babel/plugin-proposal-object-rest-spread", { "loose": true, "useBuiltIns": true }]
]
}
```
@ -86,3 +102,5 @@ z = Object.assign({ x }, y);
* [Proposal: Object Rest/Spread Properties for ECMAScript](https://github.com/sebmarkbage/ecmascript-rest-spread)
* [Spec](http://sebmarkbage.github.io/ecmascript-rest-spread)
* [Spread VS. Object.assign](http://2ality.com/2016/10/rest-spread-properties.html#spreading-objects-versus-objectassign)
* [Assigning VS. defining properties](http://exploringjs.com/es6/ch_oop-besides-classes.html#sec_assigning-vs-defining-properties)

View File

@ -2,9 +2,10 @@ import syntaxObjectRestSpread from "@babel/plugin-syntax-object-rest-spread";
import { types as t } from "@babel/core";
export default function(api, opts) {
const { useBuiltIns = false } = opts;
if (typeof useBuiltIns !== "boolean") {
throw new Error(".useBuiltIns must be a boolean, or undefined");
const { useBuiltIns = false, loose = false } = opts;
if (typeof loose !== "boolean") {
throw new Error(".loose must be a boolean, or undefined");
}
function hasRestElement(path) {
@ -366,6 +367,10 @@ export default function(api, opts) {
props = [];
}
if (t.isSpreadElement(path.node.properties[0])) {
args.push(t.objectExpression([]));
}
for (const prop of (path.node.properties: Array)) {
if (t.isSpreadElement(prop)) {
push();
@ -377,13 +382,14 @@ export default function(api, opts) {
push();
if (!t.isObjectExpression(args[0])) {
args.unshift(t.objectExpression([]));
}
const helper = useBuiltIns
let helper;
if (loose) {
helper = useBuiltIns
? t.memberExpression(t.identifier("Object"), t.identifier("assign"))
: file.addHelper("extends");
} else {
helper = file.addHelper("objectSpread");
}
path.replaceWith(t.callExpression(helper, args));
},

View File

@ -1,9 +1,11 @@
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }
z = _extends({
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
z = _objectSpread({
x
}, y);
z = {
x,
w: _extends({}, y)
w: _objectSpread({}, y)
};

View File

@ -1 +1,7 @@
({ x, ...y, a, ...b, c });
({ ...Object.prototype });
({ ...{ foo: 'bar' } });
({ ...{ get foo () { return 'foo' } } });

View File

@ -1,9 +1,24 @@
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }
_extends({
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
_objectSpread({
x
}, y, {
a
}, b, {
c
});
_objectSpread({}, Object.prototype);
_objectSpread({}, {
foo: 'bar'
});
_objectSpread({}, {
get foo() {
return 'foo';
}
});

View File

@ -0,0 +1,3 @@
z = { x, ...y };
z = { x, w: { ...y } };

View File

@ -0,0 +1,5 @@
{
"plugins": [
["proposal-object-rest-spread", { "loose": true, "useBuiltIns": true }]
]
}

View File

@ -0,0 +1,7 @@
z = Object.assign({
x
}, y);
z = {
x,
w: Object.assign({}, y)
};

View File

@ -0,0 +1,3 @@
z = { x, ...y };
z = { x, w: { ...y } };

View File

@ -0,0 +1,5 @@
{
"plugins": [
["proposal-object-rest-spread", { "loose": true }]
]
}

View File

@ -0,0 +1,9 @@
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
z = _extends({
x
}, y);
z = {
x,
w: _extends({}, y)
};

View File

@ -0,0 +1,40 @@
Object.defineProperty(Object.prototype, 'NOSET', {
set(value) {
// noop
},
});
Object.defineProperty(Object.prototype, 'NOWRITE', {
writable: false,
value: 'abc',
});
const obj = { NOSET: 123 };
// this wouldn't work as expected if transformed as Object.assign (or equivalent)
// because those trigger object setters (spread don't)
const objSpread = { ...obj };
const obj2 = { NOSET: 123, NOWRITE: 456 };
// this line would throw `TypeError: Cannot assign to read only property 'NOWRITE'`
// if transformed as Object.assign (or equivalent) because those use *assignment* for creating properties
// (spread defines them)
const obj2Spread = { ...obj2 };
assert.deepEqual(obj, objSpread);
assert.deepEqual(obj2, obj2Spread);
const KEY = Symbol('key');
const obj3Spread = { ...{ get foo () { return 'bar' } }, [KEY]: 'symbol' };
assert.equal(Object.getOwnPropertyDescriptor(obj3Spread, 'foo').value, 'bar');
assert.equal(Object.getOwnPropertyDescriptor(obj3Spread, KEY).value, 'symbol');
const obj4Spread = { ...Object.prototype };
assert.isUndefined(Object.getOwnPropertyDescriptor(obj4Spread, 'hasOwnProperty'));
assert.doesNotThrow(() => ({ ...null, ...undefined }));
const o = Object.create(null);
o.a = 'foo';
o.__proto__ = [];
const o2 = { ...o };
assert.equal(false, Array.isArray(Object.getPrototypeOf(o2)));

View File

@ -1,3 +1,5 @@
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }
var z = _extends({}, x);
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
var z = _objectSpread({}, x);

View File

@ -1,4 +0,0 @@
{
"plugins": [["proposal-object-rest-spread", { "useBuiltIns": "invalidOption" }]],
"throws": ".useBuiltIns must be a boolean, or undefined"
}

View File

@ -1,3 +0,0 @@
z = Object.assign({
x
}, y);

View File

@ -1,3 +0,0 @@
{
"plugins": [["proposal-object-rest-spread", { "useBuiltIns": true }]]
}

View File

@ -14,6 +14,10 @@ _AsyncGenerator.prototype.return = function (arg) { return this._invoke("return"
function _AwaitValue(value) { this.wrapped = value; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
var _x$y$a$b = {
@ -26,7 +30,7 @@ var _x$y$a$b = {
y = _x$y$a$b.y,
z = _objectWithoutProperties(_x$y$a$b, ["x", "y"]);
var n = Object.assign({
var n = _objectSpread({
x: x,
y: y
}, z);

View File

@ -6,9 +6,11 @@ require("core-js/modules/es6.symbol");
require("core-js/modules/es6.promise");
require("core-js/modules/es6.array.index-of");
require("core-js/modules/es6.array.for-each");
require("core-js/modules/es6.object.assign");
require("core-js/modules/es6.array.filter");
require("core-js/modules/es6.array.index-of");
function _awaitAsyncGenerator(value) { return new _AwaitValue(value); }
@ -26,6 +28,10 @@ _AsyncGenerator.prototype.return = function (arg) { return this._invoke("return"
function _AwaitValue(value) { this.wrapped = value; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
var _x$y$a$b = {
@ -38,7 +44,7 @@ var _x$y$a$b = {
y = _x$y$a$b.y,
z = _objectWithoutProperties(_x$y$a$b, ["x", "y"]);
var n = Object.assign({
var n = _objectSpread({
x: x,
y: y
}, z);

View File

@ -14,7 +14,9 @@ _AsyncGenerator.prototype.return = function (arg) { return this._invoke("return"
function _AwaitValue(value) { this.wrapped = value; }
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
@ -28,7 +30,7 @@ var _x$y$a$b = {
y = _x$y$a$b.y,
z = _objectWithoutProperties(_x$y$a$b, ["x", "y"]);
var n = _extends({
var n = _objectSpread({
x: x,
y: y
}, z);