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. } }