package sk.sorien.pimpleplugin.pimple; import com.intellij.codeInsight.completion.InsertHandler; import com.intellij.codeInsight.completion.InsertionContext; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiReference; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtilCore; import com.jetbrains.php.PhpIndex; import com.jetbrains.php.lang.psi.elements.*; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; /** * @author Stanislav Turza */ public class Utils { public static final InsertHandler<LookupElement> CONTAINER_INSERT_HANDLER = new InsertHandler<LookupElement>() { @Override public void handleInsert(InsertionContext context, LookupElement item) { PsiElement element = PsiUtilCore.getElementAtOffset(context.getFile(), context.getStartOffset()); context.getDocument().deleteString(context.getStartOffset() + item.getLookupString().length(), context.getStartOffset() + element.getText().length()); // move caret after ] context.getEditor().getCaretModel().moveCaretRelatively(2, 0, false, false, true); } }; private static Container findContainerForPimpleArrayAccess(ArrayAccessExpression arrayAccessElement, Boolean onlyParentContainers) { PsiElement children; PsiElement element = arrayAccessElement; while ((children = PsiTreeUtil.getChildOfType(element, ArrayAccessExpression.class)) != null) { element = children; } // check if var is pimple container Signature signature = new Signature(); PsiElement signatureElement = PsiTreeUtil.getChildOfAnyType(element, Variable.class, FieldReference.class); if (signatureElement == null) { return null; } if (signatureElement instanceof Variable) { signature.set(((Variable) signatureElement).getSignature()); } if (signatureElement instanceof FieldReference) { signature.set(((FieldReference) signatureElement).getSignature()); } PhpIndex phpIndex = PhpIndex.getInstance(arrayAccessElement.getProject()); ArrayList<String> parameters = new ArrayList<String>(); if (!findPimpleContainer(phpIndex, signature.base, parameters)) { return null; } Container container = ContainerResolver.get(arrayAccessElement.getProject()); // find proper base container from signature for (String parameter : parameters) { container = container.getContainers().get(getResolvedParameter(phpIndex, parameter)); if (container == null) return null; } PsiElement lastElement = onlyParentContainers ? arrayAccessElement : arrayAccessElement.getParent(); // find proper container while (!element.isEquivalentTo(lastElement) ) { ArrayIndex arrayIndex = ((ArrayAccessExpression)element).getIndex(); if (arrayIndex == null) { return null; } PsiElement arrayIndexElement = arrayIndex.getValue(); if (arrayIndexElement == null) { return null; } String containerName; if (arrayIndexElement instanceof StringLiteralExpression) { containerName = ((StringLiteralExpression) arrayIndexElement).getContents(); } else if (arrayIndexElement instanceof MemberReference) { containerName = getResolvedParameter(phpIndex, ((MemberReference) arrayIndexElement).getSignature()); } else return null; container = container.getContainers().get(containerName); if (container == null) { return null; } element = element.getParent(); } return container; } public static Container findContainerForPimpleArrayAccessLiteral(StringLiteralExpression stringLiteralExpression) { PsiElement element = stringLiteralExpression.getParent(); if (!(element instanceof ArrayIndex)) { return null; } element = element.getParent(); if (element instanceof ArrayAccessExpression) { return findContainerForPimpleArrayAccess((ArrayAccessExpression) element, true); } return null; } public static Container findContainerForFirstParameterOfPimpleMethod(StringLiteralExpression stringLiteralExpression) { PsiElement parameterList = stringLiteralExpression.getParent(); if (!(parameterList instanceof ParameterList)) { return null; } PsiElement[] params = ((ParameterList) parameterList).getParameters(); if (!(params.length > 0 && params[0].isEquivalentTo(stringLiteralExpression))) { return null; } PsiElement methodReference = parameterList.getParent(); if (!(methodReference instanceof MethodReference)) { return null; } // we have extend/raw method String methodReferenceName = ((MethodReference) methodReference).getName(); if ((methodReferenceName == null) || !(methodReferenceName.equals("extend") || methodReferenceName.equals("raw"))) { return null; } return findContainerForMethodReference((MethodReference)methodReference); } public static Container findContainerForMethodReference(MethodReference methodReference) { Signature signature = new Signature(); PsiElement signatureElement = PsiTreeUtil.getChildOfAnyType(methodReference, Variable.class, FieldReference.class, ArrayAccessExpression.class); if (signatureElement == null) { return null; } PhpIndex phpIndex = PhpIndex.getInstance(methodReference.getProject()); Container container; if (signatureElement instanceof Variable || signatureElement instanceof FieldReference) { signature.set(((PhpReference) signatureElement).getSignature()); ArrayList<String> parameters = new ArrayList<String>(); if (!Utils.findPimpleContainer(phpIndex, signature.base, parameters)) { return null; } container = ContainerResolver.get(methodReference.getProject()); // find proper base container from signature for (String parameter : parameters) { container = container.getContainers().get(getResolvedParameter(phpIndex, parameter)); if (container == null) return null; } return container; } if (signatureElement instanceof ArrayAccessExpression) { return findContainerForPimpleArrayAccess((ArrayAccessExpression) signatureElement, false); } return null; } public static Boolean isPimpleContainerClass(PhpClass phpClass) { if (phpClass == null) { return false; } else if (isPimpleContainerBaseClass(phpClass.getFQN())) { return true; } else { Integer counter = 0; while ((phpClass = phpClass.getSuperClass()) != null && counter < 5) { if (isPimpleContainerBaseClass(phpClass.getFQN())) { return true; } counter++; } return false; } } private static Boolean isPimpleContainerBaseClass(String className) { return className != null && (className.equals("\\Silex\\Application") || className.equals("\\Pimple\\Container") || className.equals("\\Pimple")); } public static Boolean findPimpleContainer(PhpIndex phpIndex, String expression, ArrayList<String> parameters) { return findPimpleContainer(phpIndex, expression, parameters, 0); } private static Boolean findPimpleContainer(PhpIndex phpIndex, String expression, ArrayList<String> parameters, int depth) { if (++depth > 5) { return false; } Signature signature = new Signature(expression); Collection<? extends PhpNamedElement> collection; if (expression.startsWith("#")) { collection = phpIndex.getBySignature(signature.base, null, 0); } else { collection = phpIndex.getClassesByFQN(signature.base); } if (collection.size() == 0) { return false; } PhpNamedElement element = collection.iterator().next(); if (element instanceof PhpClass) { if (Utils.isPimpleContainerClass((PhpClass) element)) { if (signature.hasParameter()) { parameters.add(signature.parameter); } return true; } } for (String type : element.getType().getTypes()) { if (findPimpleContainer(phpIndex, type, parameters, depth)) { if (signature.hasParameter()) { parameters.add(signature.parameter); } return true; } } return false; } public static String normalizedString(StringLiteralExpression text) { return text.isSingleQuote() ? text.getContents(): text.getContents().replace("\\\\", "\\"); } private static String getStringValue(@Nullable PsiElement psiElement) { return getStringValue(psiElement, 0); } @Nullable private static String getStringValue(@Nullable PsiElement psiElement, int depth) { if (psiElement == null || ++depth > 5) { return null; } if (psiElement instanceof StringLiteralExpression) { String resolvedString = normalizedString((StringLiteralExpression) psiElement); if (StringUtils.isEmpty(resolvedString)) { return null; } return resolvedString; } if (psiElement instanceof Field) { return getStringValue(((Field) psiElement).getDefaultValue(), depth); } if (psiElement instanceof PhpReference) { PsiReference psiReference = psiElement.getReference(); if (psiReference == null) { return null; } PsiElement ref = psiReference.resolve(); if (ref instanceof PhpReference) { return getStringValue(psiElement, depth); } if (ref instanceof Field) { PsiElement resolved = ((Field) ref).getDefaultValue(); if (resolved instanceof StringLiteralExpression) { return normalizedString((StringLiteralExpression) resolved); } } } return null; } public static String getResolvedParameter(PhpIndex phpIndex, String parameter) { // PHP 5.5 class constant: "Class\Foo::class" if(parameter.startsWith("#K#C")) { // PhpStorm9: #K#C\Class\Foo.class if(parameter.endsWith(".class")) { return parameter.substring(5, parameter.length() - 6); } // PhpStorm8: #K#C\Class\Foo. // workaround since signature has empty type if(parameter.endsWith(".")) { return parameter.substring(5, parameter.length() - 1); } } // #P#C\Class\Foo.property // #K#C\Class\Foo.CONST if(parameter.startsWith("#")) { Collection<? extends PhpNamedElement> signTypes = phpIndex.getBySignature(parameter); if(signTypes.size() == 0) { return ""; } parameter = Utils.getStringValue(signTypes.iterator().next()); if(parameter == null) { return ""; } } return parameter; } public static Boolean isParameter(PsiElement element, PsiElement[] parameters, Integer index) { return (parameters.length > index) && (parameters[index].isEquivalentTo(element)); } }