From cba64f9a09c2aebea65b587b31864758f3726d66 Mon Sep 17 00:00:00 2001 From: Zen <843968788@qq.com> Date: Tue, 12 Jan 2021 09:31:06 +0800 Subject: [PATCH] Correctly access shadowed class binding in `super.*` (#12544) * rename own binding inside methods if it collides with class ref. fix #11994 * fix name collisions in constructor * do fix name collisions in constructor * move logic in ReplaceSupers * fix tests of helper-create-class-features-plugin * remove replaceSupers in pushConstructor * use environmentVisitor * skip classLike nodes * fix super ref in computed key --- .../src/decorators.js | 1 + .../src/fields.js | 1 + .../fixtures/replace-supers/method/input.js | 6 ++ .../replace-supers/method/options.json | 3 + .../fixtures/replace-supers/method/output.js | 22 +++++++ .../babel-helper-replace-supers/src/index.js | 23 +++++++ .../src/transformClass.js | 6 +- .../input.js | 11 ++++ .../output.js | 34 ++++++++++ .../input.js | 16 +++++ .../output.js | 42 ++++++++++++ .../input.js | 27 ++++++++ .../output.js | 66 +++++++++++++++++++ 13 files changed, 253 insertions(+), 5 deletions(-) create mode 100644 packages/babel-helper-create-class-features-plugin/test/fixtures/replace-supers/method/input.js create mode 100644 packages/babel-helper-create-class-features-plugin/test/fixtures/replace-supers/method/options.json create mode 100644 packages/babel-helper-create-class-features-plugin/test/fixtures/replace-supers/method/output.js create mode 100644 packages/babel-plugin-transform-classes/test/fixtures/spec/name-collisions-with-class-ref-in-computed-key/input.js create mode 100644 packages/babel-plugin-transform-classes/test/fixtures/spec/name-collisions-with-class-ref-in-computed-key/output.js create mode 100644 packages/babel-plugin-transform-classes/test/fixtures/spec/name-collisions-with-class-ref-in-constructor/input.js create mode 100644 packages/babel-plugin-transform-classes/test/fixtures/spec/name-collisions-with-class-ref-in-constructor/output.js create mode 100644 packages/babel-plugin-transform-classes/test/fixtures/spec/name-collisions-with-class-ref-in-method/input.js create mode 100644 packages/babel-plugin-transform-classes/test/fixtures/spec/name-collisions-with-class-ref-in-method/output.js diff --git a/packages/babel-helper-create-class-features-plugin/src/decorators.js b/packages/babel-helper-create-class-features-plugin/src/decorators.js index b4fbcae13f..b5e90ad6e7 100644 --- a/packages/babel-helper-create-class-features-plugin/src/decorators.js +++ b/packages/babel-helper-create-class-features-plugin/src/decorators.js @@ -68,6 +68,7 @@ function extractElementDescriptor(/* this: File, */ classRef, superRef, path) { superRef, scope, file: this, + refToPreserve: classRef, }, true, ).replace(); diff --git a/packages/babel-helper-create-class-features-plugin/src/fields.js b/packages/babel-helper-create-class-features-plugin/src/fields.js index 6bb644f6d2..4665236de7 100644 --- a/packages/babel-helper-create-class-features-plugin/src/fields.js +++ b/packages/babel-helper-create-class-features-plugin/src/fields.js @@ -633,6 +633,7 @@ function replaceThisContext(path, ref, superRef, file, loose) { isLoose: loose, superRef, file, + refToPreserve: ref, getObjectRef() { state.needsClassRef = true; return path.node.static diff --git a/packages/babel-helper-create-class-features-plugin/test/fixtures/replace-supers/method/input.js b/packages/babel-helper-create-class-features-plugin/test/fixtures/replace-supers/method/input.js new file mode 100644 index 0000000000..55c47795be --- /dev/null +++ b/packages/babel-helper-create-class-features-plugin/test/fixtures/replace-supers/method/input.js @@ -0,0 +1,6 @@ +class A extends B { + #foo() { + let A; + super.x; + } +} \ No newline at end of file diff --git a/packages/babel-helper-create-class-features-plugin/test/fixtures/replace-supers/method/options.json b/packages/babel-helper-create-class-features-plugin/test/fixtures/replace-supers/method/options.json new file mode 100644 index 0000000000..08d63f37cf --- /dev/null +++ b/packages/babel-helper-create-class-features-plugin/test/fixtures/replace-supers/method/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["proposal-private-methods"] +} diff --git a/packages/babel-helper-create-class-features-plugin/test/fixtures/replace-supers/method/output.js b/packages/babel-helper-create-class-features-plugin/test/fixtures/replace-supers/method/output.js new file mode 100644 index 0000000000..cfdeda66e5 --- /dev/null +++ b/packages/babel-helper-create-class-features-plugin/test/fixtures/replace-supers/method/output.js @@ -0,0 +1,22 @@ +function _get(target, property, receiver) { if (typeof Reflect !== "undefined" && Reflect.get) { _get = Reflect.get; } else { _get = function _get(target, property, receiver) { var base = _superPropBase(target, property); if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property); if (desc.get) { return desc.get.call(receiver); } return desc.value; }; } return _get(target, property, receiver || target); } + +function _superPropBase(object, property) { while (!Object.prototype.hasOwnProperty.call(object, property)) { object = _getPrototypeOf(object); if (object === null) break; } return object; } + +function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } + +var _foo = new WeakSet(); + +class A extends B { + constructor(...args) { + super(...args); + + _foo.add(this); + } + +} + +var _foo2 = function _foo2() { + let _A; + + _get(_getPrototypeOf(A.prototype), "x", this); +}; diff --git a/packages/babel-helper-replace-supers/src/index.js b/packages/babel-helper-replace-supers/src/index.js index 969fd2b59b..248d44f963 100644 --- a/packages/babel-helper-replace-supers/src/index.js +++ b/packages/babel-helper-replace-supers/src/index.js @@ -76,6 +76,19 @@ const visitor = traverse.visitors.merge([ }, ]); +const unshadowSuperBindingVisitor = traverse.visitors.merge([ + environmentVisitor, + { + Scopable(path, { refName }) { + // https://github.com/Zzzen/babel/pull/1#pullrequestreview-564833183 + const binding = path.scope.getOwnBinding(refName); + if (binding && binding.identifier.name === refName) { + path.scope.rename(refName); + } + }, + }, +]); + const specHandlers = { memoise(superMember, count) { const { scope, node } = superMember; @@ -244,6 +257,9 @@ type ReplaceSupersOptionsBase = {| superRef: Object, isLoose: boolean, file: any, + // objectRef might have been shadowed in child scopes, + // in that case, we need to rename related variables. + refToPreserve?: BabelNodeIdentifier, |}; type ReplaceSupersOptions = @@ -286,6 +302,13 @@ export default class ReplaceSupers { } replace() { + // https://github.com/babel/babel/issues/11994 + if (this.opts.refToPreserve) { + this.methodPath.traverse(unshadowSuperBindingVisitor, { + refName: this.opts.refToPreserve.name, + }); + } + const handler = this.isLoose ? looseHandlers : specHandlers; memberExpressionToFunctions(this.methodPath, visitor, { diff --git a/packages/babel-plugin-transform-classes/src/transformClass.js b/packages/babel-plugin-transform-classes/src/transformClass.js index 5b8f5f5171..133fdd3ba1 100644 --- a/packages/babel-plugin-transform-classes/src/transformClass.js +++ b/packages/babel-plugin-transform-classes/src/transformClass.js @@ -174,6 +174,7 @@ export default function transformClass( superRef: classState.superName, isLoose: classState.isLoose, file: classState.file, + refToPreserve: classState.classRef, }); replaceSupers.replace(); @@ -496,11 +497,6 @@ export default function transformClass( method: { type: "ClassMethod" }, path: NodePath, ) { - // https://github.com/babel/babel/issues/1077 - if (path.scope.hasOwnBinding(classState.classRef.name)) { - path.scope.rename(classState.classRef.name); - } - setState({ userConstructorPath: path, userConstructor: method, diff --git a/packages/babel-plugin-transform-classes/test/fixtures/spec/name-collisions-with-class-ref-in-computed-key/input.js b/packages/babel-plugin-transform-classes/test/fixtures/spec/name-collisions-with-class-ref-in-computed-key/input.js new file mode 100644 index 0000000000..6db51ad876 --- /dev/null +++ b/packages/babel-plugin-transform-classes/test/fixtures/spec/name-collisions-with-class-ref-in-computed-key/input.js @@ -0,0 +1,11 @@ +class Foo extends Bar { + constructor() { + super(); + class X { + [(() => { + let Foo; + super.method(); + })()]() {} + } + } +} \ No newline at end of file diff --git a/packages/babel-plugin-transform-classes/test/fixtures/spec/name-collisions-with-class-ref-in-computed-key/output.js b/packages/babel-plugin-transform-classes/test/fixtures/spec/name-collisions-with-class-ref-in-computed-key/output.js new file mode 100644 index 0000000000..71b59391fd --- /dev/null +++ b/packages/babel-plugin-transform-classes/test/fixtures/spec/name-collisions-with-class-ref-in-computed-key/output.js @@ -0,0 +1,34 @@ +var Foo = /*#__PURE__*/function (_Bar) { + "use strict"; + + babelHelpers.inherits(Foo, _Bar); + + var _super = babelHelpers.createSuper(Foo); + + function Foo() { + var _thisSuper, _this; + + babelHelpers.classCallCheck(this, Foo); + _this = _super.call(this); + + var X = /*#__PURE__*/function () { + function X() { + babelHelpers.classCallCheck(this, X); + } + + babelHelpers.createClass(X, [{ + key: (() => { + var _Foo; + + babelHelpers.get((_thisSuper = babelHelpers.assertThisInitialized(_this), babelHelpers.getPrototypeOf(Foo.prototype)), "method", _thisSuper).call(_thisSuper); + })(), + value: function value() {} + }]); + return X; + }(); + + return _this; + } + + return Foo; +}(Bar); diff --git a/packages/babel-plugin-transform-classes/test/fixtures/spec/name-collisions-with-class-ref-in-constructor/input.js b/packages/babel-plugin-transform-classes/test/fixtures/spec/name-collisions-with-class-ref-in-constructor/input.js new file mode 100644 index 0000000000..1c9bc9d8fb --- /dev/null +++ b/packages/babel-plugin-transform-classes/test/fixtures/spec/name-collisions-with-class-ref-in-constructor/input.js @@ -0,0 +1,16 @@ +class Base { + method() {} +} + +class Foo extends Base { + constructor() { + super(); + + if (true) { + let Foo; + super.method(); + } + } + + method() { } +} \ No newline at end of file diff --git a/packages/babel-plugin-transform-classes/test/fixtures/spec/name-collisions-with-class-ref-in-constructor/output.js b/packages/babel-plugin-transform-classes/test/fixtures/spec/name-collisions-with-class-ref-in-constructor/output.js new file mode 100644 index 0000000000..8cae786109 --- /dev/null +++ b/packages/babel-plugin-transform-classes/test/fixtures/spec/name-collisions-with-class-ref-in-constructor/output.js @@ -0,0 +1,42 @@ +var Base = /*#__PURE__*/function () { + "use strict"; + + function Base() { + babelHelpers.classCallCheck(this, Base); + } + + babelHelpers.createClass(Base, [{ + key: "method", + value: function method() {} + }]); + return Base; +}(); + +var Foo = /*#__PURE__*/function (_Base) { + "use strict"; + + babelHelpers.inherits(Foo, _Base); + + var _super = babelHelpers.createSuper(Foo); + + function Foo() { + var _thisSuper, _this; + + babelHelpers.classCallCheck(this, Foo); + _this = _super.call(this); + + if (true) { + var _Foo2; + + babelHelpers.get((_thisSuper = babelHelpers.assertThisInitialized(_this), babelHelpers.getPrototypeOf(Foo.prototype)), "method", _thisSuper).call(_thisSuper); + } + + return _this; + } + + babelHelpers.createClass(Foo, [{ + key: "method", + value: function method() {} + }]); + return Foo; +}(Base); diff --git a/packages/babel-plugin-transform-classes/test/fixtures/spec/name-collisions-with-class-ref-in-method/input.js b/packages/babel-plugin-transform-classes/test/fixtures/spec/name-collisions-with-class-ref-in-method/input.js new file mode 100644 index 0000000000..95acf401ca --- /dev/null +++ b/packages/babel-plugin-transform-classes/test/fixtures/spec/name-collisions-with-class-ref-in-method/input.js @@ -0,0 +1,27 @@ +class Foo { + method(Foo) { + return super.method(Foo); + } +} + +class Bar { + method() { + return () => { + let Bar; + return super.method(Bar); + }; + } +} + +class Baz { + method() { + class Baz { + f() { + let Baz = 1; + return Baz; + } + } + + return super.method(Baz) + } +} \ No newline at end of file diff --git a/packages/babel-plugin-transform-classes/test/fixtures/spec/name-collisions-with-class-ref-in-method/output.js b/packages/babel-plugin-transform-classes/test/fixtures/spec/name-collisions-with-class-ref-in-method/output.js new file mode 100644 index 0000000000..378c7d7193 --- /dev/null +++ b/packages/babel-plugin-transform-classes/test/fixtures/spec/name-collisions-with-class-ref-in-method/output.js @@ -0,0 +1,66 @@ +var Foo = /*#__PURE__*/function () { + "use strict"; + + function Foo() { + babelHelpers.classCallCheck(this, Foo); + } + + babelHelpers.createClass(Foo, [{ + key: "method", + value: function method(_Foo) { + return babelHelpers.get(babelHelpers.getPrototypeOf(Foo.prototype), "method", this).call(this, _Foo); + } + }]); + return Foo; +}(); + +var Bar = /*#__PURE__*/function () { + "use strict"; + + function Bar() { + babelHelpers.classCallCheck(this, Bar); + } + + babelHelpers.createClass(Bar, [{ + key: "method", + value: function method() { + return () => { + var _Bar; + + return babelHelpers.get(babelHelpers.getPrototypeOf(Bar.prototype), "method", this).call(this, _Bar); + }; + } + }]); + return Bar; +}(); + +var Baz = /*#__PURE__*/function () { + "use strict"; + + function Baz() { + babelHelpers.classCallCheck(this, Baz); + } + + babelHelpers.createClass(Baz, [{ + key: "method", + value: function method() { + var _Baz = /*#__PURE__*/function () { + function _Baz() { + babelHelpers.classCallCheck(this, _Baz); + } + + babelHelpers.createClass(_Baz, [{ + key: "f", + value: function f() { + var Baz = 1; + return Baz; + } + }]); + return _Baz; + }(); + + return babelHelpers.get(babelHelpers.getPrototypeOf(Baz.prototype), "method", this).call(this, _Baz); + } + }]); + return Baz; +}();