/** * Aptana Studio * Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions). * Please see the license.html included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package com.aptana.ruby.core.ast; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import org.jrubyparser.ast.AliasNode; import org.jrubyparser.ast.ArgsNode; import org.jrubyparser.ast.ArgumentNode; import org.jrubyparser.ast.ArrayNode; import org.jrubyparser.ast.CallNode; import org.jrubyparser.ast.ClassNode; import org.jrubyparser.ast.ClassVarAsgnNode; import org.jrubyparser.ast.ClassVarDeclNode; import org.jrubyparser.ast.ClassVarNode; import org.jrubyparser.ast.Colon2Node; import org.jrubyparser.ast.ConstDeclNode; import org.jrubyparser.ast.ConstNode; import org.jrubyparser.ast.DAsgnNode; import org.jrubyparser.ast.DStrNode; import org.jrubyparser.ast.DefnNode; import org.jrubyparser.ast.DefsNode; import org.jrubyparser.ast.FCallNode; import org.jrubyparser.ast.GlobalAsgnNode; import org.jrubyparser.ast.GlobalVarNode; import org.jrubyparser.ast.HashNode; import org.jrubyparser.ast.InstAsgnNode; import org.jrubyparser.ast.InstVarNode; import org.jrubyparser.ast.IterNode; import org.jrubyparser.ast.ListNode; import org.jrubyparser.ast.LocalAsgnNode; import org.jrubyparser.ast.LocalVarNode; import org.jrubyparser.ast.MethodDefNode; import org.jrubyparser.ast.ModuleNode; import org.jrubyparser.ast.Node; import org.jrubyparser.ast.RootNode; import org.jrubyparser.ast.SClassNode; import org.jrubyparser.ast.SelfNode; import org.jrubyparser.ast.SplatNode; import org.jrubyparser.ast.StrNode; import org.jrubyparser.ast.UnnamedRestArgNode; import org.jrubyparser.ast.VCallNode; import org.jrubyparser.ast.YieldNode; import com.aptana.core.util.ArrayUtil; import com.aptana.core.util.StringUtil; import com.aptana.ruby.core.IRubyMethod; import com.aptana.ruby.core.IRubyMethod.Visibility; import com.aptana.ruby.core.ISourceElementRequestor; import com.aptana.ruby.core.ISourceElementRequestor.FieldInfo; import com.aptana.ruby.core.ISourceElementRequestor.MethodInfo; import com.aptana.ruby.core.ISourceElementRequestor.TypeInfo; /** * @author Chris Williams * @author Michael Xia */ public class SourceElementVisitor extends InOrderVisitor { private static final String CLASS_ATTRIBUTE = "class_attribute"; //$NON-NLS-1$ private static final String CATTR_ACCESSOR = "cattr_accessor"; //$NON-NLS-1$ private static final String CATTR_READER = "cattr_reader"; //$NON-NLS-1$ private static final String CATTR_WRITER = "cattr_writer"; //$NON-NLS-1$ private static final String BELONGS_TO = "belongs_to"; //$NON-NLS-1$ private static final String HAS_ONE = "has_one"; //$NON-NLS-1$ private static final String HAS_MANY = "has_many"; //$NON-NLS-1$ private static final String DELEGATE = "delegate"; //$NON-NLS-1$ private static final String EXTEND = "extend"; //$NON-NLS-1$ private static final String OBJECT = "Object"; //$NON-NLS-1$ private static final String CONSTRUCTOR_NAME = "initialize"; //$NON-NLS-1$ private static final String MODULE = "Module"; //$NON-NLS-1$ private static final String REQUIRE = "require"; //$NON-NLS-1$ private static final String LOAD = "load"; //$NON-NLS-1$ private static final String INCLUDE = "include"; //$NON-NLS-1$ private static final String PUBLIC = "public"; //$NON-NLS-1$ private static final String PROTECTED = "protected"; //$NON-NLS-1$ private static final String PRIVATE = "private"; //$NON-NLS-1$ private static final String MODULE_FUNCTION = "module_function"; //$NON-NLS-1$ private static final String ALIAS = "alias :"; //$NON-NLS-1$ private static final String ALIAS_METHOD = "alias_method"; //$NON-NLS-1$ private static final String ATTR = "attr"; //$NON-NLS-1$ private static final String ATTR_ACCESSOR = "attr_accessor"; //$NON-NLS-1$ private static final String ATTR_READER = "attr_reader"; //$NON-NLS-1$ private static final String ATTR_WRITER = "attr_writer"; //$NON-NLS-1$ private static final String CLASS_EVAL = "class_eval"; //$NON-NLS-1$ private static final String NAMESPACE_DELIMETER = "::"; //$NON-NLS-1$ private ISourceElementRequestor requestor; private List<Visibility> visibilities; private String typeName; private boolean inSingletonClass; private boolean inModuleFunction; /** * Constructor. * * @param requestor * the {@link ISourceElementRequestor} that wants to be notified of the source structure */ public SourceElementVisitor(ISourceElementRequestor requestor) { this.requestor = requestor; visibilities = new ArrayList<Visibility>(); } @Override public Object visitAliasNode(AliasNode iVisited) { Node newNameNode = iVisited.getNewName(); String name = ASTUtils.getName(newNameNode); int nameStart = iVisited.getPosition().getStartOffset() + ALIAS.length() - 1; addAliasMethod(name, iVisited.getPosition().getStartOffset(), iVisited.getPosition().getEndOffset(), nameStart); return super.visitAliasNode(iVisited); } @Override public Object visitArgsNode(ArgsNode iVisited) { ListNode args = iVisited.getPre(); if (args != null) { Node arg; int size = args.size(); for (int i = 0; i < size; ++i) { arg = args.get(i); requestor.enterField(createFieldInfo(arg)); requestor.exitField(getFieldEndOffset(arg)); } } ArgumentNode restArg = iVisited.getRest(); if (restArg != null && !(restArg instanceof UnnamedRestArgNode)) { FieldInfo field = createFieldInfo(restArg); // account for the leading "*" field.declarationStart += 1; field.nameSourceStart += 1; field.nameSourceEnd += 1; requestor.enterField(field); requestor.exitField(getFieldEndOffset(restArg) + 1); } return super.visitArgsNode(iVisited); } @Override public Object visitCallNode(CallNode iVisited) { List<String> arguments = ASTUtils.getArgumentsFromFunctionCall(iVisited); String name = iVisited.getName(); if (name.equals(PUBLIC)) { for (String methodName : arguments) { requestor.acceptMethodVisibilityChange(methodName, convertVisibility(Visibility.PUBLIC)); } } else if (name.equals(PRIVATE)) { for (String methodName : arguments) { requestor.acceptMethodVisibilityChange(methodName, convertVisibility(Visibility.PRIVATE)); } } else if (name.equals(PROTECTED)) { for (String methodName : arguments) { requestor.acceptMethodVisibilityChange(methodName, convertVisibility(Visibility.PROTECTED)); } } else if (name.equals(MODULE_FUNCTION)) { for (String methodName : arguments) { requestor.acceptModuleFunction(methodName); } } else if (name.equals(CLASS_EVAL)) { Node receiver = iVisited.getReceiverNode(); if (receiver instanceof ConstNode || receiver instanceof Colon2Node) { String receiverName = null; if (receiver instanceof Colon2Node) { receiverName = ASTUtils.getFullyQualifiedName((Colon2Node) receiver); } else { receiverName = ASTUtils.getName(receiver); } requestor.acceptMethodReference(name, arguments.size(), iVisited.getPosition().getStartOffset()); pushVisibility(Visibility.PUBLIC); TypeInfo typeInfo = new TypeInfo(); typeInfo.name = receiverName; typeInfo.declarationStart = iVisited.getPosition().getStartOffset(); typeInfo.nameSourceStart = receiver.getPosition().getStartOffset(); typeInfo.nameSourceEnd = receiver.getPosition().getEndOffset() - 1; typeInfo.modules = ArrayUtil.NO_STRINGS; requestor.enterType(typeInfo); Object ins = super.visitCallNode(iVisited); popVisibility(); requestor.exitType(iVisited.getPosition().getEndOffset() - 2); return ins; } } requestor.acceptMethodReference(name, arguments.size(), iVisited.getPosition().getStartOffset()); // TODO try and do some heuristics here to store possible value of DVarNodes inside blocks // i.e. if method is each/map/whatever, assume receiver is a collection/array, and that each item will be the // value passed into the DVarNode return super.visitCallNode(iVisited); } @Override public Object visitClassNode(ClassNode iVisited) { // This resets the visibility when opening or declaring a class to // public pushVisibility(Visibility.PUBLIC); TypeInfo typeInfo = createTypeInfo(iVisited.getCPath()); // count back from name, since preceding comment can incorrectly affect the start position! typeInfo.declarationStart = iVisited.getCPath().getPosition().getStartOffset() - 6; if (!typeInfo.name.equals(OBJECT)) { Node superNode = iVisited.getSuperNode(); if (superNode == null) { typeInfo.superclass = OBJECT; } else { typeInfo.superclass = ASTUtils.getFullyQualifiedName(superNode); } } typeName = typeInfo.name; requestor.enterType(typeInfo); Object ins = super.visitClassNode(iVisited); popVisibility(); requestor.exitType(iVisited.getPosition().getEndOffset() - 2); return ins; } @Override public Object visitClassVarAsgnNode(ClassVarAsgnNode iVisited) { FieldInfo field = createFieldInfo(iVisited); requestor.enterField(field); requestor.exitField(getFieldEndOffset(iVisited)); return super.visitClassVarAsgnNode(iVisited); } @Override public Object visitClassVarDeclNode(ClassVarDeclNode iVisited) { FieldInfo field = createFieldInfo(iVisited); requestor.enterField(field); requestor.exitField(getFieldEndOffset(iVisited)); return super.visitClassVarDeclNode(iVisited); } @Override public Object visitClassVarNode(ClassVarNode iVisited) { requestor.acceptFieldReference(iVisited.getName(), iVisited.getPosition().getStartOffset()); return super.visitClassVarNode(iVisited); } @Override public Object visitConstDeclNode(ConstDeclNode iVisited) { FieldInfo field = createFieldInfo(iVisited); requestor.enterField(field); requestor.exitField(getFieldEndOffset(iVisited)); return super.visitConstDeclNode(iVisited); } @Override public Object visitConstNode(ConstNode iVisited) { requestor.acceptTypeReference(iVisited.getName(), iVisited.getPosition().getStartOffset(), iVisited .getPosition().getEndOffset()); return super.visitConstNode(iVisited); } @Override public Object visitDAsgnNode(DAsgnNode iVisited) { FieldInfo field = createFieldInfo(iVisited); field.isDynamic = true; requestor.enterField(field); requestor.exitField(getFieldEndOffset(iVisited)); return super.visitDAsgnNode(iVisited); } @Override public Object visitDefnNode(DefnNode iVisited) { Visibility visibility = getCurrentVisibility(); MethodInfo methodInfo = createMethodInfo(iVisited); if (methodInfo.name.equals(CONSTRUCTOR_NAME)) { visibility = Visibility.PROTECTED; methodInfo.isConstructor = true; } methodInfo.isClassLevel = inSingletonClass || inModuleFunction; methodInfo.visibility = convertVisibility(visibility); if (methodInfo.isConstructor) { requestor.enterConstructor(methodInfo); } else { requestor.enterMethod(methodInfo); } Object ins = super.visitDefnNode(iVisited); int end = iVisited.getPosition().getEndOffset() - 2; if (methodInfo.isConstructor) { requestor.exitConstructor(end); } else { requestor.exitMethod(end); } return ins; } @Override public Object visitDefsNode(DefsNode iVisited) { MethodInfo methodInfo = createMethodInfo(iVisited); methodInfo.isClassLevel = true; methodInfo.visibility = convertVisibility(getCurrentVisibility()); requestor.enterMethod(methodInfo); Object ins = super.visitDefsNode(iVisited); requestor.exitMethod(iVisited.getPosition().getEndOffset() - 2); return ins; } @Override public Object visitFCallNode(FCallNode iVisited) { List<String> arguments = ASTUtils.getArgumentsFromFunctionCall(iVisited); String name = iVisited.getName(); if (REQUIRE.equals(name) || LOAD.equals(name)) { addImport(iVisited); } // Handle "extend", which acts like "include" but for instances. If this is done in class_eval, treat the same else if (EXTEND.equals(name)) { // Collect included mixins includeModule(iVisited); } else if (INCLUDE.equals(name)) { // Collect included mixins includeModule(iVisited); } else if (PUBLIC.equals(name)) { for (String methodName : arguments) { requestor.acceptMethodVisibilityChange(methodName, convertVisibility(Visibility.PUBLIC)); } } else if (PRIVATE.equals(name)) { for (String methodName : arguments) { requestor.acceptMethodVisibilityChange(methodName, convertVisibility(Visibility.PRIVATE)); } } else if (PROTECTED.equals(name)) { for (String methodName : arguments) { requestor.acceptMethodVisibilityChange(methodName, convertVisibility(Visibility.PROTECTED)); } } else if (MODULE_FUNCTION.equals(name)) { for (String methodName : arguments) { requestor.acceptModuleFunction(methodName); } } else if (ALIAS_METHOD.equals(name)) { String newName = arguments.get(0).substring(1); int nameStart = iVisited.getPosition().getStartOffset() + name.length() + 2; addAliasMethod(newName, iVisited.getPosition().getStartOffset(), iVisited.getPosition().getEndOffset(), nameStart); } // delegate method idiom from Rails 3.x else if (DELEGATE.equals(name)) { addDelegatedMethods(iVisited); } // class attribute method idiom from Rails 3.x else if (CLASS_ATTRIBUTE.equals(name)) { List<Node> nodes = ASTUtils.getArgumentNodesFromFunctionCall(iVisited); int size = arguments.size(); for (int i = 0; i < size; ++i) { // FIXME Check last arg if it's a hash, check :instance_writer value... Node node = nodes.get(i); if (node instanceof HashNode) { continue; } String arg = arguments.get(i); // Add instance query method MethodInfo info = createPublicMethod(dropLeadingColon(arg) + "?", node); //$NON-NLS-1$ requestor.enterMethod(info); requestor.exitMethod(node.getPosition().getEndOffset() - 1); // Singleton query method info = createPublicMethod(dropLeadingColon(arg) + "?", node); //$NON-NLS-1$ info.isClassLevel = true; requestor.enterMethod(info); requestor.exitMethod(node.getPosition().getEndOffset() - 1); // Singleton read/write methods addClassLevelReadMethod(arg, node); addClassLevelWriteMethod(arg, node); // Instance read/write methods addReadMethod(arg, node); // FIXME Check :instance_writer hash value in arg list! if not there, assume true addWriteMethod(arg, node); } } // Rails relationships... else if (HAS_MANY.equals(name)) { addHasManyAssociationMethods(iVisited, arguments.get(0)); } else if (HAS_ONE.equals(name)) { addHasOneAssociationMethods(iVisited, arguments.get(0)); } else if (BELONGS_TO.equals(name)) { // Adds the same methods as has_one... addHasOneAssociationMethods(iVisited, arguments.get(0)); } // class level attributes else if (CATTR_ACCESSOR.equals(name) || CATTR_READER.equals(name) || CATTR_WRITER.equals(name)) { boolean addRead = false; boolean addInstanceRead = true; boolean addWrite = false; boolean addInstanceWrite = true; if (CATTR_ACCESSOR.equals(name)) { addRead = true; addWrite = true; } else if (CATTR_READER.equals(name)) { addRead = true; } else if (CATTR_WRITER.equals(name)) { addWrite = true; } List<Node> nodes = ASTUtils.getArgumentNodesFromFunctionCall(iVisited); // Check if last node is hash, if so look for special key/value pairs to not generate instance methods... Node lastNode = nodes.get(nodes.size() - 1); if (lastNode instanceof HashNode) { HashNode hash = (HashNode) lastNode; ListNode hashValues = hash.getListNode(); for (int x = 0; x < hashValues.size(); x += 2) { Node key = hashValues.get(x); Node value = hashValues.get(x + 1); if ("false".equals(ASTUtils.getStringRepresentation(value))) //$NON-NLS-1$ { if (":instance_writer".equals(ASTUtils.getStringRepresentation(key))) //$NON-NLS-1$ { addInstanceWrite = false; } else if (":instance_reader".equals(ASTUtils.getStringRepresentation(key))) //$NON-NLS-1$ { addInstanceRead = false; } } } } int size = arguments.size(); for (int i = 0; i < size; ++i) { Node node = nodes.get(i); // Skip last hash, if provided... if (node instanceof HashNode) { continue; } String arg = arguments.get(i); if (addRead) { addClassLevelReadMethod(arg, node); if (addInstanceRead) { addReadMethod(arg, node); } } if (addWrite) { addClassLevelWriteMethod(arg, node); if (addInstanceWrite) { addWriteMethod(arg, node); } } addClassVar(arg, node); } } // Instance level attributes else if (ATTR.equals(name) || ATTR_ACCESSOR.equals(name) || ATTR_READER.equals(name) || ATTR_WRITER.equals(name)) { List<Node> nodes = ASTUtils.getArgumentNodesFromFunctionCall(iVisited); boolean addRead = false; boolean addWrite = false; if (ATTR_ACCESSOR.equals(name)) { addRead = true; addWrite = true; } else if (ATTR_READER.equals(name)) { addRead = true; } else if (ATTR_WRITER.equals(name)) { addWrite = true; } if (ATTR.equals(name)) { addRead = true; // Add write if second arg is "true" if (arguments.size() == 2 && "true".equals(arguments.get(1))) //$NON-NLS-1$ { addWrite = true; } } int size = arguments.size(); for (int i = 0; i < size; ++i) { // Only handle first arg for attr if using old second arg boolean call! if (ATTR.equals(name) && addWrite && i > 0) { break; } Node node = nodes.get(i); String arg = arguments.get(i); if (addRead) { addReadMethod(arg, node); } if (addWrite) { addWriteMethod(arg, node); } addInstanceVar(arg, node); } } requestor.acceptMethodReference(name, arguments.size(), iVisited.getPosition().getStartOffset()); return super.visitFCallNode(iVisited); } private void addInstanceVar(String arg, Node node) { FieldInfo field = new FieldInfo(); field.declarationStart = node.getPosition().getStartOffset(); String argName = dropLeadingColon(arg); field.name = "@" + argName; //$NON-NLS-1$ field.nameSourceStart = node.getPosition().getStartOffset(); field.nameSourceEnd = node.getPosition().getEndOffset() - 1; requestor.enterField(field); requestor.exitField(node.getPosition().getEndOffset() - 1); } private MethodInfo createPublicMethod(String methodName, Node node) { MethodInfo info = new MethodInfo(); info.declarationStart = node.getPosition().getStartOffset(); info.name = methodName; info.nameSourceStart = node.getPosition().getStartOffset(); info.nameSourceEnd = node.getPosition().getEndOffset() - 1; info.visibility = IRubyMethod.Visibility.PUBLIC; info.parameterNames = ArrayUtil.NO_STRINGS; return info; } private void addClassVar(String arg, Node node) { FieldInfo field = new FieldInfo(); field.name = "@@" + dropLeadingColon(arg); //$NON-NLS-1$ field.declarationStart = node.getPosition().getStartOffset(); field.nameSourceStart = node.getPosition().getStartOffset(); int end = node.getPosition().getStartOffset() + field.name.length() - 2; // subtract the @@ field.nameSourceEnd = end; requestor.enterField(field); requestor.exitField(end); } @SuppressWarnings("nls") protected void addHasManyAssociationMethods(FCallNode iVisited, String association) { // http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many String firstArg = dropLeadingColon(association); Node argsNode = iVisited.getArgsNode(); Node firstArgNode = argsNode.childNodes().iterator().next(); // FIXME Add "force_reload = false" as param addReadMethod(firstArg, firstArgNode); // FIXME take in "objects" arg as param addReadMethod(firstArg + "=", firstArgNode); // FIXME Singularize the firstArg for these last two methods! // addReadMethod(firstArg + "_ids", firstArgNode); // FIXME Add "ids" as param // addReadMethod(firstArg + "_ids=", firstArgNode); } @SuppressWarnings("nls") protected void addHasOneAssociationMethods(FCallNode iVisited, String association) { // http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_one String firstArg = dropLeadingColon(association); Node argsNode = iVisited.getArgsNode(); Node firstArgNode = argsNode.childNodes().iterator().next(); // FIXME Add "force_reload = false" as param addReadMethod(firstArg, firstArgNode); // FIXME take in "associate" arg as param addReadMethod(firstArg + "=", firstArgNode); // FIXME Add "attributes = {}" as param addReadMethod("build_" + firstArg, firstArgNode); // FIXME Add "attributes = {}" as param addReadMethod("create_" + firstArg, firstArgNode); } private String dropLeadingColon(String association) { if (association.length() > 0 && association.charAt(0) == ':') { return association.substring(1); } return association; } protected void addDelegatedMethods(FCallNode iVisited) { List<Node> nodes = ASTUtils.getArgumentNodesFromFunctionCall(iVisited); String prefix = StringUtil.EMPTY; String to = StringUtil.EMPTY; boolean useToForPrefix = false; Node lastNode = nodes.get(nodes.size() - 1); if (lastNode instanceof HashNode) { HashNode hash = (HashNode) lastNode; ListNode hashValues = hash.getListNode(); for (int x = 0; x < hashValues.size(); x += 2) { Node key = hashValues.get(x); Node value = hashValues.get(x + 1); if (":to".equals(ASTUtils.getStringRepresentation(key))) //$NON-NLS-1$ { to = ASTUtils.getStringRepresentation(value); } else if (":prefix".equals(ASTUtils.getStringRepresentation(key))) //$NON-NLS-1$ { String blah = ASTUtils.getStringRepresentation(value); if ("true".equals(blah)) //$NON-NLS-1$ { useToForPrefix = true; } else { prefix = dropLeadingColon(blah); } } } } if (useToForPrefix) { prefix = dropLeadingColon(to); } if (prefix.length() > 0) { prefix = prefix + "_"; //$NON-NLS-1$ } for (Node arg : nodes) { // skip the :to hash if (arg instanceof HashNode) { continue; } String methodName = prefix + dropLeadingColon(ASTUtils.getStringRepresentation(arg)); int start = arg.getPosition().getStartOffset(); int end = arg.getPosition().getEndOffset() - 1; MethodInfo method = new MethodInfo(); // TODO Use the visibility for the original method that this is aliasing? Visibility visibility = getCurrentVisibility(); method.declarationStart = start; method.isClassLevel = inSingletonClass; method.name = methodName; method.visibility = convertVisibility(visibility); method.nameSourceStart = start; method.nameSourceEnd = end; // TODO Use the parameters of the original method method.parameterNames = ArrayUtil.NO_STRINGS; requestor.enterMethod(method); requestor.exitMethod(end); } } @Override public Object visitGlobalAsgnNode(GlobalAsgnNode iVisited) { FieldInfo field = createFieldInfo(iVisited); requestor.enterField(field); requestor.exitField(getFieldEndOffset(iVisited)); return super.visitGlobalAsgnNode(iVisited); } @Override public Object visitGlobalVarNode(GlobalVarNode iVisited) { requestor.acceptFieldReference(iVisited.getName(), iVisited.getPosition().getStartOffset()); return super.visitGlobalVarNode(iVisited); } @Override public Object visitInstAsgnNode(InstAsgnNode iVisited) { FieldInfo field = createFieldInfo(iVisited); requestor.enterField(field); requestor.exitField(getFieldEndOffset(iVisited)); return super.visitInstAsgnNode(iVisited); } @Override public Object visitInstVarNode(InstVarNode iVisited) { requestor.acceptFieldReference(iVisited.getName(), iVisited.getPosition().getStartOffset()); return super.visitInstVarNode(iVisited); } @Override public Object visitIterNode(IterNode iVisited) { requestor.enterBlock(iVisited.getPosition().getStartOffset(), iVisited.getPosition().getEndOffset() - 1); Object ins = super.visitIterNode(iVisited); requestor.exitBlock(iVisited.getPosition().getEndOffset() - 1); return ins; } @Override public Object visitModuleNode(ModuleNode iVisited) { pushVisibility(Visibility.PUBLIC); TypeInfo typeInfo = createTypeInfo(iVisited.getCPath()); // count back from name, since preceding comment can incorrectly affect the start position! typeInfo.declarationStart = iVisited.getCPath().getPosition().getStartOffset() - 7; typeInfo.superclass = MODULE; typeInfo.isModule = true; typeName = typeInfo.name; requestor.enterType(typeInfo); Object ins = super.visitModuleNode(iVisited); popVisibility(); requestor.exitType(iVisited.getPosition().getEndOffset() - 2); inModuleFunction = false; return ins; } @Override public Object visitLocalAsgnNode(LocalAsgnNode iVisited) { FieldInfo field = createFieldInfo(iVisited); requestor.enterField(field); requestor.exitField(getFieldEndOffset(iVisited)); return super.visitLocalAsgnNode(iVisited); } @Override public Object visitRootNode(RootNode iVisited) { requestor.enterScript(); pushVisibility(Visibility.PUBLIC); Object ins = super.visitRootNode(iVisited); popVisibility(); requestor.exitScript(iVisited.getPosition().getEndOffset()); return ins; } @Override public Object visitSClassNode(SClassNode iVisited) { Node receiver = iVisited.getReceiverNode(); if (receiver instanceof SelfNode) { inSingletonClass = true; } pushVisibility(Visibility.PUBLIC); Object ins = super.visitSClassNode(iVisited); popVisibility(); if (receiver instanceof SelfNode) { inSingletonClass = false; } return ins; } @Override public Object visitVCallNode(VCallNode iVisited) { String functionName = iVisited.getName(); if (functionName.equals(PUBLIC)) { setVisibility(Visibility.PUBLIC); } else if (functionName.equals(PRIVATE)) { setVisibility(Visibility.PRIVATE); } else if (functionName.equals(PROTECTED)) { setVisibility(Visibility.PROTECTED); } else if (functionName.equals(MODULE_FUNCTION)) { inModuleFunction = true; } requestor.acceptMethodReference(functionName, 0, iVisited.getPosition().getStartOffset()); return super.visitVCallNode(iVisited); } @Override public Object visitYieldNode(YieldNode iVisited) { Node argsNode = iVisited.getArgsNode(); if (argsNode instanceof LocalVarNode) { requestor.acceptYield(((LocalVarNode) argsNode).getName()); } else if (argsNode instanceof SelfNode) { String name = null; if (typeName == null) { name = "var"; //$NON-NLS-1$ } else { name = typeName.toLowerCase(); if (name.indexOf(NAMESPACE_DELIMETER) > -1) { name = name.substring(name.lastIndexOf(NAMESPACE_DELIMETER) + 2); } } requestor.acceptYield(name); } return super.visitYieldNode(iVisited); } private void pushVisibility(Visibility visibility) { visibilities.add(visibility); } private void popVisibility() { visibilities.remove(visibilities.size() - 1); } private Visibility getCurrentVisibility() { return visibilities.get(visibilities.size() - 1); } private void setVisibility(Visibility visibility) { popVisibility(); pushVisibility(visibility); } private void addImport(FCallNode iVisited) { Node argsNode = iVisited.getArgsNode(); // TODO What if this is a SplatNode?! if (argsNode instanceof ArrayNode) { ArrayNode node = (ArrayNode) argsNode; String arg = getString(node); if (arg != null) { requestor.acceptImport(arg, iVisited.getPosition().getStartOffset(), iVisited.getPosition() .getEndOffset()); } } } private void addAliasMethod(String name, int start, int end, int nameStart) { MethodInfo method = new MethodInfo(); // TODO Use the visibility for the original method that this is aliasing? Visibility visibility = getCurrentVisibility(); if (name.equals(CONSTRUCTOR_NAME)) { visibility = Visibility.PROTECTED; method.isConstructor = true; } method.declarationStart = start; method.isClassLevel = inSingletonClass; method.name = name; method.visibility = convertVisibility(visibility); method.nameSourceStart = nameStart; method.nameSourceEnd = nameStart + name.length() - 1; // TODO Use the parameters of the original method method.parameterNames = ArrayUtil.NO_STRINGS; requestor.enterMethod(method); requestor.exitMethod(end); } private MethodInfo createReadMethod(String argument, Node node) { argument = dropLeadingColon(argument); MethodInfo info = new MethodInfo(); info.declarationStart = node.getPosition().getStartOffset(); info.name = argument; info.nameSourceStart = node.getPosition().getStartOffset(); info.nameSourceEnd = node.getPosition().getEndOffset() - 1; info.visibility = IRubyMethod.Visibility.PUBLIC; info.parameterNames = ArrayUtil.NO_STRINGS; return info; } private void addReadMethod(String argument, Node node) { requestor.enterMethod(createReadMethod(argument, node)); requestor.exitMethod(node.getPosition().getEndOffset() - 1); } private void addClassLevelReadMethod(String argument, Node node) { MethodInfo info = createReadMethod(argument, node); info.isClassLevel = true; requestor.enterMethod(info); requestor.exitMethod(node.getPosition().getEndOffset() - 1); } private MethodInfo createWriteMethod(String argument, int start, int end) { argument = dropLeadingColon(argument); MethodInfo info = new MethodInfo(); info.declarationStart = start; info.name = argument + "="; //$NON-NLS-1$ info.nameSourceStart = start; info.nameSourceEnd = end; info.visibility = IRubyMethod.Visibility.PUBLIC; info.parameterNames = new String[] { "new_value" }; //$NON-NLS-1$ return info; } private void addWriteMethod(String argument, Node node) { addWriteMethod(argument, node.getPosition().getStartOffset(), node.getPosition().getEndOffset() - 1); } private void addWriteMethod(String argument, int start, int end) { MethodInfo info = createWriteMethod(argument, start, end); requestor.enterMethod(info); requestor.exitMethod(end); } private void addClassLevelWriteMethod(String argument, Node node) { int start = node.getPosition().getStartOffset(); int end = node.getPosition().getEndOffset() - 1; MethodInfo info = createWriteMethod(argument, start, end); info.isClassLevel = true; requestor.enterMethod(info); requestor.exitMethod(end); } private void includeModule(FCallNode iVisited) { List<String> mixins = new LinkedList<String>(); Iterator<Node> iter = null; Node argsNode = iVisited.getArgsNode(); if (argsNode instanceof SplatNode) { iter = ((SplatNode) argsNode).childNodes().iterator(); } else if (argsNode instanceof ArrayNode) { iter = ((ArrayNode) argsNode).childNodes().iterator(); } if (iter != null) { Node node; while (iter.hasNext()) { node = iter.next(); if (node instanceof StrNode) { mixins.add(((StrNode) node).getValue()); } else if (node instanceof ConstNode) { mixins.add(((ConstNode) node).getName()); } else if (node instanceof Colon2Node) { mixins.add(ASTUtils.getFullyQualifiedName((Colon2Node) node)); } else if (node instanceof DStrNode) { Node next = ((DStrNode) node).childNodes().iterator().next(); if (next instanceof StrNode) { mixins.add(((StrNode) next).getValue()); } } // else if (node instanceof DVarNode) // { // FIXME track DAsgnNodes, then infer value, then try and also look at the callnode beforeiternode // and use heuristics? // } } } for (String string : mixins) { requestor.acceptMixin(string); } } private static FieldInfo createFieldInfo(Node iVisited) { FieldInfo field = new FieldInfo(); field.name = ASTUtils.getName(iVisited); field.declarationStart = iVisited.getPosition().getStartOffset(); field.nameSourceStart = iVisited.getPosition().getStartOffset(); field.nameSourceEnd = iVisited.getPosition().getStartOffset() + field.name.length() - 1; return field; } private static TypeInfo createTypeInfo(Node iVisited) { TypeInfo typeInfo = new TypeInfo(); typeInfo.name = ASTUtils.getFullyQualifiedName(iVisited); typeInfo.nameSourceStart = iVisited.getPosition().getStartOffset(); typeInfo.nameSourceEnd = iVisited.getPosition().getEndOffset() - 1; typeInfo.modules = ArrayUtil.NO_STRINGS; return typeInfo; } private static MethodInfo createMethodInfo(MethodDefNode iVisited) { MethodInfo methodInfo = new MethodInfo(); methodInfo.declarationStart = iVisited.getPosition().getStartOffset(); methodInfo.name = iVisited.getName(); methodInfo.nameSourceStart = iVisited.getNameNode().getPosition().getStartOffset(); methodInfo.nameSourceEnd = iVisited.getNameNode().getPosition().getEndOffset() - 1; methodInfo.parameterNames = ASTUtils.getArgs(iVisited.getArgsNode(), iVisited.getScope()); return methodInfo; } private static int getFieldEndOffset(Node iVisited) { return iVisited.getPosition().getEndOffset() - 1; } private static String getString(ArrayNode node) { Node child = node.childNodes().iterator().next(); if (child instanceof DStrNode) { DStrNode dstrNode = (DStrNode) child; child = dstrNode.childNodes().iterator().next(); } if (child instanceof StrNode) { return ((StrNode) child).getValue(); } return null; } private static IRubyMethod.Visibility convertVisibility(Visibility visibility) { if (visibility == Visibility.PUBLIC) { return IRubyMethod.Visibility.PUBLIC; } if (visibility == Visibility.PROTECTED) { return IRubyMethod.Visibility.PROTECTED; } return IRubyMethod.Visibility.PRIVATE; } }