From 5337ab5a08b555c7f732afee679505304cda56aa Mon Sep 17 00:00:00 2001 From: Sebastian McKenzie Date: Wed, 4 Mar 2015 22:52:07 +1100 Subject: [PATCH] statically bind super references in object expressions - fixes #943 --- .../transformation/helpers/replace-supers.js | 36 ++++++++++--------- .../transformers/es6/classes.js | 4 +-- .../transformers/es6/object-super.js | 21 +++++++++-- .../statically-bound/actual.js | 5 +++ .../statically-bound/expected.js | 13 +++++++ 5 files changed, 59 insertions(+), 20 deletions(-) create mode 100644 test/fixtures/transformation/es6-object-super/statically-bound/actual.js create mode 100644 test/fixtures/transformation/es6-object-super/statically-bound/expected.js diff --git a/src/babel/transformation/helpers/replace-supers.js b/src/babel/transformation/helpers/replace-supers.js index 3df9190db8..0482c92cac 100644 --- a/src/babel/transformation/helpers/replace-supers.js +++ b/src/babel/transformation/helpers/replace-supers.js @@ -55,14 +55,18 @@ export default class ReplaceSupers { constructor(opts, inClass) { this.topLevelThisReference = opts.topLevelThisReference; this.methodNode = opts.methodNode; - this.className = opts.className; - this.superName = opts.superName; + this.superRef = opts.superRef; this.isStatic = opts.isStatic; this.hasSuper = false; this.inClass = inClass; this.isLoose = opts.isLoose; this.scope = opts.scope; this.file = opts.file; + this.opts = opts; + } + + getObjectRef() { + return this.opts.objectRef || this.opts.getObjectRef(); } /** @@ -87,7 +91,7 @@ export default class ReplaceSupers { t.callExpression( t.memberExpression(t.identifier("Object"), t.identifier("getPrototypeOf")), [ - this.isStatic ? this.className : t.memberExpression(this.className, t.identifier("prototype")) + this.isStatic ? this.getObjectRef() : t.memberExpression(this.getObjectRef(), t.identifier("prototype")) ] ), isComputed ? property : t.literal(property.name), @@ -118,7 +122,7 @@ export default class ReplaceSupers { t.callExpression( t.memberExpression(t.identifier("Object"), t.identifier("getPrototypeOf")), [ - this.isStatic ? this.className : t.memberExpression(this.className, t.identifier("prototype")) + this.isStatic ? this.getObjectRef() : t.memberExpression(this.getObjectRef(), t.identifier("prototype")) ] ), isComputed ? property : t.literal(property.name), @@ -175,19 +179,19 @@ export default class ReplaceSupers { getLooseSuperProperty(id, parent) { var methodNode = this.methodNode; var methodName = methodNode.key; - var superName = this.superName || t.identifier("Function"); + var superRef = this.superRef || t.identifier("Function"); if (parent.property === id) { return; } else if (t.isCallExpression(parent, { callee: id })) { - // super(); -> ClassName.prototype.MethodName.call(this); + // super(); -> objectRef.prototype.MethodName.call(this); parent.arguments.unshift(t.thisExpression()); if (methodName.name === "constructor") { // constructor() { super(); } - return t.memberExpression(superName, t.identifier("call")); + return t.memberExpression(superRef, t.identifier("call")); } else { - id = superName; + id = superRef; // foo() { super(); } if (!methodNode.static) { @@ -198,10 +202,10 @@ export default class ReplaceSupers { return t.memberExpression(id, t.identifier("call")); } } else if (t.isMemberExpression(parent) && !methodNode.static) { - // super.test -> ClassName.prototype.test - return t.memberExpression(superName, t.identifier("prototype")); + // super.test -> objectRef.prototype.test + return t.memberExpression(superRef, t.identifier("prototype")); } else { - return superName; + return superRef; } } @@ -222,7 +226,7 @@ export default class ReplaceSupers { if (!t.isMemberExpression(callee)) return; if (callee.object.name !== "super") return; - // super.test(); -> ClassName.prototype.MethodName.call(this); + // super.test(); -> objectRef.prototype.MethodName.call(this); this.hasSuper = true; t.appendToMemberExpression(callee, t.identifier("call")); node.arguments.unshift(getThisReference()); @@ -251,7 +255,7 @@ export default class ReplaceSupers { if (t.isCallExpression(node)) { var callee = node.callee; if (isSuper(callee, node)) { - // super(); -> _get(Object.getPrototypeOf(ClassName), "MethodName", this).call(this); + // super(); -> _get(Object.getPrototypeOf(objectRef), "MethodName", this).call(this); property = methodNode.key; computed = methodNode.computed; args = node.arguments; @@ -264,17 +268,17 @@ export default class ReplaceSupers { throw this.file.errorWithNode(node, messages.get("classesIllegalSuperCall", methodName)); } } else if (t.isMemberExpression(callee) && isSuper(callee.object, callee)) { - // super.test(); -> _get(Object.getPrototypeOf(ClassName.prototype), "test", this).call(this); + // super.test(); -> _get(Object.getPrototypeOf(objectRef.prototype), "test", this).call(this); property = callee.property; computed = callee.computed; args = node.arguments; } } else if (t.isMemberExpression(node) && isSuper(node.object, node)) { - // super.name; -> _get(Object.getPrototypeOf(ClassName.prototype), "name", this); + // super.name; -> _get(Object.getPrototypeOf(objectRef.prototype), "name", this); property = node.property; 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); + // super.name = "val"; -> _set(Object.getPrototypeOf(objectRef.prototype), "name", this); this.hasSuper = true; return this.setSuperProperty(node.left.property, node.right, node.left.computed, getThisReference()); } diff --git a/src/babel/transformation/transformers/es6/classes.js b/src/babel/transformation/transformers/es6/classes.js index ea8bd1a532..3f36556a40 100644 --- a/src/babel/transformation/transformers/es6/classes.js +++ b/src/babel/transformation/transformers/es6/classes.js @@ -159,8 +159,8 @@ class ClassTransformer { if (t.isMethodDefinition(node)) { var replaceSupers = new ReplaceSupers({ methodNode: node, - className: this.className, - superName: this.superName, + objectRef: this.className, + superRef: this.superName, isStatic: node.static, isLoose: this.isLoose, scope: this.scope, diff --git a/src/babel/transformation/transformers/es6/object-super.js b/src/babel/transformation/transformers/es6/object-super.js index 4ccad049be..7b9fce9f52 100644 --- a/src/babel/transformation/transformers/es6/object-super.js +++ b/src/babel/transformation/transformers/es6/object-super.js @@ -5,7 +5,7 @@ export function check(node) { return t.isIdentifier(node, { name: "super" }); } -export function Property(node, parent, scope, file) { +function Property(node, scope, getObjectRef, file) { if (!node.method) return; var value = node.value; @@ -13,8 +13,8 @@ export function Property(node, parent, scope, file) { var replaceSupers = new ReplaceSupers({ topLevelThisReference: thisExpr, + getObjectRef: getObjectRef, methodNode: node, - className: thisExpr, isStatic: true, scope: scope, file: file @@ -30,3 +30,20 @@ export function Property(node, parent, scope, file) { ); } } + +export function ObjectExpression(node, parent, scope, file) { + var objectRef; + var getObjectRef = () => objectRef ||= scope.generateUidIdentifier("obj"); + + for (var i = 0; i < node.properties.length; i++) { + Property(node.properties[i], scope, getObjectRef, file); + } + + if (objectRef) { + scope.push({ + id: objectRef + }); + + return t.assignmentExpression("=", objectRef, node); + } +} diff --git a/test/fixtures/transformation/es6-object-super/statically-bound/actual.js b/test/fixtures/transformation/es6-object-super/statically-bound/actual.js new file mode 100644 index 0000000000..831e3c2d29 --- /dev/null +++ b/test/fixtures/transformation/es6-object-super/statically-bound/actual.js @@ -0,0 +1,5 @@ +var o = { + m() { + return super.x; + } +}; diff --git a/test/fixtures/transformation/es6-object-super/statically-bound/expected.js b/test/fixtures/transformation/es6-object-super/statically-bound/expected.js new file mode 100644 index 0000000000..bd6e043d00 --- /dev/null +++ b/test/fixtures/transformation/es6-object-super/statically-bound/expected.js @@ -0,0 +1,13 @@ +"use strict"; + +var _obj; + +var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + +var o = _obj = { + m: function m() { + var _this = this; + + return _get(Object.getPrototypeOf(_obj), "x", this); + } +};