Skip to content

Commit

Permalink
Implement objectRestNoSymbols, setSpreadProperties and `pureGette…
Browse files Browse the repository at this point in the history
…rs` (#12505)
  • Loading branch information
nicolo-ribaudo committed Jan 14, 2021
1 parent caa4fda commit a6af08c
Show file tree
Hide file tree
Showing 46 changed files with 452 additions and 18 deletions.
2 changes: 2 additions & 0 deletions packages/babel-core/src/config/validation/options.js
Expand Up @@ -338,10 +338,12 @@ export const assumptionsNames = new Set<string>([
"iterableIsArray",
"mutableTemplateObject",
"noDocumentAll",
"objectRestNoSymbols",
"pureGetters",
"setClassMethods",
"setComputedProperties",
"setPublicClassFields",
"setSpreadProperties",
"skipForOfIteratorClosing",
]);

Expand Down
27 changes: 15 additions & 12 deletions packages/babel-plugin-proposal-object-rest-spread/src/index.js
Expand Up @@ -23,8 +23,10 @@ export default declare((api, opts) => {
throw new Error(".loose must be a boolean, or undefined");
}

const ignoreFunctionLength =
api.assumption("ignoreFunctionLength") ?? opts.loose;
const ignoreFunctionLength = api.assumption("ignoreFunctionLength") ?? loose;
const objectRestNoSymbols = api.assumption("objectRestNoSymbols") ?? loose;
const pureGetters = api.assumption("pureGetters") ?? loose;
const setSpreadProperties = api.assumption("setSpreadProperties") ?? loose;

function getExtendsHelper(file) {
return useBuiltIns
Expand Down Expand Up @@ -136,7 +138,7 @@ export default declare((api, opts) => {
}

//expects path to an object pattern
function createObjectSpread(path, file, objRef) {
function createObjectRest(path, file, objRef) {
const props = path.get("properties");
const last = props[props.length - 1];
t.assertRestElement(last.node);
Expand Down Expand Up @@ -175,7 +177,9 @@ export default declare((api, opts) => {
impureComputedPropertyDeclarators,
restElement.argument,
t.callExpression(
file.addHelper(`objectWithoutProperties${loose ? "Loose" : ""}`),
file.addHelper(
`objectWithoutProperties${objectRestNoSymbols ? "Loose" : ""}`,
),
[t.cloneNode(objRef), keyExpression],
),
];
Expand Down Expand Up @@ -364,9 +368,9 @@ export default declare((api, opts) => {
impureComputedPropertyDeclarators,
argument,
callExpression,
] = createObjectSpread(objectPatternPath, file, ref);
] = createObjectRest(objectPatternPath, file, ref);

if (loose) {
if (pureGetters) {
removeUnusedExcludedKeys(objectPatternPath);
}

Expand Down Expand Up @@ -447,7 +451,7 @@ export default declare((api, opts) => {
impureComputedPropertyDeclarators,
argument,
callExpression,
] = createObjectSpread(leftPath, file, t.identifier(refName));
] = createObjectRest(leftPath, file, t.identifier(refName));

if (impureComputedPropertyDeclarators.length > 0) {
nodes.push(
Expand Down Expand Up @@ -556,7 +560,7 @@ export default declare((api, opts) => {
if (!hasSpread(path.node)) return;

let helper;
if (loose) {
if (setSpreadProperties) {
helper = getExtendsHelper(file);
} else {
try {
Expand Down Expand Up @@ -586,10 +590,9 @@ 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) {
// When we can assume that getters are pure and don't depend on
// the order of evaluation, we can avoid making multiple calls.
if (pureGetters) {
if (hadProps) {
exp.arguments.push(obj);
}
Expand Down
@@ -0,0 +1,9 @@
{
"plugins": [
["external-helpers", { "helperVersion": "7.100.0"}],
"proposal-object-rest-spread"
],
"assumptions": {
"objectRestNoSymbols": true
}
}
@@ -0,0 +1 @@
({ a, b, ...c } = obj);
@@ -0,0 +1,7 @@
var _obj = obj;
({
a,
b
} = _obj);
c = babelHelpers.objectWithoutPropertiesLoose(_obj, ["a", "b"]);
_obj;
@@ -0,0 +1 @@
let { [a]: b, ...c } = obj;
@@ -0,0 +1,5 @@
let _a = a,
{
[_a]: b
} = obj,
c = babelHelpers.objectWithoutPropertiesLoose(obj, [_a].map(babelHelpers.toPropertyKey));
@@ -0,0 +1,7 @@
let sym = Symbol();

let { a, ...r } = { a: 1, b: 2, [sym]: 3 };

expect(a).toBe(1);
expect(r.b).toBe(2);
expect(sym in r).toBe(false);
@@ -0,0 +1 @@
let { a, nested: { b, c, ...d }, e } = obj;
@@ -0,0 +1,9 @@
let {
a,
nested: {
b,
c
},
e
} = obj,
d = babelHelpers.objectWithoutPropertiesLoose(obj.nested, ["b", "c"]);
@@ -0,0 +1 @@
var { a, b, ...c } = obj;
@@ -0,0 +1,5 @@
var {
a,
b
} = obj,
c = babelHelpers.objectWithoutPropertiesLoose(obj, ["a", "b"]);
@@ -0,0 +1,9 @@
{
"plugins": [
["external-helpers", { "helperVersion": "7.100.0"}],
"proposal-object-rest-spread"
],
"assumptions": {
"pureGetters": true
}
}
@@ -0,0 +1,11 @@
let called = false;
let obj = {
get foo() { called = true }
};

let { foo, ...rest } = obj;

expect("foo" in rest).toBe(false);

// Without assuming that getters are pure (in this case it isn't), this should be true
expect(called).toBe(false);
@@ -0,0 +1,20 @@
// should not remove when destructuring into existing bindings
({ a2, ...b2 } = c2);

function render() {
const {
excluded,
excluded2: excludedRenamed,
used,
used2: usedRenamed,
...props
} = this.props;

console.log(used, usedRenamed);

return React.createElement("input", props);
}

function smth({ unused, ...rest }) {
call(rest);
}
@@ -0,0 +1,23 @@
// should not remove when destructuring into existing bindings
var _c = c2;
({
a2
} = _c);
b2 = babelHelpers.objectWithoutProperties(_c, ["a2"]);
_c;

function render() {
const _this$props = this.props,
{
used,
used2: usedRenamed
} = _this$props,
props = babelHelpers.objectWithoutProperties(_this$props, ["excluded", "excluded2", "used", "used2"]);
console.log(used, usedRenamed);
return React.createElement("input", props);
}

function smth(_ref) {
let rest = babelHelpers.objectWithoutProperties(_ref, ["unused"]);
call(rest);
}
@@ -0,0 +1,10 @@
let count = 0;

let withFoo = { get foo() { return count++; } };
let withBar = { get bar() { return count++; } };

let res = { ...withFoo, middle: count, ...withBar };

// Without assuming that getters are pure (in this case it isn't),
// the result should be { foo: 0, middle: 1, bar: 1 }
expect(res).toEqual({ foo: 0, middle: 0, bar: 1 });
@@ -0,0 +1 @@
let obj = { a, ...b, c, ...d, e };
@@ -0,0 +1,7 @@
let obj = babelHelpers.objectSpread2({
a
}, b, {
c
}, d, {
e
});
@@ -0,0 +1,7 @@
var x;
var y;
var z;

z = { x, ...y };

z = { x, w: { ...y } };
@@ -0,0 +1,10 @@
var x;
var y;
var z;
z = Object.assign({
x
}, y);
z = {
x,
w: Object.assign({}, y)
};
@@ -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(Object.assign(Object.assign({
x
}, y), {}, {
a
}, b), {}, {
c
});
Object.assign({}, Object.prototype);
Object.assign({}, {
foo: 'bar'
});
Object.assign(Object.assign({}, {
foo: 'bar'
}), {
bar: 'baz'
});
Object.assign({}, {
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,43 @@
"use strict";
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,9 @@
{
"plugins": [
["external-helpers", { "helperVersion": "7.100.0"}],
["proposal-object-rest-spread", { "useBuiltIns": true }]
],
"assumptions": {
"setSpreadProperties": true
}
}
@@ -0,0 +1,7 @@
var x;
var y;
var z;

z = { x, ...y };

z = { x, w: { ...y } };
@@ -0,0 +1,10 @@
var x;
var y;
var z;
z = babelHelpers.extends({
x
}, y);
z = {
x,
w: babelHelpers.extends({}, y)
};

0 comments on commit a6af08c

Please sign in to comment.