/* * 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.assertFalse; import static org.junit.Assert.assertTrue; import com.google.common.collect.ImmutableList; import com.google.javascript.jscomp.Compiler; import com.google.javascript.jscomp.CompilerOptions; import com.google.javascript.jscomp.SourceFile; import com.google.javascript.jscomp.testing.BlackHoleErrorManager; 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 Matchers}. * * @author mknichel@google.com (Mark Knichel) */ @RunWith(JUnit4.class) public class MatchersTest { @Test public void testAnything() { String input = "goog.base(this);"; Node root = compileToScriptRoot(getCompiler(input)); Node child = root.getFirstChild(); assertTrue(Matchers.anything().matches(null, null)); assertTrue(Matchers.anything().matches(root, null)); assertTrue(Matchers.anything().matches(child, null)); assertTrue(Matchers.anything().matches(child, new NodeMetadata(null))); } @Test public void testAllOf() { String input = "goog.require('goog.dom');"; Node root = compileToScriptRoot(getCompiler(input)); Node fnCall = root.getFirstFirstChild(); assertTrue(fnCall.isCall()); Matcher notMatcher = Matchers.not(Matchers.anything()); assertFalse(Matchers.allOf(notMatcher).matches(root, null)); assertFalse(Matchers.allOf(notMatcher, Matchers.functionCall()).matches(fnCall, null)); assertTrue(Matchers.allOf( Matchers.anything(), Matchers.functionCall("goog.require")).matches(fnCall, null)); } @Test public void testAnyOf() { String input = "goog.require('goog.dom');"; Node root = compileToScriptRoot(getCompiler(input)); Node fnCall = root.getFirstFirstChild(); assertTrue(fnCall.isCall()); Matcher notMatcher = Matchers.not(Matchers.anything()); assertFalse(Matchers.anyOf(notMatcher).matches(root, null)); assertTrue(Matchers.anyOf(notMatcher, Matchers.functionCall()).matches(fnCall, null)); assertTrue(Matchers.anyOf(Matchers.functionCall("goog.require")).matches(fnCall, null)); } @Test public void testNot() { assertFalse(Matchers.not(Matchers.anything()).matches(null, null)); String input = "goog.require('goog.dom');"; Node root = compileToScriptRoot(getCompiler(input)); Node fnCall = root.getFirstFirstChild(); assertTrue(fnCall.isCall()); assertFalse(Matchers.not(Matchers.functionCall()).matches(fnCall, null)); assertFalse(Matchers.not(Matchers.functionCall("goog.require")).matches(fnCall, null)); } @Test public void testConstructor_any() { String input = "/** @constructor */ var Foo = function() {};"; Node root = compileToScriptRoot(getCompiler(input)); assertTrue(Matchers.constructor().matches(root.getFirstChild(), null)); } @Test public void testConstructor_specificClass() { String input = "/** @constructor */ var Foo = function() {};"; Node root = compileToScriptRoot(getCompiler(input)); assertTrue(Matchers.constructor("Foo").matches(root.getFirstChild(), null)); assertFalse(Matchers.constructor("Bar").matches(root.getFirstChild(), null)); } @Test public void testConstructor_differentConstructorTypes() { String input = "/** @constructor */ var Foo = function() {};"; Node root = compileToScriptRoot(getCompiler(input)); Node ctorNode = root.getFirstChild(); assertTrue(Matchers.constructor("Foo").matches(ctorNode, null)); input = "/** @constructor */ bar.Foo = function() {};"; root = compileToScriptRoot(getCompiler(input)); ctorNode = root.getFirstFirstChild(); assertTrue(Matchers.constructor("bar.Foo").matches(ctorNode, null)); input = "/** @constructor */ function Foo() {};"; root = compileToScriptRoot(getCompiler(input)); ctorNode = root.getFirstChild(); assertTrue(Matchers.constructor("Foo").matches(ctorNode, null)); // TODO(mknichel): Make this test case work. // input = "ns = {\n" // + " /** @constructor */\n" // + " Foo: function() {}\n" // + "};"; // root = compileToScriptRoot(getCompiler(input)); // ctorNode = root.getFirstFirstChild().getLastChild().getFirstChild(); // assertTrue(Matchers.constructor("ns.Foo").matches(ctorNode, null)); } @Test public void testNewClass() { String input = "new Object()"; Node root = compileToScriptRoot(getCompiler(input)); Node newNode = root.getFirstFirstChild(); assertTrue(newNode.isNew()); assertTrue(Matchers.newClass().matches(newNode, null)); assertFalse(Matchers.newClass().matches(newNode.getFirstChild(), null)); } @Test public void testNewClass_specificClass() { String externs = "" + "/** @constructor */\n" + "function Foo() {};\n" + "/** @constructor */\n" + "function Bar() {};"; String input = "var foo = new Foo();"; Compiler compiler = getCompiler(externs, input); NodeMetadata metadata = new NodeMetadata(compiler); Node root = compileToScriptRoot(compiler); Node varNode = root.getFirstChild(); Node newNode = varNode.getFirstFirstChild(); assertTrue(newNode.isNew()); assertTrue(Matchers.newClass("Foo").matches(newNode, metadata)); assertFalse(Matchers.newClass("Bar").matches(newNode, metadata)); assertFalse(Matchers.newClass("Foo").matches(newNode.getFirstChild(), metadata)); } @Test public void testFunctionCall_any() { String input = "goog.base(this)"; Node root = compileToScriptRoot(getCompiler(input)); Node fnCall = root.getFirstFirstChild(); assertTrue(fnCall.isCall()); assertTrue(Matchers.functionCall().matches(fnCall, null)); } @Test public void testFunctionCall_numArgs() { String input = "goog.base(this)"; Node root = compileToScriptRoot(getCompiler(input)); Node fnCall = root.getFirstFirstChild(); assertTrue(fnCall.isCall()); assertTrue(Matchers.functionCallWithNumArgs(1).matches(fnCall, null)); assertFalse(Matchers.functionCallWithNumArgs(2).matches(fnCall, null)); assertTrue(Matchers.functionCallWithNumArgs("goog.base", 1).matches(fnCall, null)); assertFalse(Matchers.functionCallWithNumArgs("goog.base", 2).matches(fnCall, null)); assertFalse(Matchers.functionCallWithNumArgs("goog.require", 1).matches(fnCall, null)); assertFalse(Matchers.functionCallWithNumArgs("goog.require", 2).matches(fnCall, null)); } @Test public void testFunctionCall_static() { String input = "goog.require('goog.dom');"; Node root = compileToScriptRoot(getCompiler(input)); Node fnCall = root.getFirstFirstChild(); assertTrue(fnCall.isCall()); assertTrue(Matchers.functionCall("goog.require").matches(fnCall, null)); assertFalse(Matchers.functionCall("goog.provide").matches(fnCall, null)); } @Test public void testFunctionCall_prototype() { String externs = "" + "/** @constructor */\n" + "function Foo() {};\n" + "Foo.prototype.bar = function() {};\n" + "Foo.prototype.baz = function() {};\n"; String input = "var foo = new Foo(); foo.bar();"; Compiler compiler = getCompiler(externs, input); NodeMetadata metadata = new NodeMetadata(compiler); Node root = compileToScriptRoot(compiler); Node fnCall = root.getLastChild().getFirstChild(); assertTrue(fnCall.isCall()); assertTrue(Matchers.functionCall("Foo.prototype.bar").matches(fnCall, metadata)); assertFalse(Matchers.functionCall("Foo.prototype.baz").matches(fnCall, metadata)); } @Test public void testEnum() { String input = "/** @enum {string} */ var foo = {BAR: 'baz'};"; Node root = compileToScriptRoot(getCompiler(input)); Node enumNode = root.getFirstFirstChild(); assertTrue(Matchers.enumDefinition().matches(enumNode, null)); } @Test public void testEnumOfType() { String input = "/** @enum {string} */ var foo = {BAR: 'baz'};"; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); Node enumNode = root.getFirstFirstChild(); assertTrue(Matchers.enumDefinitionOfType("string").matches( enumNode, new NodeMetadata(compiler))); assertFalse(Matchers.enumDefinitionOfType("number").matches( enumNode, new NodeMetadata(compiler))); } @Test public void testAssignmentWithRhs() { String externs = "" + "var goog = {};\n" + "goog.number = function() {};\n" + "var someObj = {};\n"; String input = "someObj.foo = goog.number();"; Compiler compiler = getCompiler(externs, input); Node root = compileToScriptRoot(compiler); Node assignNode = root.getFirstFirstChild(); assertTrue( Matchers.assignmentWithRhs(Matchers.functionCall("goog.number")).matches(assignNode, null)); assertFalse( Matchers.assignmentWithRhs(Matchers.functionCall("goog.base")).matches(assignNode, null)); assertFalse(Matchers.assignmentWithRhs(Matchers.functionCall("goog.number")) .matches(assignNode.getFirstChild(), null)); } @Test public void testPrototypeDeclarations() { String input = "" + "/** @constructor */\n" + "function Foo() {}\n" + "Foo.prototype.bar = 3;\n" + "Foo.prototype.baz = function() {};\n"; Compiler compiler = getCompiler(input); NodeMetadata metadata = new NodeMetadata(compiler); Node root = compileToScriptRoot(compiler); Node prototypeVarAssign = root.getSecondChild().getFirstChild(); Node prototypeFnAssign = root.getLastChild().getFirstChild(); assertTrue(Matchers.prototypeVariableDeclaration().matches( prototypeVarAssign.getFirstChild(), metadata)); assertFalse(Matchers.prototypeMethodDeclaration().matches( prototypeVarAssign.getFirstChild(), metadata)); assertTrue(Matchers.prototypeMethodDeclaration().matches( prototypeFnAssign.getFirstChild(), metadata)); assertFalse(Matchers.prototypeVariableDeclaration().matches( prototypeFnAssign.getFirstChild(), metadata)); } @Test public void testJsDocType1() { String input = "/** @type {number} */ var foo = 1;"; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); Node node = root.getFirstFirstChild(); assertTrue(Matchers.jsDocType("number").matches(node, new NodeMetadata(compiler))); assertFalse(Matchers.jsDocType("string").matches(node, new NodeMetadata(compiler))); } @Test public void testJsDocType2() { String input = "/** @type {number} */ let foo = 1;"; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); Node node = root.getFirstFirstChild(); assertTrue(Matchers.jsDocType("number").matches(node, new NodeMetadata(compiler))); assertFalse(Matchers.jsDocType("string").matches(node, new NodeMetadata(compiler))); } @Test public void testJsDocType3() { String input = "/** @type {number} */ const foo = 1;"; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); Node node = root.getFirstFirstChild(); assertTrue(Matchers.jsDocType("number").matches(node, new NodeMetadata(compiler))); assertFalse(Matchers.jsDocType("string").matches(node, new NodeMetadata(compiler))); } @Test public void testJsDocTypeNoMatch1() { String input = "/** @const */ var foo = 1;"; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); Node node = root.getFirstFirstChild(); assertFalse(Matchers.jsDocType("number").matches(node, new NodeMetadata(compiler))); assertFalse(Matchers.jsDocType("string").matches(node, new NodeMetadata(compiler))); } @Test public void testJsDocTypeNoMatch2() { String input = "const foo = 1;"; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); Node node = root.getFirstFirstChild(); assertFalse(Matchers.jsDocType("number").matches(node, new NodeMetadata(compiler))); assertFalse(Matchers.jsDocType("string").matches(node, new NodeMetadata(compiler))); } @Test public void testPropertyAccess() { String input = "foo.bar.method();"; Compiler compiler = getCompiler(input); Node root = compileToScriptRoot(compiler); Node methodNode = root.getFirstFirstChild().getFirstChild(); Node barNode = methodNode.getFirstChild(); assertTrue(Matchers.propertyAccess().matches(methodNode, new NodeMetadata(compiler))); assertTrue(Matchers.propertyAccess().matches(barNode, new NodeMetadata(compiler))); assertTrue(Matchers.propertyAccess("foo.bar.method").matches( methodNode, new NodeMetadata(compiler))); assertTrue(Matchers.propertyAccess("foo.bar").matches( barNode, new NodeMetadata(compiler))); assertFalse(Matchers.propertyAccess("foo").matches( barNode.getFirstChild(), new NodeMetadata(compiler))); } @Test public void testPropertyAccess_instance() { String externs = "" + "/** @constructor */\n" + "function Foo() {};\n" + "Foo.prototype.bar = 3;\n"; String input = "var foo = new Foo(); foo.bar;"; Compiler compiler = getCompiler(externs, input); Node root = compileToScriptRoot(compiler); Node node = root.getLastChild().getFirstChild(); assertTrue(Matchers.propertyAccess().matches(node, new NodeMetadata(compiler))); assertTrue(Matchers.propertyAccess("Foo.prototype.bar").matches( node, new NodeMetadata(compiler))); assertTrue(Matchers.propertyAccess("foo.bar").matches( node, new NodeMetadata(compiler))); } @Test public void testConstructorPropertyDeclaration() { String externs = ""; String input = "" + "/** @constructor */\n" + "function MyClass() {\n" + " this.foo = 5;\n" + " var bar = 10;\n" + "}"; Compiler compiler = getCompiler(externs, input); Node root = compileToScriptRoot(compiler); // The ASSIGN node Node node = root.getFirstChild().getLastChild().getFirstFirstChild(); assertTrue( Matchers.constructorPropertyDeclaration().matches(node, new NodeMetadata(compiler))); // The VAR node node = root.getFirstChild().getLastChild().getLastChild().getFirstChild(); assertFalse( Matchers.constructorPropertyDeclaration().matches(node, new NodeMetadata(compiler))); } @Test public void testIsPrivate() { String externs = ""; String input = "" + "/** @private */\n" + "var foo = 3;"; Compiler compiler = getCompiler(externs, input); Node root = compileToScriptRoot(compiler); Node node = root.getFirstChild(); assertTrue(Matchers.isPrivate().matches(node, new NodeMetadata(compiler))); input = "" + "/** @package */\n" + "var foo = 3;"; compiler = getCompiler(externs, input); root = compileToScriptRoot(compiler); node = root.getFirstChild(); assertFalse(Matchers.isPrivate().matches(node, new NodeMetadata(compiler))); } /** 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) { return getCompiler("", jsInput); } private static Compiler getCompiler(String externs, String jsInput) { Compiler compiler = new Compiler(); BlackHoleErrorManager.silence(compiler); compiler.disableThreads(); CompilerOptions options = RefactoringDriver.getCompilerOptions(); compiler.compile( ImmutableList.of(SourceFile.fromCode("externs", externs)), ImmutableList.of(SourceFile.fromCode("test", jsInput)), options); return compiler; } }