package sk.sorien.pimpleplugin.pimple; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiTreeUtil; import com.jetbrains.php.PhpIndex; import com.jetbrains.php.lang.psi.elements.*; import com.jetbrains.php.lang.psi.resolve.types.PhpTypeProvider2; import org.jetbrains.annotations.Nullable; import sk.sorien.pimpleplugin.ProjectComponent; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; /** * @author Stanislav Turza */ public class PimplePhpTypeProvider implements PhpTypeProvider2 { @Override public char getKey() { return 'Š'; } @Nullable @Override public String getType(PsiElement e) { String signature = getTypeForArrayAccess(e); if (signature != null) { return signature; } signature = getTypeForParameterOfAnonymousFunction(e); if (signature != null) { return signature; } return null; } private Signature getChildElementSignature(PsiElement element) { element = PsiTreeUtil.getChildOfAnyType(element, Variable.class, FieldReference.class, ArrayAccessExpression.class); if (element == null) { return null; } Signature signature = new Signature(); if (element instanceof PhpReference) { signature.set(((PhpReference) element).getSignature()); } else if (element instanceof ArrayAccessExpression) { signature.set(getTypeForArrayAccess(element)); } return signature.hasValidClassSignature() ? signature : null; } private String getStringOrSignature(PsiElement element) { if (element instanceof StringLiteralExpression) { return Utils.normalizedString((StringLiteralExpression) element); } else if (element instanceof PhpReference) { return ((PhpReference) element).getSignature(); } return null; } private String getTypeForArrayAccess(PsiElement e) { ArrayAccessExpression arrayAccessExpression; Boolean internalResolve = false; if (e instanceof ArrayAccessExpression) { arrayAccessExpression = (ArrayAccessExpression)e; } else if (e instanceof NewExpression) { ClassReference[] classReferences = PsiTreeUtil.getChildrenOfType(e, ClassReference.class); if (classReferences == null || classReferences.length != 1) { return null; } ArrayAccessExpression[] arrayAccessExpressions = PsiTreeUtil.getChildrenOfType(classReferences[0], ArrayAccessExpression.class); if (arrayAccessExpressions == null || arrayAccessExpressions.length != 1) { return null; } arrayAccessExpression = arrayAccessExpressions[0]; internalResolve = true; } else return null; Signature signature = getChildElementSignature(arrayAccessExpression); if (signature == null) { return null; } ArrayIndex arrayIndex = arrayAccessExpression.getIndex(); if (arrayIndex == null) { return null; } String serviceName = getStringOrSignature(arrayIndex.getValue()); if (serviceName == null) { return null; } return signature.toString() + '[' + (internalResolve ? "@" : "") + serviceName + ']'; } private String getTypeForParameterOfAnonymousFunction(PsiElement e) { if (!(e instanceof com.jetbrains.php.lang.psi.elements.Parameter)) { return null; } if (PsiTreeUtil.getChildOfType(e, com.jetbrains.php.lang.psi.elements.ClassReference.class) != null) { return null; } PsiElement element = e.getParent(); if (!(element instanceof ParameterList)) { return null; } PsiElement[] anonymousFunctionParams = ((ParameterList) element).getParameters(); if (anonymousFunctionParams.length == 0) { return null; } element = element.getParent(); if (!(element instanceof Function)) { return null; } PsiElement closure = element.getParent(); if (!(closure instanceof PhpExpression)) { return null; } String serviceName = null; Signature signature = new Signature(); element = closure.getParent(); if (element instanceof ParameterList) { PsiElement[] methodParams = ((ParameterList) element).getParameters(); if (methodParams.length == 0) { return null; } element = element.getParent(); if (!(element instanceof MethodReference)) { return null; } String methodName = ((MethodReference) element).getName(); if (methodName == null) { return null; } if ((methodName.equals("factory") || methodName.equals("share")) && methodParams.length == 1 && Utils.isParameter(closure, methodParams, 0) && Utils.isParameter(e, anonymousFunctionParams, 0)) { serviceName = null; } else if (methodName.equals("extend") && Utils.isParameter(closure, methodParams, 1)) { if (Utils.isParameter(e, anonymousFunctionParams, 0)) { serviceName = getStringOrSignature(methodParams[0]); if (serviceName == null) { return null; } } } else return null; signature = getChildElementSignature(element); if (signature == null) { return null; } } else if (element instanceof AssignmentExpression) { element = PsiTreeUtil.getChildOfType(element, ArrayAccessExpression.class); if (element == null) { return null; } element = PsiTreeUtil.getChildOfType(element, Variable.class); if (element == null) { return null; } signature.set(((Variable)element).getSignature()); } else return null; return signature.toString() + ( serviceName == null ? "" : '[' + serviceName + ']'); } @Override public Collection<? extends PhpNamedElement> getBySignature(String expression, Project project) { PhpIndex phpIndex = PhpIndex.getInstance(project); Signature signature = new Signature(expression); // try to resolve service type if(ProjectComponent.isEnabled(project) && signature.hasParameter()) { ArrayList<String> parameters = new ArrayList<String>(); if (Utils.findPimpleContainer(phpIndex, expression, parameters)) { return phpIndex.getClassesByFQN(getClassNameFromParameters(phpIndex, project, parameters)); } } // if it's not a service try to get original type Collection<? extends PhpNamedElement> collection = phpIndex.getBySignature(signature.base, null, 0); if (collection.size() == 0) { return Collections.emptySet(); } // original type can be array (#C\ClassType[]) resolve to proper value type PhpNamedElement element = collection.iterator().next(); for (String type : element.getType().getTypes()) { if (type.endsWith("[]")) { Collection<? extends PhpNamedElement> result = phpIndex.getClassesByFQN(type.substring(0, type.length() - 2)); if (result.size() != 0) { return result; } } } return collection; } private String getClassNameFromParameters(PhpIndex phpIndex, Project project, List<String> parameters) { Container container = ContainerResolver.get(project); for (int i = 0; i < parameters.size() - 1; i++) { container = container.getContainers().get(Utils.getResolvedParameter(phpIndex, parameters.get(i))); if (container == null) return null; } String parameter = Utils.getResolvedParameter(phpIndex, parameters.get(parameters.size() - 1)); if (parameter.startsWith("@")) { Parameter param = container.getParameters().get(parameter.substring(1)); return param != null ? param.getValue() : null; } Service service = container.getServices().get(parameter); if (service != null) { return service.getClassName(); } if (container.getContainers().containsKey(parameter)) { return "\\Pimple\\Container"; } return null; } }