/* * Copyright 2014 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.refactoring; import static com.google.common.collect.ObjectArrays.concat; import static com.google.common.truth.Truth.assertThat; import static com.google.javascript.jscomp.CheckLevel.ERROR; import static com.google.javascript.jscomp.CheckLevel.WARNING; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.javascript.jscomp.Compiler; import com.google.javascript.jscomp.CompilerOptions; import com.google.javascript.jscomp.DiagnosticGroups; import com.google.javascript.jscomp.JSError; import com.google.javascript.jscomp.SourceFile; import java.util.Collection; import java.util.List; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Test case for {@link ErrorToFixMapper}. */ @RunWith(JUnit4.class) public class ErrorToFixMapperTest { private static final Joiner LINE_JOINER = Joiner.on('\n'); private FixingErrorManager errorManager; private CompilerOptions options; private Compiler compiler; @Before public void setUp() { errorManager = new FixingErrorManager(); compiler = new Compiler(errorManager); compiler.disableThreads(); errorManager.setCompiler(compiler); options = RefactoringDriver.getCompilerOptions(); options.setWarningLevel(DiagnosticGroups.ANALYZER_CHECKS, WARNING); options.setWarningLevel(DiagnosticGroups.CHECK_VARIABLES, ERROR); options.setWarningLevel(DiagnosticGroups.DEBUGGER_STATEMENT_PRESENT, ERROR); options.setWarningLevel(DiagnosticGroups.LINT_CHECKS, WARNING); options.setWarningLevel(DiagnosticGroups.STRICT_MISSING_REQUIRE, ERROR); options.setWarningLevel(DiagnosticGroups.EXTRA_REQUIRE, ERROR); } @Test public void testDebugger() { String code = LINE_JOINER.join( "function f() {", " debugger;", "}"); String expectedCode = LINE_JOINER.join( "function f() {", " ", "}"); assertChanges(code, expectedCode); } @Test public void testEmptyStatement1() { assertChanges("var x;;", "var x;"); } @Test public void testEmptyStatement2() { assertChanges("var x;;\nvar y;", "var x;\nvar y;"); } @Test public void testEmptyStatement3() { assertChanges("function f() {};\nf();", "function f() {}\nf();"); } @Test public void testImplicitNullability1() { String originalCode = "/** @type {Object} */ var o;"; compiler.compile( ImmutableList.<SourceFile>of(), // Externs ImmutableList.of(SourceFile.fromCode("test", originalCode)), options); assertThat(compiler.getErrors()).isEmpty(); JSError[] warnings = compiler.getWarnings(); assertThat(warnings).hasLength(1); JSError warning = warnings[0]; List<SuggestedFix> fixes = ErrorToFixMapper.getFixesForJsError(warning, compiler); assertThat(fixes).hasSize(2); // First fix is to add "!" String newCode = ApplySuggestedFixes.applySuggestedFixesToCode( ImmutableList.of(fixes.get(0)), ImmutableMap.of("test", originalCode)).get("test"); assertThat(newCode).isEqualTo("/** @type {?Object} */ var o;"); // Second fix is to add "?" newCode = ApplySuggestedFixes.applySuggestedFixesToCode( ImmutableList.of(fixes.get(1)), ImmutableMap.of("test", originalCode)).get("test"); assertThat(newCode).isEqualTo("/** @type {!Object} */ var o;"); } @Test public void testImplicitNullability2() { String originalCode = "/** @param {Object} o */ function f(o) {}"; compiler.compile( ImmutableList.<SourceFile>of(), // Externs ImmutableList.of(SourceFile.fromCode("test", originalCode)), options); assertThat(compiler.getErrors()).isEmpty(); JSError[] warnings = compiler.getWarnings(); assertThat(warnings).hasLength(1); JSError warning = warnings[0]; List<SuggestedFix> fixes = ErrorToFixMapper.getFixesForJsError(warning, compiler); assertThat(fixes).hasSize(2); // First fix is to add "!" String newCode = ApplySuggestedFixes.applySuggestedFixesToCode( ImmutableList.of(fixes.get(0)), ImmutableMap.of("test", originalCode)).get("test"); assertThat(newCode).isEqualTo("/** @param {?Object} o */ function f(o) {}"); // Second fix is to add "?" newCode = ApplySuggestedFixes.applySuggestedFixesToCode( ImmutableList.of(fixes.get(1)), ImmutableMap.of("test", originalCode)).get("test"); assertThat(newCode).isEqualTo("/** @param {!Object} o */ function f(o) {}"); } @Test public void testImplicitNullability3() { String originalCode = LINE_JOINER.join( "/**", " * Some non-ASCII characters: αβγδε", " * @param {Object} o", " */", "function f(o) {}"); compiler.compile( ImmutableList.<SourceFile>of(), // Externs ImmutableList.of(SourceFile.fromCode("test", originalCode)), options); assertThat(compiler.getErrors()).isEmpty(); JSError[] warnings = compiler.getWarnings(); assertThat(warnings).hasLength(1); JSError warning = warnings[0]; List<SuggestedFix> fixes = ErrorToFixMapper.getFixesForJsError(warning, compiler); assertThat(fixes).hasSize(2); // First fix is to add "!" String newCode = ApplySuggestedFixes.applySuggestedFixesToCode( ImmutableList.of(fixes.get(0)), ImmutableMap.of("test", originalCode)).get("test"); String expected = LINE_JOINER.join( "/**", " * Some non-ASCII characters: αβγδε", " * @param {?Object} o", " */", "function f(o) {}"); assertThat(newCode).isEqualTo(expected); // Second fix is to add "?" newCode = ApplySuggestedFixes.applySuggestedFixesToCode( ImmutableList.of(fixes.get(1)), ImmutableMap.of("test", originalCode)).get("test"); expected = LINE_JOINER.join( "/**", " * Some non-ASCII characters: αβγδε", " * @param {!Object} o", " */", "function f(o) {}"); assertThat(newCode).isEqualTo(expected); } @Test public void testRedeclaration() { String code = "function f() { var x; var x; }"; String expectedCode = "function f() { var x; }"; assertChanges(code, expectedCode); } @Test public void testRedeclaration_multipleVars1() { String code = "function f() { var x; var x, y; }"; String expectedCode = "function f() { var x; var y; }"; assertChanges(code, expectedCode); } @Test public void testRedeclaration_multipleVars2() { String code = "function f() { var x; var y, x; }"; String expectedCode = "function f() { var x; var y; }"; assertChanges(code, expectedCode); } @Test public void testRedeclaration_withValue() { String code = LINE_JOINER.join( "function f() {", " var x;", " var x = 0;", "}"); String expectedCode = LINE_JOINER.join( "function f() {", " var x;", " x = 0;", "}"); assertChanges(code, expectedCode); } @Test public void testRedeclaration_multipleVars_withValue1() { String code = LINE_JOINER.join( "function f() {", " var x;", " var x = 0, y;", "}"); String expectedCode = LINE_JOINER.join( "function f() {", " var x;", " x = 0;", "var y;", "}"); assertChanges(code, expectedCode); } @Test public void testRedeclaration_multipleVars_withValue2() { String code = LINE_JOINER.join( "function f() {", " var x;", " var y, x = 0;", "}"); String expectedCode = LINE_JOINER.join( "function f() {", " var x;", " var y;", "x = 0;", "}"); assertChanges(code, expectedCode); } // Make sure the vars stay in the same order, so that in case the get* // functions have side effects, we don't change the order they're called in. @Test public void testRedeclaration_multipleVars_withValue3() { String code = LINE_JOINER.join( "function f() {", " var y;", " var x = getX(), y = getY(), z = getZ();", "}"); String expectedCode = LINE_JOINER.join( "function f() {", " var y;", " var x = getX();", "y = getY();", "var z = getZ();", "}"); assertChanges(code, expectedCode); } @Test public void testRedeclaration_multipleVars_withValue4() { String code = LINE_JOINER.join( "function f() {", " var x;", " var x = getX(), y = getY(), z = getZ();", "}"); String expectedCode = LINE_JOINER.join( "function f() {", " var x;", " x = getX();", "var y = getY(), z = getZ();", "}"); assertChanges(code, expectedCode); } @Test public void testRedeclaration_multipleVars_withValue5() { String code = LINE_JOINER.join( "function f() {", " var z;", " var x = getX(), y = getY(), z = getZ();", "}"); String expectedCode = LINE_JOINER.join( "function f() {", " var z;", " var x = getX(), y = getY();", "z = getZ();", "}"); assertChanges(code, expectedCode); } @Test public void testRedeclarationOfParam() { assertChanges("function f(x) { var x = 3; }", "function f(x) { x = 3; }"); } @Test public void testRedeclaration_params() { assertNoChanges("function f(x, x) {}"); } @Test public void testEarlyReference() { String code = "if (x < 0) alert(1);\nvar x;"; String expectedCode = "var x;\n" + code; assertChanges(code, expectedCode); } @Test public void testEarlyReferenceInFunction() { String code = "function f() {\n if (x < 0) alert(1);\nvar x;\n}"; String expectedCode = "function f() {\n var x;\nif (x < 0) alert(1);\nvar x;\n}"; assertChanges(code, expectedCode); } @Test public void testInsertSemicolon1() { String code = "var x = 3"; String expectedCode = "var x = 3;"; assertChanges(code, expectedCode); } @Test public void testInsertSemicolon2() { String code = "function f() { return 'it' }"; String expectedCode = "function f() { return 'it'; }"; assertChanges(code, expectedCode); } @Test public void testRequiresSorted1() { assertChanges( LINE_JOINER.join( "/**", " * @fileoverview", " * @suppress {extraRequire}", " */", "", "", "goog.require('b');", "goog.require('a');", "goog.require('c');", "", "", "alert(1);"), LINE_JOINER.join( "/**", " * @fileoverview", " * @suppress {extraRequire}", " */", "", "", "goog.require('a');", "goog.require('b');", "goog.require('c');", "", "", "alert(1);")); } @Test public void testRequiresSorted2() { assertChanges( LINE_JOINER.join( "/**", " * @fileoverview", " * @suppress {extraRequire}", " */", "goog.provide('x');", "", "/** @suppress {extraRequire} */", "goog.require('b');", "goog.require('a');", "", "alert(1);"), LINE_JOINER.join( "/**", " * @fileoverview", " * @suppress {extraRequire}", " */", "goog.provide('x');", "", "goog.require('a');", "/** @suppress {extraRequire} */", "goog.require('b');", "", "alert(1);")); } @Test public void testSortRequiresInGoogModule_let() { assertChanges( LINE_JOINER.join( "goog.module('m');", "", "/** @suppress {extraRequire} */", "goog.require('a.c');", "/** @suppress {extraRequire} */", "goog.require('a.b');", "", "let localVar;"), LINE_JOINER.join( "goog.module('m');", "", "/** @suppress {extraRequire} */", "goog.require('a.b');", "/** @suppress {extraRequire} */", "goog.require('a.c');", "", "let localVar;")); } @Test public void testSortRequiresInGoogModule_const() { assertChanges( LINE_JOINER.join( "goog.module('m');", "", "/** @suppress {extraRequire} */", "goog.require('a.c');", "/** @suppress {extraRequire} */", "goog.require('a.b');", "", "const FOO = 0;"), LINE_JOINER.join( "goog.module('m');", "", "/** @suppress {extraRequire} */", "goog.require('a.b');", "/** @suppress {extraRequire} */", "goog.require('a.c');", "", "const FOO = 0;")); } /** * Using this form in a goog.module is a violation of the style guide, but still fairly common. */ @Test public void testSortRequiresInGoogModule_standalone() { assertChanges( LINE_JOINER.join( "/** @fileoverview @suppress {strictModuleChecks} */", "goog.module('m');", "", "goog.require('a.c');", "goog.require('a.b.d');", "goog.require('a.b.c');", "", "alert(a.c());", "alert(a.b.d());", "alert(a.b.c());"), LINE_JOINER.join( "/** @fileoverview @suppress {strictModuleChecks} */", "goog.module('m');", "", "goog.require('a.b.c');", "goog.require('a.b.d');", "goog.require('a.c');", "", "alert(a.c());", "alert(a.b.d());", "alert(a.b.c());")); } @Test public void testSortRequiresInGoogModule_shorthand() { assertChanges( LINE_JOINER.join( "goog.module('m');", "", "var c2 = goog.require('a.c');", "var d = goog.require('a.b.d');", "var c1 = goog.require('a.b.c');", "", "alert(c1());", "alert(d());", "alert(c2());"), LINE_JOINER.join( "goog.module('m');", "", "var c1 = goog.require('a.b.c');", "var c2 = goog.require('a.c');", "var d = goog.require('a.b.d');", "", "alert(c1());", "alert(d());", "alert(c2());")); } @Test public void testSortRequiresInGoogModule_destructuring() { assertChanges( LINE_JOINER.join( "/** @fileoverview @suppress {extraRequire} */", "goog.module('m');", "", "const {fooBar} = goog.require('x');", "const {foo, bar} = goog.require('y');"), LINE_JOINER.join( "/** @fileoverview @suppress {extraRequire} */", "goog.module('m');", "", "const {foo, bar} = goog.require('y');", "const {fooBar} = goog.require('x');")); } @Test public void testSortRequiresInGoogModule_shorthandAndStandalone() { assertChanges( LINE_JOINER.join( "/** @fileoverview @suppress {extraRequire} */", "goog.module('m');", "", "const shorthand2 = goog.require('a');", "goog.require('standalone.two');", "goog.require('standalone.one');", "const shorthand1 = goog.require('b');"), LINE_JOINER.join( "/** @fileoverview @suppress {extraRequire} */", "goog.module('m');", "", "const shorthand1 = goog.require('b');", "const shorthand2 = goog.require('a');", "goog.require('standalone.one');", "goog.require('standalone.two');")); } @Test public void testSortRequiresInGoogModule_allThreeStyles() { assertChanges( LINE_JOINER.join( "/** @fileoverview @suppress {extraRequire} */", "goog.module('m');", "", "const shorthand2 = goog.require('a');", "goog.require('standalone.two');", "const {destructuring} = goog.require('c');", "goog.require('standalone.one');", "const shorthand1 = goog.require('b');"), LINE_JOINER.join( "/** @fileoverview @suppress {extraRequire} */", "goog.module('m');", "", "const shorthand1 = goog.require('b');", "const shorthand2 = goog.require('a');", "const {destructuring} = goog.require('c');", "goog.require('standalone.one');", "goog.require('standalone.two');")); } @Test public void testMissingRequireInGoogProvideFile() { assertChanges( LINE_JOINER.join( "goog.provide('p');", "", "alert(new a.b.C());"), LINE_JOINER.join( "goog.provide('p');", "goog.require('a.b.C');", "", "alert(new a.b.C());")); } @Test public void testMissingRequire_unsorted1() { // Both the fix for requires being unsorted, and the fix for the missing require, are applied. // However, the end result is still out of order. assertChanges( LINE_JOINER.join( "goog.module('module');", "", "const Xray = goog.require('goog.Xray');", "const Anteater = goog.require('goog.Anteater');", "", "alert(new Anteater());", "alert(new Xray());", "alert(new goog.dom.DomHelper());"), LINE_JOINER.join( "goog.module('module');", "", "const DomHelper = goog.require('goog.dom.DomHelper');", "const Anteater = goog.require('goog.Anteater');", "const Xray = goog.require('goog.Xray');", "", "alert(new Anteater());", "alert(new Xray());", "alert(new DomHelper());")); } @Test public void testMissingRequire_unsorted2() { // Both the fix for requires being unsorted, and the fix for the missing require, are applied. // However, the end result is still out of order. assertChanges( LINE_JOINER.join( "goog.module('module');", "", "const DomHelper = goog.require('goog.dom.DomHelper');", "const Anteater = goog.require('goog.Anteater');", "", "alert(new Anteater());", "alert(new goog.rays.Xray());", "alert(new DomHelper());"), LINE_JOINER.join( "goog.module('module');", "const Xray = goog.require('goog.rays.Xray');", "", "const Anteater = goog.require('goog.Anteater');", "const DomHelper = goog.require('goog.dom.DomHelper');", "", "alert(new Anteater());", "alert(new Xray());", "alert(new DomHelper());")); } @Test public void testMissingRequireInGoogModule() { assertChanges( LINE_JOINER.join( "goog.module('m');", "", "alert(new a.b.C());"), LINE_JOINER.join( "goog.module('m');", "const C = goog.require('a.b.C');", "", "alert(new C());")); } @Test public void testMissingRequireInGoogModuleTwice() { assertChanges( LINE_JOINER.join( "goog.module('m');", "", "alert(new a.b.C());", "alert(new a.b.C());"), LINE_JOINER.join( "goog.module('m');", "const C = goog.require('a.b.C');", "", // TODO(tbreisacher): Can we make automatically switch both lines to use 'new C()'? "alert(new a.b.C());", "alert(new C());")); } @Test public void testMissingRequireInGoogModule_call() { assertChanges( LINE_JOINER.join( "goog.module('m');", "", "alert(a.b.c());"), LINE_JOINER.join( "goog.module('m');", "const b = goog.require('a.b');", "", "alert(b.c());")); } @Test public void testMissingRequireInGoogModule_extends() { assertChanges( LINE_JOINER.join( "goog.module('m');", "", "class Cat extends world.util.Animal {}"), LINE_JOINER.join( "goog.module('m');", "const Animal = goog.require('world.util.Animal');", "", "class Cat extends Animal {}")); } @Test public void testMissingRequireInGoogModule_atExtends() { assertChanges( LINE_JOINER.join( "goog.module('m');", "", "/** @constructor @extends {world.util.Animal} */", "function Cat() {}"), LINE_JOINER.join( "goog.module('m');", "const Animal = goog.require('world.util.Animal');", "", // TODO(tbreisacher): Change this to "@extends {Animal}" "/** @constructor @extends {world.util.Animal} */", "function Cat() {}")); } @Test public void testStandaloneVarDoesntCrashMissingRequire() { assertChanges( LINE_JOINER.join( "goog.module('m');", "", "var x;", "", "class Cat extends goog.Animal {}"), LINE_JOINER.join( "goog.module('m');", "const Animal = goog.require('goog.Animal');", "", "var x;", "", "class Cat extends Animal {}")); } @Test public void testAddLhsToGoogRequire() { assertChanges( LINE_JOINER.join( "goog.module('m');", "", "goog.require('world.util.Animal');", "", "class Cat extends world.util.Animal {}"), LINE_JOINER.join( "goog.module('m');", "", "const Animal = goog.require('world.util.Animal');", "", "class Cat extends Animal {}")); } @Test public void testAddLhsToGoogRequire_new() { assertChanges( LINE_JOINER.join( "goog.module('m');", "", "goog.require('world.util.Animal');", "", "let cat = new world.util.Animal();"), LINE_JOINER.join( "goog.module('m');", "", "const Animal = goog.require('world.util.Animal');", "", "let cat = new Animal();")); } @Test public void testAddLhsToGoogRequire_getprop() { assertChanges( LINE_JOINER.join( "goog.module('m');", "", "goog.require('magical.factories');", "goog.require('world.util.AnimalType');", "", "let cat = magical.factories.createAnimal(world.util.AnimalType.CAT);"), LINE_JOINER.join( "goog.module('m');", "", "const factories = goog.require('magical.factories');", "const AnimalType = goog.require('world.util.AnimalType');", "", "let cat = factories.createAnimal(AnimalType.CAT);")); } @Test public void testAddLhsToGoogRequire_jsdoc() { // TODO(tbreisacher): Add "const Animal = " before the goog.require and change // world.util.Animal to Animal assertNoChanges( LINE_JOINER.join( "goog.module('m');", "", "goog.require('world.util.Animal');", "", "/** @type {world.util.Animal} */", "var cat;")); } @Test public void testSwitchToShorthand_JSDoc1() { assertChanges( LINE_JOINER.join( "goog.module('m');", "var Animal = goog.require('world.util.Animal');", "", "/** @constructor @implements {world.util.Animal} */", "function Cat() {}"), LINE_JOINER.join( "goog.module('m');", "var Animal = goog.require('world.util.Animal');", "", "/** @constructor @implements {Animal} */", "function Cat() {}")); } @Test public void testSwitchToShorthand_JSDoc2() { assertChanges( LINE_JOINER.join( "goog.module('m');", "var Animal = goog.require('world.util.Animal');", "", "/** @constructor @extends {world.util.Animal} */", "function Cat() {}"), LINE_JOINER.join( "goog.module('m');", "var Animal = goog.require('world.util.Animal');", "", "/** @constructor @extends {Animal} */", "function Cat() {}")); } @Test public void testSwitchToShorthand_JSDoc3() { assertChanges( LINE_JOINER.join( "goog.module('m');", "var Animal = goog.require('world.util.Animal');", "", "/** @type {world.util.Animal} */", "var animal;"), LINE_JOINER.join( "goog.module('m');", "var Animal = goog.require('world.util.Animal');", "", "/** @type {Animal} */", "var animal;")); } @Test public void testSwitchToShorthand_JSDoc4() { assertChanges( LINE_JOINER.join( "goog.module('m');", "var Animal = goog.require('world.util.Animal');", "", "/** @type {!world.util.Animal} */", "var animal;"), LINE_JOINER.join( "goog.module('m');", "var Animal = goog.require('world.util.Animal');", "", "/** @type {!Animal} */", "var animal;")); } @Test public void testSwitchToShorthand_JSDoc5() { assertChanges( LINE_JOINER.join( "goog.module('m');", "var Animal = goog.require('world.util.Animal');", "", "/** @type {?world.util.Animal} */", "var animal;"), LINE_JOINER.join( "goog.module('m');", "var Animal = goog.require('world.util.Animal');", "", "/** @type {?Animal} */", "var animal;")); } @Test public void testSwitchToShorthand_JSDoc6() { assertChanges( LINE_JOINER.join( "goog.module('m');", "var Animal = goog.require('world.util.Animal');", "", "/** @type {?Array<world.util.Animal>} */", "var animals;"), LINE_JOINER.join( "goog.module('m');", "var Animal = goog.require('world.util.Animal');", "", "/** @type {?Array<Animal>} */", "var animals;")); } @Test public void testSwitchToShorthand_JSDoc7() { assertChanges( LINE_JOINER.join( "goog.module('m');", "var Animal = goog.require('world.util.Animal');", "", "/** @type {?Array<world.util.Animal.Turtle>} */", "var turtles;"), LINE_JOINER.join( "goog.module('m');", "var Animal = goog.require('world.util.Animal');", "", "/** @type {?Array<Animal.Turtle>} */", "var turtles;")); } @Test public void testSwitchToShorthand_JSDoc8() { assertChanges( LINE_JOINER.join( "goog.module('m');", "var AnimalAltName = goog.require('world.util.Animal');", "", "/** @type {?Array<world.util.Animal.Turtle>} */", "var turtles;"), LINE_JOINER.join( "goog.module('m');", "var AnimalAltName = goog.require('world.util.Animal');", "", "/** @type {?Array<AnimalAltName.Turtle>} */", "var turtles;")); } @Test public void testMissingRequireInGoogModule_atExtends_qname() { assertChanges( LINE_JOINER.join( "goog.module('m');", "", "/** @constructor @extends {world.util.Animal} */", "world.util.Cat = function() {};"), LINE_JOINER.join( "goog.module('m');", "const Animal = goog.require('world.util.Animal');", "", // TODO(tbreisacher): Change this to "@extends {Animal}" "/** @constructor @extends {world.util.Animal} */", "world.util.Cat = function() {};")); } @Test public void testMissingRequireInGoogModule_googString() { assertChanges( LINE_JOINER.join( "goog.module('m');", "", "alert(goog.string.trim(' str '));"), LINE_JOINER.join( "goog.module('m');", "const googString = goog.require('goog.string');", "", "alert(googString.trim(' str '));")); } @Test public void testMissingRequireInGoogModule_googStructsMap() { assertChanges( LINE_JOINER.join( "goog.module('m');", "", "alert(new goog.structs.Map());"), LINE_JOINER.join( "goog.module('m');", "const StructsMap = goog.require('goog.structs.Map');", "", "alert(new StructsMap());")); } @Test public void testMissingRequireInGoogModule_insertedInCorrectOrder() { assertChanges( LINE_JOINER.join( "goog.module('m');", "", "const A = goog.require('a.A');", "const C = goog.require('c.C');", "", "alert(new A(new x.B(new C())));"), LINE_JOINER.join( "goog.module('m');", "", // Requires are sorted by the short name, not the full namespace. "const A = goog.require('a.A');", "const B = goog.require('x.B');", "const C = goog.require('c.C');", "", "alert(new A(new B(new C())));")); } @Test public void testMissingRequireInGoogModule_alwaysInsertsConst() { assertChanges( LINE_JOINER.join( "goog.module('m');", "", "var A = goog.require('a.A');", "var C = goog.require('c.C');", "", "alert(new A(new x.B(new C())));"), LINE_JOINER.join( "goog.module('m');", "", "var A = goog.require('a.A');", "const B = goog.require('x.B');", "var C = goog.require('c.C');", "", "alert(new A(new B(new C())));")); } @Test public void testSortShorthandRequiresInGoogModule() { assertChanges( LINE_JOINER.join( "goog.module('m');", "", "var B = goog.require('x.B');", "var A = goog.require('a.A');", "var C = goog.require('c.C');", "", "alert(new A(new B(new C())));"), LINE_JOINER.join( "goog.module('m');", "", // Requires are sorted by the short name, not the full namespace. "var A = goog.require('a.A');", "var B = goog.require('x.B');", "var C = goog.require('c.C');", "", "alert(new A(new B(new C())));")); } @Test public void testShortRequireInGoogModule1() { assertChanges( LINE_JOINER.join( "goog.module('m');", "", "var c = goog.require('a.b.c');", "", "alert(a.b.c);"), LINE_JOINER.join( "goog.module('m');", "", "var c = goog.require('a.b.c');", "", "alert(c);")); } @Test public void testShortRequireInGoogModule2() { assertChanges( LINE_JOINER.join( "goog.module('m');", "", "var Classname = goog.require('a.b.Classname');", "", "alert(a.b.Classname.instance_.foo());"), LINE_JOINER.join( "goog.module('m');", "", "var Classname = goog.require('a.b.Classname');", "", "alert(Classname.instance_.foo());")); } @Test public void testShortRequireInGoogModule3() { assertChanges( LINE_JOINER.join( "goog.module('m');", "", "var Classname = goog.require('a.b.Classname');", "", "alert(a.b.Classname.INSTANCE_.foo());"), LINE_JOINER.join( "goog.module('m');", "", "var Classname = goog.require('a.b.Classname');", "", "alert(Classname.INSTANCE_.foo());")); } @Test public void testProvidesSorted1() { assertChanges( LINE_JOINER.join( "/** @fileoverview foo */", "", "", "goog.provide('b');", "goog.provide('a');", "goog.provide('c');", "", "", "alert(1);"), LINE_JOINER.join( "/** @fileoverview foo */", "", "", "goog.provide('a');", "goog.provide('b');", "goog.provide('c');", "", "", "alert(1);")); } @Test public void testExtraRequire() { assertChanges( LINE_JOINER.join( "goog.require('goog.object');", "goog.require('goog.string');", "", "alert(goog.string.parseInt('7'));"), LINE_JOINER.join( "goog.require('goog.string');", "", "alert(goog.string.parseInt('7'));")); } @Test public void testExtraRequire_module() { assertChanges( LINE_JOINER.join( "goog.module('x');", "", "const googString = goog.require('goog.string');", "const object = goog.require('goog.object');", "alert(googString.parseInt('7'));"), LINE_JOINER.join( "goog.module('x');", "", "const googString = goog.require('goog.string');", "alert(googString.parseInt('7'));")); } @Test public void testExtraRequire_destructuring1() { assertChanges( LINE_JOINER.join( "goog.module('x');", "", "const {foo, bar} = goog.require('goog.util');", "", "alert(bar(7));"), LINE_JOINER.join( "goog.module('x');", "", "const {bar} = goog.require('goog.util');", "", "alert(bar(7));")); } @Test public void testExtraRequire_destructuring2() { assertChanges( LINE_JOINER.join( "goog.module('x');", "", "const {foo, bar} = goog.require('goog.util');", "", "alert(foo(7));"), LINE_JOINER.join( "goog.module('x');", "", "const {foo} = goog.require('goog.util');", "", "alert(foo(7));")); } @Test public void testExtraRequire_destructuring3() { assertChanges( LINE_JOINER.join( "goog.module('x');", "", "const {foo, bar, qux} = goog.require('goog.util');", "", "alert(foo(qux(7)));"), LINE_JOINER.join( "goog.module('x');", "", "const {foo, qux} = goog.require('goog.util');", "", "alert(foo(qux(7)));")); } /** * Because of overlapping replacements, it takes two runs to fully fix this case. */ @Test public void testExtraRequire_destructuring4a() { assertChanges( LINE_JOINER.join( "goog.module('x');", "", "const {foo, bar, qux} = goog.require('goog.util');"), LINE_JOINER.join( "goog.module('x');", "", "const {qux} = goog.require('goog.util');")); } @Test public void testExtraRrequire_destructuring4b() { assertChanges( LINE_JOINER.join( "goog.module('x');", "", "const {qux} = goog.require('goog.util');"), LINE_JOINER.join( "goog.module('x');", "", "const {} = goog.require('goog.util');")); } @Test public void testExtraRequire_destructuring5() { assertChanges( LINE_JOINER.join( "goog.module('x');", "", "const {foo: googUtilFoo, bar} = goog.require('goog.util');", "", "alert(bar(7));"), LINE_JOINER.join( "goog.module('x');", "", "const {bar} = goog.require('goog.util');", "", "alert(bar(7));")); } @Test public void testExtraRequire_destructuring6() { assertChanges( LINE_JOINER.join( "goog.module('x');", "", "const {foo: googUtilFoo, bar} = goog.require('goog.util');", "", "alert(googUtilFoo(7));"), LINE_JOINER.join( "goog.module('x');", "", "const {foo: googUtilFoo} = goog.require('goog.util');", "", "alert(googUtilFoo(7));")); } @Test public void testExtraRequire_destructuring7() { assertChanges( LINE_JOINER.join( "goog.module('x');", "", "const {foo: googUtilFoo} = goog.require('goog.util');"), LINE_JOINER.join( "goog.module('x');", "", "const {} = goog.require('goog.util');")); } @Test public void testExtraRequire_destructuring_empty() { assertChanges( LINE_JOINER.join( "goog.module('x');", "", "const {} = goog.require('goog.util');"), LINE_JOINER.join( "goog.module('x');", "", "")); } @Test public void testExtraRequire_unsorted() { // There is also a warning because requires are not sorted. That one is not fixed because // the fix would conflict with the extra-require fix. assertChanges( LINE_JOINER.join( "goog.require('goog.string');", "goog.require('goog.object');", "goog.require('goog.dom');", "", "alert(goog.string.parseInt('7'));", "alert(goog.dom.createElement('div'));"), LINE_JOINER.join( "goog.require('goog.string');", "goog.require('goog.dom');", "", "alert(goog.string.parseInt('7'));", "alert(goog.dom.createElement('div'));")); } @Test public void testDuplicateRequire() { assertChanges( LINE_JOINER.join( "goog.require('goog.string');", "goog.require('goog.string');", "", "alert(goog.string.parseInt('7'));"), LINE_JOINER.join( "goog.require('goog.string');", "", "alert(goog.string.parseInt('7'));")); } @Test public void testDuplicateRequire_shorthand() { // We could provide a fix here, eliminating the later require. But then we'd need to switch // str to googString in the last line. Probably not worth it. assertNoChanges( LINE_JOINER.join( "const googString = goog.require('goog.string');", "const str = goog.require('goog.string');", "", "alert(googString.parseInt('7'));", "alert(str.parseInt('8'));")); } private void assertChanges(String originalCode, String expectedCode) { compiler.compile( ImmutableList.<SourceFile>of(), // Externs ImmutableList.of(SourceFile.fromCode("test", originalCode)), options); JSError[] warningsAndErrors = concat(compiler.getWarnings(), compiler.getErrors(), JSError.class); assertThat(warningsAndErrors).named("warnings/errors").isNotEmpty(); Collection<SuggestedFix> fixes = errorManager.getAllFixes(); assertThat(fixes).named("fixes").isNotEmpty(); String newCode = ApplySuggestedFixes.applySuggestedFixesToCode( fixes, ImmutableMap.of("test", originalCode)).get("test"); assertThat(newCode).isEqualTo(expectedCode); } protected void assertNoChanges(String originalCode) { compiler.compile( ImmutableList.<SourceFile>of(), // Externs ImmutableList.of(SourceFile.fromCode("test", originalCode)), options); Collection<SuggestedFix> fixes = errorManager.getAllFixes(); assertThat(fixes).isEmpty(); } }