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/class-super-constructor-call.js b/lib/6to5/transformation/templates/class-super-constructor-call.js index c9b822f013..21034b6201 100644 --- a/lib/6to5/transformation/templates/class-super-constructor-call.js +++ b/lib/6to5/transformation/templates/class-super-constructor-call.js @@ -1,3 +1,3 @@ -if (SUPER_NAME !== null) { - SUPER_NAME.apply(this, arguments); +if (Object.getPrototypeOf(CLASS_NAME) !== null) { + Object.getPrototypeOf(CLASS_NAME).apply(this, arguments); } diff --git a/lib/6to5/transformation/templates/get.js b/lib/6to5/transformation/templates/get.js new file mode 100644 index 0000000000..87e4f7218f --- /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 === undefined) { + var parent = Object.getPrototypeOf(object); + + if (parent === null) { + return undefined; + } else { + return get(parent); + } + } else if ("value" in desc && desc.writable) { + return desc.value; + } else { + var getter = desc.get; + + if (getter === undefined) { + return undefined; + } + + return getter.call(receiver); + } +}); diff --git a/lib/6to5/transformation/transformers/es6-classes.js b/lib/6to5/transformation/transformers/es6-classes.js index 8444212260..0d3ef1660b 100644 --- a/lib/6to5/transformation/transformers/es6-classes.js +++ b/lib/6to5/transformation/transformers/es6-classes.js @@ -149,7 +149,7 @@ Class.prototype.buildBody = function () { if (!this.hasConstructor && superName && !t.isFalsyExpression(superName)) { constructor.body.body.push(util.template("class-super-constructor-call", { - SUPER_NAME: superName + CLASS_NAME: className }, true)); } @@ -215,45 +215,45 @@ Class.prototype.pushMethod = function (node) { }; /** - * Given a `methodNode`, produce a `MemberExpression` super class reference. + * Gets a node representing the super class value of the named property. * - * @param {Node} methodNode MethodDefinition - * @param {Node} node Identifier - * @param {Node} parent + * @example + * + * _get(Object.getPrototypeOf(CLASS.prototype), "METHOD", this) + * + * @param {Node} property + * @param {boolean} isStatic + * @param {boolean} isComputed * * @returns {Node} */ -Class.prototype.superIdentifier = function (methodNode, id, parent) { - var methodName = methodNode.key; - var superName = this.superName || t.identifier("Function"); - - if (parent.property === id) { - return; - } else if (t.isCallExpression(parent, { callee: id })) { - // super(); -> ClassName.prototype.MethodName.call(this); - parent.arguments.unshift(t.thisExpression()); - - if (methodName.name === "constructor") { - // constructor() { super(); } - return t.memberExpression(superName, t.identifier("call")); - } else { - id = superName; - - // foo() { super(); } - if (!methodNode.static) { - id = t.memberExpression(id, t.identifier("prototype")); - } - - id = t.memberExpression(id, methodName, methodNode.computed); - 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")); - } else { - return superName; - } +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() + ] + ); }; /** @@ -268,16 +268,48 @@ 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); + if (!(t.isMemberExpression(parent) && !parent.computed && parent.property === node)) { + throw self.file.errorWithNode(node, "illegal use of bare super"); + } } 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; + } } } }); diff --git a/test/fixtures/transformation/es6-classes/accessing-super-class/actual.js b/test/fixtures/transformation/es6-classes/accessing-super-class/actual.js index 64b349152c..154d85730b 100644 --- a/test/fixtures/transformation/es6-classes/accessing-super-class/actual.js +++ b/test/fixtures/transformation/es6-classes/accessing-super-class/actual.js @@ -3,7 +3,6 @@ class Test extends Foo { woops.super.test(); super(); super.test(); - foob(super); super(...arguments); super("test", ...arguments); 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..6900d5d283 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 === undefined) { + var parent = Object.getPrototypeOf(object); + + if (parent === null) { + return undefined; + } else { + return get(parent); + } + } 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 _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,34 @@ 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); - foob(_Foo); + _get(Object.getPrototypeOf(Test.prototype), "constructor", this).call(this); + _get(Object.getPrototypeOf(Test.prototype), "test", this).call(this); - _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..0afdcd06c1 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 === undefined) { + var parent = Object.getPrototypeOf(object); + + if (parent === null) { + return undefined; + } else { + return get(parent); + } + } 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 _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/bare-super/actual.js b/test/fixtures/transformation/es6-classes/bare-super/actual.js new file mode 100644 index 0000000000..6e053ecf76 --- /dev/null +++ b/test/fixtures/transformation/es6-classes/bare-super/actual.js @@ -0,0 +1,5 @@ +class Test { + constructor() { + console.log(super); + } +} diff --git a/test/fixtures/transformation/es6-classes/bare-super/options.json b/test/fixtures/transformation/es6-classes/bare-super/options.json new file mode 100644 index 0000000000..f878b7f9cd --- /dev/null +++ b/test/fixtures/transformation/es6-classes/bare-super/options.json @@ -0,0 +1,3 @@ +{ + "throws": "illegal use of bare super" +} 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..090e82a9bb 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 === undefined) { + var parent = Object.getPrototypeOf(object); + + if (parent === null) { + return undefined; + } else { + return get(parent); + } + } 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 _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-class-id-member-expression/expected.js b/test/fixtures/transformation/es6-classes/super-class-id-member-expression/expected.js index d9a40e4753..a8ecac411b 100644 --- a/test/fixtures/transformation/es6-classes/super-class-id-member-expression/expected.js +++ b/test/fixtures/transformation/es6-classes/super-class-id-member-expression/expected.js @@ -18,8 +18,8 @@ var _inherits = function (child, parent) { var BaseController = (function () { var _Chaplin$Controller = Chaplin.Controller; var BaseController = function BaseController() { - if (_Chaplin$Controller !== null) { - _Chaplin$Controller.apply(this, arguments); + if (Object.getPrototypeOf(BaseController) !== null) { + Object.getPrototypeOf(BaseController).apply(this, arguments); } }; @@ -31,8 +31,8 @@ var BaseController = (function () { var BaseController2 = (function () { var _Chaplin$Controller$Another = Chaplin.Controller.Another; var BaseController2 = function BaseController2() { - if (_Chaplin$Controller$Another !== null) { - _Chaplin$Controller$Another.apply(this, arguments); + if (Object.getPrototypeOf(BaseController2) !== null) { + Object.getPrototypeOf(BaseController2).apply(this, arguments); } }; diff --git a/test/fixtures/transformation/es6-classes/super-class/expected.js b/test/fixtures/transformation/es6-classes/super-class/expected.js index 01534a385f..fdd356cf96 100644 --- a/test/fixtures/transformation/es6-classes/super-class/expected.js +++ b/test/fixtures/transformation/es6-classes/super-class/expected.js @@ -18,8 +18,8 @@ var _inherits = function (child, parent) { var Test = (function () { var _Foo = Foo; var Test = function Test() { - if (_Foo !== null) { - _Foo.apply(this, arguments); + if (Object.getPrototypeOf(Test) !== null) { + Object.getPrototypeOf(Test).apply(this, arguments); } }; 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..c7fd43b7ff 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 === undefined) { + var parent = Object.getPrototypeOf(object); + + if (parent === null) { + return undefined; + } else { + return get(parent); + } + } 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 Test = function Test() { + _get(Object.getPrototypeOf(Test.prototype), "hasOwnProperty", this).call(this, "test"); +}; + diff --git a/test/fixtures/transformation/optional-proto-to-assign/class/expected.js b/test/fixtures/transformation/optional-proto-to-assign/class/expected.js index 2d7d562265..b6517c4263 100644 --- a/test/fixtures/transformation/optional-proto-to-assign/class/expected.js +++ b/test/fixtures/transformation/optional-proto-to-assign/class/expected.js @@ -28,8 +28,8 @@ var _inherits = function (child, parent) { var Foo = (function () { var _Bar = Bar; var Foo = function Foo() { - if (_Bar !== null) { - _Bar.apply(this, arguments); + if (Object.getPrototypeOf(Foo) !== null) { + Object.getPrototypeOf(Foo).apply(this, arguments); } };