diff --git a/src/com/google/javascript/jscomp/Es6ConvertSuper.java b/src/com/google/javascript/jscomp/Es6ConvertSuper.java index 81a9e363138..ea958f1d19e 100644 --- a/src/com/google/javascript/jscomp/Es6ConvertSuper.java +++ b/src/com/google/javascript/jscomp/Es6ConvertSuper.java @@ -133,6 +133,7 @@ public boolean apply(Node n) { case MEMBER_FUNCTION_DEF: case GETTER_DEF: case SETTER_DEF: + case COMPUTED_PROP: return true; default: return false; @@ -143,24 +144,20 @@ public boolean apply(Node n) { if (parent.isCall()) { // super(...) visitSuperCall(node, parent, enclosingMemberDef); - } else if (parent.isGetProp()) { + } else if (parent.isGetProp() || parent.isGetElem()) { if (parent.getFirstChild() == node) { if (parent.getParent().isCall() && NodeUtil.isCallOrNewTarget(parent)) { - // super.something(...) + // super.something(...) or super['something'](..) visitSuperPropertyCall(node, parent, enclosingMemberDef); } else { - // super.something - compiler.report(JSError.make(node, CANNOT_CONVERT_YET, - "Only calls to super or to a method of super are supported.")); + // super.something or super['something'] + visitSuperPropertyAccess(node, parent, enclosingMemberDef); } } else { // super.something used in some other way compiler.report(JSError.make(node, CANNOT_CONVERT_YET, "Only calls to super or to a method of super are supported.")); } - } else if (parent.isGetElem()) { - compiler.report(JSError.make(node, CANNOT_CONVERT_YET, - "Only calls to super or to a method of super are supported.")); } else if (parent.isNew()) { // new super(...) compiler.report(JSError.make(node, INVALID_SUPER_CALL)); @@ -190,8 +187,10 @@ private void visitSuperCall(Node node, Node parent, Node enclosingMemberDef) { // implementation that should be instantiated. // A call to super() shouldn't actually exist for these cases and is problematic to // transpile, so just drop it. - NodeUtil.getEnclosingStatement(node).detach(); - compiler.reportCodeChange(); + Node enclosingStatement = NodeUtil.getEnclosingStatement(node); + Node enclosingStatementParent = enclosingStatement.getParent(); + enclosingStatement.detach(); + compiler.reportChangeToEnclosingScope(enclosingStatementParent); } // Calls to super() constructors will be transpiled by Es6ConvertSuperConstructorCalls // later. @@ -204,7 +203,7 @@ private void visitSuperCall(Node node, Node parent, Node enclosingMemberDef) { } private void visitSuperPropertyCall(Node node, Node parent, Node enclosingMemberDef) { - Preconditions.checkState(parent.isGetProp(), parent); + Preconditions.checkState(parent.isGetProp() || parent.isGetElem(), parent); Preconditions.checkState(node.isSuper(), node); Node grandparent = parent.getParent(); Preconditions.checkState(grandparent.isCall()); @@ -236,6 +235,36 @@ private void visitSuperPropertyCall(Node node, Node parent, Node enclosingMember compiler.reportChangeToEnclosingScope(grandparent); } + private void visitSuperPropertyAccess(Node node, Node parent, Node enclosingMemberDef) { + Preconditions.checkState(parent.isGetProp() || parent.isGetElem(), parent); + Preconditions.checkState(node.isSuper(), node); + Node grandparent = parent.getParent(); + + if (NodeUtil.isLValue(parent)) { + // We don't support assigning to a super property + compiler.report( + JSError.make(parent, CANNOT_CONVERT_YET, "assigning to a super property")); + return; + } + + Node clazz = NodeUtil.getEnclosingClass(node); + Node superName = clazz.getSecondChild(); + if (!superName.isQualifiedName()) { + // This will be reported as an error in Es6ToEs3Converter. + return; + } + + if (enclosingMemberDef.isStaticMember()) { + node.replaceWith(superName.cloneTree()); + } else { + String newPropName = Joiner.on('.').join(superName.getQualifiedName(), "prototype"); + Node newprop = NodeUtil.newQName(compiler, newPropName, node, "super"); + node.replaceWith(newprop); + } + + compiler.reportChangeToEnclosingScope(grandparent); + } + @Override public void process(Node externs, Node root) { // Might need to synthesize constructors for ambient classes in .d.ts externs diff --git a/test/com/google/javascript/jscomp/Es6RewriteClassTest.java b/test/com/google/javascript/jscomp/Es6RewriteClassTest.java index 94eed237246..e1f2aacc613 100644 --- a/test/com/google/javascript/jscomp/Es6RewriteClassTest.java +++ b/test/com/google/javascript/jscomp/Es6RewriteClassTest.java @@ -1025,7 +1025,7 @@ public void testAlternativeSuperCalls() { } public void testComputedSuper() { - testError( + test( LINE_JOINER.join( "class Foo {", " ['m']() { return 1; }", @@ -1036,7 +1036,14 @@ public void testComputedSuper() { " return super['m']() + 1;", " }", "}"), - CANNOT_CONVERT_YET); + LINE_JOINER.join( + "/** @constructor @struct */", + "let Foo = function() {};", + "Foo.prototype['m'] = function() { return 1; };", + "/** @constructor @struct @extends {Foo} @param {...?} var_args */", + "let Bar = function(var_args) { Foo.apply(this, arguments); };", + "$jscomp.inherits(Bar, Foo);", + "Bar.prototype['m'] = function () { return Foo.prototype['m'].call(this) + 1; };")); } public void testSuperMethodInGetter() { @@ -1268,28 +1275,145 @@ public void testClassNested() { } public void testSuperGet() { - testError("class D {} class C extends D { f() {var i = super.c;} }", - CANNOT_CONVERT_YET); + test( + "class D { d() {} } class C extends D { f() {var i = super.d;} }", + LINE_JOINER.join( + "/** @constructor @struct */", + "let D = function() {};", + "D.prototype.d = function() {};", + "/**", + " * @constructor @struct", + " * @param {...?} var_args", + " * @extends{D} */", + "let C = function(var_args) {", + " D.apply(this, arguments); ", + "};", + "$jscomp.inherits(C, D);", + "C.prototype.f = function() {", + " var i = D.prototype.d;", + "};")); + + test( + "class D { ['d']() {} } class C extends D { f() {var i = super['d'];} }", + LINE_JOINER.join( + "/** @constructor @struct */", + "let D = function() {};", + "D.prototype['d'] = function() {};", + "/**", + " * @constructor @struct", + " * @param {...?} var_args", + " * @extends{D} */", + "let C = function(var_args) {", + " D.apply(this, arguments); ", + "};", + "$jscomp.inherits(C, D);", + "C.prototype.f = function() {", + " var i = D.prototype['d'];", + "};")); - testError("class D {} class C extends D { static f() {var i = super.c;} }", - CANNOT_CONVERT_YET); + test( + "class D { d() {}} class C extends D { static f() {var i = super.d;} }", + LINE_JOINER.join( + "/** @constructor @struct */", + "let D = function() {};", + "D.prototype.d = function() {};", + "/**", + " * @constructor @struct", + " * @param {...?} var_args", + " * @extends{D} */", + "let C = function(var_args) {", + " D.apply(this, arguments); ", + "};", + "$jscomp.inherits(C, D);", + "C.f = function() {", + " var i = D.d;", + "};")); - testError("class D {} class C extends D { f() {var i; i = super[s];} }", - CANNOT_CONVERT_YET); + test( + "class D { ['d']() {}} class C extends D { static f() {var i = super['d'];} }", + LINE_JOINER.join( + "/** @constructor @struct */", + "let D = function() {};", + "D.prototype['d'] = function() {};", + "/**", + " * @constructor @struct", + " * @param {...?} var_args", + " * @extends{D} */", + "let C = function(var_args) {", + " D.apply(this, arguments); ", + "};", + "$jscomp.inherits(C, D);", + "C.f = function() {", + " var i = D['d'];", + "};")); - testError("class D {} class C extends D { f() {return super.s;} }", - CANNOT_CONVERT_YET); + test( + "class D {} class C extends D { f() {return super.s;} }", + LINE_JOINER.join( + "/** @constructor @struct */", + "let D = function() {};", + "/**", + " * @constructor @struct", + " * @param {...?} var_args", + " * @extends{D} */", + "let C = function(var_args) {", + " D.apply(this, arguments); ", + "};", + "$jscomp.inherits(C, D);", + "C.prototype.f = function() {", + " return D.prototype.s;", + "};")); - testError("class D {} class C extends D { f() {m(super.s);} }", - CANNOT_CONVERT_YET); + test( + "class D {} class C extends D { f() { m(super.s);} }", + LINE_JOINER.join( + "/** @constructor @struct */", + "let D = function() {};", + "/**", + " * @constructor @struct", + " * @param {...?} var_args", + " * @extends{D} */", + "let C = function(var_args) {", + " D.apply(this, arguments); ", + "};", + "$jscomp.inherits(C, D);", + "C.prototype.f = function() {", + " m(D.prototype.s);", + "};")); - testError( - "class D {} class C extends D { foo() { return super.m.foo(); } }", - CANNOT_CONVERT_YET); + test( + "class D {} class C extends D { foo() { return super.m.foo();} }", + LINE_JOINER.join( + "/** @constructor @struct */", + "let D = function() {};", + "/**", + " * @constructor @struct", + " * @param {...?} var_args", + " * @extends{D} */", + "let C = function(var_args) {", + " D.apply(this, arguments); ", + "};", + "$jscomp.inherits(C, D);", + "C.prototype.foo = function() {", + " return D.prototype.m.foo();", + "};")); - testError( - "class D {} class C extends D { static foo() { return super.m.foo(); } }", - CANNOT_CONVERT_YET); + test( + "class D {} class C extends D { static foo() { return super.m.foo();} }", + LINE_JOINER.join( + "/** @constructor @struct */", + "let D = function() {};", + "/**", + " * @constructor @struct", + " * @param {...?} var_args", + " * @extends{D} */", + "let C = function(var_args) {", + " D.apply(this, arguments); ", + "};", + "$jscomp.inherits(C, D);", + "C.foo = function() {", + " return D.m.foo();", + "};")); } public void testSuperNew() { diff --git a/test/com/google/javascript/jscomp/Es6ToEs3ConverterTest.java b/test/com/google/javascript/jscomp/Es6ToEs3ConverterTest.java index a3e30c84099..405a646084b 100644 --- a/test/com/google/javascript/jscomp/Es6ToEs3ConverterTest.java +++ b/test/com/google/javascript/jscomp/Es6ToEs3ConverterTest.java @@ -1026,7 +1026,7 @@ public void testAlternativeSuperCalls() { } public void testComputedSuper() { - testError( + test( LINE_JOINER.join( "class Foo {", " ['m']() { return 1; }", @@ -1037,7 +1037,14 @@ public void testComputedSuper() { " return super['m']() + 1;", " }", "}"), - CANNOT_CONVERT_YET); + LINE_JOINER.join( + "/** @constructor @struct */", + "var Foo = function() {};", + "Foo.prototype['m'] = function() { return 1; };", + "/** @constructor @struct @extends {Foo} @param {...?} var_args */", + "var Bar = function(var_args) { Foo.apply(this, arguments); };", + "$jscomp.inherits(Bar, Foo);", + "Bar.prototype['m'] = function () { return Foo.prototype['m'].call(this) + 1; };")); } public void testSuperMethodInGetter() { @@ -1269,27 +1276,209 @@ public void testClassNested() { } public void testSuperGet() { - testError("class D {} class C extends D { f() {var i = super.c;} }", - CANNOT_CONVERT_YET); + test( + "class D { d() {} } class C extends D { f() {var i = super.d;} }", + LINE_JOINER.join( + "/** @constructor @struct */", + "var D = function() {};", + "D.prototype.d = function() {};", + "/**", + " * @constructor @struct", + " * @param {...?} var_args", + " * @extends{D} */", + "var C = function(var_args) {", + " D.apply(this, arguments); ", + "};", + "$jscomp.inherits(C, D);", + "C.prototype.f = function() {", + " var i = D.prototype.d;", + "};")); - testError("class D {} class C extends D { static f() {var i = super.c;} }", - CANNOT_CONVERT_YET); + test( + "class D { ['d']() {} } class C extends D { f() {var i = super['d'];} }", + LINE_JOINER.join( + "/** @constructor @struct */", + "var D = function() {};", + "D.prototype['d'] = function() {};", + "/**", + " * @constructor @struct", + " * @param {...?} var_args", + " * @extends{D} */", + "var C = function(var_args) {", + " D.apply(this, arguments); ", + "};", + "$jscomp.inherits(C, D);", + "C.prototype.f = function() {", + " var i = D.prototype['d'];", + "};")); - testError("class D {} class C extends D { f() {var i; i = super[s];} }", - CANNOT_CONVERT_YET); + test( + "class D { d() {}} class C extends D { static f() {var i = super.d;} }", + LINE_JOINER.join( + "/** @constructor @struct */", + "var D = function() {};", + "D.prototype.d = function() {};", + "/**", + " * @constructor @struct", + " * @param {...?} var_args", + " * @extends{D} */", + "var C = function(var_args) {", + " D.apply(this, arguments); ", + "};", + "$jscomp.inherits(C, D);", + "C.f = function() {", + " var i = D.d;", + "};")); - testError("class D {} class C extends D { f() {return super.s;} }", - CANNOT_CONVERT_YET); + test( + "class D { ['d']() {}} class C extends D { static f() {var i = super['d'];} }", + LINE_JOINER.join( + "/** @constructor @struct */", + "var D = function() {};", + "D.prototype['d'] = function() {};", + "/**", + " * @constructor @struct", + " * @param {...?} var_args", + " * @extends{D} */", + "var C = function(var_args) {", + " D.apply(this, arguments); ", + "};", + "$jscomp.inherits(C, D);", + "C.f = function() {", + " var i = D['d'];", + "};")); - testError("class D {} class C extends D { f() {m(super.s);} }", - CANNOT_CONVERT_YET); + test( + "class D {} class C extends D { f() {return super.s;} }", + LINE_JOINER.join( + "/** @constructor @struct */", + "var D = function() {};", + "/**", + " * @constructor @struct", + " * @param {...?} var_args", + " * @extends{D} */", + "var C = function(var_args) {", + " D.apply(this, arguments); ", + "};", + "$jscomp.inherits(C, D);", + "C.prototype.f = function() {", + " return D.prototype.s;", + "};")); - testError( - "class D {} class C extends D { foo() { return super.m.foo(); } }", - CANNOT_CONVERT_YET); + test( + "class D {} class C extends D { f() { m(super.s);} }", + LINE_JOINER.join( + "/** @constructor @struct */", + "var D = function() {};", + "/**", + " * @constructor @struct", + " * @param {...?} var_args", + " * @extends{D} */", + "var C = function(var_args) {", + " D.apply(this, arguments); ", + "};", + "$jscomp.inherits(C, D);", + "C.prototype.f = function() {", + " m(D.prototype.s);", + "};")); + + test( + "class D {} class C extends D { foo() { return super.m.foo();} }", + LINE_JOINER.join( + "/** @constructor @struct */", + "var D = function() {};", + "/**", + " * @constructor @struct", + " * @param {...?} var_args", + " * @extends{D} */", + "var C = function(var_args) {", + " D.apply(this, arguments); ", + "};", + "$jscomp.inherits(C, D);", + "C.prototype.foo = function() {", + " return D.prototype.m.foo();", + "};")); + + test( + "class D {} class C extends D { static foo() { return super.m.foo();} }", + LINE_JOINER.join( + "/** @constructor @struct */", + "var D = function() {};", + "/**", + " * @constructor @struct", + " * @param {...?} var_args", + " * @extends{D} */", + "var C = function(var_args) {", + " D.apply(this, arguments); ", + "};", + "$jscomp.inherits(C, D);", + "C.foo = function() {", + " return D.m.foo();", + "};")); + } + + public void testSuperAccessToGettersAndSetters() { + // Getters cannot be transpiled to ES3 + setLanguageOut(LanguageMode.ECMASCRIPT5); + test( + LINE_JOINER.join( + "class Base {", + " get g() { return 'base'; }", + " set g(v) { alert('base.prototype.g = ' + v); }", + "}", + "class Sub extends Base {", + " get g() { return super.g + '-sub'; }", + "}"), + LINE_JOINER.join( + "/** @constructor @struct */", + "var Base = function() {};", + "/** @type {?} */", + "Base.prototype.g;", + "$jscomp.global.Object.defineProperties(", + " Base.prototype,", + " {", + " g:{", + " configurable:true,", + " enumerable:true,", + " /** @this {Base} */", + " get:function(){return\"base\"},", + " /** @this {Base} */", + " set:function(v){alert(\"base.prototype.g = \" + v);}", + " }", + " });", + "/**", + " * @constructor @struct", + " * @extends {Base}", + " * @param {...?} var_args", + " */", + "var Sub = function(var_args) {", + " Base.apply(this, arguments);", + "};", + "/** @type {?} */", + "Sub.prototype.g;", + "$jscomp.inherits(Sub, Base);", + "$jscomp.global.Object.defineProperties(", + " Sub.prototype,", + " {", + " g:{", + " configurable:true,", + " enumerable:true,", + " /** @this {Sub} */", + " get:function(){return Base.prototype.g + \"-sub\";},", + " }", + " });", + "")); testError( - "class D {} class C extends D { static foo() { return super.m.foo(); } }", + LINE_JOINER.join( + "class Base {", + " get g() { return 'base'; }", + " set g(v) { alert('base.prototype.g = ' + v); }", + "}", + "class Sub extends Base {", + " get g() { return super.g + '-sub'; }", + " set g(v) { super.g = v + '-sub'; }", + "}"), CANNOT_CONVERT_YET); } diff --git a/test/com/google/javascript/jscomp/runtime_tests/class_getter_setter_test.js b/test/com/google/javascript/jscomp/runtime_tests/class_getter_setter_test.js index 1112bc7debf..647e4e0b060 100644 --- a/test/com/google/javascript/jscomp/runtime_tests/class_getter_setter_test.js +++ b/test/com/google/javascript/jscomp/runtime_tests/class_getter_setter_test.js @@ -74,6 +74,34 @@ function testSubclassGetter() { assertEquals(1, s.counter); } +class FooWithSuffix extends Sub { + constructor() { + super(); + this.foo_ = 'initial-value'; + } + + /** @return {string} */ + get foo() { + // Invoke super class getter + return super.foo + '-' + this.foo_; + } + + /** @param {string} val */ + set foo(val) { + this.foo_ = val; + } +} + +function testInvokeSuperGetter() { + let s1 = new FooWithSuffix(); + let s2 = new FooWithSuffix(); + + s1.foo = 'modified'; + assertEquals('sub-modified', s1.foo); + // s2 should not have been affected + assertEquals('sub-initial-value', s2.foo); +} + var Multiple = class { get foo() { return 'foo';