/* * 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.truth.Truth.assertThat; import static com.google.javascript.refactoring.SuggestedFix.getShortNameForRequire; import static com.google.javascript.refactoring.testing.SuggestedFixes.assertChanges; import static com.google.javascript.refactoring.testing.SuggestedFixes.assertReplacement; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.SetMultimap; import com.google.javascript.jscomp.Compiler; import com.google.javascript.jscomp.CompilerOptions; import com.google.javascript.jscomp.SourceFile; import com.google.javascript.refactoring.SuggestedFix.MatchedNodeInfo; import com.google.javascript.rhino.IR; import com.google.javascript.rhino.Node; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Unit tests for JsFlume {@link SuggestedFix}. * * @author mknichel@google.com (Mark Knichel) */ @RunWith(JUnit4.class) public class SuggestedFixTest { @Test public void testInsertBefore() { String before = "var someRandomCode = {};"; String after = "/** some comment */\ngoog.foo();"; Compiler compiler = getCompiler(before + after); Node root = compileToScriptRoot(compiler); Node newNode = IR.exprResult(IR.call( IR.getprop(IR.name("goog2"), IR.string("get")), IR.string("service"))); SuggestedFix fix = new SuggestedFix.Builder() .insertBefore(root.getLastChild().getFirstChild(), newNode, compiler) .build(); CodeReplacement replacement = CodeReplacement.create(before.length(), 0, "goog2.get('service');\n"); assertReplacement(fix, replacement); } @Test public void testDelete() { String input = "var foo = new Bar();"; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .delete(root.getFirstChild()) .build(); CodeReplacement replacement = CodeReplacement.create(0, input.length(), ""); assertReplacement(fix, replacement); } @Test public void testDelete_spaceBeforeNode() { String before = "var foo = new Bar();"; String after = "\n\nvar baz = new Baz();"; String input = before + after; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .delete(root.getLastChild()) .build(); CodeReplacement replacement = CodeReplacement.create(before.length(), after.length(), ""); assertReplacement(fix, replacement); } @Test public void testDelete_dontDeleteSpaceBeforeNode() { String before = "var foo = new Bar();\n\n"; String after = "var baz = new Baz();"; String input = before + after; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .deleteWithoutRemovingWhitespaceBefore(root.getLastChild()) .build(); CodeReplacement replacement = CodeReplacement.create(before.length(), after.length(), ""); assertReplacement(fix, replacement); } @Test public void testDelete_multipleVarDeclaration() { String input = "var foo = 3, bar, baz;"; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); // Delete the 1st variable on the line. Make sure the deletion includes the assignment and the // trailing comma. SuggestedFix fix = new SuggestedFix.Builder() .delete(root.getFirstFirstChild()) .build(); CodeReplacement replacement = CodeReplacement.create(4, "foo = 3, ".length(), ""); assertReplacement(fix, replacement); // Delete the 2nd variable. fix = new SuggestedFix.Builder() .delete(root.getFirstChild().getSecondChild()) .build(); replacement = CodeReplacement.create(13, "bar, ".length(), ""); assertReplacement(fix, replacement); // Delete the last variable. Make sure it removes the leading comma. fix = new SuggestedFix.Builder() .delete(root.getFirstChild().getLastChild()) .build(); replacement = CodeReplacement.create(16, ", baz".length(), ""); assertReplacement(fix, replacement); } @Test public void testDelete_multipleLetDeclaration() { String input = "let foo = 3, bar, baz;"; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); // Delete the 1st variable on the line. Make sure the deletion includes the assignment and the // trailing comma. SuggestedFix fix = new SuggestedFix.Builder() .delete(root.getFirstFirstChild()) .build(); CodeReplacement replacement = CodeReplacement.create(4, "foo = 3, ".length(), ""); assertReplacement(fix, replacement); // Delete the 2nd variable. fix = new SuggestedFix.Builder() .delete(root.getFirstChild().getSecondChild()) .build(); replacement = CodeReplacement.create(13, "bar, ".length(), ""); assertReplacement(fix, replacement); // Delete the last variable. Make sure it removes the leading comma. fix = new SuggestedFix.Builder() .delete(root.getFirstChild().getLastChild()) .build(); replacement = CodeReplacement.create(16, ", baz".length(), ""); assertReplacement(fix, replacement); } @Test public void testDelete_multipleConstDeclaration() { String input = "const foo = 3, bar = 4, baz = 5;"; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); // Delete the 1st variable on the line. Make sure the deletion includes the assignment and the // trailing comma. SuggestedFix fix = new SuggestedFix.Builder() .delete(root.getFirstFirstChild()) .build(); CodeReplacement replacement = CodeReplacement.create(6, "foo = 3, ".length(), ""); assertReplacement(fix, replacement); // Delete the 2nd variable. fix = new SuggestedFix.Builder() .delete(root.getFirstChild().getSecondChild()) .build(); replacement = CodeReplacement.create(15, "bar = 4, ".length(), ""); assertReplacement(fix, replacement); // Delete the last variable. Make sure it removes the leading comma. fix = new SuggestedFix.Builder() .delete(root.getFirstChild().getLastChild()) .build(); replacement = CodeReplacement.create(22, ", baz = 5".length(), ""); assertReplacement(fix, replacement); } @Test public void testRenameStringKey() { String input = "var obj = {foo: 'bar'};"; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); Node node = root.getFirstFirstChild().getFirstFirstChild(); SuggestedFix fix = new SuggestedFix.Builder() .rename(node, "fooBar") .build(); CodeReplacement replacement = CodeReplacement.create(11, "foo".length(), "fooBar"); assertReplacement(fix, replacement); } @Test public void testRenameProperty_justPropertyName() { String input = "obj.test.property"; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .rename(root.getFirstFirstChild(), "renamedProperty") .build(); CodeReplacement replacement = CodeReplacement.create(9, "property".length(), "renamedProperty"); assertReplacement(fix, replacement); } @Test public void testRenameProperty_entireName() { String input = "obj.test.property"; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .rename(root.getFirstFirstChild(), "renamedProperty", true) .build(); CodeReplacement replacement = CodeReplacement.create(0, input.length(), "renamedProperty"); assertReplacement(fix, replacement); } @Test public void testRenameFunction_justFunctionName() { String input = "obj.fnCall();"; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .rename(root.getFirstFirstChild(), "renamedFnCall") .build(); CodeReplacement replacement = CodeReplacement.create(4, "fnCall".length(), "renamedFnCall"); assertReplacement(fix, replacement); } @Test public void testRenameFunction_entireName() { String fnName = "goog.dom.classes.add"; String newFnName = "goog.dom.classlist.add"; String input = fnName + "(foo, bar);"; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .rename(root.getFirstFirstChild(), newFnName, true) .build(); CodeReplacement replacement = CodeReplacement.create(0, fnName.length(), newFnName); assertReplacement(fix, replacement); } @Test public void testRenameTaggedTemplate_justFunctionName() { String prefix = "prt."; String fnName = "oldTaggedTemp"; String newFnName = "newTaggedTemp"; String input = prefix + fnName + "`${Foo}Bar`;"; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .rename(root.getFirstFirstChild(), newFnName) .build(); CodeReplacement replacement = CodeReplacement.create(prefix.length(), fnName.length(), newFnName); assertReplacement(fix, replacement); } @Test public void testRenameTaggedTemplate_entireName() { String fnName = "goog.dom.classes.oldTaggedTemp"; String newFnName = "goog.dom.classesList.newTaggedTemp"; String input = fnName + "`${Foo}Bar`;"; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .rename(root.getFirstFirstChild(), newFnName, true) .build(); CodeReplacement replacement = CodeReplacement.create(0, fnName.length(), newFnName); assertReplacement(fix, replacement); } @Test public void testReplace() { String before = "var someRandomCode = {};\n/** some comment */\n"; String after = "goog.foo();"; Compiler compiler = getCompiler(before + after); Node root = compileToScriptRoot(compiler); Node newNode = IR.exprResult(IR.call( IR.getprop(IR.name("goog2"), IR.string("get")), IR.string("service"))); SuggestedFix fix = new SuggestedFix.Builder() .replace(root.getLastChild().getFirstChild(), newNode, compiler) .build(); CodeReplacement replacement = CodeReplacement.create(before.length(), after.length(), "goog2.get('service');"); assertReplacement(fix, replacement); } @Test public void testReplace_functionArgument() { String before = "" + "var MyClass = function() {};\n" + "MyClass.prototype.foo = function() {};\n" + "MyClass.prototype.bar = function() {};\n" + "var clazz = new MyClass();\n"; String after = "alert(clazz.foo());"; Compiler compiler = getCompiler(before + after); Node root = compileToScriptRoot(compiler); Node newNode = IR.call(IR.getprop(IR.name("clazz"), IR.string("bar"))); SuggestedFix fix = new SuggestedFix.Builder() .replace(root.getLastChild().getFirstChild().getLastChild(), newNode, compiler) .build(); CodeReplacement replacement = CodeReplacement.create( before.length() + "alert(".length(), "clazz.foo()".length(), "clazz.bar()"); assertReplacement(fix, replacement); } @Test public void testReplace_leftHandSideAssignment() { String before = "var MyClass = function() {};\n"; String after = "MyClass.prototype.foo = function() {};\n"; Compiler compiler = getCompiler(before + after); Node root = compileToScriptRoot(compiler); Node newNode = IR.getprop( IR.getprop(IR.name("MyClass"), IR.string("prototype")), IR.string("bar")); SuggestedFix fix = new SuggestedFix.Builder() .replace(root.getLastChild().getFirstFirstChild(), newNode, compiler) .build(); CodeReplacement replacement = CodeReplacement.create( before.length(), "MyClass.prototype.foo".length(), "MyClass.prototype.bar"); assertReplacement(fix, replacement); } @Test public void testAddCast() { String input = "obj.fnCall();"; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); Node objNode = root.getFirstFirstChild().getFirstFirstChild(); SuggestedFix fix = new SuggestedFix.Builder() .addCast(objNode, compiler, "FooBar") .build(); CodeReplacement replacement = CodeReplacement.create(0, "obj".length(), "/** @type {FooBar} */ (obj)"); assertReplacement(fix, replacement); } @Test public void testRemoveCast() { String input = "var x = /** @type {string} */ (y);"; String expectedCode = "var x = y;"; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); Node castNode = root.getFirstFirstChild().getFirstChild(); assertTrue(castNode.isCast()); SuggestedFix fix = new SuggestedFix.Builder() .removeCast(castNode, compiler) .build(); assertChanges(fix, "", input, expectedCode); } @Test public void testRemoveCast_complexStatement() { String input = "" + "var x = /** @type {string} */ (function() {\n" + " // Inline comment that should be preserved.\n" + " var blah = bleh;\n" + "});"; String expectedCode = "" + "var x = function() {\n" + " // Inline comment that should be preserved.\n" + " var blah = bleh;\n" + "};"; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); Node castNode = root.getFirstFirstChild().getFirstChild(); assertTrue(castNode.isCast()); SuggestedFix fix = new SuggestedFix.Builder() .removeCast(castNode, compiler) .build(); assertChanges(fix, "", input, expectedCode); } @Test public void testRemoveCast_return() { String input = Joiner.on('\n').join( "function f() {", " return /** @type {string} */ (", " 'I am obviously a string. Why are you casting me?');", "}"); String expectedCode = Joiner.on('\n').join( "function f() {", " return 'I am obviously a string. Why are you casting me?';", "}"); Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); Node castNode = root.getFirstChild().getLastChild().getFirstChild().getLastChild(); assertTrue(castNode.isCast()); SuggestedFix fix = new SuggestedFix.Builder() .removeCast(castNode, compiler) .build(); assertChanges(fix, "", input, expectedCode); } @Test public void testChangeJsDocType() { String before = "/** "; String after = "@type {Foo} */\nvar foo = new Foo()"; Compiler compiler = getCompiler(before + after); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .changeJsDocType(root.getFirstChild(), compiler, "Object") .build(); CodeReplacement replacement = CodeReplacement.create(before.length(), "@type {Foo}".length(), "@type {Object}"); assertReplacement(fix, replacement); } @Test public void testChangeJsDocType2() { String code = "/** @type {Foo} */\nvar foo = new Foo()"; Compiler compiler = getCompiler(code); Node root = compileToScriptRoot(compiler); Node varNode = root.getFirstChild(); Node jsdocRoot = Iterables.getOnlyElement(varNode.getJSDocInfo().getTypeNodes()); SuggestedFix fix = new SuggestedFix.Builder() .insertBefore(jsdocRoot, "!") .build(); CodeReplacement replacement = CodeReplacement.create("/** @type {".length(), 0, "!"); assertReplacement(fix, replacement); } @Test public void testChangeJsDocType_packageType1() { String before = "/** "; String after = "@package {Foo} */\nvar foo = new Foo()"; Compiler compiler = getCompiler(before + after); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .changeJsDocType(root.getFirstChild(), compiler, "Object") .build(); CodeReplacement replacement = CodeReplacement.create(before.length(), "@package {Foo}".length(), "@package {Object}"); assertReplacement(fix, replacement); } @Test public void testChangeJsDocType_privateType1() { String before = "/** "; String after = "@private {Foo} */\nvar foo = new Foo()"; Compiler compiler = getCompiler(before + after); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .changeJsDocType(root.getFirstChild(), compiler, "Object") .build(); CodeReplacement replacement = CodeReplacement.create(before.length(), "@private {Foo}".length(), "@private {Object}"); assertReplacement(fix, replacement); } @Test public void testChangeJsDocType_privateType2() { String before = "/** @private "; String after = "@const {Foo} */\nvar foo = new Foo()"; Compiler compiler = getCompiler(before + after); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .changeJsDocType(root.getFirstChild(), compiler, "Object") .build(); CodeReplacement replacement = CodeReplacement.create(before.length(), "@const {Foo}".length(), "@const {Object}"); assertReplacement(fix, replacement); } @Test public void testChangeJsDocType_privateType3() { String before = "/** @private "; String after = "@const {Foo} */\nvar foo = new Foo()"; Compiler compiler = getCompiler(before + after); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .changeJsDocType(root.getFirstChild(), compiler, "Object") .build(); CodeReplacement replacement = CodeReplacement.create(before.length(), "@const {Foo}".length(), "@const {Object}"); assertReplacement(fix, replacement); } @Test public void testChangeJsDocType_privateType4() { String before = "/** "; String after = "@const {Foo} */\nvar foo = new Foo()"; Compiler compiler = getCompiler(before + after); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .changeJsDocType(root.getFirstChild(), compiler, "Object") .build(); CodeReplacement replacement = CodeReplacement.create(before.length(), "@const {Foo}".length(), "@const {Object}"); assertReplacement(fix, replacement); } @Test public void testInsertArguments() { String before = "goog.dom.classes.add("; String after = "foo, bar);"; Compiler compiler = getCompiler(before + after); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .insertArguments(root.getFirstFirstChild(), 0, "baz") .build(); CodeReplacement replacement = CodeReplacement.create(before.length(), 0, "baz, "); assertReplacement(fix, replacement); } @Test public void testInsertArguments_emptyArguments() { String before = "goog.dom.classes.add("; String after = ");"; Compiler compiler = getCompiler(before + after); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .insertArguments(root.getFirstFirstChild(), 0, "baz") .build(); CodeReplacement replacement = CodeReplacement.create(before.length(), 0, "baz"); assertReplacement(fix, replacement); } @Test public void testInsertArguments_notFirstArgument() { String before = "goog.dom.classes.add(foo, "; String after = "bar);"; Compiler compiler = getCompiler(before + after); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .insertArguments(root.getFirstFirstChild(), 1, "baz") .build(); CodeReplacement replacement = CodeReplacement.create(before.length(), 0, "baz, "); assertReplacement(fix, replacement); } @Test public void testInsertArguments_lastArgument() { String before = "goog.dom.classes.add(foo, bar"; String after = ");"; Compiler compiler = getCompiler(before + after); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .insertArguments(root.getFirstFirstChild(), 2, "baz") .build(); CodeReplacement replacement = CodeReplacement.create(before.length(), 0, ", baz"); assertReplacement(fix, replacement); } @Test public void testInsertArguments_beforeCastInArguments() { String originalCode = "goog.dom.classes.add(foo, /** @type {String} */ (bar));"; String expectedCode = "goog.dom.classes.add(foo, baz, /** @type {String} */ (bar));"; Compiler compiler = getCompiler(originalCode); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .insertArguments(root.getFirstFirstChild(), 1, "baz") .build(); assertChanges(fix, "", originalCode, expectedCode); } @Test public void testInsertArguments_afterCastInArguments() { String originalCode = "goog.dom.classes.add(foo, /** @type {String} */ (bar));"; String expectedCode = "goog.dom.classes.add(foo, /** @type {String} */ (bar), baz);"; Compiler compiler = getCompiler(originalCode); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .insertArguments(root.getFirstFirstChild(), 2, "baz") .build(); assertChanges(fix, "", originalCode, expectedCode); } @Test public void testDeleteArgumentFirst() { String originalCode = "f(a, b, c);"; String expectedCode = "f(b, c);"; Compiler compiler = getCompiler(originalCode); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .deleteArgument(root.getFirstFirstChild(), 0) .build(); assertChanges(fix, "", originalCode, expectedCode); } @Test public void testDeleteArgumentMiddle() { String originalCode = "f(a, b, c);"; String expectedCode = "f(a, c);"; Compiler compiler = getCompiler(originalCode); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .deleteArgument(root.getFirstFirstChild(), 1) .build(); assertChanges(fix, "", originalCode, expectedCode); } @Test public void testDeleteArgumentLast() { String originalCode = "f(a, b, c);"; String expectedCode = "f(a, b);"; Compiler compiler = getCompiler(originalCode); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .deleteArgument(root.getFirstFirstChild(), 2) .build(); assertChanges(fix, "", originalCode, expectedCode); } @Test public void testDeleteFirstArgumentWithPrefixComment() { String originalCode = "f(/** @type {number} */ (a), b, c);"; String expectedCode = "f(b, c);"; Compiler compiler = getCompiler(originalCode); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .deleteArgument(root.getFirstFirstChild(), 0) .build(); assertChanges(fix, "", originalCode, expectedCode); } @Test public void testDeleteFirstArgumentWithPostfixComment() { String originalCode = "f(a /** foo */, b, c);"; String expectedCode = "f(b, c);"; Compiler compiler = getCompiler(originalCode); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .deleteArgument(root.getFirstFirstChild(), 0) .build(); assertChanges(fix, "", originalCode, expectedCode); } @Test public void testDeleteArgumentWithPrefixComment() { String originalCode = "f(a, /** @type {number} */ (b), c);"; String expectedCode = "f(a, c);"; Compiler compiler = getCompiler(originalCode); Node root = compileToScriptRoot(compiler); SuggestedFix fix = new SuggestedFix.Builder() .deleteArgument(root.getFirstFirstChild(), 1) .build(); assertChanges(fix, "", originalCode, expectedCode); } @Test public void testDeleteArgumentIndexTooLarge() { String fnCall = "f(a, b, c);"; Compiler compiler = getCompiler(fnCall); Node root = compileToScriptRoot(compiler); try { new SuggestedFix.Builder() .deleteArgument(root.getFirstFirstChild(), 3) .build(); fail("An exception should have been thrown for an invalid index"); } catch (IllegalArgumentException e) { // Success, an exception was thrown for an invalid index. } } @Test public void testDeleteArgumentIndexTooSmall() { String fnCall = "f(a);"; Compiler compiler = getCompiler(fnCall); Node root = compileToScriptRoot(compiler); try { new SuggestedFix.Builder() .deleteArgument(root.getFirstFirstChild(), -1) .build(); fail("An exception should have been thrown for an invalid index"); } catch (IllegalArgumentException e) { // Success, an exception was thrown for an invalid index. } } @Test public void testDeleteArgumentWithNoArguments() { String fnCall = "f();"; Compiler compiler = getCompiler(fnCall); Node root = compileToScriptRoot(compiler); try { new SuggestedFix.Builder() .deleteArgument(root.getFirstFirstChild(), 0) .build(); fail("An exception should have been thrown for an invalid index"); } catch (IllegalStateException e) { // Success, an exception was thrown for an invalid index. } } @Test public void testAddGoogRequire_var() { String before = "goog.provide('js.Foo');\n"; String after = Joiner.on('\n').join( "goog.require('js.Bar');", "", "var x;", "/** @private */", "function foo_() {};"); assertAddGoogRequire(before, after, "abc.def"); } @Test public void testAddGoogRequire() { String before = "goog.provide('js.Foo');\n"; String after = "goog.require('js.Bar');\n" + "\n" + "/** @private */\n" + "function foo_() {};\n"; assertAddGoogRequire(before, after, "abc.def"); } @Test public void testAddGoogRequire_afterAllOtherGoogRequires() { String before = "goog.provide('js.Foo');\n" + "goog.require('js.Bar');\n"; String after = "\n" + "/** @private */\n" + "function foo_() {};\n"; assertAddGoogRequire(before, after, "zyx.w"); } @Test public void testAddGoogRequire_noGoogRequire() { String before = "goog.provide('js.Foo');\n"; String after = "\n" + "/** @private */\n" + "function foo_() {};\n"; assertAddGoogRequire(before, after, "abc.def"); } @Test public void testAddGoogRequire_noGoogRequireOrGoogProvide() { String before = ""; String after = "/** @private */\n" + "function foo_() {};\n"; assertAddGoogRequire(before, after, "abc.def"); } @Test public void testAddGoogRequire_alreadyExists() { String input = "goog.provide('js.Foo');\n" + "goog.require('abc.def');\n" + "\n" + "/** @private */\n" + "function foo_() {};\n"; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); Match match = new Match(root.getFirstChild(), new NodeMetadata(compiler)); SuggestedFix fix = new SuggestedFix.Builder() .addGoogRequire(match, "abc.def") .build(); SetMultimap<String, CodeReplacement> replacementMap = fix.getReplacements(); assertThat(replacementMap).isEmpty(); } private static void assertAddGoogRequire(String before, String after, String namespace) { Compiler compiler = getCompiler(before + after); Node root = compileToScriptRoot(compiler); Match match = new Match(root.getFirstChild(), new NodeMetadata(compiler)); SuggestedFix fix = new SuggestedFix.Builder() .addGoogRequire(match, namespace) .build(); CodeReplacement replacement = CodeReplacement.create( before.length(), 0, "goog.require('" + namespace + "');\n", namespace); assertReplacement(fix, replacement); } @Test public void testRemoveGoogRequire() { String before = "/** @fileoverview blah */\n\n" + "goog.provide('js.Foo');\n\n"; String googRequire = "goog.require('abc.def');"; String input = before + googRequire + "\n" + "/** @private */\n" + "function foo_() {};\n"; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); Match match = new Match(root.getFirstChild(), new NodeMetadata(compiler)); SuggestedFix fix = new SuggestedFix.Builder() .removeGoogRequire(match, "abc.def") .build(); CodeReplacement replacement = CodeReplacement.create(before.length(), googRequire.length(), ""); assertReplacement(fix, replacement); } @Test public void testRemoveGoogRequireAmongSeveralGoogRequires() { String before = "/** @fileoverview blah */\n\n" + "goog.provide('js.Foo');\n\ngoog.require('abc.abc');\n"; String googRequire = "goog.require('abc.def');\n"; String input = before + googRequire + "goog.require('def');\n" + "\n" + "/** @private */\n" + "function foo_() {};\n"; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); Match match = new Match(root.getFirstChild(), new NodeMetadata(compiler)); SuggestedFix fix = new SuggestedFix.Builder() .removeGoogRequire(match, "abc.def") .build(); CodeReplacement replacement = CodeReplacement.create(before.length(), googRequire.length(), ""); assertReplacement(fix, replacement); } @Test public void testRemoveGoogRequire_doesNotExist() { String input = "goog.require('abc.def');\n" + "\n" + "/** @private */\n" + "function foo_() {};\n"; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); Match match = new Match(root.getFirstChild(), new NodeMetadata(compiler)); SuggestedFix fix = new SuggestedFix.Builder() .removeGoogRequire(match, "fakefake") .build(); SetMultimap<String, CodeReplacement> replacementMap = fix.getReplacements(); assertThat(replacementMap).isEmpty(); } @Test public void testGenerateCode_doesNotJsEscapeStrings() { String code = "someFunc('foo<>=%&\"bar');\n"; Compiler compiler = getCompiler(code); Node node = compileToScriptRoot(compiler); String generated = new SuggestedFix.Builder().generateCode(compiler, node); assertEquals(code, generated); } @Test public void testGenerateCode_var1() { String code = "var x;\nvar y;\n"; Compiler compiler = getCompiler(code); Node node = compileToScriptRoot(compiler); String generated = new SuggestedFix.Builder().generateCode(compiler, node); assertEquals(code, generated); } @Test public void testGenerateCode_var2() { String code = "var x = 0;\nvar y = 1;\n"; Compiler compiler = getCompiler(code); Node node = compileToScriptRoot(compiler); String generated = new SuggestedFix.Builder().generateCode(compiler, node); assertEquals(code, generated); } @Test public void testAttachMatchedNodeInfo() { String before = "/** @fileoverview blah */\n\n" + "goog.provide('js.Foo');\n\n"; String googRequire = "goog.require('abc.def');"; String input = before + googRequire + "\n" + "/** @private */\n" + "function foo_() {};\n"; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); Match match = new Match(root.getFirstChild(), new NodeMetadata(compiler)); SuggestedFix fix = new SuggestedFix.Builder() // Corresponds to the goog.require node. .attachMatchedNodeInfo(root.getFirstChild().getNext().getFirstChild(), compiler) .removeGoogRequire(match, "abc.def") .build(); MatchedNodeInfo info = fix.getMatchedNodeInfo(); assertEquals("test", info.getSourceFilename()); assertEquals(5, info.getLineno()); assertEquals(0, info.getCharno()); assertTrue(info.isInClosurizedFile()); } /** Returns the root script node produced from the compiled JS input. */ private static Node compileToScriptRoot(Compiler compiler) { Node root = compiler.getRoot(); // The last child of the compiler root is a Block node, and the first child // of that is the Script node. return root.getLastChild().getFirstChild(); } private static Compiler getCompiler(String jsInput) { Compiler compiler = new Compiler(); CompilerOptions options = RefactoringDriver.getCompilerOptions(); compiler.init( ImmutableList.<SourceFile>of(), // Externs ImmutableList.of(SourceFile.fromCode("test", jsInput)), options); compiler.parse(); return compiler; } @Test public void testShortName() { assertThat(getShortNameForRequire("goog.array")).isEqualTo("googArray"); assertThat(getShortNameForRequire("goog.string")).isEqualTo("googString"); assertThat(getShortNameForRequire("goog.object")).isEqualTo("googObject"); assertThat(getShortNameForRequire("goog.structs.Map")).isEqualTo("StructsMap"); assertThat(getShortNameForRequire("array")).isEqualTo("array"); assertThat(getShortNameForRequire("Array")).isEqualTo("Array"); } }