From 2351a638b58a946e5dd00f379174f10c17d7c7e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Fri, 18 May 2018 00:04:12 +0200 Subject: [PATCH] Fix bugs in the _wrapNativeSuper helper (#7533) * Remove second wrapper from _wrapNativeSuper * Fix tests on node 4 I DON'T KNOW WHY IT WORKS. * Update fixtures * Use Reflect.construct * Parens * Fix things * Fix things * Undo changes * Fix with sham * Typo --- packages/babel-helpers/src/helpers.js | 45 ++++++++++++++----- .../fixtures/extend-builtins/loose/output.js | 6 ++- .../extend-builtins/overwritten-null/exec.js | 9 ++++ .../overwritten-null/options.json | 3 ++ .../fixtures/extend-builtins/spec/output.js | 6 ++- .../extend-builtins/super-called/exec.js | 15 +++++++ .../extend-builtins/super-called/options.json | 3 ++ .../plugins-integration/issue-7527/exec.js | 3 ++ .../plugins-integration/issue-7527/output.js | 6 ++- 9 files changed, 78 insertions(+), 18 deletions(-) create mode 100644 packages/babel-plugin-transform-classes/test/fixtures/extend-builtins/overwritten-null/exec.js create mode 100644 packages/babel-plugin-transform-classes/test/fixtures/extend-builtins/overwritten-null/options.json create mode 100644 packages/babel-plugin-transform-classes/test/fixtures/extend-builtins/super-called/exec.js create mode 100644 packages/babel-plugin-transform-classes/test/fixtures/extend-builtins/super-called/options.json diff --git a/packages/babel-helpers/src/helpers.js b/packages/babel-helpers/src/helpers.js index 7764d1bc3e..647d1e46b2 100644 --- a/packages/babel-helpers/src/helpers.js +++ b/packages/babel-helpers/src/helpers.js @@ -433,14 +433,39 @@ helpers.setPrototypeOf = () => template.program.ast` helpers.construct = () => template.program.ast` import setPrototypeOf from "setPrototypeOf"; + function isNativeReflectConstruct() { + if (typeof Reflect === "undefined" || !Reflect.construct) return false; + + // core-js@3 + if (Reflect.construct.sham) return false; + + // Proxy can't be polyfilled. Every browser implemented + // proxies before or at the same time of Reflect.construct, + // so if they support Proxy they also support Reflect.construct. + if (typeof Proxy === "function") return true; + + // Since Reflect.construct can't be properly polyfilled, some + // implementations (e.g. core-js@2) don't set the correct internal slots. + // Those polyfills don't allow us to subclass built-ins, so we need to + // use our fallback implementation. + try { + // If the internal slots aren't set, this throws an error similar to + // TypeError: this is not a Date object. + Date.prototype.toString.call(Reflect.construct(Date, [], function() {})); + return true; + } catch (e) { + return false; + } + } + export default function _construct(Parent, args, Class) { - if (typeof Reflect !== "undefined" && Reflect.construct) { + if (isNativeReflectConstruct()) { _construct = Reflect.construct; } else { _construct = function _construct(Parent, args, Class) { var a = [null]; a.push.apply(a, args); - var Constructor = Parent.bind.apply(Parent, a); + var Constructor = Function.bind.apply(Parent, a); var instance = new Constructor(); if (Class) setPrototypeOf(instance, Class.prototype); return instance; @@ -462,6 +487,7 @@ helpers.wrapNativeSuper = () => template.program.ast` var _cache = typeof Map === "function" ? new Map() : undefined; _wrapNativeSuper = function _wrapNativeSuper(Class) { + if (Class === null) return null; if (typeof Class !== "function") { throw new TypeError("Super expression must either be null or a function"); } @@ -469,7 +495,9 @@ helpers.wrapNativeSuper = () => template.program.ast` if (_cache.has(Class)) return _cache.get(Class); _cache.set(Class, Wrapper); } - function Wrapper() {} + function Wrapper() { + return _construct(Class, arguments, _gPO(this).constructor) + } Wrapper.prototype = Object.create(Class.prototype, { constructor: { value: Wrapper, @@ -478,15 +506,8 @@ helpers.wrapNativeSuper = () => template.program.ast` configurable: true, } }); - return _sPO( - Wrapper, - _sPO( - function Super() { - return construct(Class, arguments, _gPO(this).constructor); - }, - Class - ) - ); + + return _sPO(Wrapper, Class); } return _wrapNativeSuper(Class) diff --git a/packages/babel-plugin-transform-classes/test/fixtures/extend-builtins/loose/output.js b/packages/babel-plugin-transform-classes/test/fixtures/extend-builtins/loose/output.js index 1f5bfa37fa..d94f44e01c 100644 --- a/packages/babel-plugin-transform-classes/test/fixtures/extend-builtins/loose/output.js +++ b/packages/babel-plugin-transform-classes/test/fixtures/extend-builtins/loose/output.js @@ -1,8 +1,10 @@ function _inheritsLoose(subClass, superClass) { subClass.prototype.__proto__ = superClass && superClass.prototype; subClass.__proto__ = superClass; } -function _wrapNativeSuper(Class) { var _cache = typeof Map === "function" ? new Map() : undefined; _wrapNativeSuper = function _wrapNativeSuper(Class) { if (typeof Class !== "function") { throw new TypeError("Super expression must either be null or a function"); } if (typeof _cache !== "undefined") { if (_cache.has(Class)) return _cache.get(Class); _cache.set(Class, Wrapper); } function Wrapper() {} Wrapper.prototype = Object.create(Class.prototype, { constructor: { value: Wrapper, enumerable: false, writable: true, configurable: true } }); return _setPrototypeOf(Wrapper, _setPrototypeOf(function Super() { return _construct(Class, arguments, _getPrototypeOf(this).constructor); }, Class)); }; return _wrapNativeSuper(Class); } +function _wrapNativeSuper(Class) { var _cache = typeof Map === "function" ? new Map() : undefined; _wrapNativeSuper = function _wrapNativeSuper(Class) { if (Class === null) return null; if (typeof Class !== "function") { throw new TypeError("Super expression must either be null or a function"); } if (typeof _cache !== "undefined") { if (_cache.has(Class)) return _cache.get(Class); _cache.set(Class, Wrapper); } function Wrapper() { return _construct(Class, arguments, _getPrototypeOf(this).constructor); } Wrapper.prototype = Object.create(Class.prototype, { constructor: { value: Wrapper, enumerable: false, writable: true, configurable: true } }); return _setPrototypeOf(Wrapper, Class); }; return _wrapNativeSuper(Class); } -function _construct(Parent, args, Class) { if (typeof Reflect !== "undefined" && Reflect.construct) { _construct = Reflect.construct; } else { _construct = function _construct(Parent, args, Class) { var a = [null]; a.push.apply(a, args); var Constructor = Parent.bind.apply(Parent, a); var instance = new Constructor(); if (Class) _setPrototypeOf(instance, Class.prototype); return instance; }; } return _construct.apply(null, arguments); } +function isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } + +function _construct(Parent, args, Class) { if (isNativeReflectConstruct()) { _construct = Reflect.construct; } else { _construct = function _construct(Parent, args, Class) { var a = [null]; a.push.apply(a, args); var Constructor = Function.bind.apply(Parent, a); var instance = new Constructor(); if (Class) _setPrototypeOf(instance, Class.prototype); return instance; }; } return _construct.apply(null, arguments); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } diff --git a/packages/babel-plugin-transform-classes/test/fixtures/extend-builtins/overwritten-null/exec.js b/packages/babel-plugin-transform-classes/test/fixtures/extend-builtins/overwritten-null/exec.js new file mode 100644 index 0000000000..438158d38a --- /dev/null +++ b/packages/babel-plugin-transform-classes/test/fixtures/extend-builtins/overwritten-null/exec.js @@ -0,0 +1,9 @@ +var env = { + Array: null, +}; + +// Wee need to use "with" to avoid leaking the modified Array to other tests. +with (env) { + class List extends Array {} + expect(List.prototype.__proto__).toBeUndefined(); +} diff --git a/packages/babel-plugin-transform-classes/test/fixtures/extend-builtins/overwritten-null/options.json b/packages/babel-plugin-transform-classes/test/fixtures/extend-builtins/overwritten-null/options.json new file mode 100644 index 0000000000..5d03380e6f --- /dev/null +++ b/packages/babel-plugin-transform-classes/test/fixtures/extend-builtins/overwritten-null/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["transform-classes", "transform-block-scoping"] +} diff --git a/packages/babel-plugin-transform-classes/test/fixtures/extend-builtins/spec/output.js b/packages/babel-plugin-transform-classes/test/fixtures/extend-builtins/spec/output.js index 9f0e6ce53f..b6dc15272a 100644 --- a/packages/babel-plugin-transform-classes/test/fixtures/extend-builtins/spec/output.js +++ b/packages/babel-plugin-transform-classes/test/fixtures/extend-builtins/spec/output.js @@ -6,9 +6,11 @@ function _possibleConstructorReturn(self, call) { if (call && (typeof call === " function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } -function _wrapNativeSuper(Class) { var _cache = typeof Map === "function" ? new Map() : undefined; _wrapNativeSuper = function _wrapNativeSuper(Class) { if (typeof Class !== "function") { throw new TypeError("Super expression must either be null or a function"); } if (typeof _cache !== "undefined") { if (_cache.has(Class)) return _cache.get(Class); _cache.set(Class, Wrapper); } function Wrapper() {} Wrapper.prototype = Object.create(Class.prototype, { constructor: { value: Wrapper, enumerable: false, writable: true, configurable: true } }); return _setPrototypeOf(Wrapper, _setPrototypeOf(function Super() { return _construct(Class, arguments, _getPrototypeOf(this).constructor); }, Class)); }; return _wrapNativeSuper(Class); } +function _wrapNativeSuper(Class) { var _cache = typeof Map === "function" ? new Map() : undefined; _wrapNativeSuper = function _wrapNativeSuper(Class) { if (Class === null) return null; if (typeof Class !== "function") { throw new TypeError("Super expression must either be null or a function"); } if (typeof _cache !== "undefined") { if (_cache.has(Class)) return _cache.get(Class); _cache.set(Class, Wrapper); } function Wrapper() { return _construct(Class, arguments, _getPrototypeOf(this).constructor); } Wrapper.prototype = Object.create(Class.prototype, { constructor: { value: Wrapper, enumerable: false, writable: true, configurable: true } }); return _setPrototypeOf(Wrapper, Class); }; return _wrapNativeSuper(Class); } -function _construct(Parent, args, Class) { if (typeof Reflect !== "undefined" && Reflect.construct) { _construct = Reflect.construct; } else { _construct = function _construct(Parent, args, Class) { var a = [null]; a.push.apply(a, args); var Constructor = Parent.bind.apply(Parent, a); var instance = new Constructor(); if (Class) _setPrototypeOf(instance, Class.prototype); return instance; }; } return _construct.apply(null, arguments); } +function isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } + +function _construct(Parent, args, Class) { if (isNativeReflectConstruct()) { _construct = Reflect.construct; } else { _construct = function _construct(Parent, args, Class) { var a = [null]; a.push.apply(a, args); var Constructor = Function.bind.apply(Parent, a); var instance = new Constructor(); if (Class) _setPrototypeOf(instance, Class.prototype); return instance; }; } return _construct.apply(null, arguments); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } diff --git a/packages/babel-plugin-transform-classes/test/fixtures/extend-builtins/super-called/exec.js b/packages/babel-plugin-transform-classes/test/fixtures/extend-builtins/super-called/exec.js new file mode 100644 index 0000000000..cca67d46c2 --- /dev/null +++ b/packages/babel-plugin-transform-classes/test/fixtures/extend-builtins/super-called/exec.js @@ -0,0 +1,15 @@ +var called = false; + +var env = { + Array: function Array() { + called = true; + } +}; + +// Wee need to use "with" to avoid leaking the modified Array to other tests. +with (env) { + class List extends Array {}; + new List(); + + expect(called).toBe(true); +} diff --git a/packages/babel-plugin-transform-classes/test/fixtures/extend-builtins/super-called/options.json b/packages/babel-plugin-transform-classes/test/fixtures/extend-builtins/super-called/options.json new file mode 100644 index 0000000000..0dfc820234 --- /dev/null +++ b/packages/babel-plugin-transform-classes/test/fixtures/extend-builtins/super-called/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["transform-classes","transform-block-scoping"] +} diff --git a/packages/babel-preset-env/test/fixtures/plugins-integration/issue-7527/exec.js b/packages/babel-preset-env/test/fixtures/plugins-integration/issue-7527/exec.js index 928ee1da87..7150eb2e96 100644 --- a/packages/babel-preset-env/test/fixtures/plugins-integration/issue-7527/exec.js +++ b/packages/babel-preset-env/test/fixtures/plugins-integration/issue-7527/exec.js @@ -4,3 +4,6 @@ class MyDate extends Date { } } let myDate = new MyDate(); + +expect(myDate.toString).toBe(Date.prototype.toString); +expect(typeof myDate.toString()).toBe("string"); diff --git a/packages/babel-preset-env/test/fixtures/plugins-integration/issue-7527/output.js b/packages/babel-preset-env/test/fixtures/plugins-integration/issue-7527/output.js index 288081087c..35073edfe8 100644 --- a/packages/babel-preset-env/test/fixtures/plugins-integration/issue-7527/output.js +++ b/packages/babel-preset-env/test/fixtures/plugins-integration/issue-7527/output.js @@ -10,9 +10,11 @@ function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } -function _wrapNativeSuper(Class) { var _cache = typeof Map === "function" ? new Map() : undefined; _wrapNativeSuper = function _wrapNativeSuper(Class) { if (typeof Class !== "function") { throw new TypeError("Super expression must either be null or a function"); } if (typeof _cache !== "undefined") { if (_cache.has(Class)) return _cache.get(Class); _cache.set(Class, Wrapper); } function Wrapper() {} Wrapper.prototype = Object.create(Class.prototype, { constructor: { value: Wrapper, enumerable: false, writable: true, configurable: true } }); return _setPrototypeOf(Wrapper, _setPrototypeOf(function Super() { return _construct(Class, arguments, _getPrototypeOf(this).constructor); }, Class)); }; return _wrapNativeSuper(Class); } +function _wrapNativeSuper(Class) { var _cache = typeof Map === "function" ? new Map() : undefined; _wrapNativeSuper = function _wrapNativeSuper(Class) { if (Class === null) return null; if (typeof Class !== "function") { throw new TypeError("Super expression must either be null or a function"); } if (typeof _cache !== "undefined") { if (_cache.has(Class)) return _cache.get(Class); _cache.set(Class, Wrapper); } function Wrapper() { return _construct(Class, arguments, _getPrototypeOf(this).constructor); } Wrapper.prototype = Object.create(Class.prototype, { constructor: { value: Wrapper, enumerable: false, writable: true, configurable: true } }); return _setPrototypeOf(Wrapper, Class); }; return _wrapNativeSuper(Class); } -function _construct(Parent, args, Class) { if (typeof Reflect !== "undefined" && Reflect.construct) { _construct = Reflect.construct; } else { _construct = function _construct(Parent, args, Class) { var a = [null]; a.push.apply(a, args); var Constructor = Parent.bind.apply(Parent, a); var instance = new Constructor(); if (Class) _setPrototypeOf(instance, Class.prototype); return instance; }; } return _construct.apply(null, arguments); } +function isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } + +function _construct(Parent, args, Class) { if (isNativeReflectConstruct()) { _construct = Reflect.construct; } else { _construct = function _construct(Parent, args, Class) { var a = [null]; a.push.apply(a, args); var Constructor = Function.bind.apply(Parent, a); var instance = new Constructor(); if (Class) _setPrototypeOf(instance, Class.prototype); return instance; }; } return _construct.apply(null, arguments); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }