From c0af67eca1e7592a6c3647af51aae22495e40bae Mon Sep 17 00:00:00 2001 From: Sebastian McKenzie Date: Sun, 8 Feb 2015 02:01:17 +1100 Subject: [PATCH] add support for super in object literals - fixes #411 --- .../transformation/helpers/replace-supers.js | 45 ++++++++++++------- .../transformers/es6/classes.js | 3 +- .../transformers/es6/object-super.js | 31 +++++++++++++ lib/6to5/transformation/transformers/index.js | 1 + .../transformation/es6-object-super/simple.js | 15 +++++++ 5 files changed, 78 insertions(+), 17 deletions(-) create mode 100644 lib/6to5/transformation/transformers/es6/object-super.js create mode 100644 test/fixtures/transformation/es6-object-super/simple.js diff --git a/lib/6to5/transformation/helpers/replace-supers.js b/lib/6to5/transformation/helpers/replace-supers.js index abb7600233..ef4b4a250e 100644 --- a/lib/6to5/transformation/helpers/replace-supers.js +++ b/lib/6to5/transformation/helpers/replace-supers.js @@ -2,19 +2,24 @@ module.exports = ReplaceSupers; -var t = require("../../types"); +var util = require("../../util"); +var t = require("../../types"); /** * Description * * @param {Object} opts + * @param {Boolean} [inClass] */ -function ReplaceSupers(opts) { - this.topLevelThisReference = null; +function ReplaceSupers(opts, inClass) { + this.topLevelThisReference = opts.topLevelThisReference; this.methodNode = opts.methodNode; this.className = opts.className; this.superName = opts.superName; + this.isStatic = opts.isStatic; + this.hasSuper = false; + this.inClass = inClass; this.isLoose = opts.isLoose; this.scope = opts.scope; this.file = opts.file; @@ -28,19 +33,21 @@ function ReplaceSupers(opts) { * _set(Object.getPrototypeOf(CLASS.prototype), "METHOD", "VALUE", this) * * @param {Node} property - * @param {boolean} isStatic - * @param {boolean} isComputed + * @param {Node} value + * @param {Boolean} isComputed + * @param {Node} thisExpression * * @returns {Node} */ -ReplaceSupers.prototype.setSuperProperty = function (property, value, isStatic, isComputed, thisExpression) { + +ReplaceSupers.prototype.setSuperProperty = function (property, value, isComputed, thisExpression) { return t.callExpression( this.file.addHelper("set"), [ t.callExpression( t.memberExpression(t.identifier("Object"), t.identifier("getPrototypeOf")), [ - isStatic ? this.className : t.memberExpression(this.className, t.identifier("prototype")) + this.isStatic ? this.className : t.memberExpression(this.className, t.identifier("prototype")) ] ), isComputed ? property : t.literal(property.name), @@ -58,20 +65,20 @@ ReplaceSupers.prototype.setSuperProperty = function (property, value, isStatic, * _get(Object.getPrototypeOf(CLASS.prototype), "METHOD", this) * * @param {Node} property - * @param {boolean} isStatic - * @param {boolean} isComputed + * @param {Boolean} isComputed + * @param {Node} thisExpression * * @returns {Node} */ -ReplaceSupers.prototype.getSuperProperty = function (property, isStatic, isComputed, thisExpression) { +ReplaceSupers.prototype.getSuperProperty = function (property, isComputed, thisExpression) { return t.callExpression( this.file.addHelper("get"), [ t.callExpression( t.memberExpression(t.identifier("Object"), t.identifier("getPrototypeOf")), [ - isStatic ? this.className : t.memberExpression(this.className, t.identifier("prototype")) + this.isStatic ? this.className : t.memberExpression(this.className, t.identifier("prototype")) ] ), isComputed ? property : t.literal(property.name), @@ -153,7 +160,8 @@ ReplaceSupers.prototype.getThisReference = function () { * @returns {Object} */ -ReplaceSupers.prototype.getLooseSuperProperty = function (methodNode, id, parent) { +ReplaceSupers.prototype.getLooseSuperProperty = function (id, parent) { + var methodNode = this.methodNode; var methodName = methodNode.key; var superName = this.superName || t.identifier("Function"); @@ -195,13 +203,15 @@ ReplaceSupers.prototype.getLooseSuperProperty = function (methodNode, id, parent ReplaceSupers.prototype.looseHandle = function (getThisReference, node, parent) { if (t.isIdentifier(node, { name: "super" })) { - return this.getLooseSuperProperty(this.methodNode, node, parent); + this.hasSuper = true; + return this.getLooseSuperProperty(node, parent); } else if (t.isCallExpression(node)) { var callee = node.callee; if (!t.isMemberExpression(callee)) return; if (callee.object.name !== "super") return; // super.test(); -> ClassName.prototype.MethodName.call(this); + this.hasSuper = true; t.appendToMemberExpression(callee, t.identifier("call")); node.arguments.unshift(getThisReference()); } @@ -237,7 +247,7 @@ ReplaceSupers.prototype.specHandle = function (getThisReference, node, parent) { // bare `super` call is illegal inside non-constructors // - https://esdiscuss.org/topic/super-call-in-methods // - https://twitter.com/wycats/status/544553184396836864 - if (methodNode.key.name !== "constructor") { + if (methodNode.key.name !== "constructor" || !this.inClass) { var methodName = methodNode.key.name || "METHOD_NAME"; throw this.file.errorWithNode(node, "Direct super call is illegal in non-constructor, use super." + methodName + "() instead"); } @@ -253,13 +263,16 @@ ReplaceSupers.prototype.specHandle = function (getThisReference, node, parent) { computed = node.computed; } else if (t.isAssignmentExpression(node) && isSuper(node.left.object, node.left) && methodNode.kind === "set") { // super.name = "val"; -> _set(Object.getPrototypeOf(ClassName.prototype), "name", this); - return this.setSuperProperty(node.left.property, node.right, methodNode.static, node.left.computed, getThisReference()); + this.hasSuper = true; + return this.setSuperProperty(node.left.property, node.right, node.left.computed, getThisReference()); } if (!property) return; + this.hasSuper = true; + thisReference = getThisReference(); - var superProperty = this.getSuperProperty(property, methodNode.static, computed, thisReference); + var superProperty = this.getSuperProperty(property, computed, thisReference); if (args) { if (args.length === 1 && t.isSpreadElement(args[0])) { // super(...arguments); diff --git a/lib/6to5/transformation/transformers/es6/classes.js b/lib/6to5/transformation/transformers/es6/classes.js index 9372e17c06..22fc26ff84 100644 --- a/lib/6to5/transformation/transformers/es6/classes.js +++ b/lib/6to5/transformation/transformers/es6/classes.js @@ -152,10 +152,11 @@ Class.prototype.buildBody = function () { methodNode: node, className: this.className, superName: this.superName, + isStatic: node.static, isLoose: this.isLoose, scope: this.scope, file: this.file - }); + }, true); replaceSupers.replace(); if (node.key.name === "constructor") { diff --git a/lib/6to5/transformation/transformers/es6/object-super.js b/lib/6to5/transformation/transformers/es6/object-super.js new file mode 100644 index 0000000000..814096b7c1 --- /dev/null +++ b/lib/6to5/transformation/transformers/es6/object-super.js @@ -0,0 +1,31 @@ +"use strict"; + +var ReplaceSupers = require("../../helpers/replace-supers"); +var util = require("../../../util"); +var t = require("../../../types"); + +exports.Property = function (node, parent, scope, file) { + if (!node.method) return; + + var value = node.value; + var thisExpr = scope.generateUidIdentifier("this"); + + var replaceSupers = new ReplaceSupers({ + topLevelThisReference: thisExpr, + methodNode: node, + className: thisExpr, + isStatic: true, + scope: scope, + file: file + }); + + replaceSupers.replace(); + + if (replaceSupers.hasSuper) { + value.body.body.unshift( + t.variableDeclaration("var", [ + t.variableDeclarator(thisExpr, t.thisExpression()) + ]) + ); + } +}; diff --git a/lib/6to5/transformation/transformers/index.js b/lib/6to5/transformation/transformers/index.js index 56d7c84772..9d09450492 100644 --- a/lib/6to5/transformation/transformers/index.js +++ b/lib/6to5/transformation/transformers/index.js @@ -28,6 +28,7 @@ module.exports = { asyncToGenerator: require("./other/async-to-generator"), bluebirdCoroutines: require("./other/bluebird-coroutines"), + "es6.objectSuper": require("./es6/object-super"), "es7.objectRestSpread": require("./es7/object-rest-spread"), "es7.exponentiationOperator": require("./es7/exponentiation-operator"), "es6.spread": require("./es6/spread"), diff --git a/test/fixtures/transformation/es6-object-super/simple.js b/test/fixtures/transformation/es6-object-super/simple.js new file mode 100644 index 0000000000..f2ad335f84 --- /dev/null +++ b/test/fixtures/transformation/es6-object-super/simple.js @@ -0,0 +1,15 @@ +var a = { + name() { + return "Suyash"; + } +}; + +var b = { + name() { + return super.name() + " Verma"; + } +}; + +Object.setPrototypeOf(b, a); + +assert.equal(b.name(), "Suyash Verma");