Too much stuff in one commit. Rename blocks now follows language semantics. Removal of more ASTPath consumers
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4 * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
6 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7 * Other names may be trademarks of their respective owners.
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]"
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.
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.
44 package org.netbeans.modules.ruby;
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;
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;
126 * Various utilities for operating on the JRuby ASTs that are used
129 * @todo Rewrite many of the custom recursion routines to simply
130 * call {@link addNodesByType} and then iterate (without recursion) over
135 public class AstUtilities {
136 private static final Logger LOGGER = Logger.getLogger(AstUtilities.class.getName());
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"};
141 /** ActiveSupport extensions */
142 private static final String[] CATTR_ACCESSORS = {"cattr_reader", "cattr_accessor", "cattr_writer"};
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"};
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>.
154 public static RubyParseResult getParseResult(Parser.Result result) {
155 if (!(result instanceof RubyParseResult)) {
156 LOGGER.log(Level.WARNING, "Expected RubyParseResult, but have {0}", result);
159 return (RubyParseResult) result;
162 public static int getAstOffset(Parser.Result info, int lexOffset) {
163 RubyParseResult result = getParseResult(info);
164 if (result == null) return lexOffset;
166 return result.getSnapshot().getEmbeddedOffset(lexOffset);
169 public static OffsetRange getAstOffsets(Parser.Result info, OffsetRange lexicalRange) {
170 RubyParseResult result = getParseResult(info);
171 if (result == null) return lexicalRange;
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;
178 // Assumes the translated range maintains size
179 return new OffsetRange(start, start + lexicalRange.getLength());
182 /** This is a utility class only, not instantiatiable */
183 private AstUtilities() {
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.
190 public static List<String> gatherDocumentation(Snapshot baseDoc, Node node) {
191 LinkedList<String> comments = new LinkedList<String>();
192 int elementBegin = node.getPosition().getStartOffset();
195 if (elementBegin < 0 || elementBegin >= baseDoc.getText().length()) return null;
197 // Search to previous lines, locate comments. Once we have a non-whitespace line that isn't
198 // a comment, we're done
200 int offset = GsfUtilities.getRowStart(baseDoc.getText(), elementBegin);
203 // Skip empty and whitespace lines
204 while (offset >= 0) {
205 // Find beginning of line
206 offset = GsfUtilities.getRowStart(baseDoc.getText(), offset);
208 if (!GsfUtilities.isRowEmpty(baseDoc.getText(), offset) &&
209 !GsfUtilities.isRowWhite(baseDoc.getText(), offset)) {
220 while (offset >= 0) {
221 // Find beginning of line
222 offset = GsfUtilities.getRowStart(baseDoc.getText(), offset);
224 if (GsfUtilities.isRowEmpty(baseDoc.getText(), offset) || GsfUtilities.isRowWhite(baseDoc.getText(), offset)) {
225 // Empty lines not allowed within an rdoc
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();
234 // Tolerate "public", "private" and "protected" here --
235 // Test::Unit::Assertions likes to put these in front of each
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);
245 } else if (line.equals("public") || line.equals("private") ||
246 line.equals("protected")) { // NOI18N
247 // Skip newlines back up to the comment
250 while (offset >= 0) {
251 // Find beginning of line
252 offset = GsfUtilities.getRowStart(baseDoc.getText(), offset);
254 if (!GsfUtilities.isRowEmpty(baseDoc.getText(), offset) &&
255 !GsfUtilities.isRowWhite(baseDoc.getText(), offset)) {
264 // No longer in a comment
271 } catch (BadLocationException ble) {
272 // do nothing - see #154991
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);
285 // Search backwards in the document for the =begin (if any) and add all lines in reverse
287 while (offset >= 0) {
288 // Find beginning of line
289 offset = GsfUtilities.getRowStart(baseDoc.getText(), offset);
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();
296 if (line.startsWith("=begin")) return; // We're done!
298 comments.addFirst(line);
306 * <strong>This method may block for a long time; use with caution.</strong>.
308 public static Node getForeignNode(final IndexedElement elem) {
309 return getForeignNode(elem, null);
313 * <strong>This method may block for a long time; use with caution.</strong>.
315 * @param foreignInfoHolder
318 public static Node getForeignNode(final IndexedElement elem, final Parser.Result[] foreignInfoHolder) {
319 FileObject fo = elem.getFileObject();
324 Source source = Source.create(fo);
325 final Node[] nodeHolder = new Node[1];
327 ParserManager.parse(Collections.singleton(source), new UserTask() {
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;
336 Node root = AstUtilities.getRoot(result);
338 String signature = elem.getSignature();
340 if (signature != null) {
341 Node node = AstUtilities.findBySignature(root, signature);
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
348 signature = signature.replaceFirst("new", "initialize"); //NOI18N
350 node = AstUtilities.findBySignature(root, signature);
353 nodeHolder[0] = node;
358 } catch (ParseException ex) {
359 Exceptions.printStackTrace(ex);
364 return nodeHolder[0];
367 // public static Node getForeignNode(final IndexedElement o) {
368 // FileObject fo = o.getFileObject();
373 // if (file == null) {
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();
384 // if (doc == null) {
389 // return doc.getText(0, doc.getLength());
390 // } catch (BadLocationException ble) {
391 // IOException ioe = new IOException();
392 // ioe.initCause(ble);
397 // public int getCaretOffset(ParserFile fileObject) {
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);
408 // ParserResult result = listener.getParserResult();
410 // if (result == null) {
414 // Node root = AstUtilities.getRoot(result);
416 // if (root == null) {
420 // String signature = o.getSignature();
422 // if (signature == null) {
426 // Node node = AstUtilities.findBySignature(root, signature);
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);
437 public static int boundCaretOffset(ParserResult result, int caretOffset) {
438 Document doc = RubyUtils.getDocument(result);
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();
446 if (caretOffset > length) caretOffset = length;
453 * Return the set of requires that are defined in this AST
454 * (no transitive closure though).
456 public static Set<String> getRequires(Node root) {
457 Set<String> requires = new HashSet<String>();
458 addRequires(root, requires);
463 private static void addRequires(Node node, Set<String> requires) {
464 if (node.getNodeType() == NodeType.FCALLNODE) {
465 FCallNode fcall = (FCallNode) node;
467 if ("require".equals(fcall.getName()) && fcall.getArgs() instanceof ListNode) {
468 ListNode args = (ListNode) fcall.getArgs(); // FIXME: Can this ever not be fcall?
470 if (args.size() > 0) {
471 Node n = args.get(0);
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();
477 if (require != null && require.length() > 0) requires.add(require.toString());
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
486 for (Node child : node.childNodes()) {
487 addRequires(child, requires);
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;
497 for (Node child : node.childNodes()) {
498 MethodDefNode match = findMethod(child, name, arity);
500 if (match != null) return match;
507 * Gets the closest node at the given offset.
511 * @return the closest node or <code>null</code>.
513 public static Node findNodeAtOffset(Node root, int offset) {
514 return root == null ? null : root.getNodeAt(offset);
517 public static MethodDefNode findMethodAtOffset(Node root, int offset) {
518 Node child = findNodeAtOffset(root, offset);
520 return child == null ? null : child.getMethodFor();
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;
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();
537 method = findBlock(path);
538 if (method != null) return method;
540 method = path.leafParent();
542 if (method.getNodeType() == NodeType.NEWLINENODE) method = path.leafGrandParent();
543 if (method == null) method = node;
548 public static Node findDynamicScope(Node node, AstPath path) {
549 Node block = findBlock(path);
552 block = path.leafParent(); // Use parent
554 if (block == null) block = node;
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;
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
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;
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;
607 public static boolean isCall(Node node) {
608 return node.getNodeType() == NodeType.FCALLNODE ||
609 node.getNodeType() == NodeType.VCALLNODE ||
610 node.getNodeType() == NodeType.CALLNODE;
613 static boolean isRaiseCall(Node node) {
614 return isCall(node) && "raise".equals(((INameNode) node).getName());
617 static Node findNextNonNewLineNode(Node target) {
618 if (target.getNodeType() != NodeType.NEWLINENODE) {
621 NewlineNode newlineNode = (NewlineNode) target;
622 return findNextNonNewLineNode(newlineNode.getNextNode());
625 public static String getCallName(Node node) {
628 if (node instanceof INameNode) return ((INameNode) node).getName();
633 public static String getDefName(Node node) {
634 if (node instanceof MethodDefNode) return ((MethodDefNode) node).getName();
641 public static boolean isConstructorMethod(MethodDefNode node) {
642 String name = node.getName();
644 return name.equals("new") || name.equals("initialize"); // NOI18N
648 * Look for the caret offset in the parameter list; return the
649 * index of the parameter that contains it.
651 public static int findArgumentIndex(Node node, int offset) {
652 switch (node.getNodeType()) {
653 case FCALLNODE: case CALLNODE: {
654 Node argsNode = ((IArgumentNode)node).getArgs();
656 return argsNode == null ? -1 : findArgumentIndex(argsNode, offset);
659 ArgsCatNode acn = (ArgsCatNode)node;
661 int index = findArgumentIndex(acn.getFirst(), offset);
662 if (index != -1) return index;
664 index = findArgumentIndex(acn.getSecond(), offset);
665 if (index != -1) return getConstantArgs(acn) + index; // Add in arg count on the left
667 SourcePosition pos = node.getPosition();
668 if (offset >= pos.getStartOffset() && offset <= pos.getEndOffset())return getConstantArgs(acn);
673 // Everything gets glommed into the same hash parameter offset
676 if (node instanceof ListNode) {
677 List<Node> children = node.childNodes();
679 int prevEnd = Integer.MAX_VALUE;
681 for (int index = 0; index < children.size(); index++) {
682 Node child = children.get(index);
683 SourcePosition pos = child.getPosition();
685 if (offset <= pos.getEndOffset() && (offset >= prevEnd || offset >= pos.getStartOffset())) return index;
687 prevEnd = pos.getEndOffset();
690 // Caret -inside- empty parentheses?
691 SourcePosition pos = node.getPosition();
692 if (offset > pos.getStartOffset() && offset < pos.getEndOffset()) return 0;
694 SourcePosition pos = node.getPosition();
695 if (offset >= pos.getStartOffset() && offset <= pos.getEndOffset()) return 0;
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();
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
711 return children.size();
718 * Return true iff the given call note can be considered a valid call of the given method.
720 public static boolean isCallFor(Node call, Arity callArity, Node method) {
722 assert method instanceof MethodDefNode;
724 // Simple call today...
725 return getDefName(method).equals(getCallName(call)) &&
726 Arity.matches(callArity, Arity.getArity((MethodDefNode) method));
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;
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());
741 Node node = findBySignature(root, root, signature, name, lookingForMethod);
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;
751 node = findBySignature(root, root, signature, name, lookingForMethod);
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();
763 int n = signature.length();
765 // Skip leading separators
767 char c = signature.charAt(i);
770 lookingForMethod[0] = true;
772 } else if ((c == ':') || (c == '(')) {
781 char c = signature.charAt(i);
783 if ((c == '#') || (c == ':') || (c == '(')) {
790 return sb.toString();
793 private static Node findBySignature(Node root, Node node, String signature, String name, boolean[] lookingForMethod) {
794 switch (node.getNodeType()) {
796 if (name.charAt(0) == '@' && name.equals(((INameNode) node).getLexicalName())) return node;
798 case CLASSVARDECLNODE: case CLASSVARASGNNODE:
799 if (name.startsWith("@@") && name.equals(((INameNode) node).getLexicalName())) return node;
804 MethodDefNode def = (MethodDefNode) node;
805 if (lookingForMethod[0] && name.equals(def.getName())) {
806 // See if the parameter list matches
808 List<String> parameters = def.getArgs().getNormativeParameterNameList(false);
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;
815 String argList = signature.substring(1, signature.length() - 1);
816 String[] args = argList.split(",");
818 if (args.length == parameters.size()) {
819 // Should I enforce equality here?
820 boolean equal = true;
822 for (int i = 0; i < args.length; i++) {
823 if (!args[i].equals(parameters.get(i))) {
830 if (equal) return def;
833 } else if (isAttr(def)) {
834 for (SymbolNode sym : getAttrSymbols(def)) {
835 if (name.equals(sym.getName())) return node;
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;
846 } else if (TestNameResolver.isShouldaMethod(((INameNode) node).getName())) {
847 String shoulda = TestNameResolver.getTestName(new AstPath(root, node));
849 if (name.equals(shoulda)) return node;
854 AliasNode aliasNode = (AliasNode) node;
855 if (name.equals(getNameOrValue(aliasNode.getNewName())) || name.equals(getNameOrValue(aliasNode.getOldName()))) {
860 if (name.equals(getName(node))) return node;
864 Colon3Node c3n = ((IScopingNode)node).getCPath();
866 if (c3n instanceof Colon2Node) {
867 String fqn = getFqn((Colon2Node)c3n);
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);
873 if (name.length() == 0) return node; // Signature is a class/module already.
875 int index = signature.indexOf(name);
877 signature = signature.substring(index + name.length());
879 } else if (name.equals(AstUtilities.getClassOrModuleName(((IScopingNode)node)))) {
880 name = getNextSigComponent(signature, lookingForMethod);
882 if (name.length() == 0) return node; // Signature is a class/module already.
884 int index = signature.indexOf(name);
886 signature = signature.substring(index + name.length());
891 Node receiver = ((SClassNode)node).getReceiver();
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
902 if (name.equals(rn)) {
903 name = getNextSigComponent(signature, lookingForMethod);
905 if (name.length() == 0) return node; // Signature is a class/module already.
907 int index = signature.indexOf(name);
909 signature = signature.substring(index + name.length());
915 boolean old = lookingForMethod[0];
917 for (Node child : node.childNodes()) {
918 Node match = findBySignature(root, child, signature, name, lookingForMethod);
920 if (match != null) return match;
922 lookingForMethod[0] = old;
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();
931 return ((offset >= pos.getStartOffset()) && (offset <= pos.getEndOffset()));
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.
945 static OffsetRange getRangeIncludeNil(Node node) {
946 if (node != null && node.getClass().equals(NilNode.class)) {
947 SourcePosition pos = node.getPosition();
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;
956 return getRange(node);
959 * Return a range that matches the given node's source buffer range
961 public static OffsetRange getRange(Node node) {
962 if (node.getNodeType() == NodeType.NILNODE) return OffsetRange.NONE;
964 SourcePosition pos = node.getPosition();
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;
974 public static OffsetRange offsetRangeFor(SourcePosition position) {
975 return new OffsetRange(position.getStartOffset(), position.getEndOffset());
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,
988 Colon3Node c3n = ((ClassNode)node).getCPath();
990 return getRange(c3n);
992 return getRange(node);
994 } else if (node instanceof ModuleNode) {
995 // TODO - try to pull out the constnode or colon2node holding the class name,
997 Colon3Node c3n = ((ModuleNode)node).getCPath();
999 return getRange(c3n);
1001 return getRange(node);
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);
1010 // return getRange(node);
1013 return getRange(node);
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.
1020 public static OffsetRange getCallRange(Node node) {
1021 SourcePosition pos = node.getPosition();
1022 int start = pos.getStartOffset();
1024 assert isCall(node);
1025 assert node instanceof INameNode;
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();
1032 // end of "Foo::bar" + "."
1033 if (receiver != null) start = receiver.getPosition().getEndOffset() + 1;
1036 int end = node instanceof INameNode ? start + getName(node).length() : pos.getEndOffset();
1038 return new OffsetRange(start, end);
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);
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();
1059 if (INCLUDE_DEFS_PREFIX) {
1060 start = pos.getStartOffset();
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: "."
1069 OffsetRange range = new OffsetRange(start, end);
1076 return OffsetRange.NONE;
1080 * Return the OffsetRange for an AliasNode that represents the new name portion.
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();
1092 int newStart = pos.getStartOffset() + 6; // 6: "alias ".length()
1094 String name = getNameOrValue(node.getNewName());
1095 int length = name != null ? name.length() : 0;
1096 return new OffsetRange(newStart, newStart + length);
1100 * Return the OffsetRange for an AliasNode that represents the old name portion.
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();
1112 String newName = getNameOrValue(node.getNewName());
1113 int newLength = newName != null ? newName.length() : 0;
1115 String oldName = getNameOrValue(node.getOldName());
1116 int oldLength = oldName != null ? oldName.length() : 0;
1118 int oldStart = pos.getStartOffset() + 6 + newLength + 1; // 6: "alias ".length; 1: " ".length
1120 return new OffsetRange(oldStart, oldStart + oldLength);
1123 public static String getClassOrModuleName(IScopingNode node) {
1124 return getName(node.getCPath());
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);
1142 // protected Instruction visitNode(Node iVisited) {
1146 //new DefaultIteratorVisitor(findClasses).visitRootNode((RootNode)parseResult.getRootNode());
1147 List<ClassNode> classes = new ArrayList<ClassNode>();
1148 addClasses(root, classes);
1153 private static void addClasses(Node node, List<ClassNode> classes) {
1154 if (node instanceof ClassNode) classes.add((ClassNode)node);
1156 for (Node child : node.childNodes()) {
1157 addClasses(child, classes);
1161 private static void addAncestorParents(Node node, StringBuilder sb) {
1162 if (node instanceof Colon2Node) {
1163 Colon2Node c2n = (Colon2Node)node;
1164 addAncestorParents(c2n.getLeftNode(), sb);
1166 if ((sb.length() > 0) && (sb.charAt(sb.length() - 1) != ':')) {
1170 sb.append(c2n.getName());
1171 } else if (node instanceof INameNode) {
1172 if ((sb.length() > 0) && (sb.charAt(sb.length() - 1) != ':')) {
1176 sb.append(getName(node));
1180 public static String getFqn(Colon2Node c2n) {
1181 StringBuilder sb = new StringBuilder();
1183 addAncestorParents(c2n, sb);
1185 return sb.toString();
1188 public static String getSuperclass(ClassNode clz) {
1189 if (clz.getSuper() == null) return null;
1191 StringBuilder sb = new StringBuilder();
1193 addAncestorParents(clz.getSuper(), sb);
1195 return sb.toString();
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);
1203 return getName(target);
1206 static String getFqnName(AstPath path, String simpleName) {
1207 String fqn = getFqnName(path);
1208 if (fqn.length() > 0) {
1209 return fqn + "::" + simpleName;
1214 /** Compute the module/class name for the given node path */
1215 public static String getFqnName(AstPath path) {
1216 StringBuilder sb = new StringBuilder();
1218 Iterator<Node> it = path.rootToLeaf();
1220 while (it.hasNext()) {
1221 Node node = it.next();
1223 if (node instanceof ModuleNode || node instanceof ClassNode) {
1224 Colon3Node cpath = ((IScopingNode)node).getCPath();
1225 if (cpath == null) continue;
1227 if (sb.length() > 0) sb.append("::"); // NOI18N
1229 if (cpath instanceof Colon2Node) {
1230 sb.append(getFqn((Colon2Node)cpath));
1232 sb.append(cpath.getName());
1237 return sb.toString();
1240 public static boolean isAttr(Node node) {
1241 return isCallNode(node) && (isNodeNameIn((INameNode) node, ATTR_ACCESSORS) || isNodeNameIn((INameNode) node, CATTR_ACCESSORS));
1244 public static boolean isCAttr(Node node) {
1245 return isCallNode(node) && isNodeNameIn((INameNode) node, CATTR_ACCESSORS);
1248 static boolean isNamedScope(Node node) {
1249 return isCallNode(node) && isNodeNameIn((INameNode) node, NAMED_SCOPE);
1252 public static boolean isActiveRecordAssociation(Node node) {
1253 return isCallNode(node) && isNodeNameIn((INameNode) node, ActiveRecordAssociationFinder.AR_ASSOCIATIONS);
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;
1264 private static boolean isCallNode(Node node) {
1265 return node instanceof FCallNode || node instanceof VCallNode;
1269 * Returns the names or values of the nodes in the given {@code listNode};
1270 * not including null and not empty values.
1273 * @return the names/values; the result does not contain {@code null}s or empty strings.
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);
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()) {
1293 * Gets the name or the value of given node, depending on its type.
1295 * @param node the node whose value to get.
1296 * @return the name or value of the given node or <code>null</code>.
1298 public static String getNameOrValue(Node node) {
1299 if (node instanceof StrNode) return ((StrNode) node).getValue();
1300 if (node instanceof INameNode) return getName(node);
1302 if (node instanceof DSymbolNode) {
1303 if (!node.childNodes().isEmpty()) {
1304 Node child = node.childNodes().get(0);
1305 return getNameOrValue(child);
1308 if (node instanceof LiteralNode) {
1309 return ((LiteralNode) node).getName();
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);
1322 return symbolList.toArray(new SymbolNode[symbolList.size()]);
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);
1337 public static SymbolNode[] getAttrSymbols(Node node) {
1338 assert isAttr(node);
1339 return getSymbols(node);
1342 public static Node getRoot(final FileObject sourceFO) {
1343 Source source = Source.create(sourceFO);
1344 if (source == null) return null;
1346 final Node[] rootHolder = new Node[1];
1348 ParserManager.parse(Collections.singleton(source), new UserTask() {
1350 public void run(ResultIterator ri) throws Exception {
1351 Parser.Result result = ri.getParserResult();
1352 rootHolder[0] = getRoot(result);
1355 } catch (ParseException ex) {
1356 Exceptions.printStackTrace(ex);
1358 return rootHolder[0];
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>.
1367 * @param parserResult
1368 * @return the root node or <code>null</code>.
1370 public static Node getRoot(Parser.Result parserResult) {
1371 if (parserResult == null) return null;
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));
1382 return ((RubyParseResult) parserResult).getRootNode();
1386 * Get the private and protected methods in the given class
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>();
1395 List<Node> list = clz.childNodes();
1397 Modifier access = Modifier.PUBLIC;
1399 for (Node child : list) {
1400 access = getMethodAccess(child, access, publicMethodSymbols, protectedMethodSymbols,
1401 privateMethodSymbols, publicMethods, protectedMethods, privateMethods);
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
1408 privateMethodSymbols.removeAll(publicMethodSymbols);
1409 protectedMethodSymbols.removeAll(publicMethodSymbols);
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);
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);
1426 for (String name : protectedMethodSymbols) {
1427 for (Node n : publicMethods) {
1428 if (name.equals(AstUtilities.getDefName(n))) {
1429 protectedMethods.add(n);
1436 * @todo Should I really recurse into classes? If I have nested classes private
1437 * methods ther shouldn't be included for the parent!
1439 * @param access The "current" known access level (PUBLIC, PROTECTED or PRIVATE)
1440 * @return the access level to continue with at this syntactic level
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);
1455 // XXX Can I have nested method definitions? If so I may have to continue here
1457 } else if (node instanceof VCallNode || node instanceof FCallNode) {
1458 String name = getName(node);
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();
1467 for (Node param : params) {
1468 if (param instanceof ListNode) {
1469 List<Node> params2 = param.childNodes();
1471 for (Node param2 : params2) {
1472 if (param2 instanceof SymbolNode) {
1473 String symbol = getName(param2);
1474 privateMethodSymbols.add(symbol);
1480 access = Modifier.PRIVATE;
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();
1491 for (Node param : params) {
1492 if (param instanceof ListNode) {
1493 List<Node> params2 = param.childNodes();
1495 for (Node param2 : params2) {
1496 if (param2 instanceof SymbolNode) {
1497 String symbol = getName(param2);
1498 protectedMethodSymbols.add(symbol);
1504 access = Modifier.PROTECTED;
1508 } else if ("public".equals(name)) {
1509 if (!Arity.callHasArguments(node)) {
1510 access = Modifier.PUBLIC;
1514 List<Node> params = node.childNodes();
1516 for (Node param : params) {
1517 if (param instanceof ListNode) {
1518 List<Node> params2 = param.childNodes();
1520 for (Node param2 : params2) {
1521 if (param2 instanceof SymbolNode) {
1522 String symbol = getName(param2);
1523 publicMethodSymbols.add(symbol);
1532 } else if (node instanceof ClassNode || node instanceof ModuleNode) {
1536 for (Node child : node.childNodes()) {
1537 access = getMethodAccess(child, access, publicMethodSymbols, protectedMethodSymbols,
1538 privateMethodSymbols, publicMethods, protectedMethods, privateMethods);
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.
1548 public static String getMethodName(FileObject fo, final int lexOffset) {
1549 Source source = Source.create(fo);
1551 if (source == null) {
1555 final String[] methodName = new String[1];
1557 ParserManager.parse(Collections.singleton(source), new UserTask() {
1559 public void run(ResultIterator resultIterator) throws Exception {
1560 Parser.Result result = resultIterator.getParserResult();
1561 Node root = AstUtilities.getRoot(result);
1565 int astOffset = AstUtilities.getAstOffset(result, lexOffset);
1566 if (astOffset == -1) {
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);
1581 int endOffset = Utilities.getRowEnd(doc, lexOffset);
1582 if (endOffset != lexOffset) {
1583 astOffset = AstUtilities.getAstOffset(result, endOffset);
1584 if (astOffset == -1) {
1587 method = AstUtilities.findMethodAtOffset(root, astOffset);
1589 } catch (BadLocationException ble) {
1590 // do nothing - see #154991
1594 if (method != null) {
1595 methodName[0] = method.getName();
1599 } catch (ParseException ex) {
1600 Exceptions.printStackTrace(ex);
1603 return methodName[0];
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.
1610 public static String getTestName(FileObject fo, final int caretOffset) {
1611 Source source = Source.create(fo);
1613 if (source == null) {
1617 final String[] testName = new String[1];
1620 ParserManager.parse(Collections.singleton(source), new UserTask() {
1623 public void run(ResultIterator resultIterator) throws Exception {
1625 Parser.Result result = resultIterator.getParserResult();
1626 Node root = AstUtilities.getRoot(result);
1630 // Make sure the offset isn't at the beginning of a line
1631 BaseDocument doc = RubyUtils.getDocument(result, true);
1635 int lexOffset = caretOffset;
1636 int rowStart = Utilities.getRowFirstNonWhite(doc, lexOffset);
1637 if (rowStart != -1 && lexOffset <= rowStart) {
1638 lexOffset = rowStart + 1;
1640 int astOffset = AstUtilities.getAstOffset(result, lexOffset);
1641 if (astOffset == -1) {
1644 AstPath path = new AstPath(root, astOffset);
1646 testName[0] = TestNameResolver.getTestName(path);
1648 } catch (BadLocationException ex) {
1649 // do nothing - see #154991
1653 } catch (ParseException ex) {
1654 Exceptions.printStackTrace(ex);
1660 public static int findOffset(FileObject fo, final String methodName) {
1661 Source source = Source.create(fo);
1663 if (source == null) {
1667 final int[] offset = new int[1];
1671 ParserManager.parse(Collections.singleton(source), new UserTask() {
1673 public void run(ResultIterator resultIterator) throws Exception {
1674 Parser.Result result = resultIterator.getParserResult();
1675 Node root = AstUtilities.getRoot(result);
1680 org.jrubyparser.ast.Node method =
1681 AstUtilities.findMethod(root, methodName, Arity.UNKNOWN);
1683 if (method != null) {
1684 int startOffset = method.getPosition().getStartOffset();
1685 offset[0] = startOffset;
1689 } catch (ParseException ex) {
1690 Exceptions.printStackTrace(ex);
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) {
1705 for (Node child : root.childNodes()) {
1706 addNodesByType(child, nodeIds, result);
1710 public static List<Node> getOutermostToInnerMostBlocks(Node leaf) {
1711 Node scope = leaf.getInnermostIter();
1713 if (scope == null) return Collections.emptyList();
1715 List<Node> result = new ArrayList<Node>();
1716 while (scope != null) {
1719 scope = scope.getInnermostIter();
1722 Collections.reverse(result);
1727 /** Return all the blocknodes that apply to the given node. The outermost block
1728 * is returned first.
1730 public static List<Node> getApplicableBlocks(AstPath path, boolean includeNested) {
1731 Node block = AstUtilities.findBlock(path);
1733 if (block == null) { // Use parent
1734 block = path.leafParent();
1736 if (block == null) return Collections.emptyList();
1739 List<Node> result = new ArrayList<Node>();
1740 Iterator<Node> it = path.leafToRoot();
1742 // Skip the leaf node, we're going to add it unconditionally afterwards
1743 if (includeNested) {
1744 if (it.hasNext()) it.next();
1747 Node leaf = path.root();
1750 while (it.hasNext()) {
1752 switch (n.getNodeType()) {
1767 if (includeNested) {
1768 addNodesByType(leaf, new NodeType[] { NodeType.ITERNODE }, result);
1774 public static String guessName(Parser.Result result, OffsetRange lexRange, OffsetRange astRange) {
1775 String guessedName = "";
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)) {
1788 IndexedMethod targetMethod = methodHolder[0];
1789 int index = paramIndexHolder[0];
1791 List<String> params = targetMethod.getParameters();
1792 if (params == null || params.size() <= index) {
1796 String s = params.get(index);
1797 if (s.startsWith("*") || s.startsWith("&")) { // Don't include * or & in variable name
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();
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());
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();
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());
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();
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());
1843 //return constantNames;
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>();
1850 RubyCodeCompleter.addLocals(method, variables);
1852 List<Node> applicableBlocks = AstUtilities.getApplicableBlocks(path, false);
1853 for (Node block : applicableBlocks) {
1854 RubyCodeCompleter.addDynamic(block, variables);
1857 return variables.keySet();
1861 * Throws {@link ClassCastException} if the given node is not instance of
1862 * {@link INameNode} or {@link LiteralNode}.
1864 * @param node instance of {@link INameNode} or {@link LiteralNode}.
1865 * @return node's name
1867 public static String getName(final Node node) {
1868 if (node instanceof LiteralNode) return ((LiteralNode) node).getName();
1870 return ((INameNode) node).getName();
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}.
1879 * @return the name or <code>null</code>.
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();
1888 * Finds exit points of a method definition for the given node.
1890 * @param defNode {@link MethodDefNode method definition node}
1891 * @param exits accumulator for found exit points
1893 public static void findExitPoints(final MethodDefNode defNode, final Collection<? super Node> exits) {
1894 Node body = defNode.getBody();
1896 if (body != null) findExitPoints(body, exits); // method with empty body
1899 static void findExitPoints(final Node body, final Collection<? super Node> exits) {
1900 findNonLastExitPoints(body, exits);
1901 findLastNodes(body, exits);
1905 * Gets the values of the given {@code args}, as fully qualified
1906 * names in case of {@link Colon2Node}s.
1910 static List<String> getValuesAsFqn(ListNode args) {
1911 if (args.size() == 0) return Collections.emptyList();
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));
1924 private static void findLastNodes(final Node node, Collection<? super Node> result) {
1925 if (node == null) return;
1927 List<Node> children = findExitChidren(node);
1928 if (children.isEmpty()) {
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
1940 findLastNodes(child, result);
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());
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());
1957 if (node instanceof WhenNode) {
1958 WhenNode whenNode = (WhenNode) node;
1959 return Collections.singletonList(whenNode.getBody());
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();
1968 if (node instanceof RescueNode) return node.childNodes();
1970 List<Node> children = node.childNodes();
1971 if (!children.isEmpty()) {
1972 Node lastChild = children.get(children.size() -1);
1974 return Collections.singletonList(lastChild);
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:
1986 case CLASSNODE: case SCLASSNODE: case MODULENODE:
1987 return; // Don't go into sub methods, classes, etc
1989 String name = ((INameNode) node).getName();
1990 if ("fail".equals(name) || "raise".equals(name)) exits.add(node); // NOI18N
1993 if (node instanceof MethodDefNode) return; // Don't go into sub methods, classes, etc
1995 for (Node child : node.childNodes()) {
1996 findNonLastExitPoints(child, exits);