Skip to content

Commit

Permalink
Use single object spread call in loose mode (#11520)
Browse files Browse the repository at this point in the history
  • Loading branch information
jridgewell committed May 8, 2020
1 parent b8a6145 commit a080a1d
Show file tree
Hide file tree
Showing 32 changed files with 383 additions and 15 deletions.
10 changes: 10 additions & 0 deletions packages/babel-plugin-proposal-object-rest-spread/src/index.js
Expand Up @@ -579,6 +579,16 @@ export default declare((api, opts) => {
return;
}

// In loose mode, we don't want to make multiple calls. We're assuming
// that the spread objects either don't use getters, or that the
// getters are pure and don't depend on the order of evaluation.
if (loose) {
if (hadProps) {
exp.arguments.push(obj);
}
return;
}

exp = t.callExpression(t.cloneNode(helper), [
exp,
// If we have static props, we need to insert an empty object
Expand Down
@@ -0,0 +1,9 @@
var log = [];

var a = {
...{ get foo() { log.push(1); } },
get bar() { log.push(2); }
};

// Loose mode uses regular Get, not GetOwnProperty.
expect(log).toEqual([1, 2]);
@@ -0,0 +1,16 @@
var a;
var b;
var c;
var d;
var x;
var y;

({ x, ...y, a, ...b, c });

({ ...Object.prototype });

({ ...{ foo: 'bar' } });

({ ...{ foo: 'bar' }, ...{ bar: 'baz' } });

({ ...{ get foo () { return 'foo' } } });
@@ -0,0 +1,28 @@
var a;
var b;
var c;
var d;
var x;
var y;
Object.assign({
x
}, y, {
a
}, b, {
c
});
Object.assign({}, Object.prototype);
Object.assign({}, {
foo: 'bar'
});
Object.assign({}, {
foo: 'bar'
}, {
bar: 'baz'
});
Object.assign({}, {
get foo() {
return 'foo';
}

});
@@ -0,0 +1,8 @@
const oldGOPDs = Object.getOwnPropertyDescriptors;
Object.getOwnPropertyDescriptors = null;

try {
({ ...{ a: 1 }, b: 1, ...{} });
} finally {
Object.getOwnPropertyDescriptors = oldGOPDs;
}
@@ -0,0 +1,46 @@
"use strict";
const NOSET = `NOSET${__filename}`;
const NOWRITE = `NOWRITE${__filename}`;

Object.defineProperty(Object.prototype, NOSET, {
get(value) {
// noop
},
});

Object.defineProperty(Object.prototype, NOWRITE, {
writable: false,
value: 'abc',
});

const obj = { [NOSET]: 123 };
// this won't work as expected if transformed as Object.assign (or equivalent)
// because those trigger object setters (spread don't)
expect(() => {
const objSpread = { ...obj };
}).toThrow();

const obj2 = { [NOWRITE]: 456 };
// this throws `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)
expect(() => {
const obj2Spread = { ...obj2 };
}).toThrow();

const KEY = Symbol('key');
const obj3Spread = { ...{ get foo () { return 'bar' } }, [KEY]: 'symbol' };
expect(Object.getOwnPropertyDescriptor(obj3Spread, 'foo').value).toBe('bar');
expect(Object.getOwnPropertyDescriptor(obj3Spread, KEY).value).toBe('symbol');

const obj4Spread = { ...Object.prototype };
expect(Object.getOwnPropertyDescriptor(obj4Spread, 'hasOwnProperty')).toBeUndefined();

expect(() => ({ ...null, ...undefined })).not.toThrow();

const o = Object.create(null);
o.a = 'foo';
o.__proto__ = [];
const o2 = { ...o };
// Loose will do o2.__proto__ = []
expect(Array.isArray(Object.getPrototypeOf(o2))).toBe(true);
@@ -0,0 +1,12 @@
var k = { a: 1, b: 2 };
var o = { a: 3, ...k, b: k.a++ };
// Loose will evaluate the static `b: k.a++` before spreading `...k`.
// It should be {a: 1, b: 1}
expect(o).toEqual({a: 2, b: 1});

var k = { a: 1, get b() { l = { z: 9 }; return 2; } };
var l = { c: 3 };
var o = { ...k, ...l };
// Loose will evaluate the `l` before spreading `...k`.
// It should be {a: 1, b: 2, z: 9}
expect(o).toEqual({ a: 1, b: 2, c: 3 });
@@ -0,0 +1,16 @@
var k = { a: 1, b: 2 };
var o = { a: 3, ...k, b: k.a++ };

var pureA = {};
var pureB = {};
var pureC = {};
var pureD = {};
var pureE = {};

function impureFunc() {
console.log('hello')
}

var output = { ...pureA, get foo() {}, get bar() {}, ...pureB, ...pureC, ...impureFunc(), ...pureD, pureD }

var simpleOutput = { ...pureA, test: '1', ...pureB, }
@@ -0,0 +1,30 @@
var k = {
a: 1,
b: 2
};
var o = Object.assign({
a: 3
}, k, {
b: k.a++
});
var pureA = {};
var pureB = {};
var pureC = {};
var pureD = {};
var pureE = {};

function impureFunc() {
console.log('hello');
}

var output = Object.assign({}, pureA, {
get foo() {},

get bar() {}

}, pureB, pureC, impureFunc(), pureD, {
pureD
});
var simpleOutput = Object.assign({}, pureA, {
test: '1'
}, pureB);
@@ -0,0 +1,3 @@
expect([...'']).toHaveLength(0);
expect([...'abc']).toHaveLength(3);
expect([...'def']).toMatchObject(['d','e','f']);
@@ -0,0 +1 @@
var z = { ...x };
@@ -0,0 +1 @@
var z = Object.assign({}, x);
Expand Up @@ -5,5 +5,3 @@ var z;
z = { x, ...y };

z = { x, w: { ...y } };

const { q, ...rest } = z;
@@ -1,5 +1,3 @@
function _objectWithoutPropertiesLoose(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]; } return target; }

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); }

var x;
Expand All @@ -12,5 +10,3 @@ z = {
x,
w: _extends({}, y)
};

const rest = _objectWithoutPropertiesLoose(z, ["q"]);
@@ -0,0 +1,9 @@
var log = [];

var a = {
...{ get foo() { log.push(1); } },
get bar() { log.push(2); }
};

// Loose mode uses regular Get, not GetOwnProperty.
expect(log).toEqual([1, 2]);
@@ -0,0 +1,16 @@
var a;
var b;
var c;
var d;
var x;
var y;

({ x, ...y, a, ...b, c });

({ ...Object.prototype });

({ ...{ foo: 'bar' } });

({ ...{ foo: 'bar' }, ...{ bar: 'baz' } });

({ ...{ get foo () { return 'foo' } } });
@@ -0,0 +1,35 @@
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); }

var a;
var b;
var c;
var d;
var x;
var y;

_extends({
x
}, y, {
a
}, b, {
c
});

_extends({}, Object.prototype);

_extends({}, {
foo: 'bar'
});

_extends({}, {
foo: 'bar'
}, {
bar: 'baz'
});

_extends({}, {
get foo() {
return 'foo';
}

});
@@ -0,0 +1,9 @@
const oldGOPDs = Object.getOwnPropertyDescriptors;
Object.getOwnPropertyDescriptors = null;

try {
({ ...{ a: 1 }, b: 1, ...{} });
} finally {
Object.getOwnPropertyDescriptors = oldGOPDs;
}

@@ -0,0 +1,46 @@
"use strict";
const NOSET = `NOSET${__filename}`;
const NOWRITE = `NOWRITE${__filename}`;

Object.defineProperty(Object.prototype, NOSET, {
get(value) {
// noop
},
});

Object.defineProperty(Object.prototype, NOWRITE, {
writable: false,
value: 'abc',
});

const obj = { [NOSET]: 123 };
// this won't work as expected if transformed as Object.assign (or equivalent)
// because those trigger object setters (spread don't)
expect(() => {
const objSpread = { ...obj };
}).toThrow();

const obj2 = { [NOWRITE]: 456 };
// this throws `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)
expect(() => {
const obj2Spread = { ...obj2 };
}).toThrow();

const KEY = Symbol('key');
const obj3Spread = { ...{ get foo () { return 'bar' } }, [KEY]: 'symbol' };
expect(Object.getOwnPropertyDescriptor(obj3Spread, 'foo').value).toBe('bar');
expect(Object.getOwnPropertyDescriptor(obj3Spread, KEY).value).toBe('symbol');

const obj4Spread = { ...Object.prototype };
expect(Object.getOwnPropertyDescriptor(obj4Spread, 'hasOwnProperty')).toBeUndefined();

expect(() => ({ ...null, ...undefined })).not.toThrow();

const o = Object.create(null);
o.a = 'foo';
o.__proto__ = [];
const o2 = { ...o };
// Loose will do o2.__proto__ = []
expect(Array.isArray(Object.getPrototypeOf(o2))).toBe(true);
@@ -0,0 +1,5 @@
{
"plugins": [
["proposal-object-rest-spread", { "loose": true }]
]
}
@@ -0,0 +1,12 @@
var k = { a: 1, b: 2 };
var o = { a: 3, ...k, b: k.a++ };
// Loose will evaluate the static `b: k.a++` before spreading `...k`.
// It should be {a: 1, b: 1}
expect(o).toEqual({a: 2, b: 1});

var k = { a: 1, get b() { l = { z: 9 }; return 2; } };
var l = { c: 3 };
var o = { ...k, ...l };
// Loose will evaluate the `l` before spreading `...k`.
// It should be {a: 1, b: 2, z: 9}
expect(o).toEqual({ a: 1, b: 2, c: 3 });
@@ -0,0 +1,16 @@
var k = { a: 1, b: 2 };
var o = { a: 3, ...k, b: k.a++ };

var pureA = {};
var pureB = {};
var pureC = {};
var pureD = {};
var pureE = {};

function impureFunc() {
console.log('hello')
}

var output = { ...pureA, get foo() {}, get bar() {}, ...pureB, ...pureC, ...impureFunc(), ...pureD, pureD }

var simpleOutput = { ...pureA, test: '1', ...pureB, }

0 comments on commit a080a1d

Please sign in to comment.