package com.chrisfolger.needsmoredojo.intellij.reference;
import com.chrisfolger.needsmoredojo.core.amd.filesystem.DojoModuleFileResolver;
import com.chrisfolger.needsmoredojo.core.amd.psi.AMDPsiUtil;
import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandler;
import com.intellij.lang.Language;
import com.intellij.lang.javascript.psi.JSReferenceExpression;
import com.intellij.lang.javascript.psi.JSThisExpression;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
/**
* This class looks up methods of off dojo modules. If you have domConstruct.empty for example, it will determine
* that domConstruct references dojo/dom-construct and search for an "empty" method in that file to create a
* reference for it.
*/
public class MethodGotoDeclarationHandler extends DojoDeclarationHandler implements GotoDeclarationHandler
{
@Nullable
@Override
public PsiElement[] getGotoDeclarationTargets(PsiElement psiElement, int i, Editor editor)
{
if(psiElement == null || !psiElement.getLanguage().equals(Language.findLanguageByID("JavaScript")))
{
return new PsiElement[0];
}
if(!isEnabled(psiElement.getProject()))
{
return new PsiElement[0];
}
if(!(psiElement.getParent() != null && psiElement.getParent() instanceof JSReferenceExpression))
{
return new PsiElement[0];
}
if(psiElement.getPrevSibling() == null || psiElement.getPrevSibling().getPrevSibling() == null)
{
return new PsiElement[0];
}
if(!(psiElement.getPrevSibling().getPrevSibling() instanceof JSReferenceExpression) && !(psiElement.getPrevSibling().getPrevSibling() instanceof JSThisExpression))
{
return new PsiElement[0];
}
PsiElement prevPrevSibling = psiElement.getPrevSibling().getPrevSibling();
/**
* this case occurs when the user is trying to navigate ... declaration on a method off of this. At this point
* we can search its base classes for the method in question.
*/
if(prevPrevSibling instanceof JSThisExpression)
{
if(psiElement.getText().equals("inherited"))
{
return new PsiElement[0];
}
// if the method is defined in this file, return it instead of searching other files. Rely on this.inherited
// references for that behavior.
PsiElement resolvedInThisFile = AMDPsiUtil.fileHasMethod(psiElement.getContainingFile(), psiElement.getText(), false);
if(resolvedInThisFile != null)
{
return new PsiElement[] { resolvedInThisFile };
}
else
{
Set<PsiElement> resolvedMethods = AMDPsiUtil.resolveInheritedMethod(psiElement.getContainingFile(), psiElement.getProject(), psiElement.getText(), 0);
return resolvedMethods.toArray(new PsiElement[resolvedMethods.size()]);
}
}
/**
* the second case is when the user is referencing a method off of another dojo module. In this case, we
* use a less accurate approach because the module in question might not be a standard module that defines
* its methods in a nice object literal.
*/
else
{
JSReferenceExpression referencedDefine = (JSReferenceExpression) prevPrevSibling;
PsiElement resolvedDefine = AMDPsiUtil.resolveReferencedDefine(referencedDefine);
if(resolvedDefine == null)
{
return new PsiElement[0];
}
DojoModuleFileResolver resolver = new DojoModuleFileResolver();
PsiFile resolvedFile = resolver.resolveReferencedFile(psiElement.getProject(), resolvedDefine);
if(resolvedFile == null)
{
return new PsiElement[0];
}
String methodName = psiElement.getText();
PsiElement method = AMDPsiUtil.fileHasMethod(resolvedFile, methodName, true);
if(method != null)
{
// found it!
return new PsiElement[] { method };
}
else
{
// didn't find it!
return new PsiElement[0];
}
}
}
@Nullable
@Override
public String getActionText(DataContext dataContext)
{
return null; //To change body of implemented methods use File | Settings | File Templates.
}
}