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 public static Node findNodeAtOffset(ParserResult info, int caretOffset) {
524 Node root = AstUtilities.getRoot(info);
525 if (root == null) return null;
527 int astOffset = AstUtilities.getAstOffset(info, caretOffset);
528 if (astOffset == -1) return null;
530 Node closest = root.getNodeAt(astOffset);
531 if (closest == null) return null;
536 // FIXME: Replace this with something which returns nearest module...Do we really specifically need class?
537 public static ClassNode findClassAtOffset(Node root, int offset) {
538 for (Node node: new AstPath(root, offset)) {
539 if (node instanceof ClassNode) return (ClassNode) node;
545 public static Node findLocalScope(Node node, AstPath path) {
546 Node method = findMethod(path);
547 if (method != null) return method;
548 if (path.root() != null) return path.root();
550 method = findBlock(path);
551 if (method != null) return method;
553 method = path.leafParent();
555 if (method.getNodeType() == NodeType.NEWLINENODE) method = path.leafGrandParent();
556 if (method == null) method = node;
561 public static Node findDynamicScope(Node node, AstPath path) {
562 Node block = findBlock(path);
565 block = path.leafParent(); // Use parent
567 if (block == null) block = node;
573 // Enebo: This method is not what the name or comment says it is? findOuterMostBlockScope?
574 public static Node findBlock(AstPath path) {
575 Node candidate = null;
576 for (Node curr : path) {
577 if (curr instanceof IterNode) candidate = curr;
578 if (curr instanceof ILocalScope) return candidate;
584 /** Find the closest method enclosing the given node */
585 public static MethodDefNode findMethod(AstPath path) {
586 for (Node curr : path) {
587 if (curr instanceof MethodDefNode) return (MethodDefNode) curr;
588 if (curr instanceof ILocalScope) break; // No def/defs so bump off early
594 // XXX Shouldn't this go in the REVERSE direction? I might find
595 // a superclass here!
596 // XXX What about SClassNode?
597 public static ClassNode findClass(AstPath path) {
598 // Find the closest block node enclosing the given node
599 for (Node curr : path) {
600 if (curr instanceof ClassNode) {
601 return (ClassNode)curr;
608 public static IScopingNode findClassOrModule(AstPath path) {
609 // Find the closest block node enclosing the given node
610 for (Node curr : path) {
611 // XXX What about SClassNodes?
612 if (curr.getNodeType() == NodeType.CLASSNODE || curr.getNodeType() == NodeType.MODULENODE) {
613 return (IScopingNode)curr;
620 public static boolean isCall(Node node) {
621 return node.getNodeType() == NodeType.FCALLNODE ||
622 node.getNodeType() == NodeType.VCALLNODE ||
623 node.getNodeType() == NodeType.CALLNODE;
626 static boolean isRaiseCall(Node node) {
627 return isCall(node) && "raise".equals(((INameNode) node).getName());
630 static Node findNextNonNewLineNode(Node target) {
631 if (target.getNodeType() != NodeType.NEWLINENODE) {
634 NewlineNode newlineNode = (NewlineNode) target;
635 return findNextNonNewLineNode(newlineNode.getNextNode());
638 public static String getCallName(Node node) {
641 if (node instanceof INameNode) return ((INameNode) node).getName();
646 public static String getDefName(Node node) {
647 if (node instanceof MethodDefNode) return ((MethodDefNode) node).getName();
654 public static boolean isConstructorMethod(MethodDefNode node) {
655 String name = node.getName();
657 return name.equals("new") || name.equals("initialize"); // NOI18N
661 * Look for the caret offset in the parameter list; return the
662 * index of the parameter that contains it.
664 public static int findArgumentIndex(Node node, int offset) {
665 switch (node.getNodeType()) {
666 case FCALLNODE: case CALLNODE: {
667 Node argsNode = ((IArgumentNode)node).getArgs();
669 return argsNode == null ? -1 : findArgumentIndex(argsNode, offset);
672 ArgsCatNode acn = (ArgsCatNode)node;
674 int index = findArgumentIndex(acn.getFirst(), offset);
675 if (index != -1) return index;
677 index = findArgumentIndex(acn.getSecond(), offset);
678 if (index != -1) return getConstantArgs(acn) + index; // Add in arg count on the left
680 SourcePosition pos = node.getPosition();
681 if (offset >= pos.getStartOffset() && offset <= pos.getEndOffset())return getConstantArgs(acn);
686 // Everything gets glommed into the same hash parameter offset
689 if (node instanceof ListNode) {
690 List<Node> children = node.childNodes();
692 int prevEnd = Integer.MAX_VALUE;
694 for (int index = 0; index < children.size(); index++) {
695 Node child = children.get(index);
696 SourcePosition pos = child.getPosition();
698 if (offset <= pos.getEndOffset() && (offset >= prevEnd || offset >= pos.getStartOffset())) return index;
700 prevEnd = pos.getEndOffset();
703 // Caret -inside- empty parentheses?
704 SourcePosition pos = node.getPosition();
705 if (offset > pos.getStartOffset() && offset < pos.getEndOffset()) return 0;
707 SourcePosition pos = node.getPosition();
708 if (offset >= pos.getStartOffset() && offset <= pos.getEndOffset()) return 0;
715 /** Utility method used by findArgumentIndex: count the constant number of
716 * arguments in a parameter list before the argscatnode */
717 private static int getConstantArgs(ArgsCatNode acn) {
718 Node node = acn.getFirst();
720 if (node instanceof ListNode) {
721 List<Node> children = node.childNodes();
722 // TODO - if one of the children is Node.INVALID_POSITION perhaps I need to reduce the count
724 return children.size();
731 * Return true iff the given call note can be considered a valid call of the given method.
733 public static boolean isCallFor(Node call, Arity callArity, Node method) {
735 assert method instanceof MethodDefNode;
737 // Simple call today...
738 return getDefName(method).equals(getCallName(call)) &&
739 Arity.matches(callArity, Arity.getArity((MethodDefNode) method));
742 // TODO: use the structure analyzer data for more accurate traversal?
743 /** For the given signature, locating the corresponding Node within the tree that
744 * it corresponds to */
745 public static Node findBySignature(Node root, String signature) {
746 String originalSig = signature;
748 //String name = signature.split("(::)")
749 // Find next name we're looking for
750 boolean[] lookingForMethod = new boolean[1];
751 String name = getNextSigComponent(signature, lookingForMethod);
752 signature = signature.substring(name.length());
754 Node node = findBySignature(root, root, signature, name, lookingForMethod);
756 // Handle top level methods
757 if (node == null && originalSig.startsWith("Object#")) {
758 // Just look for top level method definitions instead
759 originalSig = originalSig.substring(originalSig.indexOf('#')+1);
760 name = getNextSigComponent(signature, lookingForMethod);
761 signature = originalSig.substring(name.length());
762 lookingForMethod[0] = true;
764 node = findBySignature(root, root, signature, name, lookingForMethod);
770 // For a signature of the form Foo::Bar#baz(arg1,arg2,...)
771 // pull out the next component; in the above, successively return
772 // "Foo", "Bar", "baz", etc.
773 private static String getNextSigComponent(String signature, boolean[] lookingForMethod) {
774 StringBuilder sb = new StringBuilder();
776 int n = signature.length();
778 // Skip leading separators
780 char c = signature.charAt(i);
783 lookingForMethod[0] = true;
785 } else if ((c == ':') || (c == '(')) {
794 char c = signature.charAt(i);
796 if ((c == '#') || (c == ':') || (c == '(')) {
803 return sb.toString();
806 private static Node findBySignature(Node root, Node node, String signature, String name, boolean[] lookingForMethod) {
807 switch (node.getNodeType()) {
809 if (name.charAt(0) == '@' && name.equals(((INameNode) node).getLexicalName())) return node;
811 case CLASSVARDECLNODE: case CLASSVARASGNNODE:
812 if (name.startsWith("@@") && name.equals(((INameNode) node).getLexicalName())) return node;
817 MethodDefNode def = (MethodDefNode) node;
818 if (lookingForMethod[0] && name.equals(def.getName())) {
819 // See if the parameter list matches
821 List<String> parameters = def.getArgs().getNormativeParameterNameList(false);
823 if (signature.length() == 0 && (parameters == null || parameters.isEmpty())) {
824 return def; // No args
825 } else if (signature.length() != 0) {
826 assert signature.charAt(0) == '(' : signature;
828 String argList = signature.substring(1, signature.length() - 1);
829 String[] args = argList.split(",");
831 if (args.length == parameters.size()) {
832 // Should I enforce equality here?
833 boolean equal = true;
835 for (int i = 0; i < args.length; i++) {
836 if (!args[i].equals(parameters.get(i))) {
843 if (equal) return def;
846 } else if (isAttr(def)) {
847 for (SymbolNode sym : getAttrSymbols(def)) {
848 if (name.equals(sym.getName())) return node;
854 if (isAttr(node) || isNamedScope(node) || isActiveRecordAssociation(node) ||
855 isNodeNameIn((INameNode) node, RubyStructureAnalyzer.DYNAMIC_METHODS)) {
856 for (Node each : getChildValues(node)) {
857 if (name.equals(getNameOrValue(each))) return each;
859 } else if (TestNameResolver.isShouldaMethod(((INameNode) node).getName())) {
860 String shoulda = TestNameResolver.getTestName(new AstPath(root, node));
862 if (name.equals(shoulda)) return node;
867 AliasNode aliasNode = (AliasNode) node;
868 if (name.equals(getNameOrValue(aliasNode.getNewName())) || name.equals(getNameOrValue(aliasNode.getOldName()))) {
873 if (name.equals(getName(node))) return node;
877 Colon3Node c3n = ((IScopingNode)node).getCPath();
879 if (c3n instanceof Colon2Node) {
880 String fqn = getFqn((Colon2Node)c3n);
882 if (fqn.startsWith(name) && signature.startsWith(fqn.substring(name.length()))) {
883 signature = signature.substring(fqn.substring(name.length()).length());
884 name = getNextSigComponent(signature, lookingForMethod);
886 if (name.length() == 0) return node; // Signature is a class/module already.
888 int index = signature.indexOf(name);
890 signature = signature.substring(index + name.length());
892 } else if (name.equals(AstUtilities.getClassOrModuleName(((IScopingNode)node)))) {
893 name = getNextSigComponent(signature, lookingForMethod);
895 if (name.length() == 0) return node; // Signature is a class/module already.
897 int index = signature.indexOf(name);
899 signature = signature.substring(index + name.length());
904 Node receiver = ((SClassNode)node).getReceiver();
907 if (receiver instanceof Colon2Node) {
908 // TODO - check to see if we qualify
909 rn = getName(receiver);
910 } else if (receiver instanceof ConstNode) {
911 rn = getName(receiver);
912 } // else: some other type of singleton class definition, like class << foo
915 if (name.equals(rn)) {
916 name = getNextSigComponent(signature, lookingForMethod);
918 if (name.length() == 0) return node; // Signature is a class/module already.
920 int index = signature.indexOf(name);
922 signature = signature.substring(index + name.length());
928 boolean old = lookingForMethod[0];
930 for (Node child : node.childNodes()) {
931 Node match = findBySignature(root, child, signature, name, lookingForMethod);
933 if (match != null) return match;
935 lookingForMethod[0] = old;
940 /** Return true iff the given node contains the given offset */
941 public static boolean containsOffset(Node node, int offset) {
942 SourcePosition pos = node.getPosition();
944 return ((offset >= pos.getStartOffset()) && (offset <= pos.getEndOffset()));
948 * Like {@link #getRange(org.jrubyparser.ast.Node) }, but returns a position
949 * for <code>NilNode</code>s too (but <strong>not</strong> for
950 * <code>NilImplicitNode</code>s!!). This is needed for highlightning
951 * exit points, <code>nil</code> is a common return value. Possibly
952 * {@link #getRange(org.jrubyparser.ast.Node) } itself should return a range
953 * for <code>NilNode</code>s, but I'm too afraid to change it now as it
954 * is used in a lot of places.
958 static OffsetRange getRangeIncludeNil(Node node) {
959 if (node != null && node.getClass().equals(NilNode.class)) {
960 SourcePosition pos = node.getPosition();
962 return new OffsetRange(pos.getStartOffset(), pos.getEndOffset());
963 } catch (Throwable t) {
964 // ...because there are some problems -- see AstUtilities.testStress
965 Exceptions.printStackTrace(t);
966 return OffsetRange.NONE;
969 return getRange(node);
972 * Return a range that matches the given node's source buffer range
974 public static OffsetRange getRange(Node node) {
975 if (node.getNodeType() == NodeType.NILNODE) return OffsetRange.NONE;
977 SourcePosition pos = node.getPosition();
979 return new OffsetRange(pos.getStartOffset(), pos.getEndOffset());
980 } catch (Throwable t) {
981 // ...because there are some problems -- see AstUtilities.testStress
982 Exceptions.printStackTrace(t);
983 return OffsetRange.NONE;
987 public static OffsetRange offsetRangeFor(SourcePosition position) {
988 return new OffsetRange(position.getStartOffset(), position.getEndOffset());
991 public static OffsetRange getNameRange(Node node) {
992 if (node instanceof AssignableNode) {
993 return offsetRangeFor(((AssignableNode)node).getLeftHandSidePosition());
994 } else if (node instanceof MethodDefNode) {
995 return offsetRangeFor(((INameNode) node).getLexicalNamePosition());
996 } else if (isCall(node)) {
997 return getCallRange(node);
998 } else if (node instanceof ClassNode) {
999 // TODO - try to pull out the constnode or colon2node holding the class name,
1001 Colon3Node c3n = ((ClassNode)node).getCPath();
1003 return getRange(c3n);
1005 return getRange(node);
1007 } else if (node instanceof ModuleNode) {
1008 // TODO - try to pull out the constnode or colon2node holding the class name,
1010 Colon3Node c3n = ((ModuleNode)node).getCPath();
1012 return getRange(c3n);
1014 return getRange(node);
1016 // } else if (node instanceof SClassNode) {
1017 // // TODO - try to pull out the constnode or colon2node holding the class name,
1018 // // and return it!
1019 // Colon3Node c3n = ((SClassNode)node).getCPath();
1020 // if (c3n != null) {
1021 // return getRange(c3n);
1023 // return getRange(node);
1026 return getRange(node);
1030 /** For CallNodes, the offset range for the AST node includes the entire parameter list.
1031 * We want ONLY the actual call/operator name. So compute that on our own.
1033 public static OffsetRange getCallRange(Node node) {
1034 SourcePosition pos = node.getPosition();
1035 int start = pos.getStartOffset();
1037 assert isCall(node);
1038 assert node instanceof INameNode;
1040 if (node instanceof CallNode) {
1041 // A call of the form Foo.bar. "bar" is the CallNode, "Foo" is the ReceiverNode.
1042 // Here I'm only handling named nodes; there may be others
1043 Node receiver = ((CallNode)node).getReceiver();
1045 // end of "Foo::bar" + "."
1046 if (receiver != null) start = receiver.getPosition().getEndOffset() + 1;
1049 int end = node instanceof INameNode ? start + getName(node).length() : pos.getEndOffset();
1051 return new OffsetRange(start, end);
1055 public static OffsetRange getFunctionNameRange(Node node) {
1056 // TODO - enforce MethodDefNode and call getNameNode on it!
1057 for (Node child : node.childNodes()) {
1058 if (child instanceof ArgumentNode) {
1059 OffsetRange range = AstUtilities.getRange(child);
1065 if (node instanceof MethodDefNode) {
1066 for (Node child : node.childNodes()) {
1067 if (child instanceof ConstNode) {
1068 SourcePosition pos = child.getPosition();
1069 int end = pos.getEndOffset();
1072 if (INCLUDE_DEFS_PREFIX) {
1073 start = pos.getStartOffset();
1078 // TODO - look at the source buffer and tweak offset if it's wrong
1079 // This assumes we have a single constant node, followed by a dot, followed by the name
1080 end = end + 1 + AstUtilities.getDefName(node).length(); // +1: "."
1082 OffsetRange range = new OffsetRange(start, end);
1089 return OffsetRange.NONE;
1093 * Return the OffsetRange for an AliasNode that represents the new name portion.
1095 public static OffsetRange getAliasNewRange(AliasNode node) {
1096 // XXX I don't know where the old and new names are since the user COULD
1097 // have used more than one whitespace character for separation. For now I'll
1098 // just have to assume it's the normal case with one space: alias new old.
1099 // I -could- use the getPosition.getEndOffset() to see if this looks like it's
1100 // the case (e.g. node length != "alias ".length + old.length+new.length+1).
1101 // In this case I could go peeking in the source buffer to see where the
1102 // spaces are - between alias and the first word or between old and new. XXX.
1103 SourcePosition pos = node.getPosition();
1105 int newStart = pos.getStartOffset() + 6; // 6: "alias ".length()
1107 String name = getNameOrValue(node.getNewName());
1108 int length = name != null ? name.length() : 0;
1109 return new OffsetRange(newStart, newStart + length);
1113 * Return the OffsetRange for an AliasNode that represents the old name portion.
1115 public static OffsetRange getAliasOldRange(AliasNode node) {
1116 // XXX I don't know where the old and new names are since the user COULD
1117 // have used more than one whitespace character for separation. For now I'll
1118 // just have to assume it's the normal case with one space: alias new old.
1119 // I -could- use the getPosition.getEndOffset() to see if this looks like it's
1120 // the case (e.g. node length != "alias ".length + old.length+new.length+1).
1121 // In this case I could go peeking in the source buffer to see where the
1122 // spaces are - between alias and the first word or between old and new. XXX.
1123 SourcePosition pos = node.getPosition();
1125 String newName = getNameOrValue(node.getNewName());
1126 int newLength = newName != null ? newName.length() : 0;
1128 String oldName = getNameOrValue(node.getOldName());
1129 int oldLength = oldName != null ? oldName.length() : 0;
1131 int oldStart = pos.getStartOffset() + 6 + newLength + 1; // 6: "alias ".length; 1: " ".length
1133 return new OffsetRange(oldStart, oldStart + oldLength);
1136 public static String getClassOrModuleName(IScopingNode node) {
1137 return getName(node.getCPath());
1140 public static List<ClassNode> getClasses(Node root) {
1141 // I would like to use a visitor for this, but it's not
1142 // working - I get NPE's within DefaultIteratorVisitor
1143 // on valid ASTs, and I see it's not used heavily in JRuby,
1144 // so I'm not doing it this way for now.
1145 //final List<ClassNode> classes = new ArrayList<ClassNode>();
1146 //// There could be multiple Class definitions for this
1147 //// same class, and (empirically) rdoc shows the documentation
1148 //// for the last declaration.
1149 //NodeVisitor findClasses = new AbstractVisitor() {
1150 // public Instruction visitClassNode(ClassNode node) {
1151 // classes.add(node);
1152 // return visitNode(node);
1155 // protected Instruction visitNode(Node iVisited) {
1159 //new DefaultIteratorVisitor(findClasses).visitRootNode((RootNode)parseResult.getRootNode());
1160 List<ClassNode> classes = new ArrayList<ClassNode>();
1161 addClasses(root, classes);
1166 private static void addClasses(Node node, List<ClassNode> classes) {
1167 if (node instanceof ClassNode) classes.add((ClassNode)node);
1169 for (Node child : node.childNodes()) {
1170 addClasses(child, classes);
1174 private static void addAncestorParents(Node node, StringBuilder sb) {
1175 if (node instanceof Colon2Node) {
1176 Colon2Node c2n = (Colon2Node)node;
1177 addAncestorParents(c2n.getLeftNode(), sb);
1179 if ((sb.length() > 0) && (sb.charAt(sb.length() - 1) != ':')) {
1183 sb.append(c2n.getName());
1184 } else if (node instanceof INameNode) {
1185 if ((sb.length() > 0) && (sb.charAt(sb.length() - 1) != ':')) {
1189 sb.append(getName(node));
1193 public static String getFqn(Colon2Node c2n) {
1194 StringBuilder sb = new StringBuilder();
1196 addAncestorParents(c2n, sb);
1198 return sb.toString();
1201 public static String getSuperclass(ClassNode clz) {
1202 if (clz.getSuper() == null) return null;
1204 StringBuilder sb = new StringBuilder();
1206 addAncestorParents(clz.getSuper(), sb);
1208 return sb.toString();
1211 static String getFqnName(Node root, Node target) {
1212 String fqn = getFqnName(new AstPath(root, target));
1213 if (fqn.length() > 0) {
1214 return fqn + "::" + getName(target);
1216 return getName(target);
1219 static String getFqnName(AstPath path, String simpleName) {
1220 String fqn = getFqnName(path);
1221 if (fqn.length() > 0) {
1222 return fqn + "::" + simpleName;
1227 /** Compute the module/class name for the given node path */
1228 public static String getFqnName(AstPath path) {
1229 StringBuilder sb = new StringBuilder();
1231 Iterator<Node> it = path.rootToLeaf();
1233 while (it.hasNext()) {
1234 Node node = it.next();
1236 if (node instanceof ModuleNode || node instanceof ClassNode) {
1237 Colon3Node cpath = ((IScopingNode)node).getCPath();
1238 if (cpath == null) continue;
1240 if (sb.length() > 0) sb.append("::"); // NOI18N
1242 if (cpath instanceof Colon2Node) {
1243 sb.append(getFqn((Colon2Node)cpath));
1245 sb.append(cpath.getName());
1250 return sb.toString();
1253 public static boolean isAttr(Node node) {
1254 return isCallNode(node) && (isNodeNameIn((INameNode) node, ATTR_ACCESSORS) || isNodeNameIn((INameNode) node, CATTR_ACCESSORS));
1257 public static boolean isCAttr(Node node) {
1258 return isCallNode(node) && isNodeNameIn((INameNode) node, CATTR_ACCESSORS);
1261 static boolean isNamedScope(Node node) {
1262 return isCallNode(node) && isNodeNameIn((INameNode) node, NAMED_SCOPE);
1265 public static boolean isActiveRecordAssociation(Node node) {
1266 return isCallNode(node) && isNodeNameIn((INameNode) node, ActiveRecordAssociationFinder.AR_ASSOCIATIONS);
1269 static boolean isNodeNameIn(INameNode node, String... names) {
1270 String name = node.getName();
1271 for (String each : names) {
1272 if (each.equals(name)) return true;
1277 private static boolean isCallNode(Node node) {
1278 return node instanceof FCallNode || node instanceof VCallNode;
1282 * Returns the names or values of the nodes in the given {@code listNode};
1283 * not including null and not empty values.
1286 * @return the names/values; the result does not contain {@code null}s or empty strings.
1288 static List<String> getNamesOrValues(ListNode listNode) {
1289 List<String> result = new ArrayList<String>(listNode.size());
1290 for (int i = 0, max = listNode.size(); i < max; i++) {
1291 Node n = listNode.get(i);
1293 // For dynamically computed strings, we have n instanceof DStrNode
1294 // but I can't handle these anyway
1295 if (n instanceof StrNode || n instanceof SymbolNode || n instanceof ConstNode) {
1296 String name = getNameOrValue(n);
1297 if (name != null && !name.isEmpty()) {
1306 * Gets the name or the value of given node, depending on its type.
1308 * @param node the node whose value to get.
1309 * @return the name or value of the given node or <code>null</code>.
1311 public static String getNameOrValue(Node node) {
1312 if (node instanceof StrNode) return ((StrNode) node).getValue();
1313 if (node instanceof INameNode) return getName(node);
1315 if (node instanceof DSymbolNode) {
1316 if (!node.childNodes().isEmpty()) {
1317 Node child = node.childNodes().get(0);
1318 return getNameOrValue(child);
1321 if (node instanceof LiteralNode) {
1322 return ((LiteralNode) node).getName();
1327 static SymbolNode[] getSymbols(Node node) {
1328 List<SymbolNode> symbolList = new ArrayList<SymbolNode>();
1329 for (Node child : getChildValues(node)) {
1330 if (child instanceof SymbolNode) {
1331 symbolList.add((SymbolNode) child);
1335 return symbolList.toArray(new SymbolNode[symbolList.size()]);
1338 static List<Node> getChildValues(Node node) {
1339 List<Node> result = new ArrayList<Node>();
1340 for (Node child : node.childNodes()) {
1341 if (child instanceof ListNode) {
1342 List<Node> values = child.childNodes();
1343 result.addAll(values);
1350 public static SymbolNode[] getAttrSymbols(Node node) {
1351 assert isAttr(node);
1352 return getSymbols(node);
1355 public static Node getRoot(final FileObject sourceFO) {
1356 Source source = Source.create(sourceFO);
1357 if (source == null) return null;
1359 final Node[] rootHolder = new Node[1];
1361 ParserManager.parse(Collections.singleton(source), new UserTask() {
1363 public void run(ResultIterator ri) throws Exception {
1364 Parser.Result result = ri.getParserResult();
1365 rootHolder[0] = getRoot(result);
1368 } catch (ParseException ex) {
1369 Exceptions.printStackTrace(ex);
1371 return rootHolder[0];
1375 * Gets the root node from the given <code>parserResult</code>. May return
1376 * <code>null</code> if <code>parserResult</code> was not a <code>RubyParserResult</code> or
1377 * did not have a root node. For example for parts of .erb files the parserResult might
1378 * (validly) be RhtmlParser$FakeParserResult, in which case this method returns <code>null</code>.
1380 * @param parserResult
1381 * @return the root node or <code>null</code>.
1383 public static Node getRoot(Parser.Result parserResult) {
1384 if (parserResult == null) return null;
1386 if (!(parserResult instanceof RubyParseResult)) {
1387 if (LOGGER.isLoggable(Level.FINE)) {
1388 String msg = "Expected RubyParseResult, but got " + parserResult; //NOI18N
1389 // log an exception too see the stack trace
1390 LOGGER.log(Level.FINE, msg, new Exception(msg));
1395 return ((RubyParseResult) parserResult).getRootNode();
1399 * Get the private and protected methods in the given class
1401 public static void findPrivateMethods(Node clz, Set<Node> protectedMethods,
1402 Set<Node> privateMethods) {
1403 Set<String> publicMethodSymbols = new HashSet<String>();
1404 Set<String> protectedMethodSymbols = new HashSet<String>();
1405 Set<String> privateMethodSymbols = new HashSet<String>();
1406 Set<Node> publicMethods = new HashSet<Node>();
1408 List<Node> list = clz.childNodes();
1410 Modifier access = Modifier.PUBLIC;
1412 for (Node child : list) {
1413 access = getMethodAccess(child, access, publicMethodSymbols, protectedMethodSymbols,
1414 privateMethodSymbols, publicMethods, protectedMethods, privateMethods);
1417 // Can't just return private methods directly, since sometimes you can
1418 // specify that a particular method is public before we know about it,
1419 // so I can't just remove it from the known private list when I see the
1421 privateMethodSymbols.removeAll(publicMethodSymbols);
1422 protectedMethodSymbols.removeAll(publicMethodSymbols);
1424 // Should I worry about private foo; protected :foo ?
1425 // Seems unlikely somebody would do that... I guess
1426 // I could do privateMethodSymbols.removeAll(protectedMethodSymbols) etc.
1427 //privateMethodSymbols.removeAll(protectedMethodSymbols);
1428 //protectedMethodSymbols.removeAll(privateMethodSymbols);
1430 // Add all methods known to be private into the private node set
1431 for (String name : privateMethodSymbols) {
1432 for (Node n : publicMethods) {
1433 if (name.equals(AstUtilities.getDefName(n))) {
1434 privateMethods.add(n);
1439 for (String name : protectedMethodSymbols) {
1440 for (Node n : publicMethods) {
1441 if (name.equals(AstUtilities.getDefName(n))) {
1442 protectedMethods.add(n);
1449 * @todo Should I really recurse into classes? If I have nested classes private
1450 * methods ther shouldn't be included for the parent!
1452 * @param access The "current" known access level (PUBLIC, PROTECTED or PRIVATE)
1453 * @return the access level to continue with at this syntactic level
1455 private static Modifier getMethodAccess(Node node, Modifier access,
1456 Set<String> publicMethodSymbols, Set<String> protectedMethodSymbols,
1457 Set<String> privateMethodSymbols, Set<Node> publicMethods, Set<Node> protectedMethods,
1458 Set<Node> privateMethods) {
1459 if (node instanceof MethodDefNode) {
1460 if (access == Modifier.PRIVATE) {
1461 privateMethods.add(node);
1462 } else if (access == Modifier.PUBLIC) {
1463 publicMethods.add(node);
1464 } else if (access == Modifier.PROTECTED) {
1465 protectedMethods.add(node);
1468 // XXX Can I have nested method definitions? If so I may have to continue here
1470 } else if (node instanceof VCallNode || node instanceof FCallNode) {
1471 String name = getName(node);
1473 if ("private".equals(name)) {
1474 // TODO - see if it has arguments, if it does - it's just a single
1475 // method defined to be private
1476 // Iterate over arguments and add symbols...
1477 if (Arity.callHasArguments(node)) {
1478 List<Node> params = node.childNodes();
1480 for (Node param : params) {
1481 if (param instanceof ListNode) {
1482 List<Node> params2 = param.childNodes();
1484 for (Node param2 : params2) {
1485 if (param2 instanceof SymbolNode) {
1486 String symbol = getName(param2);
1487 privateMethodSymbols.add(symbol);
1493 access = Modifier.PRIVATE;
1497 } else if ("protected".equals(name)) {
1498 // TODO - see if it has arguments, if it does - it's just a single
1499 // method defined to be private
1500 // Iterate over arguments and add symbols...
1501 if (Arity.callHasArguments(node)) {
1502 List<Node> params = node.childNodes();
1504 for (Node param : params) {
1505 if (param instanceof ListNode) {
1506 List<Node> params2 = param.childNodes();
1508 for (Node param2 : params2) {
1509 if (param2 instanceof SymbolNode) {
1510 String symbol = getName(param2);
1511 protectedMethodSymbols.add(symbol);
1517 access = Modifier.PROTECTED;
1521 } else if ("public".equals(name)) {
1522 if (!Arity.callHasArguments(node)) {
1523 access = Modifier.PUBLIC;
1527 List<Node> params = node.childNodes();
1529 for (Node param : params) {
1530 if (param instanceof ListNode) {
1531 List<Node> params2 = param.childNodes();
1533 for (Node param2 : params2) {
1534 if (param2 instanceof SymbolNode) {
1535 String symbol = getName(param2);
1536 publicMethodSymbols.add(symbol);
1545 } else if (node instanceof ClassNode || node instanceof ModuleNode) {
1549 for (Node child : node.childNodes()) {
1550 access = getMethodAccess(child, access, publicMethodSymbols, protectedMethodSymbols,
1551 privateMethodSymbols, publicMethods, protectedMethods, privateMethods);
1558 * Get the method name for the given offset - or null if it cannot be found. This
1559 * will initiate a new parse job if necessary.
1561 public static String getMethodName(FileObject fo, final int lexOffset) {
1562 Source source = Source.create(fo);
1564 if (source == null) {
1568 final String[] methodName = new String[1];
1570 ParserManager.parse(Collections.singleton(source), new UserTask() {
1572 public void run(ResultIterator resultIterator) throws Exception {
1573 Parser.Result result = resultIterator.getParserResult();
1574 Node root = AstUtilities.getRoot(result);
1578 int astOffset = AstUtilities.getAstOffset(result, lexOffset);
1579 if (astOffset == -1) {
1582 org.jrubyparser.ast.MethodDefNode method = AstUtilities.findMethodAtOffset(root, astOffset);
1583 if (method == null) {
1584 // It's possible the user had the caret on a line
1585 // that includes a method that isn't actually inside
1586 // the method block - such as the beginning of the
1587 // "def" line, or the end of a line after "end".
1588 // The latter isn't very likely, but the former can
1589 // happen, so let's check the method bodies at the
1590 // end of the current line
1591 BaseDocument doc = RubyUtils.getDocument(result);
1594 int endOffset = Utilities.getRowEnd(doc, lexOffset);
1595 if (endOffset != lexOffset) {
1596 astOffset = AstUtilities.getAstOffset(result, endOffset);
1597 if (astOffset == -1) {
1600 method = AstUtilities.findMethodAtOffset(root, astOffset);
1602 } catch (BadLocationException ble) {
1603 // do nothing - see #154991
1607 if (method != null) {
1608 methodName[0] = method.getName();
1612 } catch (ParseException ex) {
1613 Exceptions.printStackTrace(ex);
1616 return methodName[0];
1620 * Get the test name surrounding the given offset - or null if it cannot be found.
1621 * NOTE: This will initiate a new parse job if necessary.
1623 public static String getTestName(FileObject fo, final int caretOffset) {
1624 Source source = Source.create(fo);
1626 if (source == null) {
1630 final String[] testName = new String[1];
1633 ParserManager.parse(Collections.singleton(source), new UserTask() {
1636 public void run(ResultIterator resultIterator) throws Exception {
1638 Parser.Result result = resultIterator.getParserResult();
1639 Node root = AstUtilities.getRoot(result);
1643 // Make sure the offset isn't at the beginning of a line
1644 BaseDocument doc = RubyUtils.getDocument(result, true);
1648 int lexOffset = caretOffset;
1649 int rowStart = Utilities.getRowFirstNonWhite(doc, lexOffset);
1650 if (rowStart != -1 && lexOffset <= rowStart) {
1651 lexOffset = rowStart + 1;
1653 int astOffset = AstUtilities.getAstOffset(result, lexOffset);
1654 if (astOffset == -1) {
1657 AstPath path = new AstPath(root, astOffset);
1659 testName[0] = TestNameResolver.getTestName(path);
1661 } catch (BadLocationException ex) {
1662 // do nothing - see #154991
1666 } catch (ParseException ex) {
1667 Exceptions.printStackTrace(ex);
1673 public static int findOffset(FileObject fo, final String methodName) {
1674 Source source = Source.create(fo);
1676 if (source == null) {
1680 final int[] offset = new int[1];
1684 ParserManager.parse(Collections.singleton(source), new UserTask() {
1686 public void run(ResultIterator resultIterator) throws Exception {
1687 Parser.Result result = resultIterator.getParserResult();
1688 Node root = AstUtilities.getRoot(result);
1693 org.jrubyparser.ast.Node method =
1694 AstUtilities.findMethod(root, methodName, Arity.UNKNOWN);
1696 if (method != null) {
1697 int startOffset = method.getPosition().getStartOffset();
1698 offset[0] = startOffset;
1702 } catch (ParseException ex) {
1703 Exceptions.printStackTrace(ex);
1709 /** Collect nodes of the given types (node.getNodeType()==NodeType.x) under the given root */
1710 public static void addNodesByType(Node root, NodeType[] nodeIds, List<Node> result) {
1711 for (NodeType nodeId : nodeIds) {
1712 if (root.getNodeType() == nodeId) {
1718 for (Node child : root.childNodes()) {
1719 addNodesByType(child, nodeIds, result);
1723 public static List<Node> getOutermostToInnerMostBlocks(Node leaf) {
1724 Node scope = leaf.getInnermostIter();
1726 if (scope == null) return Collections.emptyList();
1728 List<Node> result = new ArrayList<Node>();
1729 while (scope != null) {
1732 scope = scope.getInnermostIter();
1735 Collections.reverse(result);
1740 /** Return all the blocknodes that apply to the given node. The outermost block
1741 * is returned first.
1743 public static List<Node> getApplicableBlocks(AstPath path, boolean includeNested) {
1744 Node block = AstUtilities.findBlock(path);
1746 if (block == null) { // Use parent
1747 block = path.leafParent();
1749 if (block == null) return Collections.emptyList();
1752 List<Node> result = new ArrayList<Node>();
1753 Iterator<Node> it = path.leafToRoot();
1755 // Skip the leaf node, we're going to add it unconditionally afterwards
1756 if (includeNested) {
1757 if (it.hasNext()) it.next();
1760 Node leaf = path.root();
1763 while (it.hasNext()) {
1765 switch (n.getNodeType()) {
1780 if (includeNested) {
1781 addNodesByType(leaf, new NodeType[] { NodeType.ITERNODE }, result);
1787 public static String guessName(Parser.Result result, OffsetRange lexRange, OffsetRange astRange) {
1788 String guessedName = "";
1790 // Try to guess the name - see if it's in a method and if so name it after the parameter
1791 IndexedMethod[] methodHolder = new IndexedMethod[1];
1792 @SuppressWarnings("unchecked") Set<IndexedMethod>[] alternatesHolder = new Set[1];
1793 int[] paramIndexHolder = new int[1];
1794 int[] anchorOffsetHolder = new int[1];
1795 if (!RubyMethodCompleter.computeMethodCall(result, lexRange.getStart(), astRange.getStart(),
1796 methodHolder, paramIndexHolder, anchorOffsetHolder, alternatesHolder, QuerySupport.Kind.PREFIX)) {
1801 IndexedMethod targetMethod = methodHolder[0];
1802 int index = paramIndexHolder[0];
1804 List<String> params = targetMethod.getParameters();
1805 if (params == null || params.size() <= index) {
1809 String s = params.get(index);
1810 if (s.startsWith("*") || s.startsWith("&")) { // Don't include * or & in variable name
1816 public static Set<String> getUsedFields(RubyIndex index, AstPath path) {
1817 String fqn = AstUtilities.getFqnName(path);
1818 if (fqn == null || fqn.length() == 0) {
1819 return Collections.emptySet();
1821 Set<IndexedField> fields = index.getInheritedFields(fqn, "", QuerySupport.Kind.PREFIX, false);
1822 Set<String> fieldNames = new HashSet<String>();
1823 for (IndexedField f : fields) {
1824 fieldNames.add(f.getName());
1830 public static Set<String> getUsedMethods(RubyIndex index, AstPath path) {
1831 String fqn = AstUtilities.getFqnName(path);
1832 if (fqn == null || fqn.length() == 0) {
1833 return Collections.emptySet();
1835 Set<IndexedMethod> methods = index.getInheritedMethods(fqn, "", QuerySupport.Kind.PREFIX);
1836 Set<String> methodNames = new HashSet<String>();
1837 for (IndexedMethod m : methods) {
1838 methodNames.add(m.getName());
1844 /** @todo Implement properly */
1845 public static Set<String> getUsedConstants(RubyIndex index, AstPath path) {
1846 //String fqn = AstUtilities.getFqnName(path);
1847 //if (fqn == null || fqn.length() == 0) {
1848 return Collections.emptySet();
1850 //Set<IndexedConstant> constants = index.getInheritedConstants(fqn, "", QuerySupport.Kind.PREFIX);
1851 //Set<String> constantNames = new HashSet<String>();
1852 //for (IndexedConstant m : constants) {
1853 // constantNames.add(m.getName());
1856 //return constantNames;
1859 public static Set<String> getUsedLocalNames(AstPath path, Node closest) {
1860 Node method = AstUtilities.findLocalScope(closest, path);
1861 Map<String, Node> variables = new HashMap<String, Node>();
1863 RubyCodeCompleter.addLocals(method, variables);
1865 List<Node> applicableBlocks = AstUtilities.getApplicableBlocks(path, false);
1866 for (Node block : applicableBlocks) {
1867 RubyCodeCompleter.addDynamic(block, variables);
1870 return variables.keySet();
1874 * Throws {@link ClassCastException} if the given node is not instance of
1875 * {@link INameNode} or {@link LiteralNode}.
1877 * @param node instance of {@link INameNode} or {@link LiteralNode}.
1878 * @return node's name
1880 public static String getName(final Node node) {
1881 if (node instanceof LiteralNode) return ((LiteralNode) node).getName();
1883 return ((INameNode) node).getName();
1887 * Like {@link #getName(org.jrubyparser.ast.Node) }, but instead of throwing
1888 * a CCE returns <code>null</code> if the given <code>node</code> wasn't an
1889 * instance of {@ INameNode}.
1892 * @return the name or <code>null</code>.
1894 static String safeGetName(final Node node) {
1895 if (node instanceof INameNode) return ((INameNode) node).getName();
1896 if (node instanceof LiteralNode) return ((LiteralNode) node).getName();
1901 * Finds exit points of a method definition for the given node.
1903 * @param defNode {@link MethodDefNode method definition node}
1904 * @param exits accumulator for found exit points
1906 public static void findExitPoints(final MethodDefNode defNode, final Collection<? super Node> exits) {
1907 Node body = defNode.getBody();
1909 if (body != null) findExitPoints(body, exits); // method with empty body
1912 static void findExitPoints(final Node body, final Collection<? super Node> exits) {
1913 findNonLastExitPoints(body, exits);
1914 findLastNodes(body, exits);
1918 * Gets the values of the given {@code args}, as fully qualified
1919 * names in case of {@link Colon2Node}s.
1923 static List<String> getValuesAsFqn(ListNode args) {
1924 if (args.size() == 0) return Collections.emptyList();
1926 List<String> result = new ArrayList<String>(args.size());
1927 for (Node n : args.childNodes()) {
1928 if (n instanceof Colon2Node) {
1929 result.add(getFqn((Colon2Node) n));
1930 } else if (n instanceof INameNode) {
1931 result.add(getName(n));
1937 private static void findLastNodes(final Node node, Collection<? super Node> result) {
1938 if (node == null) return;
1940 List<Node> children = findExitChidren(node);
1941 if (children.isEmpty()) {
1945 // AttrAssgn does not put arguments into an ArgsNode unfortunately.
1946 if (!(node instanceof AttrAssignNode)) {
1947 for (Node child : children) {
1948 if (child instanceof ArgsNode || child instanceof ArgumentNode) {
1949 // Done - no valid statement
1953 findLastNodes(child, result);
1958 private static List<Node> findExitChidren(Node node) {
1959 if (node instanceof IfNode) {
1960 IfNode ifNode = (IfNode) node;
1961 return Arrays.asList(ifNode.getThenBody(), ifNode.getElseBody());
1963 if (node instanceof CaseNode) {
1964 CaseNode caseNode = (CaseNode) node;
1965 ListNode cases = caseNode.getCases();
1966 List<Node> result = new ArrayList<Node>(cases.childNodes());
1967 result.add(caseNode.getElse());
1970 if (node instanceof WhenNode) {
1971 WhenNode whenNode = (WhenNode) node;
1972 return Collections.singletonList(whenNode.getBody());
1974 if (node instanceof OrNode) return node.childNodes();
1975 if (node instanceof AndNode) return Collections.singletonList(((AndNode) node).getSecond());
1976 if (node instanceof ReturnNode || isCall(node) || node instanceof ILiteralNode || node instanceof HashNode
1977 || node instanceof DotNode || node instanceof NotNode || node instanceof UntilNode
1978 || node instanceof DefinedNode) {
1979 return Collections.emptyList();
1981 if (node instanceof RescueNode) return node.childNodes();
1983 List<Node> children = node.childNodes();
1984 if (!children.isEmpty()) {
1985 Node lastChild = children.get(children.size() -1);
1987 return Collections.singletonList(lastChild);
1993 /** Helper for {@link #findExitPoints}. */
1994 private static void findNonLastExitPoints(final Node node, final Collection<? super Node> exits) {
1995 switch (node.getNodeType()) {
1996 case RETURNNODE: case YIELDNODE:
1999 case CLASSNODE: case SCLASSNODE: case MODULENODE:
2000 return; // Don't go into sub methods, classes, etc
2002 String name = ((INameNode) node).getName();
2003 if ("fail".equals(name) || "raise".equals(name)) exits.add(node); // NOI18N
2006 if (node instanceof MethodDefNode) return; // Don't go into sub methods, classes, etc
2008 for (Node child : node.childNodes()) {
2009 findNonLastExitPoints(child, exits);