/*
* 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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
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.rhino.IR;
import com.google.javascript.rhino.Node;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.Set;
/**
* 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 = new CodeReplacement(
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 = new CodeReplacement(0, input.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.getFirstChild().getFirstChild().getFirstChild()
.getFirstChild();
SuggestedFix fix = new SuggestedFix.Builder()
.rename(node, "fooBar")
.build();
CodeReplacement replacement = new CodeReplacement(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.getFirstChild().getFirstChild(), "renamedProperty")
.build();
CodeReplacement replacement = new CodeReplacement(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.getFirstChild().getFirstChild(), "renamedProperty", true)
.build();
CodeReplacement replacement = new CodeReplacement(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.getFirstChild().getFirstChild(), "renamedFnCall")
.build();
CodeReplacement replacement = new CodeReplacement(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.getFirstChild().getFirstChild(), newFnName, true)
.build();
CodeReplacement replacement = new CodeReplacement(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 = new CodeReplacement(
before.length(), after.length(), "goog2.get('service');\n");
assertReplacement(fix, replacement);
}
@Test
public void testAddCast() {
String input = "obj.fnCall();";
Compiler compiler = getCompiler(input);
Node root = compileToScriptRoot(compiler);
Node objNode = root.getFirstChild().getFirstChild().getFirstChild().getFirstChild();
SuggestedFix fix = new SuggestedFix.Builder()
.addCast(objNode, compiler, "FooBar")
.build();
CodeReplacement replacement = new CodeReplacement(
0, "obj".length(), "/** @type {FooBar} */ (obj)");
assertReplacement(fix, replacement);
}
@Test
public void testRemoveCast() {
String input = "var x = /** @type {string} */ (y);";
Compiler compiler = getCompiler(input);
Node root = compileToScriptRoot(compiler);
Node castNode = root.getFirstChild().getFirstChild().getFirstChild();
assertTrue(castNode.isCast());
SuggestedFix fix = new SuggestedFix.Builder()
.removeCast(castNode, compiler)
.build();
CodeReplacement replacement = new CodeReplacement(
"var x = ".length(), "/** @type {string} */ (y)".length(), "y");
assertReplacement(fix, replacement);
}
@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 = new CodeReplacement(
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 = new CodeReplacement(
"/** @type {".length(), 0, "!");
assertReplacement(fix, replacement);
}
@Test
public void testChangeJsDocType_privateType() {
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 = new CodeReplacement(
before.length(), "@private Foo".length(), "@private {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.getFirstChild().getFirstChild(), 0, "baz")
.build();
CodeReplacement replacement = new CodeReplacement(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.getFirstChild().getFirstChild(), 0, "baz")
.build();
CodeReplacement replacement = new CodeReplacement(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.getFirstChild().getFirstChild(), 1, "baz")
.build();
CodeReplacement replacement = new CodeReplacement(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.getFirstChild().getFirstChild(), 2, "baz")
.build();
CodeReplacement replacement = new CodeReplacement(before.length(), 0, ", baz");
assertReplacement(fix, replacement);
}
@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();
assertEquals(0, replacementMap.size());
}
private 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 = new CodeReplacement(
before.length(), 0, "goog.require('" + namespace + "');\n");
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 = new CodeReplacement(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();
assertEquals(0, replacementMap.size());
}
private void assertReplacement(SuggestedFix fix, CodeReplacement expectedReplacement) {
assertReplacements(fix, ImmutableSet.of(expectedReplacement));
}
private void assertReplacements(SuggestedFix fix, Set<CodeReplacement> expectedReplacements) {
SetMultimap<String, CodeReplacement> replacementMap = fix.getReplacements();
assertEquals(1, replacementMap.size());
Set<CodeReplacement> replacements = replacementMap.get("test");
assertEquals(expectedReplacements.size(), replacements.size());
assertEquals(expectedReplacements, replacements);
}
/**
* Returns the root script node produced from the compiled JS input.
*/
private 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 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;
}
}