/* * Copyright 2000-2013 JetBrains s.r.o. * Copyright 2014-2014 AS3Boyan * Copyright 2014-2014 Elias Ku * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.plugins.haxe.lang.psi; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.plugins.haxe.lang.lexer.HaxeTokenTypes; import com.intellij.plugins.haxe.util.HaxeAbstractForwardUtil; import com.intellij.plugins.haxe.util.HaxeAbstractUtil; import com.intellij.plugins.haxe.util.HaxeResolveUtil; import com.intellij.plugins.haxe.util.UsefulPsiTreeUtil; import com.intellij.psi.*; import com.intellij.psi.impl.source.resolve.ResolveCache; import com.intellij.psi.impl.source.resolve.reference.impl.providers.PackageReferenceSet; import com.intellij.psi.impl.source.resolve.reference.impl.providers.PsiPackageReference; import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; /** * @author: Fedor.Korotkov */ public class HaxeResolver implements ResolveCache.AbstractResolver<HaxeReference, List<? extends PsiElement>> { public static final HaxeResolver INSTANCE = new HaxeResolver(); public static final String IMPORT_EXTENSION = ".hx"; public static ThreadLocal<Boolean> isExtension = new ThreadLocal<Boolean>(); @Override public List<? extends PsiElement> resolve(@NotNull HaxeReference reference, boolean incompleteCode) { isExtension.set(false); final HaxeType type = PsiTreeUtil.getParentOfType(reference, HaxeType.class); final HaxeClass haxeClassInType = HaxeResolveUtil.tryResolveClassByQName(type); if (type != null && haxeClassInType != null) { return toCandidateInfoArray(haxeClassInType.getComponentName()); } // Maybe this is class name final HaxeClass resultClass = HaxeResolveUtil.tryResolveClassByQName(reference); if (resultClass != null) { return toCandidateInfoArray(resultClass.getComponentName()); } // See if it's a source file we're importing... (most likely a convenience library, such as haxe.macro.Tools) final PsiFile importFile = resolveImportFile(reference); if (null != importFile) { return toCandidateInfoArray(importFile); } // Awaiting statement with package references // TODO: optimize, get root element and check class if(PsiTreeUtil.getParentOfType(reference, HaxePackageStatement.class, HaxeImportStatementRegular.class, HaxeImportStatementWithInSupport.class, HaxeImportStatementWithWildcard.class, HaxeUsingStatement.class) != null) { return toCandidateInfoArray(resolvePackage(reference)); } // if not first in chain // foo.bar.baz final HaxeReference leftReference = HaxeResolveUtil.getLeftReference(reference); if (leftReference != null && reference.getParent() instanceof HaxeReference) { List<? extends PsiElement> result = resolveChain(leftReference, reference); if(result != null && !result.isEmpty()) { return result; } return toCandidateInfoArray(resolvePackage(reference)); } // then maybe chain // node(foo.node(bar)).node(baz) final HaxeReference[] childReferences = PsiTreeUtil.getChildrenOfType(reference, HaxeReference.class); if (childReferences != null && childReferences.length == 2) { List<? extends PsiElement> result = resolveChain(childReferences[0], childReferences[1]); if(result != null && !result.isEmpty()) { return result; } return toCandidateInfoArray(resolvePackage(reference)); } if (reference instanceof HaxeSuperExpression) { final HaxeClass haxeClass = PsiTreeUtil.getParentOfType(reference, HaxeClass.class); assert haxeClass != null; if (!haxeClass.getHaxeExtendsList().isEmpty()) { final HaxeExpression superExpression = haxeClass.getHaxeExtendsList().get(0).getReferenceExpression(); final HaxeClass superClass = superExpression instanceof HaxeReference ? ((HaxeReference)superExpression).resolveHaxeClass().getHaxeClass() : null; final HaxeNamedComponent constructor = ((superClass == null) ? null : superClass.findHaxeMethodByName(HaxeTokenTypes.ONEW.toString())); return toCandidateInfoArray(((constructor != null) ? constructor : superClass)); } } final List<PsiElement> result = new ArrayList<PsiElement>(); PsiTreeUtil.treeWalkUp(new ResolveScopeProcessor(result, reference.getCanonicalText()), reference, null, new ResolveState()); if (!result.isEmpty()) { return result; } PsiFile psiFile = reference.getContainingFile(); if (reference instanceof HaxeReferenceExpression) { String text1 = reference.getText(); PsiElement element = null; if (reference.getParent() instanceof HaxeReferenceExpression) { element = reference; while (element.getParent() instanceof HaxeReferenceExpression) { element = element.getParent(); } if (element != null) { element = element.getFirstChild(); PsiElement lastChild = element.getLastChild(); if (element instanceof HaxeReferenceExpression && lastChild instanceof HaxeReferenceExpression && element.getChildren().length == 3) { HaxeClass classByQName = HaxeResolveUtil.findClassByQName(element.getText(), element.getContext()); if (classByQName != null) { HaxeNamedComponent namedSubComponent = HaxeResolveUtil.findNamedSubComponent(classByQName, ((HaxeReferenceExpression)lastChild).getIdentifier().getText()); if (namedSubComponent != null) { result.add(namedSubComponent.getComponentName().getIdentifier()); return result; } } } } } } // try super field List<? extends PsiElement> superElements = resolveByClassAndSymbol(PsiTreeUtil.getParentOfType(reference, HaxeClass.class), reference); if (!superElements.isEmpty()) { return superElements; } if (PsiNameHelper.getInstance(reference.getProject()).isQualifiedName(reference.getText())) { PsiPackageReference packageReference = new PackageReferenceSet(reference.getText(), reference, 0).getLastReference(); PsiElement packageTarget = packageReference != null ? packageReference.resolve() : null; if (packageTarget != null) { return Arrays.asList(packageTarget); } } List<PsiElement> importStatementWithWildcardList = ContainerUtil.findAll(psiFile.getChildren(), new Condition<PsiElement>() { @Override public boolean value(PsiElement element) { return element instanceof HaxeImportStatementWithWildcard && UsefulPsiTreeUtil.isImportStatementWildcardForType(UsefulPsiTreeUtil.getQNameForImportStatementWithWildcardType( (HaxeImportStatementWithWildcard)element)); } }); for (PsiElement importStatementWithWildcard : importStatementWithWildcardList) { HaxeImportStatementWithWildcard importStatementWithWildcard1 = (HaxeImportStatementWithWildcard)importStatementWithWildcard; String qNameForImportStatementWithWildcardType = UsefulPsiTreeUtil.getQNameForImportStatementWithWildcardType(importStatementWithWildcard1); HaxeClass haxeClass = HaxeResolveUtil.findClassByQName(qNameForImportStatementWithWildcardType, importStatementWithWildcard1.getContext()); if (haxeClass != null) { String referenceText = reference.getText(); HaxeNamedComponent namedSubComponent = HaxeResolveUtil.findNamedSubComponent(haxeClass, referenceText); if (namedSubComponent != null && namedSubComponent.isStatic()) { result.add(namedSubComponent.getComponentName().getIdentifier()); return result; } } } List<PsiElement> importStatementWithInSupportList = ContainerUtil.findAll(psiFile.getChildren(), new Condition<PsiElement>() { @Override public boolean value(PsiElement element) { if (element instanceof HaxeImportStatementWithInSupport) { PsiElement resolve; HaxeReferenceExpression referenceExpression = ((HaxeImportStatementWithInSupport)element).getReferenceExpression(); resolve = referenceExpression.resolve(); if (resolve != null) { return resolve instanceof PsiMethod; } } return false; } }); HaxeImportStatementWithInSupport importStatementWithInSupport; for (int i = 0; i < importStatementWithInSupportList.size(); i++) { importStatementWithInSupport = (HaxeImportStatementWithInSupport)importStatementWithInSupportList.get(i); if (reference.getText().equals(importStatementWithInSupport.getIdentifier().getText())) { result.add(importStatementWithInSupport.getReferenceExpression().resolve()); return result; } } return ContainerUtil.emptyList(); } /** * Resolve a chain reference, given two references: the qualifier, and the name. * * @param lefthandExpression - qualifying expression (e.g. "((ref = reference).getProject())") * @param reference - field/method name to resolve. * @return the resolved element, if found; null, otherwise. */ @Nullable private List<? extends PsiElement> resolveChain(HaxeReference lefthandExpression, HaxeReference reference) { final HaxeComponentName componentName = tryResolveHelperClass(lefthandExpression, reference.getText()); if (componentName != null) { return Arrays.asList(componentName); } // Try resolving keywords (super, new), arrays, literals, etc. List<? extends PsiElement> resolvedList = resolveByClassAndSymbol(lefthandExpression.resolveHaxeClass(), reference); return resolvedList; } /** * Resolve a reference into a specific source file. (.hx extension is added to the reference text.) * * @param reference to resolve. * @return a PsiFile if the file was found and is part of the project; null, otherwise. */ @Nullable private PsiFile resolveImportFile(HaxeReference reference) { if (null == reference) return null; final HaxeReference leftReference = HaxeResolveUtil.getLeftReference(reference); final String leftName = leftReference == null ? "" : leftReference.getQualifiedName(); final String ctext = reference.getQualifiedName(); final String packageName = leftName + StringUtil.getPackageName(ctext); final String fileName = StringUtil.getShortName(ctext) + IMPORT_EXTENSION; PsiFile importPsiFile = null; final PsiPackage importPackage = JavaPsiFacade.getInstance(reference.getProject()).findPackage(packageName); if (null != importPackage) { for (PsiDirectory dir : importPackage.getDirectories()) { VirtualFile importDir = dir == null ? null : dir.getVirtualFile(); VirtualFile importVFile = importDir == null ? null : importDir.findChild(fileName); importPsiFile = importVFile == null ? null : PsiManager.getInstance(reference.getProject()).findFile(importVFile); if (importPsiFile != null) { // for addition case-sensetive check because find file is not case-sensetive if(!fileName.equals(importPsiFile.getName())) { importPsiFile = null; } else { break; } } } } return importPsiFile; } private PsiPackage resolvePackage(HaxeReference reference) { final HaxeReference leftReference = HaxeResolveUtil.getLeftReference(reference); String packageName = reference.getText(); if(leftReference != null && reference.getParent() instanceof HaxeReference) { packageName = leftReference.getText() + "." + packageName; } return JavaPsiFacade.getInstance(reference.getProject()).findPackage(packageName); } /** * Test if the leftReference is a class name (either locally or in a super-class), * and if so, find the named field/method declared inside of it. * * @param leftReference - a potential class name. * @param helperName - the field/method to find. * @return the name of the found field/method. null if not found. */ @Nullable private HaxeComponentName tryResolveHelperClass(HaxeReference leftReference, String helperName) { HaxeComponentName componentName = null; HaxeClass leftResultClass = HaxeResolveUtil.tryResolveClassByQName(leftReference); if (leftResultClass != null) { // helper reference via class com.bar.FooClass.HelperClass final HaxeClass componentDeclaration = HaxeResolveUtil.findComponentDeclaration(leftResultClass.getContainingFile(), helperName); componentName = componentDeclaration == null ? null : componentDeclaration.getComponentName(); } else { // try to find component at abstract forwarding underlying class leftResultClass = leftReference.resolveHaxeClass().getHaxeClass(); final Boolean isAbstractForward = HaxeAbstractForwardUtil.isAbstractForward(leftResultClass); if (isAbstractForward) { final List<HaxeNamedComponent> forwardingHaxeNamedComponents = HaxeAbstractForwardUtil.findAbstractForwardingNamedSubComponents(leftResultClass); if (forwardingHaxeNamedComponents != null) { for (HaxeNamedComponent namedComponent : forwardingHaxeNamedComponents) { final HaxeComponentName forwardingComponentName = namedComponent.getComponentName(); if (forwardingComponentName != null && forwardingComponentName.getText().equals(helperName)) { componentName = forwardingComponentName; break; } } } } } return componentName; } private static List<? extends PsiElement> toCandidateInfoArray(@Nullable PsiElement element) { return element == null ? Collections.<PsiElement>emptyList() : Arrays.asList(element); } private static List<? extends PsiElement> resolveByClassAndSymbol(@Nullable HaxeClassResolveResult resolveResult, @NotNull HaxeReference reference) { return resolveResult == null ? Collections.<PsiElement>emptyList() : resolveByClassAndSymbol(resolveResult.getHaxeClass(), reference); } private static List<? extends PsiElement> resolveByClassAndSymbol(@Nullable HaxeClass leftClass, @NotNull HaxeReference reference) { HaxeNamedComponent namedSubComponent = HaxeResolveUtil.findNamedSubComponent(leftClass, reference.getText()); HaxeComponentName componentName = namedSubComponent == null ? null : namedSubComponent.getComponentName(); if (componentName != null) { return toCandidateInfoArray(componentName); } // if class is abstract try find in forwards final boolean isAbstractForward = HaxeAbstractForwardUtil.isAbstractForward(leftClass); if (isAbstractForward) { final HaxeClass underlyingClass = HaxeAbstractUtil.getAbstractUnderlyingClass(leftClass); if (underlyingClass != null) { namedSubComponent = HaxeResolveUtil.findNamedSubComponent(underlyingClass, reference.getText()); componentName = namedSubComponent == null ? null : namedSubComponent.getComponentName(); if (componentName != null) { return toCandidateInfoArray(componentName); } } } // try find using for (HaxeClass haxeClass : HaxeResolveUtil.findUsingClasses(reference.getContainingFile())) { final HaxeNamedComponent haxeNamedComponent = HaxeResolveUtil.findNamedSubComponent(haxeClass, reference.getCanonicalText()); if (haxeNamedComponent != null && haxeNamedComponent.isPublic() && haxeNamedComponent.isStatic() && haxeNamedComponent.getComponentName() != null) { final HaxeClassResolveResult resolveResult = HaxeResolveUtil.findFirstParameterClass(haxeNamedComponent); final HaxeClass resolvedClass = resolveResult.getHaxeClass(); final HashSet<HaxeClass> baseClassesSet = leftClass != null ? HaxeResolveUtil.getBaseClassesSet(leftClass) : null; final boolean needToAdd = resolvedClass == null || resolvedClass == leftClass || (baseClassesSet != null && baseClassesSet.contains(resolvedClass)); if (needToAdd) { isExtension.set(true); return toCandidateInfoArray(haxeNamedComponent.getComponentName()); } } } return Collections.emptyList(); } private class ResolveScopeProcessor implements PsiScopeProcessor { private final List<PsiElement> result; final String name; private ResolveScopeProcessor(List<PsiElement> result, String name) { this.result = result; this.name = name; } @Override public boolean execute(@NotNull PsiElement element, ResolveState state) { if (element instanceof HaxeNamedComponent) { final HaxeComponentName componentName = ((HaxeNamedComponent)element).getComponentName(); if (componentName != null && name.equals(componentName.getText())) { result.add(componentName); return false; } } return true; } @Override public <T> T getHint(@NotNull Key<T> hintKey) { return null; } @Override public void handleEvent(Event event, @Nullable Object associated) { } } }