From 324a4a1b22c5cd6b624d7689f8ec97a8c36eb165 Mon Sep 17 00:00:00 2001 From: Brian Donovan Date: Tue, 6 Jan 2015 08:00:08 -0800 Subject: [PATCH] Fix super with getters and setters and with class prototypes changing. --- lib/6to5/file.js | 3 +- lib/6to5/transformation/templates/get.js | 23 ++++ .../transformers/es6-classes.js | 120 ++++++++++++++++-- .../accessing-super-class/expected.js | 53 +++++--- .../accessing-super-properties/expected.js | 27 +++- .../calling-super-properties/expected.js | 29 ++++- .../super-function-fallback/expected.js | 27 +++- 7 files changed, 246 insertions(+), 36 deletions(-) create mode 100644 lib/6to5/transformation/templates/get.js diff --git a/lib/6to5/file.js b/lib/6to5/file.js index da5b168fa3..9f73eae828 100644 --- a/lib/6to5/file.js +++ b/lib/6to5/file.js @@ -36,7 +36,8 @@ File.helpers = [ "interop-require-wildcard", "typeof", "exports-wildcard", - "extends" + "extends", + "get" ]; File.excludeHelpersFromRuntime = [ diff --git a/lib/6to5/transformation/templates/get.js b/lib/6to5/transformation/templates/get.js new file mode 100644 index 0000000000..472d5b7481 --- /dev/null +++ b/lib/6to5/transformation/templates/get.js @@ -0,0 +1,23 @@ +(function get(object, property, receiver) { + var desc = Object.getOwnPropertyDescriptor(object, property); + + if (desc === void 0) { + var parent = Object.getPrototypeOf(object); + + if (parent === null) { + return void 0; + } else { + return get(parent); + } + } else if ("value" in desc && "writable" in desc) { + return desc.value; + } else { + var getter = desc.get; + + if (getter === void 0) { + return void 0; + } + + return getter.call(receiver); + } +}); diff --git a/lib/6to5/transformation/transformers/es6-classes.js b/lib/6to5/transformation/transformers/es6-classes.js index f0922ebb8b..ca56cc687d 100644 --- a/lib/6to5/transformation/transformers/es6-classes.js +++ b/lib/6to5/transformation/transformers/es6-classes.js @@ -218,7 +218,7 @@ Class.prototype.pushMethod = function (node) { * Given a `methodNode`, produce a `MemberExpression` super class reference. * * @param {Node} methodNode MethodDefinition - * @param {Node} node Identifier + * @param {Node} id Identifier * @param {Node} parent * * @returns {Node} @@ -227,26 +227,48 @@ Class.prototype.pushMethod = function (node) { Class.prototype.superIdentifier = function (methodNode, id, parent) { var methodName = methodNode.key; var superName = this.superName || t.identifier("Function"); + var className = this.className; if (parent.property === id) { return; } else if (t.isCallExpression(parent, { callee: id })) { - // super(); -> ClassName.prototype.MethodName.call(this); + // super(); -> _get(Object.getPrototypeOf(ClassName.prototype), "MethodName", this).call(this); + var superProto = t.callExpression( + t.memberExpression( + t.identifier("Object"), + t.identifier("getPrototypeOf"), + false + ), + [ + methodNode.static ? + className : + t.memberExpression(className, t.identifier("prototype")) + ] + ); + var superCallee = t.callExpression( + this.file.addHelper("get"), + [ + superProto, + methodNode.computed ? methodName : t.literal(methodName.name), + t.thisExpression() + ] + ); + parent.arguments.unshift(t.thisExpression()); if (methodName.name === "constructor") { // constructor() { super(); } - return t.memberExpression(superName, t.identifier("call")); + return t.memberExpression(superCallee, t.identifier("call")); } else { id = superName; // foo() { super(); } - if (!methodNode.static) { - id = t.memberExpression(id, t.identifier("prototype")); - } + //if (!methodNode.static) { + // id = t.memberExpression(id, t.identifier("prototype")); + //} - id = t.memberExpression(id, methodName, methodNode.computed); - return t.memberExpression(id, t.identifier("call")); + //id = t.memberExpression(id, methodName, methodNode.computed); + return t.memberExpression(superCallee, t.identifier("call")); } } else if (t.isMemberExpression(parent) && !methodNode.static) { // super.test -> ClassName.prototype.test @@ -268,21 +290,93 @@ Class.prototype.replaceInstanceSuperReferences = function (methodNode) { traverse(method, { enter: function (node, parent) { + var property; + var computed; + var args; + if (t.isIdentifier(node, { name: "super" })) { return self.superIdentifier(methodNode, node, parent); } else if (t.isCallExpression(node)) { var callee = node.callee; - if (!t.isMemberExpression(callee)) return; - if (callee.object.name !== "super") return; + if (t.isIdentifier(callee) && callee.name === "super") { + // super(); -> _get(Object.getPrototypeOf(ClassName), "MethodName", this).call(this); + property = methodNode.key; + computed = methodNode.computed; + args = node.arguments; + } else { + if (!t.isMemberExpression(callee)) return; + if (callee.object.name !== "super") return; - // super.test(); -> ClassName.prototype.MethodName.call(this); - t.appendToMemberExpression(callee, t.identifier("call")); - node.arguments.unshift(t.thisExpression()); + // super.test(); -> _get(Object.getPrototypeOf(ClassName.prototype), "test", this).call(this); + property = callee.property; + computed = callee.computed; + args = node.arguments; + } + } else if (t.isMemberExpression(node)) { + if (!t.isIdentifier(node.object, { name: "super" })) return; + + // super.name; -> _get(Object.getPrototypeOf(ClassName.prototype), "name", this); + property = node.property; + computed = node.computed; + } + + if (property) { + var superProperty = self.superProperty(property, methodNode.static, computed); + if (args) { + return t.callExpression( + t.memberExpression(superProperty, t.identifier('call'), false), + [t.thisExpression()].concat(args) + ); + } else { + return superProperty; + } } } }); }; +/** + * Gets a node representing the super class value of the named property. + * + * @example + * + * _get(Object.getPrototypeOf(CLASS.prototype), "METHOD", this) + * + * @param {Node} property + * @param {boolean} isStatic + * @param {boolean} isComputed + * + * @returns {Node} + */ + +Class.prototype.superProperty = function (property, isStatic, isComputed) { + return t.callExpression( + this.file.addHelper('get'), + [ + t.callExpression( + t.memberExpression( + t.identifier('Object'), + t.identifier('getPrototypeOf'), + false + ), + [ + isStatic ? + this.className : + t.memberExpression( + this.className, + t.identifier('prototype'), + false + ) + ] + ), + isComputed ? + property : + t.literal(property.name), + t.thisExpression() + ] + ); +}; + /** * Replace the constructor body of our class. * diff --git a/test/fixtures/transformation/es6-classes/accessing-super-class/expected.js b/test/fixtures/transformation/es6-classes/accessing-super-class/expected.js index 1600d655fb..b7ba571c53 100644 --- a/test/fixtures/transformation/es6-classes/accessing-super-class/expected.js +++ b/test/fixtures/transformation/es6-classes/accessing-super-class/expected.js @@ -1,6 +1,28 @@ "use strict"; var _slice = Array.prototype.slice; +var _get = function get(object, property, receiver) { + var desc = Object.getOwnPropertyDescriptor(object, property); + + if (desc === void 0) { + var parent = Object.getPrototypeOf(object); + + if (parent === null) { + return void 0; + } else { + return get(parent); + } + } else if ("value" in desc && "writable" in desc) { + return desc.value; + } else { + var getter = desc.get; + if (getter === void 0) { + return void 0; + } + return getter.call(receiver); + } +}; + var _inherits = function (child, parent) { if (typeof parent !== "function" && parent !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof parent); @@ -19,34 +41,35 @@ var _inherits = function (child, parent) { var Test = (function () { var _Foo = Foo; var Test = function Test() { - var _Foo$prototype$test, _Foo$prototype$test2; + var _get2, _get3, _get4, _get5; woops["super"].test(); - _Foo.call(this); - _Foo.prototype.test.call(this); + _get(Object.getPrototypeOf(Test.prototype), "constructor", this).call(this); + _get(Object.getPrototypeOf(Test.prototype), "test", this).call(this); foob(_Foo); - _Foo.call.apply(_Foo, [this].concat(_slice.call(arguments))); - _Foo.call.apply(_Foo, [this, "test"].concat(_slice.call(arguments))); + (_get2 = _get(Object.getPrototypeOf(Test.prototype), "constructor", this)).call.apply(_get2, [this].concat(_slice.call(arguments))); + (_get3 = _get(Object.getPrototypeOf(Test.prototype), "constructor", this)).call.apply(_get3, [this, "test"].concat(_slice.call(arguments))); - (_Foo$prototype$test = _Foo.prototype.test).call.apply(_Foo$prototype$test, [this].concat(_slice.call(arguments))); - (_Foo$prototype$test2 = _Foo.prototype.test).call.apply(_Foo$prototype$test2, [this, "test"].concat(_slice.call(arguments))); + (_get4 = _get(Object.getPrototypeOf(Test.prototype), "test", this)).call.apply(_get4, [this].concat(_slice.call(arguments))); + (_get5 = _get(Object.getPrototypeOf(Test.prototype), "test", this)).call.apply(_get5, [this, "test"].concat(_slice.call(arguments))); }; _inherits(Test, _Foo); Test.prototype.test = function () { - var _Foo$prototype$test3, _Foo$prototype$test4; - _Foo.prototype.test.call(this); - (_Foo$prototype$test3 = _Foo.prototype.test).call.apply(_Foo$prototype$test3, [this].concat(_slice.call(arguments))); - (_Foo$prototype$test4 = _Foo.prototype.test).call.apply(_Foo$prototype$test4, [this, "test"].concat(_slice.call(arguments))); + var _get6, _get7; + _get(Object.getPrototypeOf(Test.prototype), "test", this).call(this); + (_get6 = _get(Object.getPrototypeOf(Test.prototype), "test", this)).call.apply(_get6, [this].concat(_slice.call(arguments))); + (_get7 = _get(Object.getPrototypeOf(Test.prototype), "test", this)).call.apply(_get7, [this, "test"].concat(_slice.call(arguments))); }; Test.foo = function () { - var _Foo$foo, _Foo$foo2; - _Foo.foo.call(this); - (_Foo$foo = _Foo.foo).call.apply(_Foo$foo, [this].concat(_slice.call(arguments))); - (_Foo$foo2 = _Foo.foo).call.apply(_Foo$foo2, [this, "test"].concat(_slice.call(arguments))); + var _get8, _get9; + _get(Object.getPrototypeOf(Test), "foo", this).call(this); + (_get8 = _get(Object.getPrototypeOf(Test), "foo", this)).call.apply(_get8, [this].concat(_slice.call(arguments))); + (_get9 = _get(Object.getPrototypeOf(Test), "foo", this)).call.apply(_get9, [this, "test"].concat(_slice.call(arguments))); }; return Test; })(); + diff --git a/test/fixtures/transformation/es6-classes/accessing-super-properties/expected.js b/test/fixtures/transformation/es6-classes/accessing-super-properties/expected.js index 29526be129..280ae65b77 100644 --- a/test/fixtures/transformation/es6-classes/accessing-super-properties/expected.js +++ b/test/fixtures/transformation/es6-classes/accessing-super-properties/expected.js @@ -1,5 +1,27 @@ "use strict"; +var _get = function get(object, property, receiver) { + var desc = Object.getOwnPropertyDescriptor(object, property); + + if (desc === void 0) { + var parent = Object.getPrototypeOf(object); + + if (parent === null) { + return void 0; + } else { + return get(parent); + } + } else if ("value" in desc && "writable" in desc) { + return desc.value; + } else { + var getter = desc.get; + if (getter === void 0) { + return void 0; + } + return getter.call(receiver); + } +}; + var _inherits = function (child, parent) { if (typeof parent !== "function" && parent !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof parent); @@ -18,11 +40,12 @@ var _inherits = function (child, parent) { var Test = (function () { var _Foo = Foo; var Test = function Test() { - _Foo.prototype.test; - _Foo.prototype.test.whatever; + _get(Object.getPrototypeOf(Test.prototype), "test", this); + _get(Object.getPrototypeOf(Test.prototype), "test", this).whatever; }; _inherits(Test, _Foo); return Test; })(); + diff --git a/test/fixtures/transformation/es6-classes/calling-super-properties/expected.js b/test/fixtures/transformation/es6-classes/calling-super-properties/expected.js index 7e8f2bfd43..172db52398 100644 --- a/test/fixtures/transformation/es6-classes/calling-super-properties/expected.js +++ b/test/fixtures/transformation/es6-classes/calling-super-properties/expected.js @@ -1,5 +1,27 @@ "use strict"; +var _get = function get(object, property, receiver) { + var desc = Object.getOwnPropertyDescriptor(object, property); + + if (desc === void 0) { + var parent = Object.getPrototypeOf(object); + + if (parent === null) { + return void 0; + } else { + return get(parent); + } + } else if ("value" in desc && "writable" in desc) { + return desc.value; + } else { + var getter = desc.get; + if (getter === void 0) { + return void 0; + } + return getter.call(receiver); + } +}; + var _inherits = function (child, parent) { if (typeof parent !== "function" && parent !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof parent); @@ -18,15 +40,16 @@ var _inherits = function (child, parent) { var Test = (function () { var _Foo = Foo; var Test = function Test() { - _Foo.prototype.test.whatever(); - _Foo.prototype.test.call(this); + _get(Object.getPrototypeOf(Test.prototype), "test", this).whatever(); + _get(Object.getPrototypeOf(Test.prototype), "test", this).call(this); }; _inherits(Test, _Foo); Test.test = function () { - return _Foo.wow.call(this); + return _get(Object.getPrototypeOf(Test), "wow", this).call(this); }; return Test; })(); + diff --git a/test/fixtures/transformation/es6-classes/super-function-fallback/expected.js b/test/fixtures/transformation/es6-classes/super-function-fallback/expected.js index e17e74db06..390f5a4d4a 100644 --- a/test/fixtures/transformation/es6-classes/super-function-fallback/expected.js +++ b/test/fixtures/transformation/es6-classes/super-function-fallback/expected.js @@ -1,5 +1,28 @@ "use strict"; -var Test = function Test() { - Function.prototype.hasOwnProperty.call(this, "test"); +var _get = function get(object, property, receiver) { + var desc = Object.getOwnPropertyDescriptor(object, property); + + if (desc === void 0) { + var parent = Object.getPrototypeOf(object); + + if (parent === null) { + return void 0; + } else { + return get(parent); + } + } else if ("value" in desc && "writable" in desc) { + return desc.value; + } else { + var getter = desc.get; + if (getter === void 0) { + return void 0; + } + return getter.call(receiver); + } }; + +var Test = function Test() { + _get(Object.getPrototypeOf(Test.prototype), "hasOwnProperty", this).call(this, "test"); +}; +