/* * Copyright 2016 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.common.collect.ImmutableList; /** Tests for {@link RemoveSuperMethodsPass} */ public final class RemoveSuperMethodsPassTest extends CompilerTestCase { private static final String BOILERPLATE = LINE_JOINER.join( "/** @constructor */", "var FooBase = function() {};", "FooBase.prototype.bar = function() {};", "/** @param {number} time */", "/** @param {number} loc */", "/** @return {number} */", "FooBase.prototype.baz = function(time, loc) { return 2; }", "/** @param {number} time */", "/** @param {number=} opt_loc */", "FooBase.prototype.buzz = function(time, opt_loc) {} ", "/** @param {...number} var_args */", "FooBase.prototype.var = function(var_args) {} ", "/** @constructor @extends {FooBase} */", "var Foo = function() {}", "Foo.superClass_ = FooBase.prototype", "var ns = {};", "/** @constructor */ ns.FooBase = function() {};", "ns.FooBase.prototype.bar = function() {};", "/** @constructor @extends {ns.FooBase} */ ns.Foo = function() {};", "ns.Foo.superClass_ = ns.FooBase.prototype"); public RemoveSuperMethodsPassTest() { super(DEFAULT_EXTERNS); enableTypeCheck(); } @Override protected CompilerPass getProcessor(final Compiler compiler) { return new RemoveSuperMethodsPass(compiler); } private void testOptimize(String code) { test(LINE_JOINER.join(BOILERPLATE, code), BOILERPLATE); } private void testNoOptimize(String code) { testSame(LINE_JOINER.join(BOILERPLATE, code)); } public void testOptimize_noArgs() { testOptimize( LINE_JOINER.join( "/** @override */", "Foo.prototype.bar = function() { Foo.superClass_.bar.call(this); };")); } public void testOptimize_baseClassName_noArgs() { testOptimize( LINE_JOINER.join( "/** @override */", "Foo.prototype.bar = function() { FooBase.prototype.bar.call(this) };")); } public void testOptimize_noArgs_namespace() { testOptimize( LINE_JOINER.join( "/** @override */", "ns.Foo.prototype.bar = function() { ns.Foo.superClass_.bar.call(this); };")); } public void testOptimize_noArgs_baseClassName_namespace() { testOptimize( LINE_JOINER.join( "/** @override */", "ns.Foo.prototype.bar = function() { ns.FooBase.prototype.bar.call(this); };")); } public void testOptimize_twoArgs() { testOptimize( LINE_JOINER.join( "/** @override */", "Foo.prototype.baz = function(time, loc) {", " return Foo.superClass_.baz.call(this, time, loc);", "};")); } public void testOptimize_varArgs() { testOptimize( LINE_JOINER.join( "/** @override */", "Foo.prototype.var = function(var_args) {", " return Foo.superClass_.var.call(this, var_args);", "};")); } public void testNoOptimize_numArgsMismatch() { testNoOptimize( LINE_JOINER.join( "/** @override */", "Foo.prototype.buzz = function(time, opt_loc) {", " return Foo.superClass_.buzz.call(this, time);", "};")); } public void testNoOptimize_notBaseClass() { testNoOptimize( LINE_JOINER.join( "/** @override */", "Foo.prototype.bar = function() { FooBaz.prototype.bar.call(this) };")); } public void testNoOptimize_argOrderMismatch() { testNoOptimize( LINE_JOINER.join( "/** @override */", "Foo.prototype.baz = function(time, loc) {", " return Foo.superClass_.baz.call(this, loc, time);", "};")); } public void testNoOptimize_argNameMismatch() { testNoOptimize( LINE_JOINER.join( "/** @override */", "Foo.prototype.baz = function(time, loc) {", " return Foo.superClass_.baz.call(this, time + 2, loc);", "};")); } public void testNoOptimize_methodNameMismatch() { testNoOptimize( LINE_JOINER.join( "/** @override @suppress {checkTypes} */", "Foo.prototype.baz = function(time, loc) {", " return Foo.superClass_.buzz.call(this, time, loc);", "};")); testNoOptimize( LINE_JOINER.join( "Foo.baw = { prototype: { baz: function(time, loc) {} }};", "/** @override */", "Foo.baw.prototype.baz = function(time, loc) {", " return Foo.superClass_.baz.call(this, time, loc);", "};")); testNoOptimize( LINE_JOINER.join( "Foo.prototype.baw = {", " superClass_: { baz: /** @return {number} */ function(time, loc) {} }", "};", "/** @override */", "Foo.prototype.baz = function(time, loc) {", " return Foo.prototype.baw.superClass_.baz.call(this, time, loc);", "};")); testNoOptimize( LINE_JOINER.join( "Foo.prototype.baw = {};", "Foo.prototype.baw.prototype = Foo.prototype;", "/** @override */", "Foo.prototype.baz = function(time, loc) {", " return FooBase.prototype.baw.prototype.baz.call(this, time, loc);", "};")); } public void testNoOptimize_unsound_grandparentClass() { // It's technically unsound to remove a call to the grandparent class's method if that makes the // parent's override to be skipped. testNoOptimize( LINE_JOINER.join( "/** @constructor @extends {Foo} */", "var Bar = function() {}", "/** @override */", "Bar.prototype.baz = function(time, loc) {", " return FooBase.prototype.baz.call(this, time, loc);", "};")); } public void testNoOptimize_moreThanOneStatement() { testNoOptimize( LINE_JOINER.join( "/** @override */", "Foo.prototype.baz = function(time, loc) {", " this.bar();", " return Foo.superClass_.baz.call(this, time, loc);", "};")); } public void testNoOptimize_missingReturn() { testNoOptimize( LINE_JOINER.join( "/** @override */", "Foo.prototype.baz = function(time, loc) {", " FooBase.prototype.baz.call(this, time, loc);", "};")); } public void testNoOptimize_wizaction() { testNoOptimize( LINE_JOINER.join( "/** @override @wizaction */", "Foo.prototype.bar = function() { Foo.superClass_.bar.call(this); };")); } public void testNoOptimize_duplicate() { testSame(ImmutableList.of( SourceFile.fromCode("file1.js", LINE_JOINER.join(BOILERPLATE, "/** @override */", "Foo.prototype.bar = function() { Foo.superClass_.bar.call(this); };")), SourceFile.fromCode("file2.js", LINE_JOINER.join( "/** @override @suppress {duplicate} */", "Foo.prototype.bar = function() { Foo.superClass_.bar.call(this); };")))); } }