package com.aptana.rdt.internal.parser.warnings;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.jruby.ast.ClassNode;
import org.jruby.ast.ClassVarAsgnNode;
import org.jruby.ast.ClassVarDeclNode;
import org.jruby.ast.ClassVarNode;
import org.jruby.ast.ConstDeclNode;
import org.jruby.ast.DVarNode;
import org.jruby.ast.DefnNode;
import org.jruby.ast.DefsNode;
import org.jruby.ast.InstAsgnNode;
import org.jruby.ast.InstVarNode;
import org.jruby.ast.LocalAsgnNode;
import org.jruby.ast.MethodDefNode;
import org.jruby.ast.ModuleNode;
import org.jruby.ast.Node;
import org.jruby.ast.RootNode;
import org.rubypeople.rdt.core.parser.warnings.RubyLintVisitor;
import org.rubypeople.rdt.internal.core.util.ASTUtil;
import com.aptana.rdt.AptanaRDTPlugin;
import com.aptana.rdt.IProblem;
public class UncommunicativeName extends RubyLintVisitor
{
private static List<Pattern> reject = new ArrayList<Pattern>();
private static List<String> accept = new ArrayList<String>();
static
{
reject.add(Pattern.compile("^.[0-9]*$"));
accept.add("Inline::C");
}
private List<Node> scopes; // stack of nodes that represent scopes for variables
private Map<Node, Map<String, Node>> scopedVariables; // Map from scopes to the variable names and nodes in them
public UncommunicativeName(String src)
{
super(AptanaRDTPlugin.getDefault().getOptions(), src);
}
@Override
protected String getOptionKey()
{
return AptanaRDTPlugin.COMPILER_PB_UNCOMMUNICATIVE_NAME;
}
@Override
protected int getProblemID()
{
return IProblem.UncommunicativeName;
}
@Override
public Object visitRootNode(RootNode visited)
{
// init
scopes = new ArrayList<Node>();
scopedVariables = new HashMap<Node, Map<String, Node>>();
pushScope(visited);
return super.visitRootNode(visited);
}
@Override
public void exitRootNode(RootNode visited)
{
popScope();
// clean up
scopes = null;
scopedVariables = null;
super.exitRootNode(visited);
}
private void popScope()
{
Node scopingNode = scopes.remove(0);
Map<String, Node> varsInScope = scopedVariables.get(scopingNode);
for (Map.Entry<String, Node> entry : varsInScope.entrySet())
{
considerVariable(entry.getKey(), entry.getValue());
}
}
private void pushScope(Node visited)
{
scopes.add(visited);
scopedVariables.put(visited, new HashMap<String, Node>());
}
@Override
public Object visitModuleNode(ModuleNode visited)
{
considerName(visited);
pushScope(visited);
return super.visitModuleNode(visited);
}
@Override
public Object visitClassNode(ClassNode visited)
{
considerName(visited);
pushScope(visited);
return super.visitClassNode(visited);
}
@Override
public Object visitDefnNode(DefnNode visited)
{
considerName(visited);
return super.visitDefnNode(visited);
}
@Override
public Object visitDefsNode(DefsNode visited)
{
considerName(visited);
return super.visitDefsNode(visited);
}
private void considerName(Node node)
{
String fullName = fullyQualifiedName(node);
if (accept.contains(fullName))
return;
String shortName = ASTUtil.getNameReflectively(node);
if (isBadName(shortName))
{
createProblem(node.getPosition(), "Uncommunicative name: " + fullName);
}
}
private String fullyQualifiedName(Node node)
{
if (node instanceof MethodDefNode)
return ((MethodDefNode) node).getName();
return ASTUtil.getFullyQualifiedTypeName(getRootNode(), node);
}
private Node getRootNode()
{
if (scopes == null || scopes.isEmpty())
return null;
return scopes.get(0);
}
private boolean isBadName(String var)
{
if (var.equals("*") || accept.contains(var))
return false;
for (Pattern p : reject)
{
if (p.matcher(var).find())
return true;
}
return false;
}
@Override
public Object visitLocalAsgnNode(LocalAsgnNode visited)
{
addVariable(visited.getName(), visited);
return super.visitLocalAsgnNode(visited);
}
@Override
public Object visitConstDeclNode(ConstDeclNode visited)
{
addVariable(visited.getName(), visited);
return super.visitConstDeclNode(visited);
}
@Override
public Object visitClassVarDeclNode(ClassVarDeclNode visited)
{
addVariable(visited.getName(), visited);
return super.visitClassVarDeclNode(visited);
}
@Override
public Object visitClassVarAsgnNode(ClassVarAsgnNode visited)
{
addVariable(visited.getName(), visited);
return super.visitClassVarAsgnNode(visited);
}
private void addVariable(String name, Node visited)
{
Node enclosingScope = null;
// FIXME Handle pushing instance, constants and class vars up to type level scope!
if (visited instanceof ClassVarNode || visited instanceof ClassVarDeclNode
|| visited instanceof ClassVarAsgnNode || visited instanceof InstVarNode
|| visited instanceof InstAsgnNode || visited instanceof ConstDeclNode)
{
for (int i = scopes.size() - 1; i >= 0; i--)
{
enclosingScope = scopes.get(i);
if (enclosingScope instanceof ClassNode || enclosingScope instanceof RootNode
|| enclosingScope instanceof ModuleNode)
break;
}
}
else
{
enclosingScope = scopes.get(scopes.size() - 1);
}
Map<String, Node> vars = scopedVariables.get(enclosingScope);
if (vars == null)
{
vars = new HashMap<String, Node>();
scopedVariables.put(enclosingScope, vars);
}
if (vars.containsKey(name))
return;
vars.put(name, visited);
}
@Override
public Object visitInstAsgnNode(InstAsgnNode visited)
{
addVariable(visited.getName(), visited);
return super.visitInstAsgnNode(visited);
}
@Override
public Object visitDVarNode(DVarNode visited)
{
considerVariable(visited.getName(), visited);
return super.visitDVarNode(visited);
}
private void considerVariable(String name, Node node)
{
name = effectiveName(name);
if (isBadName(name))
{
createProblem(node.getPosition(), "Variable has an uncommunicative name: " + name);
}
}
@Override
public void exitClassNode(ClassNode visited)
{
popScope();
super.exitClassNode(visited);
}
private String effectiveName(String name)
{
if (name.startsWith("@@"))
return name.substring(2);
if (name.startsWith("@"))
return name.substring(1);
return name;
}
}