From 0e9c4b59d34482d1bd334a10e9fdc8c9c2b37735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Thu, 14 Jan 2021 02:58:48 +0100 Subject: [PATCH] Implement `objectRestNoSymbols`, `setSpreadProperties` and `pureGetters` (#12505) --- .../src/config/validation/options.js | 2 + .../src/index.js | 27 ++++++------ .../options.json | 9 ++++ .../rest-assignment-expression/input.js | 1 + .../rest-assignment-expression/output.js | 7 +++ .../rest-computed/input.js | 1 + .../rest-computed/output.js | 5 +++ .../rest-ignore-symbols/exec.js | 7 +++ .../rest-nested/input.js | 1 + .../rest-nested/output.js | 9 ++++ .../rest-var-declaration/input.js | 1 + .../rest-var-declaration/output.js | 5 +++ .../assumption-pureGetters/options.json | 9 ++++ .../rest-remove-unused-excluded-keys/exec.js | 11 +++++ .../rest-remove-unused-excluded-keys/input.js | 20 +++++++++ .../output.js | 23 ++++++++++ .../spread-single-call/exec.js | 10 +++++ .../spread-single-call/input.js | 1 + .../spread-single-call/output.js | 7 +++ .../assignment/input.js | 7 +++ .../assignment/output.js | 10 +++++ .../expression/exec.js | 9 ++++ .../expression/input.js | 16 +++++++ .../expression/output.js | 28 ++++++++++++ .../no-getOwnPropertyDescriptors/exec.js | 9 ++++ .../no-object-assign-exec/exec.js | 43 +++++++++++++++++++ .../options.json | 9 ++++ .../assignment/input.js | 7 +++ .../assignment/output.js | 10 +++++ .../expression/exec.js | 9 ++++ .../expression/input.js | 16 +++++++ .../expression/output.js | 28 ++++++++++++ .../no-getOwnPropertyDescriptors/exec.js | 9 ++++ .../no-object-assign-exec/exec.js | 43 +++++++++++++++++++ .../options.json | 9 ++++ .../src/index.js | 12 +++--- .../options.json | 9 ++++ .../rest-assignment-expression/input.js | 1 + .../rest-assignment-expression/output.js | 5 +++ .../rest-computed/input.js | 1 + .../rest-computed/output.js | 4 ++ .../rest-ignore-symbols/exec.js | 7 +++ .../rest-nested/input.js | 1 + .../rest-nested/output.js | 7 +++ .../rest-var-declaration/input.js | 1 + .../rest-var-declaration/output.js | 4 ++ 46 files changed, 452 insertions(+), 18 deletions(-) create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/options.json create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-assignment-expression/input.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-assignment-expression/output.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-computed/input.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-computed/output.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-ignore-symbols/exec.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-nested/input.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-nested/output.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-var-declaration/input.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-var-declaration/output.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/options.json create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/rest-remove-unused-excluded-keys/exec.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/rest-remove-unused-excluded-keys/input.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/rest-remove-unused-excluded-keys/output.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/spread-single-call/exec.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/spread-single-call/input.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/spread-single-call/output.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/assignment/input.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/assignment/output.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/expression/exec.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/expression/input.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/expression/output.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/no-getOwnPropertyDescriptors/exec.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/no-object-assign-exec/exec.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/options.json create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/assignment/input.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/assignment/output.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/expression/exec.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/expression/input.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/expression/output.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/no-getOwnPropertyDescriptors/exec.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/no-object-assign-exec/exec.js create mode 100644 packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/options.json create mode 100644 packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/options.json create mode 100644 packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-assignment-expression/input.js create mode 100644 packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-assignment-expression/output.js create mode 100644 packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-computed/input.js create mode 100644 packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-computed/output.js create mode 100644 packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-ignore-symbols/exec.js create mode 100644 packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-nested/input.js create mode 100644 packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-nested/output.js create mode 100644 packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-var-declaration/input.js create mode 100644 packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-var-declaration/output.js diff --git a/packages/babel-core/src/config/validation/options.js b/packages/babel-core/src/config/validation/options.js index 5b45a0dcbd74..c5ea61699562 100644 --- a/packages/babel-core/src/config/validation/options.js +++ b/packages/babel-core/src/config/validation/options.js @@ -338,10 +338,12 @@ export const assumptionsNames = new Set([ "iterableIsArray", "mutableTemplateObject", "noDocumentAll", + "objectRestNoSymbols", "pureGetters", "setClassMethods", "setComputedProperties", "setPublicClassFields", + "setSpreadProperties", "skipForOfIteratorClosing", ]); diff --git a/packages/babel-plugin-proposal-object-rest-spread/src/index.js b/packages/babel-plugin-proposal-object-rest-spread/src/index.js index 5fdc599b655f..7179c20899ba 100644 --- a/packages/babel-plugin-proposal-object-rest-spread/src/index.js +++ b/packages/babel-plugin-proposal-object-rest-spread/src/index.js @@ -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 @@ -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); @@ -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], ), ]; @@ -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); } @@ -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( @@ -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 { @@ -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); } diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/options.json b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/options.json new file mode 100644 index 000000000000..0640a8088af0 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/options.json @@ -0,0 +1,9 @@ +{ + "plugins": [ + ["external-helpers", { "helperVersion": "7.100.0"}], + "proposal-object-rest-spread" + ], + "assumptions": { + "objectRestNoSymbols": true + } +} diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-assignment-expression/input.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-assignment-expression/input.js new file mode 100644 index 000000000000..f03be37640bc --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-assignment-expression/input.js @@ -0,0 +1 @@ +({ a, b, ...c } = obj); diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-assignment-expression/output.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-assignment-expression/output.js new file mode 100644 index 000000000000..a1b0485e897b --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-assignment-expression/output.js @@ -0,0 +1,7 @@ +var _obj = obj; +({ + a, + b +} = _obj); +c = babelHelpers.objectWithoutPropertiesLoose(_obj, ["a", "b"]); +_obj; diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-computed/input.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-computed/input.js new file mode 100644 index 000000000000..8ddf55177eac --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-computed/input.js @@ -0,0 +1 @@ +let { [a]: b, ...c } = obj; diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-computed/output.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-computed/output.js new file mode 100644 index 000000000000..ffc31792901e --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-computed/output.js @@ -0,0 +1,5 @@ +let _a = a, + { + [_a]: b +} = obj, + c = babelHelpers.objectWithoutPropertiesLoose(obj, [_a].map(babelHelpers.toPropertyKey)); diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-ignore-symbols/exec.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-ignore-symbols/exec.js new file mode 100644 index 000000000000..2c85a4c5711e --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-ignore-symbols/exec.js @@ -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); diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-nested/input.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-nested/input.js new file mode 100644 index 000000000000..e1025ccc4cb9 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-nested/input.js @@ -0,0 +1 @@ +let { a, nested: { b, c, ...d }, e } = obj; diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-nested/output.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-nested/output.js new file mode 100644 index 000000000000..8a0d9f86b1b7 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-nested/output.js @@ -0,0 +1,9 @@ +let { + a, + nested: { + b, + c + }, + e +} = obj, + d = babelHelpers.objectWithoutPropertiesLoose(obj.nested, ["b", "c"]); diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-var-declaration/input.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-var-declaration/input.js new file mode 100644 index 000000000000..099aa76e4907 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-var-declaration/input.js @@ -0,0 +1 @@ +var { a, b, ...c } = obj; diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-var-declaration/output.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-var-declaration/output.js new file mode 100644 index 000000000000..dbacd1a572a3 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-objectRestNoSymbols/rest-var-declaration/output.js @@ -0,0 +1,5 @@ +var { + a, + b +} = obj, + c = babelHelpers.objectWithoutPropertiesLoose(obj, ["a", "b"]); diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/options.json b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/options.json new file mode 100644 index 000000000000..63724ae13025 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/options.json @@ -0,0 +1,9 @@ +{ + "plugins": [ + ["external-helpers", { "helperVersion": "7.100.0"}], + "proposal-object-rest-spread" + ], + "assumptions": { + "pureGetters": true + } +} diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/rest-remove-unused-excluded-keys/exec.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/rest-remove-unused-excluded-keys/exec.js new file mode 100644 index 000000000000..d3ad45c50d79 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/rest-remove-unused-excluded-keys/exec.js @@ -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); diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/rest-remove-unused-excluded-keys/input.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/rest-remove-unused-excluded-keys/input.js new file mode 100644 index 000000000000..848847b6d5af --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/rest-remove-unused-excluded-keys/input.js @@ -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); +} diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/rest-remove-unused-excluded-keys/output.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/rest-remove-unused-excluded-keys/output.js new file mode 100644 index 000000000000..cf80c637a3ef --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/rest-remove-unused-excluded-keys/output.js @@ -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); +} diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/spread-single-call/exec.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/spread-single-call/exec.js new file mode 100644 index 000000000000..16e821917d0e --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/spread-single-call/exec.js @@ -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 }); diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/spread-single-call/input.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/spread-single-call/input.js new file mode 100644 index 000000000000..bfc9f72440df --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/spread-single-call/input.js @@ -0,0 +1 @@ +let obj = { a, ...b, c, ...d, e }; diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/spread-single-call/output.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/spread-single-call/output.js new file mode 100644 index 000000000000..6aae4df0db26 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-pureGetters/spread-single-call/output.js @@ -0,0 +1,7 @@ +let obj = babelHelpers.objectSpread2({ + a +}, b, { + c +}, d, { + e +}); diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/assignment/input.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/assignment/input.js new file mode 100644 index 000000000000..093a8e9f9351 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/assignment/input.js @@ -0,0 +1,7 @@ +var x; +var y; +var z; + +z = { x, ...y }; + +z = { x, w: { ...y } }; diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/assignment/output.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/assignment/output.js new file mode 100644 index 000000000000..8e9602898ed6 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/assignment/output.js @@ -0,0 +1,10 @@ +var x; +var y; +var z; +z = Object.assign({ + x +}, y); +z = { + x, + w: Object.assign({}, y) +}; diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/expression/exec.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/expression/exec.js new file mode 100644 index 000000000000..b57d8d3222b9 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/expression/exec.js @@ -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]); diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/expression/input.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/expression/input.js new file mode 100644 index 000000000000..da4e7b689505 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/expression/input.js @@ -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' } } }); diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/expression/output.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/expression/output.js new file mode 100644 index 000000000000..cfd3f3b62882 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/expression/output.js @@ -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'; + } + +}); diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/no-getOwnPropertyDescriptors/exec.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/no-getOwnPropertyDescriptors/exec.js new file mode 100644 index 000000000000..e4674d812a50 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/no-getOwnPropertyDescriptors/exec.js @@ -0,0 +1,9 @@ +const oldGOPDs = Object.getOwnPropertyDescriptors; +Object.getOwnPropertyDescriptors = null; + +try { + ({ ...{ a: 1 }, b: 1, ...{} }); +} finally { + Object.getOwnPropertyDescriptors = oldGOPDs; +} + diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/no-object-assign-exec/exec.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/no-object-assign-exec/exec.js new file mode 100644 index 000000000000..4d937710e367 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/no-object-assign-exec/exec.js @@ -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); diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/options.json b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/options.json new file mode 100644 index 000000000000..d9e80e039563 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties-with-useBuiltIns/options.json @@ -0,0 +1,9 @@ +{ + "plugins": [ + ["external-helpers", { "helperVersion": "7.100.0"}], + ["proposal-object-rest-spread", { "useBuiltIns": true }] + ], + "assumptions": { + "setSpreadProperties": true + } +} diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/assignment/input.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/assignment/input.js new file mode 100644 index 000000000000..093a8e9f9351 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/assignment/input.js @@ -0,0 +1,7 @@ +var x; +var y; +var z; + +z = { x, ...y }; + +z = { x, w: { ...y } }; diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/assignment/output.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/assignment/output.js new file mode 100644 index 000000000000..3e34747bd999 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/assignment/output.js @@ -0,0 +1,10 @@ +var x; +var y; +var z; +z = babelHelpers.extends({ + x +}, y); +z = { + x, + w: babelHelpers.extends({}, y) +}; diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/expression/exec.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/expression/exec.js new file mode 100644 index 000000000000..b57d8d3222b9 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/expression/exec.js @@ -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]); diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/expression/input.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/expression/input.js new file mode 100644 index 000000000000..da4e7b689505 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/expression/input.js @@ -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' } } }); diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/expression/output.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/expression/output.js new file mode 100644 index 000000000000..a21536eeb3aa --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/expression/output.js @@ -0,0 +1,28 @@ +var a; +var b; +var c; +var d; +var x; +var y; +babelHelpers.extends(babelHelpers.extends(babelHelpers.extends({ + x +}, y), {}, { + a +}, b), {}, { + c +}); +babelHelpers.extends({}, Object.prototype); +babelHelpers.extends({}, { + foo: 'bar' +}); +babelHelpers.extends(babelHelpers.extends({}, { + foo: 'bar' +}), { + bar: 'baz' +}); +babelHelpers.extends({}, { + get foo() { + return 'foo'; + } + +}); diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/no-getOwnPropertyDescriptors/exec.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/no-getOwnPropertyDescriptors/exec.js new file mode 100644 index 000000000000..e4674d812a50 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/no-getOwnPropertyDescriptors/exec.js @@ -0,0 +1,9 @@ +const oldGOPDs = Object.getOwnPropertyDescriptors; +Object.getOwnPropertyDescriptors = null; + +try { + ({ ...{ a: 1 }, b: 1, ...{} }); +} finally { + Object.getOwnPropertyDescriptors = oldGOPDs; +} + diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/no-object-assign-exec/exec.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/no-object-assign-exec/exec.js new file mode 100644 index 000000000000..4d937710e367 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/no-object-assign-exec/exec.js @@ -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); diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/options.json b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/options.json new file mode 100644 index 000000000000..2ac8b8a6e836 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/assumption-setSpreadProperties/options.json @@ -0,0 +1,9 @@ +{ + "plugins": [ + ["external-helpers", { "helperVersion": "7.100.0"}], + "proposal-object-rest-spread" + ], + "assumptions": { + "setSpreadProperties": true + } +} diff --git a/packages/babel-plugin-transform-destructuring/src/index.js b/packages/babel-plugin-transform-destructuring/src/index.js index 56e1789972d8..d42c2bfc7a5d 100644 --- a/packages/babel-plugin-transform-destructuring/src/index.js +++ b/packages/babel-plugin-transform-destructuring/src/index.js @@ -4,15 +4,13 @@ import { types as t } from "@babel/core"; export default declare((api, options) => { api.assertVersion(7); - const { loose = false, useBuiltIns = false } = options; - - if (typeof loose !== "boolean") { - throw new Error(`.loose must be a boolean or undefined`); - } + const { useBuiltIns = false } = options; const iterableIsArray = api.assumption("iterableIsArray") ?? options.loose; const arrayLikeIsIterable = options.allowArrayLike ?? api.assumption("arrayLikeIsIterable"); + const objectRestNoSymbols = + api.assumption("objectRestNoSymbols") ?? options.loose; function getExtendsHelper(file) { return useBuiltIns @@ -233,7 +231,9 @@ export default declare((api, options) => { } value = t.callExpression( - this.addHelper(`objectWithoutProperties${loose ? "Loose" : ""}`), + this.addHelper( + `objectWithoutProperties${objectRestNoSymbols ? "Loose" : ""}`, + ), [t.cloneNode(objRef), keyExpression], ); } diff --git a/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/options.json b/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/options.json new file mode 100644 index 000000000000..d48381a6db92 --- /dev/null +++ b/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/options.json @@ -0,0 +1,9 @@ +{ + "plugins": [ + ["external-helpers", { "helperVersion": "7.100.0"}], + "transform-destructuring" + ], + "assumptions": { + "objectRestNoSymbols": true + } +} diff --git a/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-assignment-expression/input.js b/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-assignment-expression/input.js new file mode 100644 index 000000000000..f03be37640bc --- /dev/null +++ b/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-assignment-expression/input.js @@ -0,0 +1 @@ +({ a, b, ...c } = obj); diff --git a/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-assignment-expression/output.js b/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-assignment-expression/output.js new file mode 100644 index 000000000000..462fb0f56a31 --- /dev/null +++ b/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-assignment-expression/output.js @@ -0,0 +1,5 @@ +var _obj = obj; +a = _obj.a; +b = _obj.b; +c = babelHelpers.objectWithoutPropertiesLoose(_obj, ["a", "b"]); +_obj; diff --git a/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-computed/input.js b/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-computed/input.js new file mode 100644 index 000000000000..8ddf55177eac --- /dev/null +++ b/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-computed/input.js @@ -0,0 +1 @@ +let { [a]: b, ...c } = obj; diff --git a/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-computed/output.js b/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-computed/output.js new file mode 100644 index 000000000000..3e00dda0fa08 --- /dev/null +++ b/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-computed/output.js @@ -0,0 +1,4 @@ +let _obj = obj, + _a = a, + b = _obj[_a], + c = babelHelpers.objectWithoutPropertiesLoose(_obj, [_a].map(babelHelpers.toPropertyKey)); diff --git a/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-ignore-symbols/exec.js b/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-ignore-symbols/exec.js new file mode 100644 index 000000000000..2c85a4c5711e --- /dev/null +++ b/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-ignore-symbols/exec.js @@ -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); diff --git a/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-nested/input.js b/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-nested/input.js new file mode 100644 index 000000000000..e1025ccc4cb9 --- /dev/null +++ b/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-nested/input.js @@ -0,0 +1 @@ +let { a, nested: { b, c, ...d }, e } = obj; diff --git a/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-nested/output.js b/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-nested/output.js new file mode 100644 index 000000000000..db6b37a3420f --- /dev/null +++ b/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-nested/output.js @@ -0,0 +1,7 @@ +let _obj = obj, + a = _obj.a, + _obj$nested = _obj.nested, + b = _obj$nested.b, + c = _obj$nested.c, + d = babelHelpers.objectWithoutPropertiesLoose(_obj$nested, ["b", "c"]), + e = _obj.e; diff --git a/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-var-declaration/input.js b/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-var-declaration/input.js new file mode 100644 index 000000000000..099aa76e4907 --- /dev/null +++ b/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-var-declaration/input.js @@ -0,0 +1 @@ +var { a, b, ...c } = obj; diff --git a/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-var-declaration/output.js b/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-var-declaration/output.js new file mode 100644 index 000000000000..96c7228e6297 --- /dev/null +++ b/packages/babel-plugin-transform-destructuring/test/fixtures/assumption-objectRestNoSymbols/rest-var-declaration/output.js @@ -0,0 +1,4 @@ +var _obj = obj, + a = _obj.a, + b = _obj.b, + c = babelHelpers.objectWithoutPropertiesLoose(_obj, ["a", "b"]);