0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041: package org.netbeans.modules.ruby;
0042:
0043: import java.io.IOException;
0044: import java.util.ArrayList;
0045: import java.util.Collections;
0046: import java.util.HashMap;
0047: import java.util.HashSet;
0048: import java.util.Iterator;
0049: import java.util.LinkedList;
0050: import java.util.List;
0051: import java.util.Map;
0052: import java.util.Set;
0053:
0054: import javax.swing.text.BadLocationException;
0055: import javax.swing.text.Document;
0056:
0057: import org.jruby.ast.AliasNode;
0058: import org.jruby.ast.ArgsCatNode;
0059: import org.jruby.ast.ArgsNode;
0060: import org.jruby.ast.ArgumentNode;
0061: import org.jruby.ast.AssignableNode;
0062: import org.jruby.ast.CallNode;
0063: import org.jruby.ast.ClassNode;
0064: import org.jruby.ast.Colon2Node;
0065: import org.jruby.ast.Colon3Node;
0066: import org.jruby.ast.ConstNode;
0067: import org.jruby.ast.FCallNode;
0068: import org.jruby.ast.IScopingNode;
0069: import org.jruby.ast.ListNode;
0070: import org.jruby.ast.LocalAsgnNode;
0071: import org.jruby.ast.MethodDefNode;
0072: import org.jruby.ast.ModuleNode;
0073: import org.jruby.ast.Node;
0074: import org.jruby.ast.NodeTypes;
0075: import org.jruby.ast.SClassNode;
0076: import org.jruby.ast.StrNode;
0077: import org.jruby.ast.SymbolNode;
0078: import org.jruby.ast.VCallNode;
0079: import org.jruby.ast.types.INameNode;
0080: import org.jruby.lexer.yacc.ISourcePosition;
0081: import org.jruby.util.ByteList;
0082: import org.netbeans.modules.gsf.api.CancellableTask;
0083: import org.netbeans.modules.gsf.api.CompilationInfo;
0084: import org.netbeans.modules.gsf.api.Modifier;
0085: import org.netbeans.modules.gsf.api.NameKind;
0086: import org.netbeans.modules.gsf.api.OffsetRange;
0087: import org.netbeans.modules.gsf.api.Parser;
0088: import org.netbeans.modules.gsf.api.ParserFile;
0089: import org.netbeans.modules.gsf.api.ParserResult;
0090: import org.netbeans.modules.gsf.api.SourceFileReader;
0091: import org.netbeans.modules.gsf.api.SourceModel;
0092: import org.netbeans.modules.gsf.api.SourceModelFactory;
0093: import org.netbeans.modules.gsf.api.TranslatedSource;
0094: import org.netbeans.editor.BaseDocument;
0095: import org.netbeans.editor.Utilities;
0096: import org.netbeans.modules.ruby.elements.IndexedElement;
0097: import org.netbeans.modules.ruby.elements.IndexedField;
0098: import org.netbeans.modules.ruby.elements.IndexedMethod;
0099: import org.netbeans.modules.ruby.lexer.LexUtilities;
0100: import org.netbeans.modules.gsf.spi.DefaultParseListener;
0101: import org.openide.filesystems.FileObject;
0102: import org.openide.util.Exceptions;
0103:
0104: /**
0105: * Various utilities for operating on the JRuby ASTs that are used
0106: * elsewhere.
0107: *
0108: * @todo Rewrite many of the custom recursion routines to simply
0109: * call {@link addNodesByType} and then iterate (without recursion) over
0110: * the result set.
0111: *
0112: * @author Tor Norbye
0113: */
0114: public class AstUtilities {
0115: /** Whether or not the prefixes for defs should be highlighted, e.g. in
0116: * def HTTP.foo
0117: * Should "HTTP." be highlighted, or just the foo portion?
0118: */
0119: private static final boolean INCLUDE_DEFS_PREFIX = false;
0120:
0121: public static int getAstOffset(CompilationInfo info, int lexOffset) {
0122: ParserResult result = info.getEmbeddedResult(
0123: RubyMimeResolver.RUBY_MIME_TYPE, 0);
0124: if (result != null) {
0125: TranslatedSource ts = result.getTranslatedSource();
0126: if (ts != null) {
0127: return ts.getAstOffset(lexOffset);
0128: }
0129: }
0130:
0131: return lexOffset;
0132: }
0133:
0134: public static OffsetRange getAstOffsets(CompilationInfo info,
0135: OffsetRange lexicalRange) {
0136: ParserResult result = info.getEmbeddedResult(
0137: RubyMimeResolver.RUBY_MIME_TYPE, 0);
0138: if (result != null) {
0139: TranslatedSource ts = result.getTranslatedSource();
0140: if (ts != null) {
0141: int rangeStart = lexicalRange.getStart();
0142: int start = ts.getAstOffset(rangeStart);
0143: if (start == rangeStart) {
0144: return lexicalRange;
0145: } else if (start == -1) {
0146: return OffsetRange.NONE;
0147: } else {
0148: // Assumes the translated range maintains size
0149: return new OffsetRange(start, start
0150: + lexicalRange.getLength());
0151: }
0152: }
0153: }
0154: return lexicalRange;
0155: }
0156:
0157: /** This is a utility class only, not instantiatiable */
0158: private AstUtilities() {
0159: }
0160:
0161: /**
0162: * Get the rdoc documentation associated with the given node in the given document.
0163: * The node must have position information that matches the source in the document.
0164: */
0165: public static List<String> gatherDocumentation(
0166: CompilationInfo info, BaseDocument baseDoc, Node node) {
0167: LinkedList<String> comments = new LinkedList<String>();
0168: int elementBegin = node.getPosition().getStartOffset();
0169: if (info != null) {
0170: elementBegin = LexUtilities.getLexerOffset(info,
0171: elementBegin);
0172: }
0173:
0174: try {
0175: if (elementBegin >= baseDoc.getLength()) {
0176: return null;
0177: }
0178:
0179: // Search to previous lines, locate comments. Once we have a non-whitespace line that isn't
0180: // a comment, we're done
0181:
0182: int offset = Utilities.getRowStart(baseDoc, elementBegin);
0183: offset--;
0184:
0185: // Skip empty and whitespace lines
0186: while (offset >= 0) {
0187: // Find beginning of line
0188: offset = Utilities.getRowStart(baseDoc, offset);
0189:
0190: if (!Utilities.isRowEmpty(baseDoc, offset)
0191: && !Utilities.isRowWhite(baseDoc, offset)) {
0192: break;
0193: }
0194:
0195: offset--;
0196: }
0197:
0198: if (offset < 0) {
0199: return null;
0200: }
0201:
0202: while (offset >= 0) {
0203: // Find beginning of line
0204: offset = Utilities.getRowStart(baseDoc, offset);
0205:
0206: if (Utilities.isRowEmpty(baseDoc, offset)
0207: || Utilities.isRowWhite(baseDoc, offset)) {
0208: // Empty lines not allowed within an rdoc
0209: break;
0210: }
0211:
0212: // This is a comment line we should include
0213: int lineBegin = Utilities.getRowFirstNonWhite(baseDoc,
0214: offset);
0215: int lineEnd = Utilities.getRowLastNonWhite(baseDoc,
0216: offset) + 1;
0217: String line = baseDoc.getText(lineBegin, lineEnd
0218: - lineBegin);
0219:
0220: // Tolerate "public", "private" and "protected" here --
0221: // Test::Unit::Assertions likes to put these in front of each
0222: // method.
0223: if (line.startsWith("#")) {
0224: comments.addFirst(line);
0225: } else if ((comments.size() == 0)
0226: && line.startsWith("=end")
0227: && (lineBegin == Utilities.getRowStart(baseDoc,
0228: offset))) {
0229: // It could be a =begin,=end document - see scanf.rb in Ruby lib for example. Treat this differently.
0230: gatherInlineDocumentation(comments, baseDoc, offset);
0231:
0232: return comments;
0233: } else if (line.equals("public")
0234: || line.equals("private")
0235: || line.equals("protected")) { // NOI18N
0236: // Skip newlines back up to the comment
0237: offset--;
0238:
0239: while (offset >= 0) {
0240: // Find beginning of line
0241: offset = Utilities.getRowStart(baseDoc, offset);
0242:
0243: if (!Utilities.isRowEmpty(baseDoc, offset)
0244: && !Utilities.isRowWhite(baseDoc,
0245: offset)) {
0246: break;
0247: }
0248:
0249: offset--;
0250: }
0251:
0252: continue;
0253: } else {
0254: // No longer in a comment
0255: break;
0256: }
0257:
0258: // Previous line
0259: offset--;
0260: }
0261: } catch (BadLocationException ble) {
0262: Exceptions.printStackTrace(ble);
0263: }
0264:
0265: return comments;
0266: }
0267:
0268: private static void gatherInlineDocumentation(
0269: LinkedList<String> comments, BaseDocument baseDoc,
0270: int offset) throws BadLocationException {
0271: // offset points to a line containing =end
0272: // Skip the =end list
0273: offset = Utilities.getRowStart(baseDoc, offset);
0274: offset--;
0275:
0276: // Search backwards in the document for the =begin (if any) and add all lines in reverse
0277: // order in between.
0278: while (offset >= 0) {
0279: // Find beginning of line
0280: offset = Utilities.getRowStart(baseDoc, offset);
0281:
0282: // This is a comment line we should include
0283: int lineBegin = offset;
0284: int lineEnd = Utilities.getRowEnd(baseDoc, offset);
0285: String line = baseDoc.getText(lineBegin, lineEnd
0286: - lineBegin);
0287:
0288: if (line.startsWith("=begin")) {
0289: // We're done!
0290: return;
0291: }
0292:
0293: comments.addFirst(line);
0294:
0295: // Previous line
0296: offset--;
0297: }
0298: }
0299:
0300: public static Node getForeignNode(final IndexedElement o,
0301: Node[] foreignRootRet) {
0302: ParserFile file = o.getFile();
0303:
0304: if (file == null) {
0305: return null;
0306: }
0307:
0308: List<ParserFile> files = Collections.singletonList(file);
0309: SourceFileReader reader = new SourceFileReader() {
0310: public CharSequence read(ParserFile file)
0311: throws IOException {
0312: Document doc = o.getDocument();
0313:
0314: if (doc == null) {
0315: return "";
0316: }
0317:
0318: try {
0319: return doc.getText(0, doc.getLength());
0320: } catch (BadLocationException ble) {
0321: IOException ioe = new IOException();
0322: ioe.initCause(ble);
0323: throw ioe;
0324: }
0325: }
0326:
0327: public int getCaretOffset(ParserFile fileObject) {
0328: return -1;
0329: }
0330: };
0331:
0332: DefaultParseListener listener = new DefaultParseListener();
0333: // TODO - embedding model?
0334: TranslatedSource translatedSource = null; // TODO - determine this here?
0335: Parser.Job job = new Parser.Job(files, listener, reader,
0336: translatedSource);
0337: new RubyParser().parseFiles(job);
0338:
0339: ParserResult result = listener.getParserResult();
0340:
0341: if (result == null) {
0342: return null;
0343: }
0344:
0345: Node root = AstUtilities.getRoot(result);
0346:
0347: if (root == null) {
0348: return null;
0349: } else if (foreignRootRet != null) {
0350: foreignRootRet[0] = root;
0351: }
0352:
0353: String signature = o.getSignature();
0354:
0355: if (signature == null) {
0356: return null;
0357: }
0358:
0359: Node node = AstUtilities.findBySignature(root, signature);
0360:
0361: // Special handling for "new" - these are synthesized from "initialize" methods
0362: if ((node == null) && "new".equals(o.getName())) { // NOI18N
0363: signature = signature.replaceFirst("new", "initialize"); //NOI18N
0364: node = AstUtilities.findBySignature(root, signature);
0365: }
0366:
0367: return node;
0368: }
0369:
0370: public static int boundCaretOffset(CompilationInfo info,
0371: int caretOffset) {
0372: Document doc = null;
0373:
0374: try {
0375: doc = info.getDocument();
0376: } catch (IOException e) {
0377: Exceptions.printStackTrace(e);
0378: }
0379:
0380: // If you invoke code completion while indexing is in progress, the
0381: // completion job (which stores the caret offset) will be delayed until
0382: // indexing is complete - potentially minutes later. When the job
0383: // is finally run we need to make sure the caret position is still valid.
0384: int length = doc.getLength();
0385:
0386: if (caretOffset > length) {
0387: caretOffset = length;
0388: }
0389:
0390: return caretOffset;
0391: }
0392:
0393: /**
0394: * Return the set of requires that are defined in this AST
0395: * (no transitive closure though).
0396: */
0397: public static Set<String> getRequires(Node root) {
0398: Set<String> requires = new HashSet<String>();
0399: addRequires(root, requires);
0400:
0401: return requires;
0402: }
0403:
0404: private static void addRequires(Node node, Set<String> requires) {
0405: if (node.nodeId == NodeTypes.FCALLNODE) {
0406: // A method call
0407: String name = ((INameNode) node).getName();
0408:
0409: if (name.equals("require")) { // XXX Load too?
0410:
0411: Node argsNode = ((FCallNode) node).getArgsNode();
0412:
0413: if (argsNode instanceof ListNode) {
0414: ListNode args = (ListNode) argsNode;
0415:
0416: if (args.size() > 0) {
0417: Node n = args.get(0);
0418:
0419: // For dynamically computed strings, we have n instanceof DStrNode
0420: // but I can't handle these anyway
0421: if (n instanceof StrNode) {
0422: ByteList require = ((StrNode) n).getValue();
0423:
0424: if ((require != null)
0425: && (require.length() > 0)) {
0426: requires.add(require.toString());
0427: }
0428: }
0429: }
0430: }
0431: }
0432: } else if (node.nodeId == NodeTypes.MODULENODE
0433: || node.nodeId == NodeTypes.CLASSNODE
0434: || node.nodeId == NodeTypes.DEFNNODE
0435: || node.nodeId == NodeTypes.DEFSNODE) {
0436: // Only look for require statements at the top level
0437: return;
0438: }
0439:
0440: @SuppressWarnings("unchecked")
0441: List<Node> list = node.childNodes();
0442:
0443: for (Node child : list) {
0444: addRequires(child, requires);
0445: }
0446: }
0447:
0448: /** Locate the method of the given name and arity */
0449: public static MethodDefNode findMethod(Node node, String name,
0450: Arity arity) {
0451: // Recursively search for methods or method calls that match the name and arity
0452: if ((node.nodeId == NodeTypes.DEFNNODE || node.nodeId == NodeTypes.DEFSNODE)
0453: && ((MethodDefNode) node).getName().equals(name)) {
0454: Arity defArity = Arity.getDefArity(node);
0455:
0456: if (Arity.matches(arity, defArity)) {
0457: return (MethodDefNode) node;
0458: }
0459: }
0460:
0461: @SuppressWarnings("unchecked")
0462: List<Node> list = node.childNodes();
0463:
0464: for (Node child : list) {
0465: MethodDefNode match = findMethod(child, name, arity);
0466:
0467: if (match != null) {
0468: return match;
0469: }
0470: }
0471:
0472: return null;
0473: }
0474:
0475: public static MethodDefNode findMethodAtOffset(Node root, int offset) {
0476: AstPath path = new AstPath(root, offset);
0477: Iterator<Node> it = path.leafToRoot();
0478:
0479: while (it.hasNext()) {
0480: Node node = it.next();
0481:
0482: if (node instanceof MethodDefNode) {
0483: return (MethodDefNode) node;
0484: }
0485: }
0486:
0487: return null;
0488: }
0489:
0490: public static ClassNode findClassAtOffset(Node root, int offset) {
0491: AstPath path = new AstPath(root, offset);
0492: Iterator<Node> it = path.leafToRoot();
0493:
0494: while (it.hasNext()) {
0495: Node node = it.next();
0496:
0497: if (node instanceof ClassNode) {
0498: return (ClassNode) node;
0499: }
0500: }
0501:
0502: return null;
0503: }
0504:
0505: public static Node findLocalScope(Node node, AstPath path) {
0506: Node method = findMethod(path);
0507:
0508: if (method == null) {
0509: Iterator<Node> it = path.leafToRoot();
0510: while (it.hasNext()) {
0511: Node n = it.next();
0512: switch (n.nodeId) {
0513: case NodeTypes.DEFNNODE:
0514: case NodeTypes.DEFSNODE:
0515: case NodeTypes.CLASSNODE:
0516: case NodeTypes.SCLASSNODE:
0517: case NodeTypes.MODULENODE:
0518: return n;
0519: }
0520: }
0521:
0522: if (path.root() != null) {
0523: return path.root();
0524: }
0525:
0526: method = findBlock(path);
0527: }
0528:
0529: if (method == null) {
0530: method = path.leafParent();
0531:
0532: if (method.nodeId == NodeTypes.NEWLINENODE) {
0533: method = path.leafGrandParent();
0534: }
0535:
0536: if (method == null) {
0537: method = node;
0538: }
0539: }
0540:
0541: return method;
0542: }
0543:
0544: public static Node findDynamicScope(Node node, AstPath path) {
0545: Node block = findBlock(path);
0546:
0547: if (block == null) {
0548: // Use parent
0549: block = path.leafParent();
0550:
0551: if (block == null) {
0552: block = node;
0553: }
0554: }
0555:
0556: return block;
0557: }
0558:
0559: public static Node findBlock(AstPath path) {
0560: // Find the most distant block node enclosing the given node (within
0561: // the current method/class/module
0562: Node candidate = null;
0563: for (Node curr : path) {
0564: switch (curr.nodeId) {
0565: //case NodeTypes.BLOCKNODE:
0566: case NodeTypes.ITERNODE:
0567: candidate = curr;
0568: break;
0569: case NodeTypes.DEFNNODE:
0570: case NodeTypes.DEFSNODE:
0571: case NodeTypes.CLASSNODE:
0572: case NodeTypes.SCLASSNODE:
0573: case NodeTypes.MODULENODE:
0574: return candidate;
0575: }
0576: }
0577:
0578: return candidate;
0579: }
0580:
0581: public static MethodDefNode findMethod(AstPath path) {
0582: // Find the closest block node enclosing the given node
0583: for (Node curr : path) {
0584: if (curr.nodeId == NodeTypes.DEFNNODE
0585: || curr.nodeId == NodeTypes.DEFSNODE) {
0586: return (MethodDefNode) curr;
0587: }
0588: if (curr.nodeId == NodeTypes.CLASSNODE
0589: || curr.nodeId == NodeTypes.SCLASSNODE
0590: || curr.nodeId == NodeTypes.MODULENODE) {
0591: break;
0592: }
0593: }
0594:
0595: return null;
0596: }
0597:
0598: // XXX Shouldn't this go in the REVERSE direction? I might find
0599: // a superclass here!
0600: // XXX What about SClassNode?
0601: public static ClassNode findClass(AstPath path) {
0602: // Find the closest block node enclosing the given node
0603: for (Node curr : path) {
0604: if (curr instanceof ClassNode) {
0605: return (ClassNode) curr;
0606: }
0607: }
0608:
0609: return null;
0610: }
0611:
0612: public static IScopingNode findClassOrModule(AstPath path) {
0613: // Find the closest block node enclosing the given node
0614: for (Node curr : path) {
0615: // XXX What about SClassNodes?
0616: if (curr.nodeId == NodeTypes.CLASSNODE
0617: || curr.nodeId == NodeTypes.MODULENODE) {
0618: return (IScopingNode) curr;
0619: }
0620: }
0621:
0622: return null;
0623: }
0624:
0625: public static boolean isCall(Node node) {
0626: return node.nodeId == NodeTypes.FCALLNODE
0627: || node.nodeId == NodeTypes.VCALLNODE
0628: || node.nodeId == NodeTypes.CALLNODE;
0629: }
0630:
0631: public static String getCallName(Node node) {
0632: assert isCall(node);
0633:
0634: if (node instanceof INameNode) {
0635: return ((INameNode) node).getName();
0636: }
0637: assert false : node;
0638:
0639: return null;
0640: }
0641:
0642: public static String getDefName(Node node) {
0643: if (node instanceof MethodDefNode) {
0644: return ((MethodDefNode) node).getName();
0645: }
0646: assert false : node;
0647:
0648: return null;
0649: }
0650:
0651: public static ArgumentNode getDefNameNode(MethodDefNode node) {
0652: return node.getNameNode();
0653: }
0654:
0655: public static boolean isConstructorMethod(MethodDefNode node) {
0656: String name = node.getName();
0657: if (name.equals("new") || name.equals("initialize")) { // NOI18N
0658: return true;
0659: }
0660:
0661: return false;
0662: }
0663:
0664: /** Find the direct child which is an ArgsNode, and pick out the argument names
0665: * @param node The method definition node
0666: * @param namesOnly If true, return only the parameter names for rest args and
0667: * blocks. If false, include "*" and "&".
0668: */
0669: @SuppressWarnings("unchecked")
0670: public static List<String> getDefArgs(MethodDefNode node,
0671: boolean namesOnly) {
0672: // TODO - do anything special about (&), blocks, argument lists (*), etc?
0673: List<Node> nodes = (List<Node>) node.childNodes();
0674:
0675: // TODO - use AstElement.getParameters?
0676: for (Node c : nodes) {
0677: if (c instanceof ArgsNode) {
0678: ArgsNode an = (ArgsNode) c;
0679:
0680: List<Node> args = (List<Node>) an.childNodes();
0681: List<String> parameters = new ArrayList<String>();
0682:
0683: for (Node arg : args) {
0684: if (arg instanceof ListNode) {
0685: List<Node> args2 = (List<Node>) arg
0686: .childNodes();
0687:
0688: for (Node arg2 : args2) {
0689: if (arg2 instanceof ArgumentNode) {
0690: String name = ((ArgumentNode) arg2)
0691: .getName();
0692: parameters.add(name);
0693: } else if (arg2 instanceof LocalAsgnNode) {
0694: String name = ((LocalAsgnNode) arg2)
0695: .getName();
0696: parameters.add(name);
0697: }
0698: }
0699: }
0700: }
0701:
0702: // Rest args
0703: if (an.getRestArgNode() != null) {
0704: String name = an.getRestArgNode().getName();
0705:
0706: if (!namesOnly) {
0707: name = "*" + name;
0708: }
0709:
0710: parameters.add(name);
0711: }
0712:
0713: // Block args
0714: if (an.getBlockArgNode() != null) {
0715: String name = an.getBlockArgNode().getName();
0716:
0717: if (!namesOnly) {
0718: name = "&" + name;
0719: }
0720:
0721: parameters.add(name);
0722: }
0723:
0724: return parameters;
0725: }
0726: }
0727:
0728: return null;
0729: }
0730:
0731: public static String getDefSignature(MethodDefNode node) {
0732: StringBuilder sb = new StringBuilder();
0733: sb.append(getDefName(node));
0734:
0735: List<String> args = getDefArgs(node, false);
0736:
0737: if ((args != null) && (args.size() > 0)) {
0738: sb.append('(');
0739:
0740: Iterator<String> it = args.iterator();
0741: sb.append(it.next());
0742:
0743: while (it.hasNext()) {
0744: sb.append(',');
0745: sb.append(it.next());
0746: }
0747:
0748: sb.append(')');
0749: }
0750:
0751: return sb.toString();
0752: }
0753:
0754: /**
0755: * Look for the caret offset in the parameter list; return the
0756: * index of the parameter that contains it.
0757: */
0758: @SuppressWarnings("unchecked")
0759: public static int findArgumentIndex(Node node, int offset) {
0760: switch (node.nodeId) {
0761: case NodeTypes.FCALLNODE: {
0762: Node argsNode = ((FCallNode) node).getArgsNode();
0763:
0764: return findArgumentIndex(argsNode, offset);
0765: }
0766: case NodeTypes.CALLNODE: {
0767: Node argsNode = ((CallNode) node).getArgsNode();
0768:
0769: return findArgumentIndex(argsNode, offset);
0770: }
0771: case NodeTypes.ARGSCATNODE: {
0772: ArgsCatNode acn = (ArgsCatNode) node;
0773:
0774: int index = findArgumentIndex(acn.getFirstNode(), offset);
0775:
0776: if (index != -1) {
0777: return index;
0778: }
0779:
0780: index = findArgumentIndex(acn.getSecondNode(), offset);
0781:
0782: if (index != -1) {
0783: // Add in arg count on the left
0784: return getConstantArgs(acn) + index;
0785: }
0786:
0787: ISourcePosition pos = node.getPosition();
0788:
0789: if ((offset >= pos.getStartOffset())
0790: && (offset <= pos.getEndOffset())) {
0791: return getConstantArgs(acn);
0792: }
0793: }
0794: case NodeTypes.HASHNODE:
0795: // Everything gets glommed into the same hash parameter offset
0796: return offset;
0797: default:
0798: if (node instanceof ListNode) {
0799: List<Node> children = node.childNodes();
0800:
0801: int prevEnd = Integer.MAX_VALUE;
0802:
0803: for (int index = 0; index < children.size(); index++) {
0804: Node child = children.get(index);
0805: if (child.nodeId == NodeTypes.HASHNODE) {
0806: // Invalid offsets - the hashnode often has the wrong offset
0807: OffsetRange range = AstUtilities
0808: .getRange(child);
0809: if ((offset <= range.getEnd())
0810: && ((offset >= prevEnd) || (offset >= range
0811: .getStart()))) {
0812: return index;
0813: }
0814:
0815: prevEnd = range.getEnd();
0816: } else {
0817: ISourcePosition pos = child.getPosition();
0818: if ((offset <= pos.getEndOffset())
0819: && ((offset >= prevEnd) || (offset >= pos
0820: .getStartOffset()))) {
0821: return index;
0822: }
0823:
0824: prevEnd = pos.getEndOffset();
0825: }
0826:
0827: }
0828:
0829: // Caret -inside- empty parentheses?
0830: ISourcePosition pos = node.getPosition();
0831:
0832: if ((offset > pos.getStartOffset())
0833: && (offset < pos.getEndOffset())) {
0834: return 0;
0835: }
0836: } else {
0837: ISourcePosition pos = node.getPosition();
0838:
0839: if ((offset >= pos.getStartOffset())
0840: && (offset <= pos.getEndOffset())) {
0841: return 0;
0842: }
0843: }
0844:
0845: return -1;
0846: }
0847: }
0848:
0849: /** Utility method used by findArgumentIndex: count the constant number of
0850: * arguments in a parameter list before the argscatnode */
0851: private static int getConstantArgs(ArgsCatNode acn) {
0852: Node node = acn.getFirstNode();
0853:
0854: if (node instanceof ListNode) {
0855: List children = node.childNodes();
0856:
0857: return children.size();
0858: } else {
0859: return 1;
0860: }
0861: }
0862:
0863: /**
0864: * Return true iff the given call note can be considered a valid call of the given method.
0865: */
0866: public static boolean isCallFor(Node call, Arity callArity,
0867: Node method) {
0868: assert isCall(call);
0869: assert method instanceof MethodDefNode;
0870:
0871: // Simple call today...
0872: return getDefName(method).equals(getCallName(call))
0873: && Arity.matches(callArity, Arity.getDefArity(method));
0874: }
0875:
0876: // TODO: use the structure analyzer data for more accurate traversal?
0877: /** For the given signature, locating the corresponding Node within the tree that
0878: * it corresponds to */
0879: public static Node findBySignature(Node root, String signature) {
0880: String originalSig = signature;
0881:
0882: //String name = signature.split("(::)")
0883: // Find next name we're looking for
0884: String name = getNextSigComponent(signature);
0885: signature = signature.substring(name.length());
0886:
0887: Node node = findBySignature(root, signature, name);
0888:
0889: // Handle top level methods
0890: if (node == null && originalSig.startsWith("Object#")) {
0891: // Just look for top level method definitions instead
0892: originalSig = originalSig.substring(originalSig
0893: .indexOf('#') + 1);
0894: name = getNextSigComponent(signature);
0895: signature = originalSig.substring(name.length());
0896:
0897: node = findBySignature(root, signature, name);
0898: }
0899:
0900: return node;
0901: }
0902:
0903: // For a signature of the form Foo::Bar#baz(arg1,arg2,...)
0904: // pull out the next component; in the above, successively return
0905: // "Foo", "Bar", "baz", etc.
0906: private static String getNextSigComponent(String signature) {
0907: StringBuilder sb = new StringBuilder();
0908: int i = 0;
0909: int n = signature.length();
0910:
0911: // Skip leading separators
0912: for (; i < n; i++) {
0913: char c = signature.charAt(i);
0914:
0915: if ((c == '#') || (c == ':') || (c == '(')) {
0916: continue;
0917: }
0918:
0919: break;
0920: }
0921:
0922: // Add the name
0923: for (; i < n; i++) {
0924: char c = signature.charAt(i);
0925:
0926: if ((c == '#') || (c == ':') || (c == '(')) {
0927: break;
0928: }
0929:
0930: sb.append(c);
0931: }
0932:
0933: return sb.toString();
0934: }
0935:
0936: private static Node findBySignature(Node node, String signature,
0937: String name) {
0938: switch (node.nodeId) {
0939: case NodeTypes.INSTASGNNODE:
0940: if (name.charAt(0) == '@') {
0941: String n = ((INameNode) node).getName();
0942: //if (name.regionMatches(1, n, 0, n.length())) {
0943: if (name.equals(n)) {
0944: return node;
0945: }
0946: }
0947: break;
0948: case NodeTypes.CLASSVARDECLNODE:
0949: case NodeTypes.CLASSVARASGNNODE:
0950: if (name.startsWith("@@")) {
0951: String n = ((INameNode) node).getName();
0952: //if (name.regionMatches(2, n, 0, n.length())) {
0953: if (name.equals(n)) {
0954: return node;
0955: }
0956: }
0957: break;
0958:
0959: case NodeTypes.DEFNNODE:
0960: case NodeTypes.DEFSNODE:
0961: boolean lookingForMethod = (Character.isLowerCase(name
0962: .charAt(0)));
0963: if (lookingForMethod
0964: && name.equals(AstUtilities.getDefName(node))) {
0965: // See if the parameter list matches
0966: // XXX TODO
0967: List<String> parameters = getDefArgs(
0968: (MethodDefNode) node, false);
0969:
0970: if ((signature.length() == 0)
0971: && ((parameters == null) || (parameters.size() == 0))) {
0972: // No args
0973: return node;
0974: } else if (signature.length() != 0) {
0975: assert signature.charAt(0) == '(';
0976:
0977: String argList = signature.substring(1, signature
0978: .length() - 1);
0979: String[] args = argList.split(",");
0980:
0981: if (args.length == parameters.size()) {
0982: // Should I enforce equality here?
0983: boolean equal = true;
0984:
0985: for (int i = 0; i < args.length; i++) {
0986: if (!args[i].equals(parameters.get(i))) {
0987: equal = false;
0988:
0989: break;
0990: }
0991: }
0992:
0993: if (equal) {
0994: return node;
0995: }
0996: }
0997: }
0998: } else if (isAttr(node)) {
0999: SymbolNode[] symbols = getAttrSymbols(node);
1000: for (SymbolNode sym : symbols) {
1001: if (name.equals(sym.getName())) {
1002: return node;
1003: }
1004: }
1005: }
1006: break;
1007:
1008: case NodeTypes.CLASSNODE:
1009: case NodeTypes.MODULENODE: {
1010: Colon3Node c3n = ((IScopingNode) node).getCPath();
1011:
1012: if (c3n instanceof Colon2Node) {
1013: String fqn = getFqn((Colon2Node) c3n);
1014:
1015: if (fqn.startsWith(name)
1016: && signature.startsWith(fqn.substring(name
1017: .length()))) {
1018: signature = signature.substring(fqn.substring(
1019: name.length()).length());
1020: name = getNextSigComponent(signature);
1021:
1022: if (name.length() == 0) {
1023: // The signature points to a class (or module) - just return it
1024: return node;
1025: }
1026:
1027: int index = signature.indexOf(name);
1028: assert index != -1;
1029: signature = signature.substring(index
1030: + name.length());
1031: }
1032: } else if (name.equals(AstUtilities
1033: .getClassOrModuleName(((IScopingNode) node)))) {
1034: name = getNextSigComponent(signature);
1035:
1036: if (name.length() == 0) {
1037: // The signature points to a class (or module) - just return it
1038: return node;
1039: }
1040:
1041: int index = signature.indexOf(name);
1042: assert index != -1;
1043: signature = signature.substring(index + name.length());
1044: }
1045: break;
1046: }
1047: case NodeTypes.SCLASSNODE:
1048: Node receiver = ((SClassNode) node).getReceiverNode();
1049: String rn = null;
1050:
1051: if (receiver instanceof Colon2Node) {
1052: // TODO - check to see if we qualify
1053: rn = ((Colon2Node) receiver).getName();
1054: } else if (receiver instanceof ConstNode) {
1055: rn = ((ConstNode) receiver).getName();
1056: } // else: some other type of singleton class definition, like class << foo
1057:
1058: if (rn != null) {
1059: if (name.equals(rn)) {
1060: name = getNextSigComponent(signature);
1061:
1062: if (name.length() == 0) {
1063: // The signature points to a class (or module) - just return it
1064: return node;
1065: }
1066:
1067: int index = signature.indexOf(name);
1068: assert index != -1;
1069: signature = signature.substring(index
1070: + name.length());
1071: }
1072: }
1073: break;
1074: }
1075: @SuppressWarnings("unchecked")
1076: List<Node> list = node.childNodes();
1077:
1078: for (Node child : list) {
1079: Node match = findBySignature(child, signature, name);
1080:
1081: if (match != null) {
1082: return match;
1083: }
1084: }
1085:
1086: return null;
1087: }
1088:
1089: /** Return true iff the given node contains the given offset */
1090: public static boolean containsOffset(Node node, int offset) {
1091: ISourcePosition pos = node.getPosition();
1092:
1093: return ((offset >= pos.getStartOffset()) && (offset <= pos
1094: .getEndOffset()));
1095: }
1096:
1097: /**
1098: * Return a range that matches the given node's source buffer range
1099: */
1100: @SuppressWarnings("unchecked")
1101: public static OffsetRange getRange(Node node) {
1102: if (node.nodeId == NodeTypes.NOTNODE) {
1103: ISourcePosition pos = node.getPosition();
1104: // "unless !(x < 5)" gives a not-node with wrong offsets - starts
1105: // with ! but doesn't include the closing )
1106: List<Node> list = node.childNodes();
1107: if (list != null && list.size() > 0) {
1108: Node first = list.get(0);
1109: if (first.nodeId == NodeTypes.NEWLINENODE) {
1110: OffsetRange range = getRange(first);
1111: return new OffsetRange(pos.getStartOffset(), range
1112: .getEnd());
1113: }
1114: }
1115: return new OffsetRange(pos.getStartOffset(), pos
1116: .getEndOffset());
1117: } else if (node.nodeId == NodeTypes.HASHNODE) {
1118: // Workaround for incorrect JRuby AST offsets for hashnodes :
1119: // render :action => 'list'
1120: // has wrong argument offsets, which we want to correct.
1121: // Just adopt the start offset of its first child (if any) and
1122: // the end offset of its last child (if any)
1123: List<Node> list = node.childNodes();
1124: if (list != null && list.size() > 0) {
1125: int start = list.get(0).getPosition().getStartOffset();
1126: int end = list.get(list.size() - 1).getPosition()
1127: .getEndOffset();
1128: return new OffsetRange(start, end);
1129: } else {
1130: ISourcePosition pos = node.getPosition();
1131: return new OffsetRange(pos.getStartOffset(), pos
1132: .getEndOffset());
1133: }
1134: } else {
1135: ISourcePosition pos = node.getPosition();
1136: return new OffsetRange(pos.getStartOffset(), pos
1137: .getEndOffset());
1138: }
1139: }
1140:
1141: /**
1142: * Return a range that matches the lvalue for an assignment. The node must be namable.
1143: */
1144: public static OffsetRange getLValueRange(AssignableNode node) {
1145: assert node instanceof INameNode;
1146:
1147: ISourcePosition pos = node.getPosition();
1148: OffsetRange range = new OffsetRange(pos.getStartOffset(), pos
1149: .getStartOffset()
1150: + ((INameNode) node).getName().length());
1151:
1152: return range;
1153: }
1154:
1155: public static OffsetRange getNameRange(Node node) {
1156: if (node instanceof AssignableNode) {
1157: return getLValueRange((AssignableNode) node);
1158: } else if (node instanceof MethodDefNode) {
1159: return getFunctionNameRange(node);
1160: } else if (isCall(node)) {
1161: return getCallRange(node);
1162: } else if (node instanceof ClassNode) {
1163: // TODO - try to pull out the constnode or colon2node holding the class name,
1164: // and return it!
1165: Colon3Node c3n = ((ClassNode) node).getCPath();
1166: if (c3n != null) {
1167: return getRange(c3n);
1168: } else {
1169: return getRange(node);
1170: }
1171: } else if (node instanceof ModuleNode) {
1172: // TODO - try to pull out the constnode or colon2node holding the class name,
1173: // and return it!
1174: Colon3Node c3n = ((ModuleNode) node).getCPath();
1175: if (c3n != null) {
1176: return getRange(c3n);
1177: } else {
1178: return getRange(node);
1179: }
1180: // } else if (node instanceof SClassNode) {
1181: // // TODO - try to pull out the constnode or colon2node holding the class name,
1182: // // and return it!
1183: // Colon3Node c3n = ((SClassNode)node).getCPath();
1184: // if (c3n != null) {
1185: // return getRange(c3n);
1186: // } else {
1187: // return getRange(node);
1188: // }
1189: } else {
1190: return getRange(node);
1191: }
1192: }
1193:
1194: /** For CallNodes, the offset range for the AST node includes the entire parameter list.
1195: * We want ONLY the actual call/operator name. So compute that on our own.
1196: */
1197: public static OffsetRange getCallRange(Node node) {
1198: ISourcePosition pos = node.getPosition();
1199: int start = pos.getStartOffset();
1200: int end = pos.getEndOffset();
1201: assert isCall(node);
1202: assert node instanceof INameNode;
1203:
1204: if (node instanceof CallNode) {
1205: // A call of the form Foo.bar. "bar" is the CallNode, "Foo" is the ReceiverNode.
1206: // Here I'm only handling named nodes; there may be others
1207: Node receiver = ((CallNode) node).getReceiverNode();
1208:
1209: if (receiver != null) {
1210: start = receiver.getPosition().getEndOffset() + 1; // end of "Foo::bar" + "."
1211: }
1212: }
1213:
1214: if (node instanceof INameNode) {
1215: end = start + ((INameNode) node).getName().length();
1216: }
1217:
1218: return new OffsetRange(start, end);
1219: }
1220:
1221: @SuppressWarnings("unchecked")
1222: public static OffsetRange getFunctionNameRange(Node node) {
1223: // TODO - enforce MethodDefNode and call getNameNode on it!
1224: for (Node child : (List<Node>) node.childNodes()) {
1225: if (child instanceof ArgumentNode) {
1226: OffsetRange range = AstUtilities.getRange(child);
1227:
1228: return range;
1229: }
1230: }
1231:
1232: if (node instanceof MethodDefNode) {
1233: for (Node child : (List<Node>) node.childNodes()) {
1234: if (child instanceof ConstNode) {
1235: ISourcePosition pos = child.getPosition();
1236: int end = pos.getEndOffset();
1237: int start;
1238:
1239: if (INCLUDE_DEFS_PREFIX) {
1240: start = pos.getStartOffset();
1241: } else {
1242: start = end + 1;
1243: }
1244:
1245: // TODO - look at the source buffer and tweak offset if it's wrong
1246: // This assumes we have a single constant node, followed by a dot, followed by the name
1247: end = end + 1
1248: + AstUtilities.getDefName(node).length(); // +1: "."
1249:
1250: OffsetRange range = new OffsetRange(start, end);
1251:
1252: return range;
1253: }
1254: }
1255: }
1256:
1257: return OffsetRange.NONE;
1258: }
1259:
1260: /**
1261: * Return the OffsetRange for an AliasNode that represents the new name portion.
1262: */
1263: public static OffsetRange getAliasNewRange(AliasNode node) {
1264: // XXX I don't know where the old and new names are since the user COULD
1265: // have used more than one whitespace character for separation. For now I'll
1266: // just have to assume it's the normal case with one space: alias new old.
1267: // I -could- use the getPosition.getEndOffset() to see if this looks like it's
1268: // the case (e.g. node length != "alias ".length + old.length+new.length+1).
1269: // In this case I could go peeking in the source buffer to see where the
1270: // spaces are - between alias and the first word or between old and new. XXX.
1271: ISourcePosition pos = node.getPosition();
1272:
1273: int newStart = pos.getStartOffset() + 6; // 6: "alias ".length()
1274:
1275: return new OffsetRange(newStart, newStart
1276: + node.getNewName().length());
1277: }
1278:
1279: /**
1280: * Return the OffsetRange for an AliasNode that represents the old name portion.
1281: */
1282: public static OffsetRange getAliasOldRange(AliasNode node) {
1283: // XXX I don't know where the old and new names are since the user COULD
1284: // have used more than one whitespace character for separation. For now I'll
1285: // just have to assume it's the normal case with one space: alias new old.
1286: // I -could- use the getPosition.getEndOffset() to see if this looks like it's
1287: // the case (e.g. node length != "alias ".length + old.length+new.length+1).
1288: // In this case I could go peeking in the source buffer to see where the
1289: // spaces are - between alias and the first word or between old and new. XXX.
1290: ISourcePosition pos = node.getPosition();
1291:
1292: int oldStart = pos.getStartOffset() + 6
1293: + node.getNewName().length() + 1; // 6: "alias ".length; 1: " ".length
1294:
1295: return new OffsetRange(oldStart, oldStart
1296: + node.getOldName().length());
1297: }
1298:
1299: public static String getClassOrModuleName(IScopingNode node) {
1300: return ((INameNode) node.getCPath()).getName();
1301: }
1302:
1303: public static List<ClassNode> getClasses(Node root) {
1304: // I would like to use a visitor for this, but it's not
1305: // working - I get NPE's within DefaultIteratorVisitor
1306: // on valid ASTs, and I see it's not used heavily in JRuby,
1307: // so I'm not doing it this way for now.
1308: //final List<ClassNode> classes = new ArrayList<ClassNode>();
1309: //// There could be multiple Class definitions for this
1310: //// same class, and (empirically) rdoc shows the documentation
1311: //// for the last declaration.
1312: //NodeVisitor findClasses = new AbstractVisitor() {
1313: // public Instruction visitClassNode(ClassNode node) {
1314: // classes.add(node);
1315: // return visitNode(node);
1316: // }
1317: //
1318: // protected Instruction visitNode(Node iVisited) {
1319: // return null;
1320: // }
1321: //};
1322: //new DefaultIteratorVisitor(findClasses).visitRootNode((RootNode)parseResult.getRootNode());
1323: List<ClassNode> classes = new ArrayList<ClassNode>();
1324: addClasses(root, classes);
1325:
1326: return classes;
1327: }
1328:
1329: private static void addClasses(Node node, List<ClassNode> classes) {
1330: if (node instanceof ClassNode) {
1331: classes.add((ClassNode) node);
1332: }
1333:
1334: @SuppressWarnings("unchecked")
1335: List<Node> list = node.childNodes();
1336:
1337: for (Node child : list) {
1338: addClasses(child, classes);
1339: }
1340: }
1341:
1342: private static void addAncestorParents(Node node, StringBuilder sb) {
1343: if (node instanceof Colon2Node) {
1344: Colon2Node c2n = (Colon2Node) node;
1345: addAncestorParents(c2n.getLeftNode(), sb);
1346:
1347: if ((sb.length() > 0)
1348: && (sb.charAt(sb.length() - 1) != ':')) {
1349: sb.append("::");
1350: }
1351:
1352: sb.append(c2n.getName());
1353: } else if (node instanceof INameNode) {
1354: if ((sb.length() > 0)
1355: && (sb.charAt(sb.length() - 1) != ':')) {
1356: sb.append("::");
1357: }
1358:
1359: sb.append(((INameNode) node).getName());
1360: }
1361: }
1362:
1363: public static String getFqn(Colon2Node c2n) {
1364: StringBuilder sb = new StringBuilder();
1365:
1366: addAncestorParents(c2n, sb);
1367:
1368: return sb.toString();
1369: }
1370:
1371: public static String getSuperclass(ClassNode clz) {
1372: StringBuilder sb = new StringBuilder();
1373:
1374: if (clz.getSuperNode() != null) {
1375: addAncestorParents(clz.getSuperNode(), sb);
1376:
1377: return sb.toString();
1378: }
1379:
1380: return null;
1381: }
1382:
1383: /** Compute the module/class name for the given node path */
1384: public static String getFqnName(AstPath path) {
1385: StringBuilder sb = new StringBuilder();
1386:
1387: Iterator<Node> it = path.rootToLeaf();
1388:
1389: while (it.hasNext()) {
1390: Node node = it.next();
1391:
1392: if (node instanceof ModuleNode || node instanceof ClassNode) {
1393: Colon3Node cpath = ((IScopingNode) node).getCPath();
1394:
1395: if (cpath == null) {
1396: continue;
1397: }
1398:
1399: if (sb.length() > 0) {
1400: sb.append("::"); // NOI18N
1401: }
1402:
1403: if (cpath instanceof Colon2Node) {
1404: sb.append(getFqn((Colon2Node) cpath));
1405: } else {
1406: sb.append(cpath.getName());
1407: }
1408: }
1409: }
1410:
1411: return sb.toString();
1412: }
1413:
1414: public static boolean isAttr(Node node) {
1415: if (!(node instanceof FCallNode)) {
1416: return false;
1417: }
1418:
1419: String name = ((INameNode) node).getName();
1420:
1421: if (name.startsWith("attr")) { // NOI18N
1422:
1423: if ("attr".equals(name)
1424: || "attr_reader".equals(name)
1425: || // NOI18N
1426: "attr_accessor".equals(name)
1427: || "attr_writer".equals(name)
1428: || // NOI18N
1429: // Rails: Special definitions which builds methods that have actual fields
1430: // backing the attribute. Important to include these since they're
1431: // used for key Rails members like headers, session, etc.
1432: "attr_internal".equals(name)
1433: || "attr_internal_reader".equals(name)
1434: || "attr_internal_writer".equals(name) || // NOI18N
1435: "attr_internal_accessor".equals(name)) { // NOI18N
1436:
1437: return true;
1438: }
1439: }
1440:
1441: return false;
1442: }
1443:
1444: @SuppressWarnings("unchecked")
1445: public static SymbolNode[] getAttrSymbols(Node node) {
1446: assert isAttr(node);
1447:
1448: List<Node> list = node.childNodes();
1449:
1450: for (Node child : list) {
1451: if (child instanceof ListNode) {
1452: List<Node> symbols = (List<Node>) child.childNodes();
1453: List<SymbolNode> symbolList = new ArrayList<SymbolNode>(
1454: symbols.size());
1455:
1456: for (Node symbol : symbols) {
1457: if (symbol instanceof SymbolNode) {
1458: symbolList.add((SymbolNode) symbol);
1459: }
1460: }
1461:
1462: return symbolList.toArray(new SymbolNode[symbolList
1463: .size()]);
1464: }
1465: }
1466:
1467: return new SymbolNode[0];
1468: }
1469:
1470: public static RubyParseResult getParseResult(CompilationInfo info) {
1471: ParserResult result = info.getEmbeddedResult(
1472: RubyMimeResolver.RUBY_MIME_TYPE, 0);
1473:
1474: if (result == null) {
1475: return null;
1476: } else {
1477: return ((RubyParseResult) result);
1478: }
1479: }
1480:
1481: public static Node getRoot(CompilationInfo info) {
1482: return getRoot(info, RubyMimeResolver.RUBY_MIME_TYPE);
1483: }
1484:
1485: public static Node getRoot(CompilationInfo info, String mimeType) {
1486: ParserResult result = info.getEmbeddedResult(mimeType, 0);
1487:
1488: if (result == null) {
1489: return null;
1490: }
1491:
1492: return getRoot(result);
1493: }
1494:
1495: public static Node getRoot(ParserResult r) {
1496: assert r instanceof RubyParseResult;
1497:
1498: RubyParseResult result = (RubyParseResult) r;
1499:
1500: return result.getRootNode();
1501: }
1502:
1503: /**
1504: * Get the private and protected methods in the given class
1505: */
1506: public static void findPrivateMethods(Node clz,
1507: Set<Node> protectedMethods, Set<Node> privateMethods) {
1508: Set<String> publicMethodSymbols = new HashSet<String>();
1509: Set<String> protectedMethodSymbols = new HashSet<String>();
1510: Set<String> privateMethodSymbols = new HashSet<String>();
1511: Set<Node> publicMethods = new HashSet<Node>();
1512:
1513: @SuppressWarnings("unchecked")
1514: List<Node> list = clz.childNodes();
1515:
1516: Modifier access = Modifier.PUBLIC;
1517:
1518: for (Node child : list) {
1519: access = getMethodAccess(child, access,
1520: publicMethodSymbols, protectedMethodSymbols,
1521: privateMethodSymbols, publicMethods,
1522: protectedMethods, privateMethods);
1523: }
1524:
1525: // Can't just return private methods directly, since sometimes you can
1526: // specify that a particular method is public before we know about it,
1527: // so I can't just remove it from the known private list when I see the
1528: // access modifier
1529: privateMethodSymbols.removeAll(publicMethodSymbols);
1530: protectedMethodSymbols.removeAll(publicMethodSymbols);
1531:
1532: // Should I worry about private foo; protected :foo ?
1533: // Seems unlikely somebody would do that... I guess
1534: // I could do privateMethodSymbols.removeAll(protectedMethodSymbols) etc.
1535: //privateMethodSymbols.removeAll(protectedMethodSymbols);
1536: //protectedMethodSymbols.removeAll(privateMethodSymbols);
1537:
1538: // Add all methods known to be private into the private node set
1539: for (String name : privateMethodSymbols) {
1540: for (Node n : publicMethods) {
1541: if (name.equals(AstUtilities.getDefName(n))) {
1542: privateMethods.add(n);
1543: }
1544: }
1545: }
1546:
1547: for (String name : protectedMethodSymbols) {
1548: for (Node n : publicMethods) {
1549: if (name.equals(AstUtilities.getDefName(n))) {
1550: protectedMethods.add(n);
1551: }
1552: }
1553: }
1554: }
1555:
1556: /**
1557: * @todo Should I really recurse into classes? If I have nested classes private
1558: * methods ther shouldn't be included for the parent!
1559: *
1560: * @param access The "current" known access level (PUBLIC, PROTECTED or PRIVATE)
1561: * @return the access level to continue with at this syntactic level
1562: */
1563: @SuppressWarnings("unchecked")
1564: private static Modifier getMethodAccess(Node node, Modifier access,
1565: Set<String> publicMethodSymbols,
1566: Set<String> protectedMethodSymbols,
1567: Set<String> privateMethodSymbols, Set<Node> publicMethods,
1568: Set<Node> protectedMethods, Set<Node> privateMethods) {
1569: if (node instanceof MethodDefNode) {
1570: if (access == Modifier.PRIVATE) {
1571: privateMethods.add(node);
1572: } else if (access == Modifier.PUBLIC) {
1573: publicMethods.add(node);
1574: } else if (access == Modifier.PROTECTED) {
1575: protectedMethods.add(node);
1576: }
1577:
1578: // XXX Can I have nested method definitions? If so I may have to continue here
1579: return access;
1580: } else if (node instanceof VCallNode
1581: || node instanceof FCallNode) {
1582: String name = ((INameNode) node).getName();
1583:
1584: if ("private".equals(name)) {
1585: // TODO - see if it has arguments, if it does - it's just a single
1586: // method defined to be private
1587: // Iterate over arguments and add symbols...
1588: if (Arity.callHasArguments(node)) {
1589: List<Node> params = (List<Node>) node.childNodes();
1590:
1591: for (Node param : params) {
1592: if (param instanceof ListNode) {
1593: List<Node> params2 = (List<Node>) param
1594: .childNodes();
1595:
1596: for (Node param2 : params2) {
1597: if (param2 instanceof SymbolNode) {
1598: String symbol = ((SymbolNode) param2)
1599: .getName();
1600: privateMethodSymbols.add(symbol);
1601: }
1602: }
1603: }
1604: }
1605: } else {
1606: access = Modifier.PRIVATE;
1607: }
1608:
1609: return access;
1610: } else if ("protected".equals(name)) {
1611: // TODO - see if it has arguments, if it does - it's just a single
1612: // method defined to be private
1613: // Iterate over arguments and add symbols...
1614: if (Arity.callHasArguments(node)) {
1615: List<Node> params = (List<Node>) node.childNodes();
1616:
1617: for (Node param : params) {
1618: if (param instanceof ListNode) {
1619: List<Node> params2 = (List<Node>) param
1620: .childNodes();
1621:
1622: for (Node param2 : params2) {
1623: if (param2 instanceof SymbolNode) {
1624: String symbol = ((SymbolNode) param2)
1625: .getName();
1626: protectedMethodSymbols.add(symbol);
1627: }
1628: }
1629: }
1630: }
1631: } else {
1632: access = Modifier.PROTECTED;
1633: }
1634:
1635: return access;
1636: } else if ("public".equals(name)) {
1637: if (!Arity.callHasArguments(node)) {
1638: access = Modifier.PUBLIC;
1639:
1640: return access;
1641: } else {
1642: List<Node> params = (List<Node>) node.childNodes();
1643:
1644: for (Node param : params) {
1645: if (param instanceof ListNode) {
1646: List<Node> params2 = (List<Node>) param
1647: .childNodes();
1648:
1649: for (Node param2 : params2) {
1650: if (param2 instanceof SymbolNode) {
1651: String symbol = ((SymbolNode) param2)
1652: .getName();
1653: publicMethodSymbols.add(symbol);
1654: }
1655: }
1656: }
1657: }
1658: }
1659: }
1660:
1661: return access;
1662: } else if (node instanceof ClassNode
1663: || node instanceof ModuleNode) {
1664: return access;
1665: }
1666:
1667: @SuppressWarnings("unchecked")
1668: List<Node> list = node.childNodes();
1669:
1670: for (Node child : list) {
1671: access = getMethodAccess(child, access,
1672: publicMethodSymbols, protectedMethodSymbols,
1673: privateMethodSymbols, publicMethods,
1674: protectedMethods, privateMethods);
1675: }
1676:
1677: return access;
1678: }
1679:
1680: /**
1681: * Get the method name for the given offset - or null if it cannot be found. This
1682: * will initiate a new parse job if necessary.
1683: */
1684: public static String getMethodName(FileObject fo, final int offset) {
1685: SourceModel js = SourceModelFactory.getInstance().getModel(fo);
1686:
1687: if (js == null) {
1688: return null;
1689: }
1690:
1691: if (js.isScanInProgress()) {
1692: return null;
1693: }
1694:
1695: final String[] result = new String[1];
1696:
1697: try {
1698: js.runUserActionTask(
1699: new CancellableTask<CompilationInfo>() {
1700: public void cancel() {
1701: }
1702:
1703: public void run(CompilationInfo info) {
1704: org.jruby.ast.Node root = AstUtilities
1705: .getRoot(info);
1706:
1707: if (root == null) {
1708: return;
1709: }
1710:
1711: org.jruby.ast.MethodDefNode method = AstUtilities
1712: .findMethodAtOffset(root, offset);
1713:
1714: if (method == null) {
1715: // It's possible the user had the caret on a line
1716: // that includes a method that isn't actually inside
1717: // the method block - such as the beginning of the
1718: // "def" line, or the end of a line after "end".
1719: // The latter isn't very likely, but the former can
1720: // happen, so let's check the method bodies at the
1721: // end of the current line
1722: try {
1723: BaseDocument doc = (BaseDocument) info
1724: .getDocument();
1725: int endOffset = Utilities
1726: .getRowEnd(doc, offset);
1727:
1728: if (endOffset != offset) {
1729: method = AstUtilities
1730: .findMethodAtOffset(
1731: root, endOffset);
1732: }
1733: } catch (BadLocationException ble) {
1734: Exceptions.printStackTrace(ble);
1735: } catch (IOException ioe) {
1736: Exceptions.printStackTrace(ioe);
1737: }
1738: }
1739:
1740: if (method != null) {
1741: result[0] = method.getName();
1742: }
1743: }
1744: }, true);
1745: } catch (IOException ioe) {
1746: Exceptions.printStackTrace(ioe);
1747: }
1748:
1749: return result[0];
1750: }
1751:
1752: public static int findOffset(FileObject fo, final String methodName) {
1753: SourceModel js = SourceModelFactory.getInstance().getModel(fo);
1754:
1755: if (js == null) {
1756: return -1;
1757: }
1758:
1759: if (js.isScanInProgress()) {
1760: return -1;
1761: }
1762:
1763: final int[] result = new int[1];
1764: result[0] = -1;
1765:
1766: try {
1767: js.runUserActionTask(
1768: new CancellableTask<CompilationInfo>() {
1769: public void cancel() {
1770: }
1771:
1772: public void run(CompilationInfo info) {
1773: org.jruby.ast.Node root = AstUtilities
1774: .getRoot(info);
1775:
1776: if (root == null) {
1777: return;
1778: }
1779:
1780: org.jruby.ast.Node method = AstUtilities
1781: .findMethod(root, methodName,
1782: Arity.UNKNOWN);
1783:
1784: if (method != null) {
1785: int startOffset = method.getPosition()
1786: .getStartOffset();
1787: result[0] = startOffset;
1788: }
1789: }
1790: }, true);
1791: } catch (IOException ioe) {
1792: Exceptions.printStackTrace(ioe);
1793: }
1794:
1795: return result[0];
1796: }
1797:
1798: /** Collect nodes of the given types (node.nodeId==NodeTypes.x) under the given root */
1799: public static void addNodesByType(Node root, int[] nodeIds,
1800: List<Node> result) {
1801: for (int i = 0; i < nodeIds.length; i++) {
1802: if (root.nodeId == nodeIds[i]) {
1803: result.add(root);
1804: break;
1805: }
1806: }
1807:
1808: @SuppressWarnings("unchecked")
1809: List<Node> list = root.childNodes();
1810:
1811: for (Node child : list) {
1812: addNodesByType(child, nodeIds, result);
1813: }
1814: }
1815:
1816: /** Return all the blocknodes that apply to the given node. The outermost block
1817: * is returned first.
1818: */
1819: public static List<Node> getApplicableBlocks(AstPath path,
1820: boolean includeNested) {
1821: Node block = AstUtilities.findBlock(path);
1822:
1823: if (block == null) {
1824: // Use parent
1825: block = path.leafParent();
1826:
1827: if (block == null) {
1828: return Collections.emptyList();
1829: }
1830: }
1831:
1832: List<Node> result = new ArrayList<Node>();
1833: Iterator<Node> it = path.leafToRoot();
1834:
1835: // Skip the leaf node, we're going to add it unconditionally afterwards
1836: if (includeNested) {
1837: if (it.hasNext()) {
1838: it.next();
1839: }
1840: }
1841:
1842: Node leaf = path.root();
1843:
1844: while_loop: while (it.hasNext()) {
1845: Node n = it.next();
1846: switch (n.nodeId) {
1847: //case NodeTypes.BLOCKNODE:
1848: case NodeTypes.ITERNODE:
1849: leaf = n;
1850: result.add(n);
1851: break;
1852: case NodeTypes.DEFNNODE:
1853: case NodeTypes.DEFSNODE:
1854: case NodeTypes.CLASSNODE:
1855: case NodeTypes.SCLASSNODE:
1856: case NodeTypes.MODULENODE:
1857: leaf = n;
1858: break while_loop;
1859: }
1860: }
1861:
1862: if (includeNested) {
1863: addNodesByType(
1864: leaf,
1865: new int[] { /*NodeTypes.BLOCKNODE,*/NodeTypes.ITERNODE },
1866: result);
1867: }
1868:
1869: return result;
1870: }
1871:
1872: public static String guessName(CompilationInfo info,
1873: OffsetRange lexRange, OffsetRange astRange) {
1874: String guessedName = "";
1875:
1876: // Try to guess the name - see if it's in a method and if so name it after the parameter
1877: IndexedMethod[] methodHolder = new IndexedMethod[1];
1878: @SuppressWarnings("unchecked")
1879: Set<IndexedMethod>[] alternatesHolder = new Set[1];
1880: int[] paramIndexHolder = new int[1];
1881: int[] anchorOffsetHolder = new int[1];
1882: if (!CodeCompleter.computeMethodCall(info, lexRange.getStart(),
1883: astRange.getStart(), methodHolder, paramIndexHolder,
1884: anchorOffsetHolder, alternatesHolder)) {
1885:
1886: return guessedName;
1887: }
1888:
1889: IndexedMethod targetMethod = methodHolder[0];
1890: int index = paramIndexHolder[0];
1891:
1892: List<String> params = targetMethod.getParameters();
1893: if (params == null || params.size() <= index) {
1894: return guessedName;
1895: }
1896:
1897: return params.get(index);
1898: }
1899:
1900: public static Set<String> getUsedFields(RubyIndex index,
1901: AstPath path) {
1902: String fqn = AstUtilities.getFqnName(path);
1903: if (fqn == null || fqn.length() == 0) {
1904: return Collections.emptySet();
1905: }
1906: Set<IndexedField> fields = index.getInheritedFields(fqn, "",
1907: NameKind.PREFIX, false);
1908: Set<String> fieldNames = new HashSet<String>();
1909: for (IndexedField f : fields) {
1910: fieldNames.add(f.getName());
1911: }
1912:
1913: return fieldNames;
1914: }
1915:
1916: public static Set<String> getUsedMethods(RubyIndex index,
1917: AstPath path) {
1918: String fqn = AstUtilities.getFqnName(path);
1919: if (fqn == null || fqn.length() == 0) {
1920: return Collections.emptySet();
1921: }
1922: Set<IndexedMethod> methods = index.getInheritedMethods(fqn, "",
1923: NameKind.PREFIX);
1924: Set<String> methodNames = new HashSet<String>();
1925: for (IndexedMethod m : methods) {
1926: methodNames.add(m.getName());
1927: }
1928:
1929: return methodNames;
1930: }
1931:
1932: /** @todo Implement properly */
1933: public static Set<String> getUsedConstants(RubyIndex index,
1934: AstPath path) {
1935: //String fqn = AstUtilities.getFqnName(path);
1936: //if (fqn == null || fqn.length() == 0) {
1937: return Collections.emptySet();
1938: //}
1939: //Set<IndexedConstant> constants = index.getInheritedConstants(fqn, "", NameKind.PREFIX);
1940: //Set<String> constantNames = new HashSet<String>();
1941: //for (IndexedConstant m : constants) {
1942: // constantNames.add(m.getName());
1943: //}
1944: //
1945: //return constantNames;
1946: }
1947:
1948: public static Set<String> getUsedLocalNames(AstPath path,
1949: Node closest) {
1950: Node method = AstUtilities.findLocalScope(closest, path);
1951: Map<String, Node> variables = new HashMap<String, Node>();
1952: // Add locals
1953: CodeCompleter.addLocals(method, variables);
1954:
1955: List<Node> applicableBlocks = AstUtilities.getApplicableBlocks(
1956: path, false);
1957: for (Node block : applicableBlocks) {
1958: CodeCompleter.addDynamic(block, variables);
1959: }
1960:
1961: return variables.keySet();
1962: }
1963: }
|