ruby/src/org/netbeans/modules/ruby/AstUtilities.java
author enebo@netbeans.org
Sun, 08 Dec 2013 11:39:16 -0600
changeset 4554 07958c1ff402
parent 4553 32cac83e9ae7
child 4555 3773928e70d0
permissions -rw-r--r--
Too much stuff in one commit. Rename blocks now follows language semantics. Removal of more ASTPath consumers
     1 /*
     2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3  *
     4  * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
     5  *
     6  * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
     7  * Other names may be trademarks of their respective owners.
     8  *
     9  * The contents of this file are subject to the terms of either the GNU
    10  * General Public License Version 2 only ("GPL") or the Common
    11  * Development and Distribution License("CDDL") (collectively, the
    12  * "License"). You may not use this file except in compliance with the
    13  * License. You can obtain a copy of the License at
    14  * http://www.netbeans.org/cddl-gplv2.html
    15  * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    16  * specific language governing permissions and limitations under the
    17  * License.  When distributing the software, include this License Header
    18  * Notice in each file and include the License file at
    19  * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    20  * particular file as subject to the "Classpath" exception as provided
    21  * by Oracle in the GPL Version 2 section of the License file that
    22  * accompanied this code. If applicable, add the following below the
    23  * License Header, with the fields enclosed by brackets [] replaced by
    24  * your own identifying information:
    25  * "Portions Copyrighted [year] [name of copyright owner]"
    26  *
    27  * Contributor(s):
    28  *
    29  * The Original Software is NetBeans. The Initial Developer of the Original
    30  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun
    31  * Microsystems, Inc. All Rights Reserved.
    32  *
    33  * If you wish your version of this file to be governed by only the CDDL
    34  * or only the GPL Version 2, indicate your decision by adding
    35  * "[Contributor] elects to include this software in this distribution
    36  * under the [CDDL or GPL Version 2] license." If you do not indicate a
    37  * single choice of license, a recipient has the option to distribute
    38  * your version of this file under either the CDDL, the GPL Version 2 or
    39  * to extend the choice of license to its licensees as provided above.
    40  * However, if you add GPL Version 2 code and therefore, elected the GPL
    41  * Version 2 license, then the option applies only if the new code is
    42  * made subject to such option by the copyright holder.
    43  */
    44 package org.netbeans.modules.ruby;
    45 
    46 import java.util.ArrayList;
    47 import java.util.Arrays;
    48 import java.util.Collection;
    49 import java.util.Collections;
    50 import java.util.HashMap;
    51 import java.util.HashSet;
    52 import java.util.Iterator;
    53 import java.util.LinkedList;
    54 import java.util.List;
    55 import java.util.Map;
    56 import java.util.Set;
    57 import java.util.logging.Level;
    58 import java.util.logging.Logger;
    59 import javax.swing.text.BadLocationException;
    60 import javax.swing.text.Document;
    61 import org.jrubyparser.ast.AliasNode;
    62 import org.jrubyparser.ast.ArgsCatNode;
    63 import org.jrubyparser.ast.ArgsNode;
    64 import org.jrubyparser.ast.ArgumentNode;
    65 import org.jrubyparser.ast.AssignableNode;
    66 import org.jrubyparser.ast.CallNode;
    67 import org.jrubyparser.ast.ClassNode;
    68 import org.jrubyparser.ast.Colon2Node;
    69 import org.jrubyparser.ast.Colon3Node;
    70 import org.jrubyparser.ast.ConstNode;
    71 import org.jrubyparser.ast.FCallNode;
    72 import org.jrubyparser.ast.IScopingNode;
    73 import org.jrubyparser.ast.ListNode;
    74 import org.jrubyparser.ast.MethodDefNode;
    75 import org.jrubyparser.ast.ModuleNode;
    76 import org.jrubyparser.ast.Node;
    77 import org.jrubyparser.ast.NodeType;
    78 import org.jrubyparser.ast.SClassNode;
    79 import org.jrubyparser.ast.StrNode;
    80 import org.jrubyparser.ast.SymbolNode;
    81 import org.jrubyparser.ast.VCallNode;
    82 import org.jrubyparser.ast.INameNode;
    83 import org.jrubyparser.SourcePosition;
    84 import org.jrubyparser.ast.AndNode;
    85 import org.jrubyparser.ast.AttrAssignNode;
    86 import org.jrubyparser.ast.CaseNode;
    87 import org.jrubyparser.ast.DSymbolNode;
    88 import org.jrubyparser.ast.DefinedNode;
    89 import org.jrubyparser.ast.DotNode;
    90 import org.jrubyparser.ast.HashNode;
    91 import org.jrubyparser.ast.IArgumentNode;
    92 import org.jrubyparser.ast.ILiteralNode;
    93 import org.jrubyparser.ast.ILocalScope;
    94 import org.jrubyparser.ast.IfNode;
    95 import org.jrubyparser.ast.IterNode;
    96 import org.jrubyparser.ast.LiteralNode;
    97 import org.jrubyparser.ast.NewlineNode;
    98 import org.jrubyparser.ast.NilNode;
    99 import org.jrubyparser.ast.NotNode;
   100 import org.jrubyparser.ast.OrNode;
   101 import org.jrubyparser.ast.RescueNode;
   102 import org.jrubyparser.ast.ReturnNode;
   103 import org.jrubyparser.ast.UntilNode;
   104 import org.jrubyparser.ast.WhenNode;
   105 import org.netbeans.editor.BaseDocument;
   106 import org.netbeans.editor.Utilities;
   107 import org.netbeans.modules.csl.api.Modifier;
   108 import org.netbeans.modules.csl.api.OffsetRange;
   109 import org.netbeans.modules.csl.spi.GsfUtilities;
   110 import org.netbeans.modules.csl.spi.ParserResult;
   111 import org.netbeans.modules.parsing.api.ParserManager;
   112 import org.netbeans.modules.parsing.api.ResultIterator;
   113 import org.netbeans.modules.parsing.api.Snapshot;
   114 import org.netbeans.modules.parsing.api.Source;
   115 import org.netbeans.modules.parsing.api.UserTask;
   116 import org.netbeans.modules.parsing.spi.ParseException;
   117 import org.netbeans.modules.parsing.spi.Parser;
   118 import org.netbeans.modules.parsing.spi.indexing.support.QuerySupport;
   119 import org.netbeans.modules.ruby.elements.IndexedElement;
   120 import org.netbeans.modules.ruby.elements.IndexedField;
   121 import org.netbeans.modules.ruby.elements.IndexedMethod;
   122 import org.openide.filesystems.FileObject;
   123 import org.openide.util.Exceptions;
   124 
   125 /**
   126  * Various utilities for operating on the JRuby ASTs that are used
   127  * elsewhere.
   128  * 
   129  * @todo Rewrite many of the custom recursion routines to simply 
   130  *  call {@link addNodesByType} and then iterate (without recursion) over
   131  *  the result set.
   132  *
   133  * @author Tor Norbye
   134  */
   135 public class AstUtilities {
   136     private static final Logger LOGGER = Logger.getLogger(AstUtilities.class.getName());
   137 
   138     private static final String[] ATTR_ACCESSORS = {"attr", "attr_reader", "attr_accessor", "attr_writer",
   139         "attr_internal", "attr_internal_accessor", "attr_internal_reader", "attr_internal_writer"};
   140 
   141     /** ActiveSupport extensions */
   142     private static final String[] CATTR_ACCESSORS = {"cattr_reader", "cattr_accessor", "cattr_writer"};
   143 
   144     /** The names of AR scope methods - named_scope in rails 2.x, rails 3.x deprecates it
   145      in favor of 'scope' */
   146     private static final String[] NAMED_SCOPE = {"named_scope", "scope"};
   147     /**
   148      * Tries to cast the given <code>result</code> to <code>RubyParseResult</code> 
   149      * and returns it. Returns <code>null</code> if it wasn't an instance of <code>RubyParseResult</code>.
   150      * 
   151      * @param result
   152      * @return
   153      */
   154     public static RubyParseResult getParseResult(Parser.Result result) {
   155         if (!(result instanceof RubyParseResult)) {
   156             LOGGER.log(Level.WARNING, "Expected RubyParseResult, but have {0}", result);
   157             return null;
   158         }
   159         return (RubyParseResult) result;
   160     }
   161 
   162     public static int getAstOffset(Parser.Result info, int lexOffset) {
   163         RubyParseResult result = getParseResult(info);
   164         if (result == null) return lexOffset;
   165 
   166         return result.getSnapshot().getEmbeddedOffset(lexOffset);
   167     }
   168 
   169     public static OffsetRange getAstOffsets(Parser.Result info, OffsetRange lexicalRange) {
   170         RubyParseResult result = getParseResult(info);
   171         if (result == null) return lexicalRange;
   172         
   173         int rangeStart = lexicalRange.getStart();
   174         int start = result.getSnapshot().getEmbeddedOffset(rangeStart);
   175         if (start == rangeStart) return lexicalRange;
   176         if (start == -1) return OffsetRange.NONE;
   177 
   178         // Assumes the translated range maintains size
   179         return new OffsetRange(start, start + lexicalRange.getLength());
   180     }
   181 
   182     /** This is a utility class only, not instantiatiable */
   183     private AstUtilities() {
   184     }
   185 
   186     /**
   187      * Get the rdoc documentation associated with the given node in the given document.
   188      * The node must have position information that matches the source in the document.
   189      */
   190     public static List<String> gatherDocumentation(Snapshot baseDoc, Node node) {
   191         LinkedList<String> comments = new LinkedList<String>();
   192         int elementBegin = node.getPosition().getStartOffset();
   193 
   194         try {
   195             if (elementBegin < 0 || elementBegin >= baseDoc.getText().length()) return null;
   196 
   197             // Search to previous lines, locate comments. Once we have a non-whitespace line that isn't
   198             // a comment, we're done
   199 
   200             int offset = GsfUtilities.getRowStart(baseDoc.getText(), elementBegin);
   201             offset--;
   202 
   203             // Skip empty and whitespace lines
   204             while (offset >= 0) {
   205                 // Find beginning of line
   206                 offset = GsfUtilities.getRowStart(baseDoc.getText(), offset);
   207 
   208                 if (!GsfUtilities.isRowEmpty(baseDoc.getText(), offset) &&
   209                         !GsfUtilities.isRowWhite(baseDoc.getText(), offset)) {
   210                     break;
   211                 }
   212 
   213                 offset--;
   214             }
   215 
   216             if (offset < 0) {
   217                 return null;
   218             }
   219 
   220             while (offset >= 0) {
   221                 // Find beginning of line
   222                 offset = GsfUtilities.getRowStart(baseDoc.getText(), offset);
   223 
   224                 if (GsfUtilities.isRowEmpty(baseDoc.getText(), offset) || GsfUtilities.isRowWhite(baseDoc.getText(), offset)) {
   225                     // Empty lines not allowed within an rdoc
   226                     break;
   227                 }
   228 
   229                 // This is a comment line we should include
   230                 int lineBegin = GsfUtilities.getRowFirstNonWhite(baseDoc.getText(), offset);
   231                 int lineEnd = GsfUtilities.getRowLastNonWhite(baseDoc.getText(), offset) + 1;
   232                 String line = baseDoc.getText().subSequence(lineBegin, lineEnd).toString();
   233 
   234                 // Tolerate "public", "private" and "protected" here --
   235                 // Test::Unit::Assertions likes to put these in front of each
   236                 // method.
   237                 if (line.startsWith("#")) {
   238                     comments.addFirst(line);
   239                 } else if ((comments.size() == 0) && line.startsWith("=end") &&
   240                         (lineBegin == GsfUtilities.getRowStart(baseDoc.getText(), offset))) {
   241                     // It could be a =begin,=end document - see scanf.rb in Ruby lib for example. Treat this differently.
   242                     gatherInlineDocumentation(comments, baseDoc, offset);
   243 
   244                     return comments;
   245                 } else if (line.equals("public") || line.equals("private") ||
   246                         line.equals("protected")) { // NOI18N
   247                                                     // Skip newlines back up to the comment
   248                     offset--;
   249 
   250                     while (offset >= 0) {
   251                         // Find beginning of line
   252                         offset = GsfUtilities.getRowStart(baseDoc.getText(), offset);
   253 
   254                         if (!GsfUtilities.isRowEmpty(baseDoc.getText(), offset) &&
   255                                 !GsfUtilities.isRowWhite(baseDoc.getText(), offset)) {
   256                             break;
   257                         }
   258 
   259                         offset--;
   260                     }
   261 
   262                     continue;
   263                 } else {
   264                     // No longer in a comment
   265                     break;
   266                 }
   267 
   268                 // Previous line
   269                 offset--;
   270             }
   271         } catch (BadLocationException ble) {
   272             // do nothing - see #154991
   273         }
   274 
   275         return comments;
   276     }
   277 
   278     private static void gatherInlineDocumentation(LinkedList<String> comments,
   279         Snapshot baseDoc, int offset) throws BadLocationException {
   280         // offset points to a line containing =end
   281         // Skip the =end list
   282         offset = GsfUtilities.getRowStart(baseDoc.getText(), offset);
   283         offset--;
   284 
   285         // Search backwards in the document for the =begin (if any) and add all lines in reverse
   286         // order in between.
   287         while (offset >= 0) {
   288             // Find beginning of line
   289             offset = GsfUtilities.getRowStart(baseDoc.getText(), offset);
   290 
   291             // This is a comment line we should include
   292             int lineBegin = offset;
   293             int lineEnd = GsfUtilities.getRowEnd(baseDoc.getText(), offset);
   294             String line = baseDoc.getText().subSequence(lineBegin, lineEnd).toString();
   295 
   296             if (line.startsWith("=begin")) return;  // We're done!
   297 
   298             comments.addFirst(line);
   299 
   300             // Previous line
   301             offset--;
   302         }
   303     }
   304 
   305     /**
   306      * <strong>This method may block for a long time; use with caution.</strong>.
   307      */
   308     public static Node getForeignNode(final IndexedElement elem) {
   309         return getForeignNode(elem, null);
   310     }
   311 
   312     /**
   313      * <strong>This method may block for a long time; use with caution.</strong>.
   314      * @param elem
   315      * @param foreignInfoHolder
   316      * @return
   317      */
   318     public static Node getForeignNode(final IndexedElement elem, final Parser.Result[] foreignInfoHolder) {
   319         FileObject fo = elem.getFileObject();
   320         if (fo == null) {
   321             return null;
   322         }
   323 
   324         Source source = Source.create(fo);
   325         final Node[] nodeHolder = new Node[1];
   326         try {
   327             ParserManager.parse(Collections.singleton(source), new UserTask() {
   328                 @Override
   329                 public void run(ResultIterator resultIterator) throws Exception {
   330                     Parser.Result result = resultIterator.getParserResult();
   331                     if (foreignInfoHolder != null) {
   332                         assert foreignInfoHolder.length == 1;
   333                         foreignInfoHolder[0] = result;
   334                     }
   335 
   336                     Node root = AstUtilities.getRoot(result);
   337                     if (root != null) {
   338                         String signature = elem.getSignature();
   339 
   340                         if (signature != null) {
   341                             Node node = AstUtilities.findBySignature(root, signature);
   342 
   343                             // Special handling for "new" - these are synthesized from "initialize" methods
   344                             if ((node == null) && "new".equals(elem.getName())) { // NOI18N
   345                                 if (signature.indexOf("#new") != -1) {
   346                                     signature = signature.replaceFirst("#new", "#initialize"); //NOI18N
   347                                 } else {
   348                                     signature = signature.replaceFirst("new", "initialize"); //NOI18N
   349                                 }
   350                                 node = AstUtilities.findBySignature(root, signature);
   351                             }
   352 
   353                             nodeHolder[0] = node;
   354                         }
   355                     }
   356                 }
   357             });
   358         } catch (ParseException ex) {
   359             Exceptions.printStackTrace(ex);
   360             return null;
   361         }
   362 
   363 
   364         return nodeHolder[0];
   365     }
   366 
   367 //    public static Node getForeignNode(final IndexedElement o) {
   368 //        FileObject fo = o.getFileObject();
   369 //        if (fo == null) {
   370 //            return null;
   371 //        }
   372 //
   373 //        if (file == null) {
   374 //            return null;
   375 //        }
   376 //
   377 //        List<ParserFile> files = Collections.singletonList(file);
   378 //        SourceFileReader reader =
   379 //            new SourceFileReader() {
   380 //                public CharSequence read(ParserFile file)
   381 //                    throws IOException {
   382 //                    Document doc = o.getDocument();
   383 //
   384 //                    if (doc == null) {
   385 //                        return "";
   386 //                    }
   387 //
   388 //                    try {
   389 //                        return doc.getText(0, doc.getLength());
   390 //                    } catch (BadLocationException ble) {
   391 //                        IOException ioe = new IOException();
   392 //                        ioe.initCause(ble);
   393 //                        throw ioe;
   394 //                    }
   395 //                }
   396 //
   397 //                public int getCaretOffset(ParserFile fileObject) {
   398 //                    return -1;
   399 //                }
   400 //            };
   401 //
   402 //        DefaultParseListener listener = new DefaultParseListener();
   403 //        // TODO - embedding model?
   404 //        TranslatedSource translatedSource = null; // TODO - determine this here?
   405 //        Parser.Job job = new Parser.Job(files, listener, reader, translatedSource);
   406 //        new RubyParser().parseFiles(job);
   407 //
   408 //        ParserResult result = listener.getParserResult();
   409 //
   410 //        if (result == null) {
   411 //            return null;
   412 //        }
   413 //
   414 //        Node root = AstUtilities.getRoot(result);
   415 //
   416 //        if (root == null) {
   417 //            return null;
   418 //        }
   419 //
   420 //        String signature = o.getSignature();
   421 //
   422 //        if (signature == null) {
   423 //            return null;
   424 //        }
   425 //
   426 //        Node node = AstUtilities.findBySignature(root, signature);
   427 //
   428 //        // Special handling for "new" - these are synthesized from "initialize" methods
   429 //        if ((node == null) && "new".equals(o.getName())) { // NOI18N
   430 //            signature = signature.replaceFirst("new", "initialize"); //NOI18N
   431 //            node = AstUtilities.findBySignature(root, signature);
   432 //        }
   433 //
   434 //        return node;
   435 //    }
   436 
   437     public static int boundCaretOffset(ParserResult result, int caretOffset) {
   438         Document doc = RubyUtils.getDocument(result);
   439         if (doc != null) {
   440             // If you invoke code completion while indexing is in progress, the
   441             // completion job (which stores the caret offset) will be delayed until
   442             // indexing is complete - potentially minutes later. When the job
   443             // is finally run we need to make sure the caret position is still valid.
   444             int length = doc.getLength();
   445 
   446             if (caretOffset > length) caretOffset = length;
   447         }
   448 
   449         return caretOffset;
   450     }
   451 
   452     /**
   453      * Return the set of requires that are defined in this AST
   454      * (no transitive closure though).
   455      */
   456     public static Set<String> getRequires(Node root) {
   457         Set<String> requires = new HashSet<String>();
   458         addRequires(root, requires);
   459 
   460         return requires;
   461     }
   462     
   463     private static void addRequires(Node node, Set<String> requires) {
   464         if (node.getNodeType() == NodeType.FCALLNODE) {
   465             FCallNode fcall = (FCallNode) node;
   466 
   467             if ("require".equals(fcall.getName()) && fcall.getArgs() instanceof ListNode) {
   468                 ListNode args = (ListNode) fcall.getArgs(); // FIXME: Can this ever not be fcall?
   469 
   470                 if (args.size() > 0) {
   471                     Node n = args.get(0);
   472 
   473                     // For dyn-strings, we have n instances DStrNode butwe can't handle these
   474                     if (n instanceof StrNode) {
   475                         String require = ((StrNode)n).getValue();
   476 
   477                         if (require != null && require.length() > 0) requires.add(require.toString());
   478                     }
   479                 }
   480             }
   481         } else if (node.getNodeType() == NodeType.MODULENODE || node.getNodeType() == NodeType.CLASSNODE ||
   482                 node.getNodeType() == NodeType.DEFNNODE || node.getNodeType() == NodeType.DEFSNODE) {
   483             return; // Only look for require statements at the top level
   484         }
   485 
   486         for (Node child : node.childNodes()) {
   487             addRequires(child, requires);
   488         }
   489     }
   490 
   491     /** Locate the method of the given name and arity */
   492     public static MethodDefNode findMethod(Node node, String name, Arity arity) {
   493         // Recursively search for methods or method calls that match the name and arity
   494         if (node instanceof MethodDefNode && (((INameNode) node).getName()).equals(name) && 
   495                 Arity.matches(arity, Arity.getArity((MethodDefNode) node))) return (MethodDefNode) node;
   496 
   497         for (Node child : node.childNodes()) {
   498             MethodDefNode match = findMethod(child, name, arity);
   499             
   500             if (match != null) return match;
   501         }
   502 
   503         return null;
   504     }
   505 
   506     /**
   507      * Gets the closest node at the given offset.
   508      * 
   509      * @param root
   510      * @param offset
   511      * @return the closest node or <code>null</code>.
   512      */
   513     public static Node findNodeAtOffset(Node root, int offset) {
   514         return root == null ? null : root.getNodeAt(offset);
   515     }
   516 
   517     public static MethodDefNode findMethodAtOffset(Node root, int offset) {
   518         Node child = findNodeAtOffset(root, offset);
   519         
   520         return child == null ? null : child.getMethodFor();
   521     }
   522 
   523     // FIXME: Replace this with something which returns nearest module...Do we really specifically need class?
   524     public static ClassNode findClassAtOffset(Node root, int offset) {
   525         for (Node node: new AstPath(root, offset)) {
   526             if (node instanceof ClassNode) return (ClassNode) node;
   527         }
   528 
   529         return null;
   530     }
   531 
   532     public static Node findLocalScope(Node node, AstPath path) {
   533         Node method = findMethod(path);
   534         if (method != null) return method;
   535         if (path.root() != null) return path.root();
   536 
   537         method = findBlock(path);
   538         if (method != null) return method;
   539 
   540         method = path.leafParent();
   541 
   542         if (method.getNodeType() == NodeType.NEWLINENODE) method = path.leafGrandParent();
   543         if (method == null) method = node;
   544 
   545         return method;
   546     }
   547 
   548     public static Node findDynamicScope(Node node, AstPath path) {
   549         Node block = findBlock(path);
   550 
   551         if (block == null) {
   552             block = path.leafParent(); // Use parent
   553 
   554             if (block == null) block = node;
   555         }
   556 
   557         return block;
   558     }
   559 
   560     // Enebo: This method is not what the name or comment says it is?  findOuterMostBlockScope?
   561     public static Node findBlock(AstPath path) {
   562         Node candidate = null;
   563         for (Node curr : path) {
   564             if (curr instanceof IterNode) candidate = curr;
   565             if (curr instanceof ILocalScope) return candidate;
   566         }
   567 
   568         return candidate;
   569     }
   570 
   571     /** Find the closest method enclosing the given node */
   572     public static MethodDefNode findMethod(AstPath path) {
   573         for (Node curr : path) {
   574             if (curr instanceof MethodDefNode) return (MethodDefNode) curr;
   575             if (curr instanceof ILocalScope) break; // No def/defs so bump off early
   576         }
   577 
   578         return null;
   579     }
   580 
   581     // XXX Shouldn't this go in the REVERSE direction? I might find
   582     // a superclass here!
   583     // XXX What about SClassNode?
   584     public static ClassNode findClass(AstPath path) {
   585         // Find the closest block node enclosing the given node
   586         for (Node curr : path) {
   587             if (curr instanceof ClassNode) {
   588                 return (ClassNode)curr;
   589             }
   590         }
   591 
   592         return null;
   593     }
   594 
   595     public static IScopingNode findClassOrModule(AstPath path) {
   596         // Find the closest block node enclosing the given node
   597         for (Node curr : path) {
   598             // XXX What about SClassNodes?
   599             if (curr.getNodeType() == NodeType.CLASSNODE || curr.getNodeType() == NodeType.MODULENODE) {
   600                 return (IScopingNode)curr;
   601             }
   602         }
   603 
   604         return null;
   605     }
   606 
   607     public static boolean isCall(Node node) {
   608         return node.getNodeType() == NodeType.FCALLNODE ||
   609                 node.getNodeType() == NodeType.VCALLNODE ||
   610                 node.getNodeType() == NodeType.CALLNODE;
   611     }
   612 
   613     static boolean isRaiseCall(Node node) {
   614         return isCall(node) && "raise".equals(((INameNode) node).getName());
   615     }
   616 
   617     static Node findNextNonNewLineNode(Node target) {
   618         if (target.getNodeType() != NodeType.NEWLINENODE) {
   619             return target;
   620         }
   621         NewlineNode newlineNode = (NewlineNode) target;
   622         return findNextNonNewLineNode(newlineNode.getNextNode());
   623     }
   624     
   625     public static String getCallName(Node node) {
   626         assert isCall(node);
   627 
   628         if (node instanceof INameNode) return ((INameNode) node).getName();
   629 
   630         return null;
   631     }
   632 
   633     public static String getDefName(Node node) {
   634         if (node instanceof MethodDefNode) return ((MethodDefNode) node).getName();
   635 
   636         assert false : node;
   637 
   638         return null;
   639     }
   640     
   641     public static boolean isConstructorMethod(MethodDefNode node) {
   642         String name = node.getName();
   643 
   644         return name.equals("new") || name.equals("initialize"); // NOI18N
   645     }
   646 
   647     /**
   648      * Look for the caret offset in the parameter list; return the
   649      * index of the parameter that contains it.
   650      */
   651     public static int findArgumentIndex(Node node, int offset) {
   652         switch (node.getNodeType()) {
   653         case FCALLNODE: case CALLNODE: {
   654             Node argsNode = ((IArgumentNode)node).getArgs();
   655             
   656             return argsNode == null ? -1 : findArgumentIndex(argsNode, offset);
   657         }
   658         case ARGSCATNODE: {
   659             ArgsCatNode acn = (ArgsCatNode)node;
   660 
   661             int index = findArgumentIndex(acn.getFirst(), offset);
   662             if (index != -1) return index;
   663 
   664             index = findArgumentIndex(acn.getSecond(), offset);
   665             if (index != -1) return getConstantArgs(acn) + index; // Add in arg count on the left
   666                 
   667             SourcePosition pos = node.getPosition();
   668             if (offset >= pos.getStartOffset() && offset <= pos.getEndOffset())return getConstantArgs(acn);
   669             
   670             return -1;
   671         }
   672         case HASHNODE: 
   673             // Everything gets glommed into the same hash parameter offset
   674             return offset;
   675         default:
   676             if (node instanceof ListNode) {
   677                 List<Node> children = node.childNodes();
   678 
   679                 int prevEnd = Integer.MAX_VALUE;
   680 
   681                 for (int index = 0; index < children.size(); index++) {
   682                     Node child = children.get(index);
   683                     SourcePosition pos = child.getPosition();
   684 
   685                     if  (offset <= pos.getEndOffset() && (offset >= prevEnd || offset >= pos.getStartOffset())) return index;
   686 
   687                     prevEnd = pos.getEndOffset();
   688                 }
   689 
   690                 // Caret -inside- empty parentheses?
   691                 SourcePosition pos = node.getPosition();
   692                 if (offset > pos.getStartOffset() && offset < pos.getEndOffset()) return 0;
   693             } else {
   694                 SourcePosition pos = node.getPosition();
   695                 if (offset >= pos.getStartOffset() && offset <= pos.getEndOffset()) return 0;
   696             }
   697 
   698             return -1;
   699         }
   700     }
   701 
   702     /** Utility method used by findArgumentIndex: count the constant number of
   703      * arguments in a parameter list before the argscatnode */
   704     private static int getConstantArgs(ArgsCatNode acn) {
   705         Node node = acn.getFirst();
   706 
   707         if (node instanceof ListNode) {
   708             List<Node> children = node.childNodes();
   709 // TODO - if one of the children is Node.INVALID_POSITION perhaps I need to reduce the count            
   710 
   711             return children.size();
   712         } 
   713 
   714         return 1;
   715     }
   716 
   717     /**
   718      * Return true iff the given call note can be considered a valid call of the given method.
   719      */
   720     public static boolean isCallFor(Node call, Arity callArity, Node method) {
   721         assert isCall(call);
   722         assert method instanceof MethodDefNode;
   723 
   724         // Simple call today...
   725         return getDefName(method).equals(getCallName(call)) &&
   726         Arity.matches(callArity, Arity.getArity((MethodDefNode) method));
   727     }
   728 
   729     // TODO: use the structure analyzer data for more accurate traversal?
   730     /** For the given signature, locating the corresponding Node within the tree that
   731      * it corresponds to */
   732     public static Node findBySignature(Node root, String signature) {
   733         String originalSig = signature;
   734 
   735         //String name = signature.split("(::)")
   736         // Find next name we're looking for
   737         boolean[] lookingForMethod = new boolean[1];
   738         String name = getNextSigComponent(signature, lookingForMethod);
   739         signature = signature.substring(name.length());
   740 
   741         Node node = findBySignature(root, root, signature, name, lookingForMethod);
   742         
   743         // Handle top level methods
   744         if (node == null && originalSig.startsWith("Object#")) {
   745             // Just look for top level method definitions instead
   746             originalSig = originalSig.substring(originalSig.indexOf('#')+1);
   747             name = getNextSigComponent(signature, lookingForMethod);
   748             signature = originalSig.substring(name.length());
   749             lookingForMethod[0] = true;
   750             
   751             node = findBySignature(root, root, signature, name, lookingForMethod);
   752         }
   753 
   754         return node;
   755     }
   756 
   757     // For a signature of the form Foo::Bar#baz(arg1,arg2,...)
   758     // pull out the next component; in the above, successively return
   759     // "Foo", "Bar", "baz", etc.
   760     private static String getNextSigComponent(String signature, boolean[] lookingForMethod) {
   761         StringBuilder sb = new StringBuilder();
   762         int i = 0;
   763         int n = signature.length();
   764 
   765         // Skip leading separators
   766         for (; i < n; i++) {
   767             char c = signature.charAt(i);
   768 
   769             if (c == '#') {
   770                 lookingForMethod[0] = true;
   771                 continue;
   772             } else if ((c == ':') || (c == '(')) {
   773                 continue;
   774             }
   775 
   776             break;
   777         }
   778 
   779         // Add the name
   780         for (; i < n; i++) {
   781             char c = signature.charAt(i);
   782 
   783             if ((c == '#') || (c == ':') || (c == '(')) {
   784                 break;
   785             }
   786 
   787             sb.append(c);
   788         }
   789 
   790         return sb.toString();
   791     }
   792 
   793     private static Node findBySignature(Node root, Node node, String signature, String name, boolean[] lookingForMethod) {
   794         switch (node.getNodeType()) {
   795         case INSTASGNNODE:
   796             if (name.charAt(0) == '@' && name.equals(((INameNode) node).getLexicalName())) return node;
   797             break;
   798         case CLASSVARDECLNODE: case CLASSVARASGNNODE:
   799             if (name.startsWith("@@") && name.equals(((INameNode) node).getLexicalName())) return node;
   800             break;
   801 
   802         case DEFNNODE:
   803         case DEFSNODE: {
   804             MethodDefNode def = (MethodDefNode) node;
   805             if (lookingForMethod[0] && name.equals(def.getName())) {
   806                 // See if the parameter list matches
   807                 // XXX TODO
   808                 List<String> parameters = def.getArgs().getNormativeParameterNameList(false);
   809 
   810                 if (signature.length() == 0 && (parameters == null || parameters.isEmpty())) {
   811                     return def; // No args
   812                 } else if (signature.length() != 0) {
   813                     assert signature.charAt(0) == '(' : signature;
   814 
   815                     String argList = signature.substring(1, signature.length() - 1);
   816                     String[] args = argList.split(",");
   817 
   818                     if (args.length == parameters.size()) {
   819                         // Should I enforce equality here?
   820                         boolean equal = true;
   821 
   822                         for (int i = 0; i < args.length; i++) {
   823                             if (!args[i].equals(parameters.get(i))) {
   824                                 equal = false;
   825 
   826                                 break;
   827                             }
   828                         }
   829 
   830                         if (equal) return def;
   831                     }
   832                 }
   833             } else if (isAttr(def)) {
   834                 for (SymbolNode sym : getAttrSymbols(def)) {
   835                     if (name.equals(sym.getName())) return node;
   836                 }
   837             }
   838             break;
   839         }
   840         case FCALLNODE:
   841             if (isAttr(node) || isNamedScope(node) || isActiveRecordAssociation(node) || 
   842                     isNodeNameIn((INameNode) node, RubyStructureAnalyzer.DYNAMIC_METHODS)) {
   843                 for (Node each : getChildValues(node)) {
   844                     if (name.equals(getNameOrValue(each))) return each;
   845                 }
   846             } else if (TestNameResolver.isShouldaMethod(((INameNode) node).getName())) {
   847                 String shoulda = TestNameResolver.getTestName(new AstPath(root, node));
   848             
   849                 if (name.equals(shoulda)) return node;
   850             }
   851 
   852             break;
   853         case ALIASNODE:
   854             AliasNode aliasNode = (AliasNode) node;
   855             if (name.equals(getNameOrValue(aliasNode.getNewName())) || name.equals(getNameOrValue(aliasNode.getOldName()))) {
   856                 return aliasNode;
   857             }
   858             break;
   859         case CONSTDECLNODE:
   860             if (name.equals(getName(node))) return node;
   861         break;
   862         case CLASSNODE:
   863         case MODULENODE: {
   864                 Colon3Node c3n = ((IScopingNode)node).getCPath();
   865 
   866                 if (c3n instanceof Colon2Node) {
   867                     String fqn = getFqn((Colon2Node)c3n);
   868 
   869                     if (fqn.startsWith(name) && signature.startsWith(fqn.substring(name.length()))) {
   870                         signature = signature.substring(fqn.substring(name.length()).length());
   871                         name = getNextSigComponent(signature, lookingForMethod);
   872 
   873                         if (name.length() == 0) return node; // Signature is a class/module already.
   874 
   875                         int index = signature.indexOf(name);
   876                         assert index != -1;
   877                         signature = signature.substring(index + name.length());
   878                     }
   879                 } else if (name.equals(AstUtilities.getClassOrModuleName(((IScopingNode)node)))) {
   880                     name = getNextSigComponent(signature, lookingForMethod);
   881 
   882                     if (name.length() == 0) return node; // Signature is a class/module already.
   883 
   884                     int index = signature.indexOf(name);
   885                     assert index != -1;
   886                     signature = signature.substring(index + name.length());
   887                 }
   888             break;
   889         }
   890         case SCLASSNODE:
   891             Node receiver = ((SClassNode)node).getReceiver();
   892             String rn = null;
   893 
   894             if (receiver instanceof Colon2Node) {
   895                 // TODO - check to see if we qualify
   896                 rn = getName(receiver);
   897             } else if (receiver instanceof ConstNode) {
   898                 rn = getName(receiver);
   899             } // else: some other type of singleton class definition, like class << foo
   900 
   901             if (rn != null) {
   902                 if (name.equals(rn)) {
   903                     name = getNextSigComponent(signature, lookingForMethod);
   904 
   905                     if (name.length() == 0) return node; // Signature is a class/module already.
   906 
   907                     int index = signature.indexOf(name);
   908                     assert index != -1;
   909                     signature = signature.substring(index + name.length());
   910                 }
   911             }
   912             break;
   913         }
   914 
   915         boolean old = lookingForMethod[0];
   916 
   917         for (Node child : node.childNodes()) {
   918             Node match = findBySignature(root, child, signature, name, lookingForMethod);
   919 
   920             if (match != null) return match;
   921         }
   922         lookingForMethod[0] = old;
   923 
   924         return null;
   925     }
   926 
   927     /** Return true iff the given node contains the given offset */
   928     public static boolean containsOffset(Node node, int offset) {
   929         SourcePosition pos = node.getPosition();
   930 
   931         return ((offset >= pos.getStartOffset()) && (offset <= pos.getEndOffset()));
   932     }
   933 
   934     /**
   935      * Like {@link #getRange(org.jrubyparser.ast.Node) }, but returns a position
   936      * for <code>NilNode</code>s too (but <strong>not</strong> for 
   937      * <code>NilImplicitNode</code>s!!). This is needed for highlightning 
   938      * exit points, <code>nil</code> is a common return value. Possibly 
   939      * {@link #getRange(org.jrubyparser.ast.Node) } itself should return a range
   940      * for <code>NilNode</code>s, but I'm too afraid to change it now as it 
   941      * is used in a lot of places.
   942      * @param node
   943      * @return
   944      */
   945     static OffsetRange getRangeIncludeNil(Node node) {
   946         if (node != null && node.getClass().equals(NilNode.class)) {
   947             SourcePosition pos = node.getPosition();
   948             try {
   949                 return new OffsetRange(pos.getStartOffset(), pos.getEndOffset());
   950             } catch (Throwable t) {
   951                 // ...because there are some problems -- see AstUtilities.testStress
   952                 Exceptions.printStackTrace(t);
   953                 return OffsetRange.NONE;
   954             }
   955         }
   956         return getRange(node);
   957     }
   958     /**
   959      * Return a range that matches the given node's source buffer range
   960      */
   961     public static OffsetRange getRange(Node node) {
   962         if (node.getNodeType() == NodeType.NILNODE) return OffsetRange.NONE;
   963 
   964         SourcePosition pos = node.getPosition();
   965         try {
   966             return new OffsetRange(pos.getStartOffset(), pos.getEndOffset());
   967         } catch (Throwable t) {
   968             // ...because there are some problems -- see AstUtilities.testStress
   969             Exceptions.printStackTrace(t);
   970             return OffsetRange.NONE;
   971         }
   972     }
   973     
   974     public static OffsetRange offsetRangeFor(SourcePosition position) {
   975         return new OffsetRange(position.getStartOffset(), position.getEndOffset());
   976     }
   977 
   978     public static OffsetRange getNameRange(Node node) {
   979         if (node instanceof AssignableNode) {
   980             return offsetRangeFor(((AssignableNode)node).getLeftHandSidePosition());
   981         } else if (node instanceof MethodDefNode) {
   982             return offsetRangeFor(((INameNode) node).getLexicalNamePosition());
   983         } else if (isCall(node)) {
   984             return getCallRange(node);
   985         } else if (node instanceof ClassNode) {
   986             // TODO - try to pull out the constnode or colon2node holding the class name,
   987             // and return it!
   988             Colon3Node c3n = ((ClassNode)node).getCPath();
   989             if (c3n != null) {
   990                 return getRange(c3n);
   991             } else {
   992                 return getRange(node);
   993             }
   994         } else if (node instanceof ModuleNode) {
   995             // TODO - try to pull out the constnode or colon2node holding the class name,
   996             // and return it!
   997             Colon3Node c3n = ((ModuleNode)node).getCPath();
   998             if (c3n != null) {
   999                 return getRange(c3n);
  1000             } else {
  1001                 return getRange(node);
  1002             }
  1003 //        } else if (node instanceof SClassNode) {
  1004 //            // TODO - try to pull out the constnode or colon2node holding the class name,
  1005 //            // and return it!
  1006 //            Colon3Node c3n = ((SClassNode)node).getCPath();
  1007 //            if (c3n != null) {
  1008 //                return getRange(c3n);
  1009 //            } else {
  1010 //                return getRange(node);
  1011 //            }
  1012         } else {
  1013             return getRange(node);
  1014         }
  1015     }
  1016     
  1017     /** For CallNodes, the offset range for the AST node includes the entire parameter list.
  1018      *  We want ONLY the actual call/operator name. So compute that on our own.
  1019      */
  1020     public static OffsetRange getCallRange(Node node) {
  1021         SourcePosition pos = node.getPosition();
  1022         int start = pos.getStartOffset();
  1023         
  1024         assert isCall(node);
  1025         assert node instanceof INameNode;
  1026 
  1027         if (node instanceof CallNode) {
  1028             // A call of the form Foo.bar. "bar" is the CallNode, "Foo" is the ReceiverNode.
  1029             // Here I'm only handling named nodes; there may be others
  1030             Node receiver = ((CallNode)node).getReceiver();
  1031 
  1032             // end of "Foo::bar" + "."
  1033             if (receiver != null) start = receiver.getPosition().getEndOffset() + 1; 
  1034         }
  1035 
  1036         int end = node instanceof INameNode ? start + getName(node).length() : pos.getEndOffset();
  1037 
  1038         return new OffsetRange(start, end);
  1039     }
  1040 
  1041     /*
  1042     public static OffsetRange getFunctionNameRange(Node node) {
  1043         // TODO - enforce MethodDefNode and call getNameNode on it!
  1044         for (Node child : node.childNodes()) {
  1045             if (child instanceof ArgumentNode) {
  1046                 OffsetRange range = AstUtilities.getRange(child);
  1047 
  1048                 return range;
  1049             }
  1050         }
  1051 
  1052         if (node instanceof MethodDefNode) {
  1053             for (Node child : node.childNodes()) {
  1054                 if (child instanceof ConstNode) {
  1055                     SourcePosition pos = child.getPosition();
  1056                     int end = pos.getEndOffset();
  1057                     int start;
  1058 
  1059                     if (INCLUDE_DEFS_PREFIX) {
  1060                         start = pos.getStartOffset();
  1061                     } else {
  1062                         start = end + 1;
  1063                     }
  1064 
  1065                     // TODO - look at the source buffer and tweak offset if it's wrong
  1066                     // This assumes we have a single constant node, followed by a dot, followed by the name
  1067                     end = end + 1 + AstUtilities.getDefName(node).length(); // +1: "."
  1068 
  1069                     OffsetRange range = new OffsetRange(start, end);
  1070 
  1071                     return range;
  1072                 }
  1073             }
  1074         }
  1075 
  1076         return OffsetRange.NONE;
  1077     }*/
  1078 
  1079     /**
  1080      * Return the OffsetRange for an AliasNode that represents the new name portion.
  1081      */
  1082     public static OffsetRange getAliasNewRange(AliasNode node) {
  1083         // XXX I don't know where the old and new names are since the user COULD
  1084         // have used more than one whitespace character for separation. For now I'll
  1085         // just have to assume it's the normal case with one space:  alias new old.
  1086         // I -could- use the getPosition.getEndOffset() to see if this looks like it's
  1087         // the case (e.g. node length != "alias ".length + old.length+new.length+1).
  1088         // In this case I could go peeking in the source buffer to see where the
  1089         // spaces are - between alias and the first word or between old and new. XXX.
  1090         SourcePosition pos = node.getPosition();
  1091 
  1092         int newStart = pos.getStartOffset() + 6; // 6: "alias ".length()
  1093 
  1094         String name = getNameOrValue(node.getNewName());
  1095         int length = name != null ? name.length() : 0;
  1096         return new OffsetRange(newStart, newStart + length);
  1097     }
  1098 
  1099     /**
  1100      * Return the OffsetRange for an AliasNode that represents the old name portion.
  1101      */
  1102     public static OffsetRange getAliasOldRange(AliasNode node) {
  1103         // XXX I don't know where the old and new names are since the user COULD
  1104         // have used more than one whitespace character for separation. For now I'll
  1105         // just have to assume it's the normal case with one space:  alias new old.
  1106         // I -could- use the getPosition.getEndOffset() to see if this looks like it's
  1107         // the case (e.g. node length != "alias ".length + old.length+new.length+1).
  1108         // In this case I could go peeking in the source buffer to see where the
  1109         // spaces are - between alias and the first word or between old and new. XXX.
  1110         SourcePosition pos = node.getPosition();
  1111 
  1112         String newName = getNameOrValue(node.getNewName());
  1113         int newLength = newName != null ? newName.length() : 0;
  1114 
  1115         String oldName = getNameOrValue(node.getOldName());
  1116         int oldLength = oldName != null ? oldName.length() : 0;
  1117 
  1118         int oldStart = pos.getStartOffset() + 6 + newLength + 1; // 6: "alias ".length; 1: " ".length
  1119 
  1120         return new OffsetRange(oldStart, oldStart + oldLength);
  1121     }
  1122 
  1123     public static String getClassOrModuleName(IScopingNode node) {
  1124         return getName(node.getCPath());
  1125     }
  1126 
  1127     public static List<ClassNode> getClasses(Node root) {
  1128         // I would like to use a visitor for this, but it's not
  1129         // working - I get NPE's within DefaultIteratorVisitor
  1130         // on valid ASTs, and I see it's not used heavily in JRuby,
  1131         // so I'm not doing it this way for now.
  1132         //final List<ClassNode> classes = new ArrayList<ClassNode>();
  1133         //// There could be multiple Class definitions for this
  1134         //// same class, and (empirically) rdoc shows the documentation
  1135         //// for the last declaration.
  1136         //NodeVisitor findClasses = new AbstractVisitor() {
  1137         //    public Instruction visitClassNode(ClassNode node) {
  1138         //        classes.add(node);
  1139         //        return visitNode(node);
  1140         //    }
  1141         //
  1142         //    protected Instruction visitNode(Node iVisited) {
  1143         //        return null;
  1144         //    }
  1145         //};
  1146         //new DefaultIteratorVisitor(findClasses).visitRootNode((RootNode)parseResult.getRootNode());
  1147         List<ClassNode> classes = new ArrayList<ClassNode>();
  1148         addClasses(root, classes);
  1149 
  1150         return classes;
  1151     }
  1152 
  1153     private static void addClasses(Node node, List<ClassNode> classes) {
  1154         if (node instanceof ClassNode) classes.add((ClassNode)node);
  1155 
  1156         for (Node child : node.childNodes()) {
  1157             addClasses(child, classes);
  1158         }
  1159     }
  1160 
  1161     private static void addAncestorParents(Node node, StringBuilder sb) {
  1162         if (node instanceof Colon2Node) {
  1163             Colon2Node c2n = (Colon2Node)node;
  1164             addAncestorParents(c2n.getLeftNode(), sb);
  1165 
  1166             if ((sb.length() > 0) && (sb.charAt(sb.length() - 1) != ':')) {
  1167                 sb.append("::");
  1168             }
  1169 
  1170             sb.append(c2n.getName());
  1171         } else if (node instanceof INameNode) {
  1172             if ((sb.length() > 0) && (sb.charAt(sb.length() - 1) != ':')) {
  1173                 sb.append("::");
  1174             }
  1175 
  1176             sb.append(getName(node));
  1177         }
  1178     }
  1179 
  1180     public static String getFqn(Colon2Node c2n) {
  1181         StringBuilder sb = new StringBuilder();
  1182 
  1183         addAncestorParents(c2n, sb);
  1184 
  1185         return sb.toString();
  1186     }
  1187 
  1188     public static String getSuperclass(ClassNode clz) {
  1189         if (clz.getSuper() == null) return null;
  1190         
  1191         StringBuilder sb = new StringBuilder();
  1192         
  1193         addAncestorParents(clz.getSuper(), sb);
  1194 
  1195         return sb.toString();
  1196     }
  1197 
  1198     static String getFqnName(Node root, Node target) {
  1199         String fqn = getFqnName(new AstPath(root, target));
  1200         if (fqn.length() > 0) {
  1201             return fqn + "::" + getName(target);
  1202         }
  1203         return getName(target);
  1204     }
  1205 
  1206     static String getFqnName(AstPath path, String simpleName) {
  1207         String fqn = getFqnName(path);
  1208         if (fqn.length() > 0) {
  1209             return fqn + "::" + simpleName;
  1210         }
  1211         return simpleName;
  1212     }
  1213 
  1214     /** Compute the module/class name for the given node path */
  1215     public static String getFqnName(AstPath path) {
  1216         StringBuilder sb = new StringBuilder();
  1217 
  1218         Iterator<Node> it = path.rootToLeaf();
  1219 
  1220         while (it.hasNext()) {
  1221             Node node = it.next();
  1222 
  1223             if (node instanceof ModuleNode || node instanceof ClassNode) {
  1224                 Colon3Node cpath = ((IScopingNode)node).getCPath();
  1225                 if (cpath == null) continue;
  1226 
  1227                 if (sb.length() > 0) sb.append("::"); // NOI18N
  1228 
  1229                 if (cpath instanceof Colon2Node) {
  1230                     sb.append(getFqn((Colon2Node)cpath));
  1231                 } else {
  1232                     sb.append(cpath.getName());
  1233                 }
  1234             }
  1235         }
  1236 
  1237         return sb.toString();
  1238     }
  1239 
  1240     public static boolean isAttr(Node node) {
  1241         return isCallNode(node) && (isNodeNameIn((INameNode) node, ATTR_ACCESSORS) || isNodeNameIn((INameNode) node, CATTR_ACCESSORS));
  1242     }
  1243 
  1244     public static boolean isCAttr(Node node) {
  1245         return isCallNode(node) && isNodeNameIn((INameNode) node, CATTR_ACCESSORS);
  1246     }
  1247 
  1248     static boolean isNamedScope(Node node) {
  1249         return isCallNode(node) && isNodeNameIn((INameNode) node, NAMED_SCOPE);
  1250     }
  1251 
  1252     public static boolean isActiveRecordAssociation(Node node) {
  1253         return isCallNode(node) && isNodeNameIn((INameNode) node, ActiveRecordAssociationFinder.AR_ASSOCIATIONS);
  1254     }
  1255 
  1256     static boolean isNodeNameIn(INameNode node, String... names) {
  1257         String name = node.getName();
  1258         for (String each : names) {
  1259             if (each.equals(name)) return true;
  1260         }
  1261         return false;
  1262     }
  1263 
  1264     private static boolean isCallNode(Node node) {
  1265         return node instanceof FCallNode || node instanceof VCallNode;
  1266     }
  1267 
  1268     /**
  1269      * Returns the names or values of the nodes in the given {@code listNode}; 
  1270      * not including null and not empty values.
  1271      *
  1272      * @param listNode
  1273      * @return the names/values; the result does not contain {@code null}s or empty strings.
  1274      */
  1275     static List<String> getNamesOrValues(ListNode listNode) {
  1276         List<String> result = new ArrayList<String>(listNode.size());
  1277         for (int i = 0, max = listNode.size(); i < max; i++) {
  1278             Node n = listNode.get(i);
  1279 
  1280             // For dynamically computed strings, we have n instanceof DStrNode
  1281             // but I can't handle these anyway
  1282             if (n instanceof StrNode || n instanceof SymbolNode || n instanceof ConstNode) {
  1283                 String name = getNameOrValue(n);
  1284                 if (name != null && !name.isEmpty()) {
  1285                     result.add(name);
  1286                 }
  1287             } 
  1288         }
  1289         return result;
  1290 
  1291     }
  1292     /**
  1293      * Gets the name or the value of given node, depending on its type.
  1294      * 
  1295      * @param node the node whose value to get.
  1296      * @return the name or value of the given node or <code>null</code>.
  1297      */
  1298     public static String getNameOrValue(Node node) {
  1299         if (node instanceof StrNode) return ((StrNode) node).getValue();
  1300         if (node instanceof INameNode) return getName(node);
  1301 
  1302         if (node instanceof DSymbolNode) {
  1303             if (!node.childNodes().isEmpty()) {
  1304                 Node child = node.childNodes().get(0);
  1305                 return getNameOrValue(child);
  1306             }
  1307         }
  1308         if (node instanceof LiteralNode) {
  1309             return ((LiteralNode) node).getName();
  1310         }
  1311         return null;
  1312     }
  1313 
  1314     static SymbolNode[] getSymbols(Node node) {
  1315         List<SymbolNode> symbolList = new ArrayList<SymbolNode>();
  1316         for (Node child : getChildValues(node)) {
  1317             if (child instanceof SymbolNode) {
  1318                 symbolList.add((SymbolNode) child);
  1319             }
  1320 
  1321         }
  1322         return symbolList.toArray(new SymbolNode[symbolList.size()]);
  1323     }
  1324 
  1325     static List<Node> getChildValues(Node node) {
  1326         List<Node> result = new ArrayList<Node>();
  1327         for (Node child : node.childNodes()) {
  1328             if (child instanceof ListNode) {
  1329                 List<Node> values = child.childNodes();
  1330                 result.addAll(values);
  1331             }
  1332         }
  1333         return result;
  1334 
  1335     }
  1336 
  1337     public static SymbolNode[] getAttrSymbols(Node node) {
  1338         assert isAttr(node);
  1339         return getSymbols(node);
  1340     }
  1341 
  1342     public static Node getRoot(final FileObject sourceFO) {
  1343         Source source = Source.create(sourceFO);
  1344         if (source == null) return null;
  1345 
  1346         final Node[] rootHolder = new Node[1];
  1347         try {
  1348             ParserManager.parse(Collections.singleton(source), new UserTask() {
  1349                 @Override
  1350                 public void run(ResultIterator ri) throws Exception {
  1351                     Parser.Result result = ri.getParserResult();
  1352                     rootHolder[0] = getRoot(result);
  1353                 }
  1354             });
  1355         } catch (ParseException ex) {
  1356             Exceptions.printStackTrace(ex);
  1357         }
  1358         return rootHolder[0];
  1359     }
  1360 
  1361     /**
  1362      * Gets the root node from the given <code>parserResult</code>. May return
  1363      * <code>null</code> if <code>parserResult</code> was not a <code>RubyParserResult</code> or
  1364      * did not have a root node. For example for parts of .erb files the parserResult might
  1365      * (validly) be RhtmlParser$FakeParserResult, in which case this method returns <code>null</code>.
  1366      * 
  1367      * @param parserResult 
  1368      * @return the root node or <code>null</code>.
  1369      */
  1370     public static Node getRoot(Parser.Result parserResult) {
  1371         if (parserResult == null) return null;
  1372         
  1373         if (!(parserResult instanceof RubyParseResult)) {
  1374             if (LOGGER.isLoggable(Level.FINE)) {
  1375                 String msg = "Expected RubyParseResult, but got " + parserResult; //NOI18N
  1376                 // log an exception too see the stack trace
  1377                 LOGGER.log(Level.FINE, msg, new Exception(msg));
  1378             }
  1379             return null;
  1380         }
  1381 
  1382         return ((RubyParseResult) parserResult).getRootNode();
  1383     }
  1384 
  1385     /**
  1386      * Get the private and protected methods in the given class
  1387      */
  1388     public static void findPrivateMethods(Node clz, Set<Node> protectedMethods,
  1389         Set<Node> privateMethods) {
  1390         Set<String> publicMethodSymbols = new HashSet<String>();
  1391         Set<String> protectedMethodSymbols = new HashSet<String>();
  1392         Set<String> privateMethodSymbols = new HashSet<String>();
  1393         Set<Node> publicMethods = new HashSet<Node>();
  1394 
  1395         List<Node> list = clz.childNodes();
  1396 
  1397         Modifier access = Modifier.PUBLIC;
  1398 
  1399         for (Node child : list) {
  1400             access = getMethodAccess(child, access, publicMethodSymbols, protectedMethodSymbols,
  1401                     privateMethodSymbols, publicMethods, protectedMethods, privateMethods);
  1402         }
  1403 
  1404         // Can't just return private methods directly, since sometimes you can
  1405         // specify that a particular method is public before we know about it,
  1406         // so I can't just remove it from the known private list when I see the
  1407         // access modifier
  1408         privateMethodSymbols.removeAll(publicMethodSymbols);
  1409         protectedMethodSymbols.removeAll(publicMethodSymbols);
  1410 
  1411         // Should I worry about private foo;  protected :foo ?
  1412         // Seems unlikely somebody would do that... I guess
  1413         // I could do privateMethodSymbols.removeAll(protectedMethodSymbols) etc.
  1414         //privateMethodSymbols.removeAll(protectedMethodSymbols);
  1415         //protectedMethodSymbols.removeAll(privateMethodSymbols);
  1416 
  1417         // Add all methods known to be private into the private node set
  1418         for (String name : privateMethodSymbols) {
  1419             for (Node n : publicMethods) {
  1420                 if (name.equals(AstUtilities.getDefName(n))) {
  1421                     privateMethods.add(n);
  1422                 }
  1423             }
  1424         }
  1425 
  1426         for (String name : protectedMethodSymbols) {
  1427             for (Node n : publicMethods) {
  1428                 if (name.equals(AstUtilities.getDefName(n))) {
  1429                     protectedMethods.add(n);
  1430                 }
  1431             }
  1432         }
  1433     }
  1434 
  1435     /**
  1436      * @todo Should I really recurse into classes? If I have nested classes private
  1437      *  methods ther shouldn't be included for the parent!
  1438      *
  1439      * @param access The "current" known access level (PUBLIC, PROTECTED or PRIVATE)
  1440      * @return the access level to continue with at this syntactic level
  1441      */
  1442     private static Modifier getMethodAccess(Node node, Modifier access,
  1443         Set<String> publicMethodSymbols, Set<String> protectedMethodSymbols,
  1444         Set<String> privateMethodSymbols, Set<Node> publicMethods, Set<Node> protectedMethods,
  1445         Set<Node> privateMethods) {
  1446         if (node instanceof MethodDefNode) {
  1447             if (access == Modifier.PRIVATE) {
  1448                 privateMethods.add(node);
  1449             } else if (access == Modifier.PUBLIC) {
  1450                 publicMethods.add(node);
  1451             } else if (access == Modifier.PROTECTED) {
  1452                 protectedMethods.add(node);
  1453             }
  1454 
  1455             // XXX Can I have nested method definitions? If so I may have to continue here
  1456             return access;
  1457         } else if (node instanceof VCallNode || node instanceof FCallNode) {
  1458             String name = getName(node);
  1459 
  1460             if ("private".equals(name)) {
  1461                 // TODO - see if it has arguments, if it does - it's just a single
  1462                 // method defined to be private
  1463                 // Iterate over arguments and add symbols...
  1464                 if (Arity.callHasArguments(node)) {
  1465                     List<Node> params = node.childNodes();
  1466 
  1467                     for (Node param : params) {
  1468                         if (param instanceof ListNode) {
  1469                             List<Node> params2 = param.childNodes();
  1470 
  1471                             for (Node param2 : params2) {
  1472                                 if (param2 instanceof SymbolNode) {
  1473                                     String symbol = getName(param2);
  1474                                     privateMethodSymbols.add(symbol);
  1475                                 }
  1476                             }
  1477                         }
  1478                     }
  1479                 } else {
  1480                     access = Modifier.PRIVATE;
  1481                 }
  1482 
  1483                 return access;
  1484             } else if ("protected".equals(name)) {
  1485                 // TODO - see if it has arguments, if it does - it's just a single
  1486                 // method defined to be private
  1487                 // Iterate over arguments and add symbols...
  1488                 if (Arity.callHasArguments(node)) {
  1489                     List<Node> params = node.childNodes();
  1490 
  1491                     for (Node param : params) {
  1492                         if (param instanceof ListNode) {
  1493                             List<Node> params2 = param.childNodes();
  1494 
  1495                             for (Node param2 : params2) {
  1496                                 if (param2 instanceof SymbolNode) {
  1497                                     String symbol = getName(param2);
  1498                                     protectedMethodSymbols.add(symbol);
  1499                                 }
  1500                             }
  1501                         }
  1502                     }
  1503                 } else {
  1504                     access = Modifier.PROTECTED;
  1505                 }
  1506 
  1507                 return access;
  1508             } else if ("public".equals(name)) {
  1509                 if (!Arity.callHasArguments(node)) {
  1510                     access = Modifier.PUBLIC;
  1511 
  1512                     return access;
  1513                 } else {
  1514                     List<Node> params = node.childNodes();
  1515 
  1516                     for (Node param : params) {
  1517                         if (param instanceof ListNode) {
  1518                             List<Node> params2 = param.childNodes();
  1519 
  1520                             for (Node param2 : params2) {
  1521                                 if (param2 instanceof SymbolNode) {
  1522                                     String symbol = getName(param2);
  1523                                     publicMethodSymbols.add(symbol);
  1524                                 }
  1525                             }
  1526                         }
  1527                     }
  1528                 }
  1529             }
  1530 
  1531             return access;
  1532         } else if (node instanceof ClassNode || node instanceof ModuleNode) {
  1533             return access;
  1534         }
  1535 
  1536         for (Node child : node.childNodes()) {
  1537             access = getMethodAccess(child, access, publicMethodSymbols, protectedMethodSymbols,
  1538                     privateMethodSymbols, publicMethods, protectedMethods, privateMethods);
  1539         }
  1540 
  1541         return access;
  1542     }
  1543     
  1544     /**
  1545      * Get the method name for the given offset - or null if it cannot be found. This
  1546      * will initiate a new parse job if necessary.
  1547      */
  1548     public static String getMethodName(FileObject fo, final int lexOffset) {
  1549         Source source = Source.create(fo);
  1550 
  1551         if (source == null) {
  1552             return null;
  1553         }
  1554 
  1555         final String[] methodName = new String[1];
  1556         try {
  1557             ParserManager.parse(Collections.singleton(source), new UserTask() {
  1558                 @Override
  1559                 public void run(ResultIterator resultIterator) throws Exception {
  1560                     Parser.Result result = resultIterator.getParserResult();
  1561                     Node root = AstUtilities.getRoot(result);
  1562                     if (root == null) {
  1563                         return;
  1564                     }
  1565                     int astOffset = AstUtilities.getAstOffset(result, lexOffset);
  1566                     if (astOffset == -1) {
  1567                         return;
  1568                     }
  1569                     org.jrubyparser.ast.MethodDefNode method = AstUtilities.findMethodAtOffset(root, astOffset);
  1570                     if (method == null) {
  1571                         // It's possible the user had the caret on a line
  1572                         // that includes a method that isn't actually inside
  1573                         // the method block - such as the beginning of the
  1574                         // "def" line, or the end of a line after "end".
  1575                         // The latter isn't very likely, but the former can
  1576                         // happen, so let's check the method bodies at the
  1577                         // end of the current line
  1578                         BaseDocument doc = RubyUtils.getDocument(result);
  1579                         if (doc != null) {
  1580                             try {
  1581                                 int endOffset = Utilities.getRowEnd(doc, lexOffset);
  1582                                 if (endOffset != lexOffset) {
  1583                                     astOffset = AstUtilities.getAstOffset(result, endOffset);
  1584                                     if (astOffset == -1) {
  1585                                         return;
  1586                                     }
  1587                                     method = AstUtilities.findMethodAtOffset(root, astOffset);
  1588                                 }
  1589                             } catch (BadLocationException ble) {
  1590                                 // do nothing - see #154991
  1591                             }
  1592                         }
  1593                     }
  1594                     if (method != null) {
  1595                         methodName[0] = method.getName();
  1596                     }
  1597                 }
  1598             });
  1599         } catch (ParseException ex) {
  1600             Exceptions.printStackTrace(ex);
  1601         }
  1602 
  1603         return methodName[0];
  1604     }
  1605 
  1606     /**
  1607      * Get the test name surrounding the given offset - or null if it cannot be found.
  1608      * NOTE: This will initiate a new parse job if necessary. 
  1609      */
  1610     public static String getTestName(FileObject fo, final int caretOffset) {
  1611         Source source = Source.create(fo);
  1612 
  1613         if (source == null) {
  1614             return null;
  1615         }
  1616         
  1617         final String[] testName = new String[1];
  1618 
  1619         try {
  1620             ParserManager.parse(Collections.singleton(source), new UserTask() {
  1621 
  1622                 @Override
  1623                 public void run(ResultIterator resultIterator) throws Exception {
  1624                     try {
  1625                         Parser.Result result = resultIterator.getParserResult();
  1626                         Node root = AstUtilities.getRoot(result);
  1627                         if (root == null) {
  1628                             return;
  1629                         }
  1630                         // Make sure the offset isn't at the beginning of a line
  1631                         BaseDocument doc = RubyUtils.getDocument(result, true);
  1632                         if (doc == null) {
  1633                             return;
  1634                         }
  1635                         int lexOffset = caretOffset;
  1636                         int rowStart = Utilities.getRowFirstNonWhite(doc, lexOffset);
  1637                         if (rowStart != -1 && lexOffset <= rowStart) {
  1638                             lexOffset = rowStart + 1;
  1639                         }
  1640                         int astOffset = AstUtilities.getAstOffset(result, lexOffset);
  1641                         if (astOffset == -1) {
  1642                             return;
  1643                         }
  1644                         AstPath path = new AstPath(root, astOffset);
  1645                         
  1646                         testName[0] = TestNameResolver.getTestName(path);
  1647                         
  1648                     } catch (BadLocationException ex) {
  1649                         // do nothing - see #154991
  1650                     }
  1651                 }
  1652             });
  1653         } catch (ParseException ex) {
  1654             Exceptions.printStackTrace(ex);
  1655         }
  1656 
  1657         return testName[0];
  1658     }
  1659 
  1660     public static int findOffset(FileObject fo, final String methodName) {
  1661         Source source = Source.create(fo);
  1662 
  1663         if (source == null) {
  1664             return -1;
  1665         }
  1666         
  1667         final int[] offset = new int[1];
  1668         offset[0] = -1;
  1669 
  1670         try {
  1671             ParserManager.parse(Collections.singleton(source), new UserTask() {
  1672                 @Override
  1673                 public void run(ResultIterator resultIterator) throws Exception {
  1674                     Parser.Result result = resultIterator.getParserResult();
  1675                     Node root = AstUtilities.getRoot(result);
  1676                     if (root == null) {
  1677                         return;
  1678                     }
  1679 
  1680                     org.jrubyparser.ast.Node method =
  1681                             AstUtilities.findMethod(root, methodName, Arity.UNKNOWN);
  1682 
  1683                     if (method != null) {
  1684                         int startOffset = method.getPosition().getStartOffset();
  1685                         offset[0] = startOffset;
  1686                     }
  1687                 }
  1688             });
  1689         } catch (ParseException ex) {
  1690             Exceptions.printStackTrace(ex);
  1691         }
  1692 
  1693         return offset[0];
  1694     }
  1695     
  1696     /** Collect nodes of the given types (node.getNodeType()==NodeType.x) under the given root */
  1697     public static void addNodesByType(Node root, NodeType[] nodeIds, List<Node> result) {
  1698         for (NodeType nodeId : nodeIds) {
  1699             if (root.getNodeType() == nodeId) {
  1700                 result.add(root);
  1701                 break;
  1702             }
  1703         }
  1704 
  1705         for (Node child : root.childNodes()) {
  1706             addNodesByType(child, nodeIds, result);
  1707         }
  1708     }
  1709     
  1710     public static List<Node> getOutermostToInnerMostBlocks(Node leaf) {
  1711         Node scope = leaf.getInnermostIter();
  1712         
  1713         if (scope == null) return Collections.emptyList();
  1714         
  1715         List<Node> result = new ArrayList<Node>();
  1716         while (scope != null) {
  1717             result.add(scope);
  1718             
  1719             scope = scope.getInnermostIter();
  1720         }
  1721         
  1722         Collections.reverse(result);
  1723         
  1724         return result;
  1725     }
  1726     
  1727     /** Return all the blocknodes that apply to the given node. The outermost block
  1728      * is returned first.
  1729      */
  1730     public static List<Node> getApplicableBlocks(AstPath path, boolean includeNested) {
  1731         Node block = AstUtilities.findBlock(path);
  1732 
  1733         if (block == null) { // Use parent
  1734             block = path.leafParent();
  1735 
  1736             if (block == null) return Collections.emptyList();
  1737         }
  1738         
  1739         List<Node> result = new ArrayList<Node>();
  1740         Iterator<Node> it = path.leafToRoot();
  1741         
  1742         // Skip the leaf node, we're going to add it unconditionally afterwards
  1743         if (includeNested) {
  1744             if (it.hasNext()) it.next();
  1745         }
  1746 
  1747         Node leaf = path.root();
  1748 
  1749         while_loop:
  1750         while (it.hasNext()) {
  1751             Node n = it.next();
  1752             switch (n.getNodeType()) {
  1753                 case ITERNODE:
  1754                     leaf = n;
  1755                     result.add(n);
  1756                     break;
  1757                 case DEFNNODE:
  1758                 case DEFSNODE:
  1759                 case CLASSNODE:
  1760                 case SCLASSNODE:
  1761                 case MODULENODE:
  1762                     leaf = n;
  1763                     break while_loop;
  1764             }
  1765         }
  1766 
  1767         if (includeNested) {
  1768             addNodesByType(leaf, new NodeType[] { NodeType.ITERNODE }, result);
  1769         }
  1770         
  1771       return result;
  1772     }
  1773     
  1774     public static String guessName(Parser.Result result, OffsetRange lexRange, OffsetRange astRange) {
  1775         String guessedName = "";
  1776         
  1777         // Try to guess the name - see if it's in a method and if so name it after the parameter
  1778         IndexedMethod[] methodHolder = new IndexedMethod[1];
  1779         @SuppressWarnings("unchecked") Set<IndexedMethod>[] alternatesHolder = new Set[1];
  1780         int[] paramIndexHolder = new int[1];
  1781         int[] anchorOffsetHolder = new int[1];
  1782         if (!RubyMethodCompleter.computeMethodCall(result, lexRange.getStart(), astRange.getStart(),
  1783                 methodHolder, paramIndexHolder, anchorOffsetHolder, alternatesHolder, QuerySupport.Kind.PREFIX)) {
  1784 
  1785             return guessedName;
  1786         }
  1787 
  1788         IndexedMethod targetMethod = methodHolder[0];
  1789         int index = paramIndexHolder[0];
  1790 
  1791         List<String> params = targetMethod.getParameters();
  1792         if (params == null || params.size() <= index) {
  1793             return guessedName;
  1794         }
  1795         
  1796         String s = params.get(index);
  1797         if (s.startsWith("*") || s.startsWith("&")) { // Don't include * or & in variable name
  1798             s = s.substring(1);
  1799         }
  1800         return s;
  1801     }
  1802     
  1803     public static Set<String> getUsedFields(RubyIndex index, AstPath path) {
  1804         String fqn = AstUtilities.getFqnName(path);
  1805         if (fqn == null || fqn.length() == 0) {
  1806             return Collections.emptySet();
  1807         }
  1808         Set<IndexedField> fields = index.getInheritedFields(fqn, "", QuerySupport.Kind.PREFIX, false);
  1809         Set<String> fieldNames = new HashSet<String>();
  1810         for (IndexedField f : fields) {
  1811             fieldNames.add(f.getName());
  1812         }
  1813         
  1814         return fieldNames;
  1815     }
  1816     
  1817     public static Set<String> getUsedMethods(RubyIndex index, AstPath path) {
  1818         String fqn = AstUtilities.getFqnName(path);
  1819         if (fqn == null || fqn.length() == 0) {
  1820             return Collections.emptySet();
  1821         }
  1822         Set<IndexedMethod> methods = index.getInheritedMethods(fqn, "", QuerySupport.Kind.PREFIX);
  1823         Set<String> methodNames = new HashSet<String>();
  1824         for (IndexedMethod m : methods) {
  1825             methodNames.add(m.getName());
  1826         }
  1827         
  1828         return methodNames;
  1829     }
  1830     
  1831     /** @todo Implement properly */
  1832     public static Set<String> getUsedConstants(RubyIndex index, AstPath path) {
  1833         //String fqn = AstUtilities.getFqnName(path);
  1834         //if (fqn == null || fqn.length() == 0) {
  1835             return Collections.emptySet();
  1836         //}
  1837         //Set<IndexedConstant> constants = index.getInheritedConstants(fqn, "", QuerySupport.Kind.PREFIX);
  1838         //Set<String> constantNames = new HashSet<String>();
  1839         //for (IndexedConstant m : constants) {
  1840         //    constantNames.add(m.getName());
  1841         //}
  1842         //
  1843         //return constantNames;
  1844     }
  1845     
  1846     public static Set<String> getUsedLocalNames(AstPath path, Node closest) {
  1847         Node method = AstUtilities.findLocalScope(closest, path);
  1848         Map<String, Node> variables = new HashMap<String, Node>();
  1849         // Add locals
  1850         RubyCodeCompleter.addLocals(method, variables);
  1851 
  1852         List<Node> applicableBlocks = AstUtilities.getApplicableBlocks(path, false);
  1853         for (Node block : applicableBlocks) {
  1854             RubyCodeCompleter.addDynamic(block, variables);
  1855         }
  1856         
  1857         return variables.keySet();
  1858     }
  1859 
  1860     /**
  1861      * Throws {@link ClassCastException} if the given node is not instance of
  1862      * {@link INameNode} or {@link LiteralNode}.
  1863      *
  1864      * @param node instance of {@link INameNode} or {@link LiteralNode}.
  1865      * @return node's name
  1866      */
  1867     public static String getName(final Node node) {
  1868         if (node instanceof LiteralNode) return ((LiteralNode) node).getName();
  1869 
  1870         return ((INameNode) node).getName();
  1871     }
  1872 
  1873     /**
  1874      * Like {@link #getName(org.jrubyparser.ast.Node) }, but instead of throwing
  1875      * a CCE returns <code>null</code> if the given <code>node</code> wasn't an
  1876      * instance of {@ INameNode}.
  1877      *
  1878      * @param node
  1879      * @return the name or <code>null</code>.
  1880      */
  1881     static String safeGetName(final Node node) {
  1882         if (node instanceof INameNode) return ((INameNode) node).getName();
  1883         if (node instanceof LiteralNode) return ((LiteralNode) node).getName();
  1884         return null;
  1885     }
  1886 
  1887     /**
  1888      * Finds exit points of a method definition for the given node.
  1889      *
  1890      * @param defNode {@link MethodDefNode method definition node}
  1891      * @param exits accumulator for found exit points
  1892      */
  1893     public static void findExitPoints(final MethodDefNode defNode, final Collection<? super Node> exits) {
  1894         Node body = defNode.getBody();
  1895 
  1896         if (body != null) findExitPoints(body, exits); // method with empty body
  1897     }
  1898 
  1899     static void findExitPoints(final Node body, final Collection<? super Node> exits) {
  1900         findNonLastExitPoints(body, exits);
  1901         findLastNodes(body, exits);
  1902     }
  1903 
  1904     /**
  1905      * Gets the values of the given {@code args}, as fully qualified 
  1906      * names in case of {@link Colon2Node}s.
  1907      * @param args
  1908      * @return
  1909      */
  1910     static List<String> getValuesAsFqn(ListNode args) {
  1911         if (args.size() == 0) return Collections.emptyList();
  1912 
  1913         List<String> result = new ArrayList<String>(args.size());
  1914         for (Node n : args.childNodes()) {
  1915             if (n instanceof Colon2Node) {
  1916                 result.add(getFqn((Colon2Node) n));
  1917             } else if (n instanceof INameNode) {
  1918                 result.add(getName(n));
  1919             }
  1920         }
  1921         return result;
  1922     }
  1923 
  1924     private static void findLastNodes(final Node node, Collection<? super Node> result) {
  1925         if (node == null) return;
  1926 
  1927         List<Node> children = findExitChidren(node);
  1928         if (children.isEmpty()) {
  1929             result.add(node);
  1930             return;
  1931         }
  1932         // AttrAssgn does not put arguments into an ArgsNode unfortunately.
  1933         if (!(node instanceof AttrAssignNode)) {
  1934             for (Node child : children) {
  1935                 if (child instanceof ArgsNode || child instanceof ArgumentNode) {
  1936                     // Done - no valid statement
  1937                     result.add(node);
  1938                     return;
  1939                 }
  1940                 findLastNodes(child, result);
  1941             }
  1942         }
  1943     }
  1944 
  1945     private static List<Node> findExitChidren(Node node) {
  1946         if (node instanceof IfNode) {
  1947             IfNode ifNode = (IfNode) node;
  1948             return Arrays.asList(ifNode.getThenBody(), ifNode.getElseBody());
  1949         }
  1950         if (node instanceof CaseNode) {
  1951             CaseNode caseNode = (CaseNode) node;
  1952             ListNode cases = caseNode.getCases();
  1953             List<Node> result = new ArrayList<Node>(cases.childNodes());
  1954             result.add(caseNode.getElse());
  1955             return result;
  1956         }
  1957         if (node instanceof WhenNode) {
  1958             WhenNode whenNode = (WhenNode) node;
  1959             return Collections.singletonList(whenNode.getBody());
  1960         }
  1961         if (node instanceof OrNode) return node.childNodes();
  1962         if (node instanceof AndNode) return Collections.singletonList(((AndNode) node).getSecond());
  1963         if (node instanceof ReturnNode || isCall(node) || node instanceof ILiteralNode || node instanceof HashNode
  1964                 || node instanceof DotNode || node instanceof NotNode || node instanceof UntilNode
  1965                 || node instanceof DefinedNode) {
  1966             return Collections.emptyList();
  1967         }
  1968         if (node instanceof RescueNode) return node.childNodes();
  1969 
  1970         List<Node> children = node.childNodes();
  1971         if (!children.isEmpty()) {
  1972             Node lastChild = children.get(children.size() -1);
  1973             
  1974             return Collections.singletonList(lastChild);
  1975         }
  1976         return children;
  1977     }
  1978 
  1979 
  1980     /** Helper for {@link #findExitPoints}. */
  1981     private static void findNonLastExitPoints(final Node node, final Collection<? super Node> exits) {
  1982         switch (node.getNodeType()) {
  1983             case RETURNNODE: case YIELDNODE:
  1984                 exits.add(node);
  1985                 break;
  1986             case CLASSNODE: case SCLASSNODE: case MODULENODE:
  1987                 return; // Don't go into sub methods, classes, etc
  1988             case FCALLNODE:
  1989                 String name = ((INameNode) node).getName();
  1990                 if ("fail".equals(name) || "raise".equals(name)) exits.add(node); // NOI18N
  1991                 break;
  1992         }
  1993         if (node instanceof MethodDefNode) return;  // Don't go into sub methods, classes, etc
  1994 
  1995         for (Node child : node.childNodes()) {
  1996             findNonLastExitPoints(child, exits);
  1997         }
  1998     }
  1999 }