/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved. * * Oracle and Java are registered trademarks of Oracle and/or its affiliates. * Other names may be trademarks of their respective owners. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * Contributor(s): * * The Original Software is NetBeans. The Initial Developer of the Original * Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun * Microsystems, Inc. All Rights Reserved. * * If you wish your version of this file to be governed by only the CDDL * or only the GPL Version 2, indicate your decision by adding * "[Contributor] elects to include this software in this distribution * under the [CDDL or GPL Version 2] license." If you do not indicate a * single choice of license, a recipient has the option to distribute * your version of this file under either the CDDL, the GPL Version 2 or * to extend the choice of license to its licensees as provided above. * However, if you add GPL Version 2 code and therefore, elected the GPL * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. */ package org.netbeans.modules.ruby; import java.util.ArrayList; import java.util.Arrays; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.jrubyparser.ast.AliasNode; import org.jrubyparser.ast.ArrayNode; import org.jrubyparser.ast.AssignableNode; import org.jrubyparser.ast.CallNode; import org.jrubyparser.ast.ClassNode; import org.jrubyparser.ast.Colon2Node; import org.jrubyparser.ast.DStrNode; import org.jrubyparser.ast.DefnNode; import org.jrubyparser.ast.FCallNode; import org.jrubyparser.ast.FalseNode; import org.jrubyparser.ast.FixnumNode; import org.jrubyparser.ast.IterNode; import org.jrubyparser.ast.MethodDefNode; import org.jrubyparser.ast.Node; import org.jrubyparser.ast.NodeType; import org.jrubyparser.ast.ReturnNode; import org.jrubyparser.ast.StrNode; import org.jrubyparser.ast.INameNode; import org.jrubyparser.ast.NilNode; import org.jrubyparser.ast.OrNode; import org.jrubyparser.ast.TrueNode; import org.jrubyparser.ast.VCallNode; import org.netbeans.api.java.classpath.ClassPath; import org.netbeans.editor.BaseDocument; import org.netbeans.modules.csl.api.OffsetRange; import org.netbeans.modules.parsing.spi.Parser; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; /** * @todo Lots of other methods to test! * * @author Tor Norbye */ public class AstUtilitiesTest extends RubyTestBase { public AstUtilitiesTest(String testName) { super(testName); } @Override protected void setUp() throws Exception { super.setUp(); // makes the indexer to index also top level methods in tests RubyIndexer.userSourcesTest = true; } @Override protected Map<String, ClassPath> createClassPathsForTest() { return rubyTestsClassPath(); } public void testFindBySignature1() throws Exception { // Test top level methods Node root = getRootNode("testfiles/top_level.rb"); Node node = AstUtilities.findBySignature(root, "Object#bar(baz)"); assertNotNull(node); assertEquals("bar", ((INameNode)node).getName()); } public void testFindBySignature2() throws Exception { Node root = getRootNode("testfiles/ape.rb"); Node node = AstUtilities.findBySignature(root, "Ape#test_sorting(coll)"); assertNotNull(node); assertEquals("test_sorting", ((INameNode)node).getName()); } public void testFindMethodBySignatureNested() throws Exception { Node root = getRootNode("testfiles/resolv.rb"); Node node = AstUtilities.findBySignature(root, "Resolv::DNS#lazy_initialize"); assertNotNull(node); assertEquals("lazy_initialize", ((INameNode)node).getName()); } public void testFindConstantBySignatureNested() throws Exception { Node root = getRootNode("testfiles/constants.rb"); Node node = AstUtilities.findBySignature(root, "Colors::Converter#VERSION"); assertNotNull(node); assertEquals("VERSION", ((INameNode)node).getName()); } public void testFindConstantBySignature() throws Exception { Node root = getRootNode("testfiles/constants.rb"); Node node = AstUtilities.findBySignature(root, "Colors#RED"); assertNotNull(node); assertEquals("RED", ((INameNode)node).getName()); } public void testFindBySignatureInstance() throws Exception { Node root = getRootNode("testfiles/ape.rb"); Node node = AstUtilities.findBySignature(root, "Ape#@dialogs"); assertNotNull(node); assertEquals(node.getNodeType(), NodeType.INSTASGNNODE); assertEquals("@dialogs", ((INameNode)node).getName()); } public void testFindBySignatureClassVar() throws Exception { Node root = getRootNode("testfiles/ape.rb"); Node node = AstUtilities.findBySignature(root, "Ape#@@debugging"); assertNotNull(node); assertEquals(node.getNodeType(), NodeType.CLASSVARASGNNODE); assertEquals("@@debugging", ((INameNode)node).getName()); } public void testFindRequires() throws Exception { Node root = getRootNode("testfiles/ape.rb"); Set<String> requires = AstUtilities.getRequires(root); List<String> expected = Arrays.asList(new String[] { "rexml/document", "rubygems", "builder", "getter", "service", "samples", "entry", "poster", "collection", "deleter", "putter", "feed", "html", "crumbs", "escaper", "categories", "names", "validator", "authent" }); assertEquals(expected, requires); } public void testExitPointsFoo() { Node root = getRootNode("testfiles/exit_points.rb"); MethodDefNode methodDef = (MethodDefNode) AstUtilities.findBySignature(root, "Dummy#foo"); Set<Node> exits = new LinkedHashSet<Node>(); AstUtilities.findExitPoints(methodDef, exits); assertEquals("two exit points, was: " + exits, 2, exits.size()); Iterator<Node> it = exits.iterator(); assertTrue("return node", it.next() instanceof ReturnNode); assertTrue("return node", it.next() instanceof ArrayNode); } public void testExitPointsBoo() { Node root = getRootNode("testfiles/exit_points.rb"); MethodDefNode methodDef = (MethodDefNode) AstUtilities.findBySignature(root, "Dummy#boo(unusedparam,unusedparam2,usedparam)"); Set<Node> exits = new LinkedHashSet<Node>(); AstUtilities.findExitPoints(methodDef, exits); assertEquals("one exit point, was: " + exits, 1, exits.size()); Iterator<Node> it = exits.iterator(); assertTrue("return node", it.next() instanceof FCallNode); } public void testExitPointsBaz() { Node root = getRootNode("testfiles/exit_points.rb"); MethodDefNode methodDef = (MethodDefNode) AstUtilities.findBySignature(root, "Dummy#baz"); List<Node> exits = new ArrayList<Node>(); AstUtilities.findExitPoints(methodDef, exits); assertEquals("two exit points, was: " + exits, 2, exits.size()); assertTrue(exits.get(0) instanceof StrNode); assertTrue(exits.get(1) instanceof FixnumNode); } public void testExitPointsBar() { Node root = getRootNode("testfiles/exit_points.rb"); MethodDefNode methodDef = (MethodDefNode) AstUtilities.findBySignature(root, "Dummy#bar"); List<Node> exits = new ArrayList<Node>(); AstUtilities.findExitPoints(methodDef, exits); assertEquals("Was: " + exits, 3, exits.size()); assertEquals("if", AstUtilities.getNameOrValue(exits.get(0))); assertEquals("elsif", AstUtilities.getNameOrValue(exits.get(1))); assertEquals("else", AstUtilities.getNameOrValue(exits.get(2))); } public void testExitPointsQux() { Node root = getRootNode("testfiles/exit_points.rb"); MethodDefNode methodDef = (MethodDefNode) AstUtilities.findBySignature(root, "Dummy#qux"); List<Node> exits = new ArrayList<Node>(); AstUtilities.findExitPoints(methodDef, exits); assertEquals("Was: " + exits, 3, exits.size()); assertEquals("1", AstUtilities.getNameOrValue(exits.get(0))); assertEquals("2", AstUtilities.getNameOrValue(exits.get(1))); assertTrue(exits.get(2) instanceof FixnumNode); } public void testExitPointsThud() { Node root = getRootNode("testfiles/exit_points.rb"); MethodDefNode methodDef = (MethodDefNode) AstUtilities.findBySignature(root, "Dummy#thud"); List<Node> exits = new ArrayList<Node>(); AstUtilities.findExitPoints(methodDef, exits); assertEquals("Was: " + exits, 1, exits.size()); assertEquals("2", AstUtilities.getNameOrValue(exits.get(0))); } public void testExitPointsCorge() { Node root = getRootNode("testfiles/exit_points.rb"); MethodDefNode methodDef = (MethodDefNode) AstUtilities.findBySignature(root, "Dummy#corge(z)"); List<Node> exits = new ArrayList<Node>(); AstUtilities.findExitPoints(methodDef, exits); assertEquals("Was: " + exits, 2, exits.size()); assertEquals("a", AstUtilities.getNameOrValue(exits.get(0))); assertTrue(exits.get(1) instanceof NilNode); } public void testExitPointsQuux() { Node root = getRootNode("testfiles/exit_points.rb"); MethodDefNode methodDef = (MethodDefNode) AstUtilities.findBySignature(root, "Dummy#quux"); List<Node> exits = new ArrayList<Node>(); AstUtilities.findExitPoints(methodDef, exits); assertEquals("Was: " + exits, 4, exits.size()); assertTrue(exits.get(0) instanceof TrueNode); assertTrue(exits.get(1) instanceof FalseNode); assertTrue(exits.get(2) instanceof VCallNode); assertTrue(exits.get(3) instanceof CallNode); } public void testExitPointsFred() { Node root = getRootNode("testfiles/exit_points.rb"); MethodDefNode methodDef = (MethodDefNode) AstUtilities.findBySignature(root, "Dummy#fred"); List<Node> exits = new ArrayList<Node>(); AstUtilities.findExitPoints(methodDef, exits); assertEquals("Was: " + exits, 1, exits.size()); Node node = exits.get(0); assertTrue("Was " + node, node instanceof CallNode); } public void testExitPointsBarry() { Node root = getRootNode("testfiles/exit_points.rb"); MethodDefNode methodDef = (MethodDefNode) AstUtilities.findBySignature(root, "Dummy#barry(p)"); List<Node> exits = new ArrayList<Node>(); AstUtilities.findExitPoints(methodDef, exits); assertEquals("Was: " + exits, 3, exits.size()); assertEquals("raise", AstUtilities.getNameOrValue(exits.get(0))); assertTrue(exits.get(1) instanceof FixnumNode); assertEquals("77", AstUtilities.getNameOrValue(exits.get(2))); } public void testGetMethodName() { String testFile = "testfiles/ape.rb"; FileObject fileObject = getTestFile(testFile); String text = readFile(fileObject); int offset = 0; String method = AstUtilities.getMethodName(fileObject, offset); assertNull(method); offset = text.indexOf("@w.text! lines[-1]"); method = AstUtilities.getMethodName(fileObject, offset); assertEquals("report_li", method); offset = text.indexOf("step[1 .. -1].each { |li| report_li(nil, nil, li) }"); method = AstUtilities.getMethodName(fileObject, offset); assertEquals("report_html", method); } public void testGetTestName() { String testFile = "testfiles/new_test.rb"; FileObject fileObject = getTestFile(testFile); String text = readFile(fileObject); int offset = 0; String test = null; offset = text.indexOf("something should happen to me okay?"); test = AstUtilities.getTestName(fileObject, offset); assertEquals("test_something_should_happen_to_me_okay?", test); offset = text.indexOf("something else should happen to me okay?"); test = AstUtilities.getTestName(fileObject, offset); assertEquals("test_something_else_should_happen_to_me_okay?", test); offset = text.indexOf("test \"something "); test = AstUtilities.getTestName(fileObject, offset); assertEquals("test_something_should_happen_to_me_okay?", test); } public void testGetTestNameForShouldaTest() { String testFile = "testfiles/shoulda_test.rb"; FileObject fileObject = getTestFile(testFile); String text = readFile(fileObject); int offset = 0; String test = null; offset = text.indexOf("be empty"); test = AstUtilities.getTestName(fileObject, offset); assertEquals("Queue should be empty", test); offset = text.indexOf("work "); test = AstUtilities.getTestName(fileObject, offset); assertEquals("A Queue instance with a space at the end should work ", test); offset = text.indexOf("respond to :push"); test = AstUtilities.getTestName(fileObject, offset); assertEquals("A Queue instance should respond to :push", test); offset = text.indexOf("return that element on :pop"); test = AstUtilities.getTestName(fileObject, offset); assertEquals("A Queue instance with a single element should return that element on :pop", test); } public void testAddNodesByType() { Node root = getRootNode("testfiles/unused.rb"); List<Node> result = new ArrayList<Node>(); AstUtilities.addNodesByType(root, new NodeType[] { NodeType.ITERNODE }, result); assertEquals(1, result.size()); assertTrue(result.get(0) instanceof IterNode); } public void testAddNodesByType2() { Node root = getRootNode("testfiles/top_level.rb"); List<Node> result = new ArrayList<Node>(); AstUtilities.addNodesByType(root, new NodeType[] { NodeType.DEFNNODE }, result); assertEquals(2, result.size()); assertTrue(result.get(0) instanceof DefnNode); } private void addAllNodes(Node node, List<Node> list, Node parent, Map<Node,Node> parents) { try { node.getPosition().getStartOffset(); node.getPosition().getEndOffset(); } catch (UnsupportedOperationException uoe) { OffsetRange parentOffset = parent != null ? AstUtilities.getRange(parent) : OffsetRange.NONE; fail(uoe.getMessage() + " node=" + node + " with parent" + parent + " at offset " + parentOffset.toString()); } list.add(node); parents.put(node, parent); List<Node> children = node.childNodes(); assertNotNull(children); for (Node child : children) { if (child.isInvisible()) { parents.put(child, node); continue; } // No null nodes as children assertNotNull(child); addAllNodes(child, list, node, parents); } } public void testGuessName() throws Exception { //public static String guessName(ParserResult info, OffsetRange lexRange, OffsetRange astRange) { Parser.Result parserResult = getParserResult("testfiles/arguments.rb"); String text = getText(parserResult); int caretOffset = getCaretOffset(text, "call1(^x)"); OffsetRange range = new OffsetRange(caretOffset, caretOffset); String name = AstUtilities.guessName(parserResult, range, range); assertEquals("foo", name); caretOffset = getCaretOffset(text, "call2(^y)"); range = new OffsetRange(caretOffset, caretOffset); name = AstUtilities.guessName(parserResult, range, range); assertEquals("foo", name); caretOffset = getCaretOffset(text, "call3(^x,y,z)"); range = new OffsetRange(caretOffset, caretOffset); name = AstUtilities.guessName(parserResult, range, range); assertEquals("a", name); caretOffset = getCaretOffset(text, "call3(x,^y,z)"); range = new OffsetRange(caretOffset, caretOffset); name = AstUtilities.guessName(parserResult, range, range); assertEquals("b", name); caretOffset = getCaretOffset(text, "call4(^x,y,z,w)"); range = new OffsetRange(caretOffset, caretOffset); name = AstUtilities.guessName(parserResult, range, range); assertEquals("a", name); caretOffset = getCaretOffset(text, "call4(x,^y,z,w)"); range = new OffsetRange(caretOffset, caretOffset); name = AstUtilities.guessName(parserResult, range, range); assertEquals("b", name); caretOffset = getCaretOffset(text, "call4(x,y,^z,w)"); range = new OffsetRange(caretOffset, caretOffset); name = AstUtilities.guessName(parserResult, range, range); assertEquals("c", name); caretOffset = getCaretOffset(text, "call4(x,y,z,^w)"); range = new OffsetRange(caretOffset, caretOffset); name = AstUtilities.guessName(parserResult, range, range); assertEquals("d", name); } // Make sure we don't bomb out analyzing any of these files public void testStress() throws Throwable { List<FileObject> files = findJRubyRubyFiles(); for (FileObject file : files) { Parser.Result parserResult = getParserResult(file); BaseDocument doc = RubyUtils.getDocument(parserResult, true); assertNotNull("Document for file: " + file.getPath() + " must not be null.", doc); List<Node> allNodes = new ArrayList<Node>(); Node root = AstUtilities.getRoot(parserResult); if (root == null || root.isInvisible()) { continue; } assertNotNull(file + " had unexpected parsing errors", root); Map<Node,Node> parents = new IdentityHashMap<Node,Node>(1000); addAllNodes(root, allNodes, null, parents); if (root == null) { continue; } AstUtilities.getClasses(root); AstUtilities.getRequires(root); for (Node node : allNodes) { try { node.getPosition().getStartOffset(); node.getPosition().getEndOffset(); // Known exceptions - broken for getNameRange/getRange if (node instanceof StrNode || node instanceof DStrNode) { // See AstOffsetTest.testStringOffset1 continue; } AstUtilities.getFunctionNameRange(node); AstUtilities.getNameRange(node); OffsetRange nodeRange = AstUtilities.getRange(node); // 147800 if (AstUtilities.isCall(node)) { AstUtilities.getCallRange(node); AstUtilities.getCallName(node); for (int offset = nodeRange.getStart(); offset <= nodeRange.getEnd(); offset++) { AstUtilities.findArgumentIndex(node, offset); } } if (node instanceof AliasNode) { AliasNode an = (AliasNode)node; AstUtilities.getAliasOldRange(an); AstUtilities.getAliasNewRange(an); } if (AstUtilities.isAttr(node)) { AstUtilities.getAttrSymbols(node); } if (node instanceof MethodDefNode) { AstUtilities.getDefName(node); } if (node instanceof Colon2Node) { AstUtilities.getFqn((Colon2Node)node); } if (node instanceof AssignableNode) { AstUtilities.getLValueRange((AssignableNode)node); } if (node instanceof ClassNode) { AstUtilities.getSuperclass((ClassNode)node); } } catch (Throwable t) { Node parent = parents.get(node); OffsetRange parentOffset = parent != null ? AstUtilities.getRange(parent) : OffsetRange.NONE; fail(t.getMessage() + " while parsing " + FileUtil.getFileDisplayName(file) + " and node=" + node + " with parent" + parent + " at offset " + parentOffset.toString()); } } //ParserResult result = info.getEmbeddedResult(RubyInstallation.RUBY_MIME_TYPE, 0); //LinkedList<AstElement> list = new LinkedList<AstElement>(); //RubyParseResult rpr = (RubyParseResult)result; //list.addAll(rpr.getStructure().getElements()); //while (!list.isEmpty()) { // AstElement el = list.removeFirst(); // String signature = el.getFqn(); // if (signature == null) { // signature = el.getIn() + "." + el.getName(); // } // if (signature.length() > 0 && !".".equals(signature)) { // assertNotNull(el.toString(), signature); // AstUtilities.findBySignature(root, signature); // } // list.addAll(el.getChildren()); //} } } public void testFindArguments1() throws Exception { Parser.Result parserResult = getParserResult("testfiles/ape.rb"); String text = getText(parserResult); int caretOffset = getCaretOffset(text, "might_^fail(uri, requested_e_coll, requested_m_coll)"); Node root = AstUtilities.getRoot(parserResult); AstPath path = new AstPath(root, caretOffset); Node call = path.leaf(); assertTrue(AstUtilities.isCall(call)); caretOffset = getCaretOffset(text, "might_fail(^uri, requested_e_coll, requested_m_coll)"); assertEquals(0, AstUtilities.findArgumentIndex(call, caretOffset)); caretOffset = getCaretOffset(text, "might_fail(uri^, requested_e_coll, requested_m_coll)"); assertEquals(0, AstUtilities.findArgumentIndex(call, caretOffset)); caretOffset = getCaretOffset(text, "might_fail(uri,^ requested_e_coll, requested_m_coll)"); assertEquals(1, AstUtilities.findArgumentIndex(call, caretOffset)); caretOffset = getCaretOffset(text, "might_fail(uri, requested_e_coll^, requested_m_coll)"); assertEquals(1, AstUtilities.findArgumentIndex(call, caretOffset)); caretOffset = getCaretOffset(text, "might_fail(uri, requested_e_coll,^ requested_m_coll)"); assertEquals(2, AstUtilities.findArgumentIndex(call, caretOffset)); caretOffset = getCaretOffset(text, "might_fail(uri, requested_e_coll, requested_m_coll^)"); assertEquals(2, AstUtilities.findArgumentIndex(call, caretOffset)); caretOffset = getCaretOffset(text, "might_fail(uri, requested_e_coll, requested_m_coll)^"); assertEquals(-1, AstUtilities.findArgumentIndex(call, caretOffset)); } public void testFindArguments2() throws Exception { Parser.Result parserResult = getParserResult("testfiles/rubygems.rb"); String text = getText(parserResult); //int caretOffset = 2755; // "new" call from failed earlier test int caretOffset = getCaretOffset(text, "MUTEX = Mutex.^new"); Node root = AstUtilities.getRoot(parserResult); AstPath path = new AstPath(root, caretOffset); Node call = path.leaf(); assertTrue(AstUtilities.isCall(call)); assertEquals(-1, AstUtilities.findArgumentIndex(call, caretOffset)); assertEquals(-1, AstUtilities.findArgumentIndex(call, caretOffset+3)); } public void testFindArguments3() throws Exception { Parser.Result parserResult = getParserResult("testfiles/rubygems.rb"); String text = getText(parserResult); //int caretOffset = 2755; // "new" call from failed earlier test int caretOffset = getCaretOffset(text, "Gem.ac^tivate(gem_name, *version_requirements)"); Node root = AstUtilities.getRoot(parserResult); AstPath path = new AstPath(root, caretOffset); Node call = path.leaf(); assertTrue(AstUtilities.isCall(call)); caretOffset = getCaretOffset(text, "Gem.^activate(gem_name, *version_requirements)"); assertEquals(-1, AstUtilities.findArgumentIndex(call, caretOffset)); caretOffset = getCaretOffset(text, "Gem.activate(^gem_name, *version_requirements)"); assertEquals(0, AstUtilities.findArgumentIndex(call, caretOffset)); caretOffset = getCaretOffset(text, "Gem.activate(gem_name^, *version_requirements)"); assertEquals(0, AstUtilities.findArgumentIndex(call, caretOffset)); caretOffset = getCaretOffset(text, "Gem.activate(gem_name,^ *version_requirements)"); assertEquals(1, AstUtilities.findArgumentIndex(call, caretOffset)); caretOffset = getCaretOffset(text, "Gem.activate(gem_name, ^*version_requirements)"); assertEquals(1, AstUtilities.findArgumentIndex(call, caretOffset)); caretOffset = getCaretOffset(text, "Gem.activate(gem_name, *^version_requirements)"); assertEquals(1, AstUtilities.findArgumentIndex(call, caretOffset)); caretOffset = getCaretOffset(text, "Gem.activate(gem_name, *version_requirements^)"); assertEquals(1, AstUtilities.findArgumentIndex(call, caretOffset)); caretOffset = getCaretOffset(text, "Gem::Dependency.^new(gem, version_requirements)"); caretOffset = getCaretOffset(text, "Gem::ConfigFile.^new []"); caretOffset = getCaretOffset(text, "StringIO.^new data"); caretOffset = getCaretOffset(text, "Gem::Dependency.^new gem, requirements"); // TODO - make sure I add up nested calls correctly, e.g. // foo(bar, baz(boo(bazy))) // TODO - test argscatnode - missing fallthrough node! } }