Fix evaluation order with object spread, 2 (#11471)

This commit is contained in:
Justin Ridgewell 2020-04-26 14:30:48 -04:00 committed by GitHub
parent 83d365acb6
commit c1d65d8842
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 47 additions and 73 deletions

View File

@ -281,6 +281,7 @@ export default declare((api, opts) => {
); );
} }
}, },
// adapted from transform-destructuring/src/index.js#pushObjectRest // adapted from transform-destructuring/src/index.js#pushObjectRest
// const { a, ...b } = c; // const { a, ...b } = c;
VariableDeclarator(path, file) { VariableDeclarator(path, file) {
@ -385,6 +386,7 @@ export default declare((api, opts) => {
} }
}); });
}, },
// taken from transform-destructuring/src/index.js#visitor // taken from transform-destructuring/src/index.js#visitor
// export var { a, ...b } = c; // export var { a, ...b } = c;
ExportNamedDeclaration(path) { ExportNamedDeclaration(path) {
@ -410,11 +412,13 @@ export default declare((api, opts) => {
path.replaceWith(declaration.node); path.replaceWith(declaration.node);
path.insertAfter(t.exportNamedDeclaration(null, specifiers)); path.insertAfter(t.exportNamedDeclaration(null, specifiers));
}, },
// try {} catch ({a, ...b}) {} // try {} catch ({a, ...b}) {}
CatchClause(path) { CatchClause(path) {
const paramPath = path.get("param"); const paramPath = path.get("param");
replaceRestElement(paramPath.parentPath, paramPath); replaceRestElement(paramPath.parentPath, paramPath);
}, },
// ({a, ...b} = c); // ({a, ...b} = c);
AssignmentExpression(path, file) { AssignmentExpression(path, file) {
const leftPath = path.get("left"); const leftPath = path.get("left");
@ -457,6 +461,7 @@ export default declare((api, opts) => {
path.replaceWithMultiple(nodes); path.replaceWithMultiple(nodes);
} }
}, },
// taken from transform-destructuring/src/index.js#visitor // taken from transform-destructuring/src/index.js#visitor
ForXStatement(path) { ForXStatement(path) {
const { node, scope } = path; const { node, scope } = path;
@ -506,6 +511,7 @@ export default declare((api, opts) => {
); );
} }
}, },
// [{a, ...b}] = c; // [{a, ...b}] = c;
ArrayPattern(path) { ArrayPattern(path) {
const objectPatterns = []; const objectPatterns = [];
@ -537,34 +543,11 @@ export default declare((api, opts) => {
); );
} }
}, },
// var a = { ...b, ...c } // var a = { ...b, ...c }
ObjectExpression(path, file) { ObjectExpression(path, file) {
if (!hasSpread(path.node)) return; if (!hasSpread(path.node)) return;
const { scope } = path;
// a non-SpreadElement and SpreadElement striped array
const args = [];
let props = [];
function push() {
args.push(t.objectExpression(props));
props = [];
}
for (const prop of (path.node.properties: Array)) {
if (t.isSpreadElement(prop)) {
push();
args.push(prop.argument);
} else {
props.push(prop);
}
}
if (props.length) {
push();
}
let helper; let helper;
if (loose) { if (loose) {
helper = getExtendsHelper(file); helper = getExtendsHelper(file);
@ -582,53 +565,40 @@ export default declare((api, opts) => {
helper = file.addHelper("objectSpread"); helper = file.addHelper("objectSpread");
} }
} }
// We cannot call _objectSpread with more than two elements directly, since any element could cause side effects. For
// example: let exp = null;
// var k = { a: 1, b: 2 }; let props = [];
// var o = { a: 3, ...k, b: k.a++ };
// // expected: { a: 1, b: 1 } function make() {
// If we translate the above to `_objectSpread({ a: 3 }, k, { b: k.a++ })`, the `k.a++` will evaluate before const hadProps = props.length > 0;
// `k` is spread and we end up with `{ a: 2, b: 1 }`. const obj = t.objectExpression(props);
// adapted from https://github.com/microsoft/TypeScript/blob/eb105efdcd6db8a73f5b983bf329cb7a5eee55e1/src/compiler/transformers/es2018.ts#L272 props = [];
const chunks = [];
let currentChunk = []; if (!exp) {
for (let i = 0; i < args.length; i++) { exp = t.callExpression(helper, [obj]);
currentChunk.push(args[i]); return;
const isCurrentChunkEmptyObject =
currentChunk.length === 1 &&
t.isObjectExpression(args[i]) &&
args[i].properties.length === 0;
const isNextArgEffectful =
i < args.length - 1 && !scope.isPure(args[i + 1]);
// prevent current chunk from pollution unless current chunk is an empty object
if (!isCurrentChunkEmptyObject && isNextArgEffectful) {
chunks.push(currentChunk);
currentChunk = [];
} }
exp = t.callExpression(t.cloneNode(helper), [
exp,
// If we have static props, we need to insert an empty object
// becuase the odd arguments are copied with [[Get]], not
// [[GetOwnProperty]]
...(hadProps ? [t.objectExpression([]), obj] : []),
]);
} }
if (currentChunk.length) { for (const prop of (path.node.properties: Array)) {
chunks.push(currentChunk); if (t.isSpreadElement(prop)) {
currentChunk = []; make();
} exp.arguments.push(prop.argument);
let exp = t.callExpression(helper, chunks[0]);
let nthArg = chunks[0].length;
for (let i = 1; i < chunks.length; i++) {
// reference: packages/babel-helpers/src/helpers.js#objectSpread2
if (nthArg % 2) {
exp = t.callExpression(helper, [exp, ...chunks[i]]);
} else { } else {
exp = t.callExpression(helper, [ props.push(prop);
exp,
t.objectExpression([]),
...chunks[i],
]);
} }
nthArg += chunks[i].length;
} }
if (props.length) make();
path.replaceWith(exp); path.replaceWith(exp);
}, },
}, },

View File

@ -11,11 +11,11 @@ var d;
var x; var x;
var y; var y;
_objectSpread({ _objectSpread(_objectSpread(_objectSpread({
x x
}, y, { }, y), {}, {
a a
}, b, { }, b), {}, {
c c
}); });
@ -25,9 +25,9 @@ _objectSpread({}, {
foo: 'bar' foo: 'bar'
}); });
_objectSpread({}, { _objectSpread(_objectSpread({}, {
foo: 'bar' foo: 'bar'
}, {}, { }), {
bar: 'baz' bar: 'baz'
}); });

View File

@ -1,4 +1,8 @@
var k = { a: 1, b: 2 }; var k = { a: 1, b: 2 };
var o = { a: 3, ...k, b: k.a++ }; var o = { a: 3, ...k, b: k.a++ };
expect(o).toEqual({a: 1, b: 1}); expect(o).toEqual({a: 1, b: 1});
var k = { a: 1, get b() { l = { z: 9 }; return 2; } };
var l = { c: 3 };
var o = { ...k, ...l };
expect(o).toEqual({ a: 1, b: 2, z: 9 });

View File

@ -25,15 +25,15 @@ function impureFunc() {
console.log('hello'); console.log('hello');
} }
var output = _objectSpread(_objectSpread(_objectSpread({}, pureA), {}, { var output = _objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread({}, pureA), {}, {
get foo() {}, get foo() {},
get bar() {} get bar() {}
}, pureB, {}, pureC, {}), impureFunc(), {}, pureD, { }, pureB), pureC), impureFunc()), pureD), {}, {
pureD pureD
}); });
var simpleOutput = _objectSpread({}, pureA, { var simpleOutput = _objectSpread(_objectSpread({}, pureA), {}, {
test: '1' test: '1'
}, pureB); }, pureB);