/* * Copyright 2015 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.javascript.jscomp.CompilerOptions.LanguageMode; /** * Test case for {@link Es6ToEs3ClassSideInheritance}. * * @author tbreisacher@google.com (Tyler Breisacher) */ public class Es6ToEs3ClassSideInheritanceTest extends CompilerTestCase { @Override public void setUp() { setAcceptedLanguage(LanguageMode.ECMASCRIPT_2015); setLanguageOut(LanguageMode.ECMASCRIPT3); allowExternsChanges(true); } @Override protected CompilerPass getProcessor(Compiler compiler) { return new Es6ToEs3ClassSideInheritance(compiler); } @Override protected int getNumRepetitions() { return 1; } public void testSimple() { test( LINE_JOINER.join( "let x = 1;", "/** @constructor */", "function Example() {}", "Example.staticMethod = function() { alert(1); }", "", "/** @constructor @extends {Example} */", "function Subclass() {}", "$jscomp.inherits(Subclass, Example);"), LINE_JOINER.join( "let x = 1;", "/** @constructor */", "function Example() {}", "Example.staticMethod = function() { alert(1); }", "", "/** @constructor @extends {Example} */", "function Subclass() {}", "$jscomp.inherits(Subclass, Example);", "", "/** @suppress {visibility} */", "Subclass.staticMethod = Example.staticMethod;")); } public void testTyped() { test( LINE_JOINER.join( "let x = 1;", "/** @constructor */", "function Example() {}", "", "/** @return {string} */", "Example.staticMethod = function() { return ''; }", "", "/** @constructor @extends {Example} */", "function Subclass() {}", "$jscomp.inherits(Subclass, Example);"), LINE_JOINER.join( "let x = 1;", "/** @constructor */", "function Example() {}", "", "/** @return {string} */", "Example.staticMethod = function() { return ''; }", "", "/** @constructor @extends {Example} */", "function Subclass() {}", "$jscomp.inherits(Subclass,Example);", "", "/** @return {string} @suppress {visibility} */", "Subclass.staticMethod = Example.staticMethod;")); } public void testOverride() { testSame( LINE_JOINER.join( "let x = 1;", "/** @constructor */", "function Example() {}", "", "/** @return {string} */", "Example.staticMethod = function() { return ''; }", "", "/** @constructor @extends {Example} */", "function Subclass() {}", "$jscomp.inherits(Subclass, Example);", "", "Subclass.staticMethod = function() { return 5; };")); testSame( LINE_JOINER.join( "let x = 1;", "/** @constructor */", "function Example() {}", "Example.staticProp = 5;", "/** @constructor @extends {Example} */", "function Subclass() {}", "$jscomp.inherits(Subclass, Example);", "Subclass.staticProp = 6;")); } /** * In this example, the base class has a static field which is not a function. */ public void testStaticNonMethod() { test( LINE_JOINER.join( "let x = 1;", "/** @constructor */", "function Example() {}", "", "/** @type {number} */", "Example.staticField = 5;", "", "/** @constructor @extends {Example} */", "function Subclass() {}", "$jscomp.inherits(Subclass, Example);"), LINE_JOINER.join( "let x = 1;", "/** @constructor */", "function Example() {}", "", "/** @type {number} */", "Example.staticField = 5;", "", "/** @constructor @extends {Example} */", "function Subclass() {}", "$jscomp.inherits(Subclass, Example);", "", "/** @type {number} @suppress {visibility} */", "Subclass.staticField = Example.staticField;")); } public void testGetterSetterSimple() { // This is what the Es6ToEs3Converter produces for: // // class Example { // static get property() {} // } // // or // // class Example { // static set property(x) {} // } test( LINE_JOINER.join( "let x = 1;", "/** @constructor */", "function Example() {}", "", "/** @type {string} */", "Example.property;", "Object.defineProperties(Example, {property: { configurable:true, enumerable:true,", " get:function() { return 1; },", " set:function(a) {}", "}});", "", "/** @constructor @extends {Example} */", "function Subclass() {}", "$jscomp.inherits(Subclass, Example);"), LINE_JOINER.join( "let x = 1;", "/** @constructor */", "function Example() {}", "", "/** @type {string} */", "Example.property;", "Object.defineProperties(Example, {property:{configurable:true, enumerable:true,", " get:function() { return 1; },", " set:function(a) {}", "}});", "", "/** @constructor @extends {Example} */", "function Subclass() {}", "", "/** @type {string} @suppress {visibility} */", "Subclass.property;", "$jscomp.inherits(Subclass, Example);")); } public void testGetterSetterQualifiedClassName() { test( LINE_JOINER.join( "let TestCase = {};", "TestCase.A = /** @constructor */function() {};", "", "/** @type {string} */", "TestCase.A.property;", "Object.defineProperties(TestCase.A, {property: { configurable:true, enumerable:true,", " get:function() { return 1; },", " set:function(a) {}", "}});", "", "/** @constructor @extends {TestCase.A} */", "function Subclass() {}", "$jscomp.inherits(Subclass, TestCase.A);"), LINE_JOINER.join( "let TestCase = {};", "TestCase.A = /** @constructor */function() {};", "", "/** @type {string} */", "TestCase.A.property;", "Object.defineProperties(TestCase.A, {property: { configurable:true, enumerable:true,", " get:function() { return 1; },", " set:function(a) {}", "}});", "", "/** @constructor @extends {TestCase.A} */", "function Subclass() {}", "/** @type {string} @suppress {visibility} */", "Subclass.property;", "$jscomp.inherits(Subclass, TestCase.A);")); } /** * In this case the stub is not really a stub. It's just a no-op getter, we would be able to * detect this and not copy the stub since there is a member with this name. */ public void testGetterSetterFakeStub() { test( LINE_JOINER.join( "let x = 1;", "/** @constructor */", "function A() {}", "", "/** @type {string} */", "A.property;", "A.property = 'string'", "", "/** @constructor @extends {A} */", "function B() {}", "$jscomp.inherits(B, A);"), LINE_JOINER.join( "let x = 1;", "/** @constructor */", "function A() {}", "", "/** @type {string} */", "A.property;", "A.property = 'string'", "", "/** @constructor @extends {A} */", "function B() {}", "$jscomp.inherits(B, A);", "/** @suppress {visibility} */", "B.property = A.property;")); } public void testGetterSetterSubclassSubclass() { test( LINE_JOINER.join( "let x = 1;", "/** @constructor */", "function A() {}", "", "/** @type {string} */", "A.property;", "Object.defineProperties(A, {property: { configurable:true, enumerable:true,", " get:function() { return 1; },", " set:function(a) {}", "}});", "", "/** @constructor @extends {A} */", "function B() {}", "$jscomp.inherits(B, A);", "", "/** @constructor @extends {B} */", "function C() {}", "$jscomp.inherits(C, B);", ""), LINE_JOINER.join( "let x = 1;", "/** @constructor */", "function A() {}", "", "/** @type {string} */", "A.property;", "Object.defineProperties(A, {property: { configurable:true, enumerable:true,", " get:function() { return 1; },", " set:function(a) {}", "}});", "", "/** @constructor @extends {A} */", "function B() {}", "/** @type {string} @suppress {visibility} */", "B.property;", "$jscomp.inherits(B, A);", "", "/** @constructor @extends {B} */", "function C() {}", "/** @type {string} @suppress {visibility} */", "C.property;", "$jscomp.inherits(C, B);", "")); } /** * If the subclass overrides the property we don't want to redeclare the stub. */ public void testGetterSetterSubclassOverride() { testSame( LINE_JOINER.join( "let x = 1;", "/** @constructor */", "function A() {}", "", "/** @type {string} */", "A.property;", "Object.defineProperties(A, {property: { configurable:true, enumerable:true,", " get:function() { return 1; },", " set:function(a) {}", "}});", "", "/** @constructor @extends {A} */", "function B() {}", "/** @type {string} */", "B.property;", "Object.defineProperties(B, {property: { configurable:true, enumerable:true,", " get:function() { return 2; },", " set:function(a) {}", "}});", "$jscomp.inherits(B, A);", "")); testSame( LINE_JOINER.join( "let x = 1;", "/** @constructor */", "function A() {}", "", "/** @type {string} */", "A.property;", "Object.defineProperties(A, {property: { configurable:true, enumerable:true,", " get:function() { return 1; },", " set:function(a) {}", "}});", "", "/** @constructor @extends {A} */", "function B() {}", "/** @type {string} */", "$jscomp.inherits(B, A);", "B.property = 'asdf';", "")); } public void testGetterSetter_noType() { test( LINE_JOINER.join( "let x = 1;", "/** @constructor */", "function Example() {}", "", "Example.property;", "Object.defineProperties(Example, {property: { configurable:true, enumerable:true,", " get:function() { return 1; },", " set:function(a) {}", "}});", "", "/** @constructor @extends {Example} */", "function Subclass() {}", "$jscomp.inherits(Subclass, Example);"), LINE_JOINER.join( "let x = 1;", "/** @constructor */", "function Example() {}", "", "Example.property;", "Object.defineProperties(Example, {property: { configurable:true, enumerable:true,", " get:function() { return 1; },", " set:function(a) {}", "}});", "", "/** @constructor @extends {Example} */", "function Subclass() {}", "", "/** @type {?} @suppress {visibility} */", "Subclass.property;", "$jscomp.inherits(Subclass, Example);")); } public void testInheritFromExterns() { test( LINE_JOINER.join( "let x;", "/** @constructor */ function ExternsClass() {}", "ExternsClass.m = function() {};"), LINE_JOINER.join( "let y = 1;", "/** @constructor @struct @extends {ExternsClass} */", "var CodeClass = function(var_args) {", " ExternsClass.apply(this,arguments)", "};", "$jscomp.inherits(CodeClass,ExternsClass)"), LINE_JOINER.join( "let y = 1;", "/** @constructor @struct @extends {ExternsClass} */", "var CodeClass = function(var_args) {", " ExternsClass.apply(this,arguments)", "};", "$jscomp.inherits(CodeClass,ExternsClass);", "/** @suppress {visibility} */", "CodeClass.m = ExternsClass.m;"), null, null); } public void testAliasing() { test( LINE_JOINER.join( "let x = 1;", "/** @constructor */", "function Foo() {}", "Foo.prop = 123;", "var aliasFoo = Foo;", "/** @constructor @extends {aliasFoo} */", "function Bar() {}", "$jscomp.inherits(Bar, aliasFoo);"), LINE_JOINER.join( "let x = 1;", "/** @constructor */", "function Foo() {}", "Foo.prop = 123;", "var aliasFoo = Foo;", "/** @constructor @extends {aliasFoo} */", "function Bar() {}", "$jscomp.inherits(Bar, aliasFoo);", "/** @suppress {visibility} */", "Bar.prop = aliasFoo.prop;")); test( LINE_JOINER.join( "let x = 1;", "/** @constructor */", "function Foo() {}", "var aliasFoo = Foo;", "aliasFoo.prop = 123;", "/** @constructor @extends {Foo} */", "function Bar() {}", "$jscomp.inherits(Bar, Foo);"), LINE_JOINER.join( "let x = 1;", "/** @constructor */", "function Foo() {}", "var aliasFoo = Foo;", "aliasFoo.prop = 123;", "/** @constructor @extends {Foo} */", "function Bar() {}", "$jscomp.inherits(Bar, Foo);", "/** @suppress {visibility} */", "Bar.prop = Foo.prop;")); } public void testScopeHandling() { testSame( LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "", "function f() {", " let Foo = {};", " Foo.prop = 123;", // Not a reference to the Foo class, so no change. "}", "/** @constructor @extends {Foo} */", "function Bar() {}", "$jscomp.inherits(Bar, Foo);")); testSame( LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "", "function f() {", " let Foo = {};", " function g() {", " Foo.prop = 123;", // Not a reference to the Foo class, so no change. " }", "}", "/** @constructor @extends {Foo} */", "function Bar() {}", "$jscomp.inherits(Bar, Foo);")); } public void testInlineTypes() { test( LINE_JOINER.join( "/** @constructor @struct */", "let A = function() {}", "A.foo = function(/** number */ x) {}", "", "/** @constructor @struct @extends {A} */", "var B = function(var_args) { A.apply(this,arguments); };", "$jscomp.inherits(B, A);"), LINE_JOINER.join( "/** @constructor @struct */", "let A = function() {}", "A.foo = function(/** number */ x) {}", "", "/** @constructor @struct @extends {A} */", "var B = function(var_args) { A.apply(this,arguments); };", "$jscomp.inherits(B, A);", "/**", " * @param {number} x", " * @suppress {visibility}", " */", "B.foo = A.foo;")); } /** * Examples which are handled incorrectly but are unlikely to come up in practice. */ public void testIncorrectScopeHandling() { test( LINE_JOINER.join( "let example = {};", "/** @constructor */", "example.Foo = function() {};", "", "function f() {", " var example = {};", " example.Foo = {};", " // Not a reference to the example.Foo class, so there should be no change.", " example.Foo.prop = 123;", "}", "/** @constructor @extends {example.Foo} */", "function Bar() {}", "$jscomp.inherits(Bar, example.Foo);"), LINE_JOINER.join( "let example = {};", "/** @constructor */", "example.Foo = function() {};", "", "function f() {", " var example = {};", " example.Foo = {};", " example.Foo.prop = 123;", "}", "/** @constructor @extends {example.Foo} */", "function Bar() {}", "$jscomp.inherits(Bar, example.Foo);", "/** @suppress {visibility} */", "Bar.prop = example.Foo.prop")); testSame( LINE_JOINER.join( "let x = 1;", "function a() {", " /** @constructor */", " function Foo() {}", " Foo.bar = function() {};", "", " /** @constructor @extends {Foo} */", " function Bar() {}", " $jscomp.inherits(Bar, Foo);", " // There should be a declaration for Bar.bar.", "}")); } }