package fr.adrienbrault.idea.symfony2plugin.util; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.jetbrains.php.PhpIndex; import com.jetbrains.php.lang.psi.elements.*; import com.jetbrains.php.lang.psi.resolve.types.PhpType; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; /** * @author Daniel Espendiller <daniel@espendiller.net> */ public class PhpTypeProviderUtil { /** * Deprecated for external plugins */ @Deprecated public static String getReferenceSignature(MethodReference methodReference, char trimKey) { return getReferenceSignatureByFirstParameter(methodReference, trimKey); } /** * Deprecated for external plugins */ @Deprecated public static String getReferenceSignature(MethodReference methodReference, char trimKey, int equalParameterCount) { return getReferenceSignatureByFirstParameter(methodReference, trimKey); } /** * Creates a signature for PhpType implementation which must be resolved inside 'getBySignature' * * eg. foo(MyClass::class) => "#F\\foo|#K#C\\Foo.class" * * foo($this->foo), foo('foobar') */ @Nullable public static String getReferenceSignatureByFirstParameter(@NotNull FunctionReference functionReference, char trimKey) { String refSignature = functionReference.getSignature(); if(StringUtil.isEmpty(refSignature)) { return null; } PsiElement[] parameters = functionReference.getParameters(); if(parameters.length == 0) { return null; } PsiElement parameter = parameters[0]; // we already have a string value if ((parameter instanceof StringLiteralExpression)) { String param = ((StringLiteralExpression)parameter).getContents(); if (StringUtil.isNotEmpty(param)) { return refSignature + trimKey + param; } return null; } // whitelist here; we can also provide some more but think of performance // Service::NAME, $this->name and Entity::CLASS; if (parameter instanceof PhpReference && (parameter instanceof ClassConstantReference || parameter instanceof FieldReference)) { String signature = ((PhpReference) parameter).getSignature(); if (StringUtil.isNotEmpty(signature)) { return refSignature + trimKey + signature; } } return null; } /** * we can also pipe php references signatures and resolve them here * overwrite parameter to get string value */ @Nullable public static String getResolvedParameter(@NotNull PhpIndex phpIndex, @NotNull 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 StringUtils.stripStart(parameter.substring(4, parameter.length() - 6), "\\"); } } // #K#C\Class\Foo.property // #K#C\Class\Foo.CONST if(parameter.startsWith("#")) { // get psi element from signature Collection<? extends PhpNamedElement> signTypes = phpIndex.getBySignature(parameter, null, 0); if(signTypes.size() == 0) { return null; } // get string value parameter = PhpElementsUtil.getStringValue(signTypes.iterator().next()); if(parameter == null) { return null; } } return parameter; } /** * Mostly factory pattern doest not return a type, but eg in Doctrine getRepository we need to fallback to an interface */ @NotNull public static Collection<? extends PhpNamedElement> mergeSignatureResults(@NotNull Collection<? extends PhpNamedElement> phpNamedElements, final @NotNull PhpNamedElement phpNamed) { Collection<PhpNamedElement> result = new ArrayList<>(); result.add(phpNamed); // invalidate state; we dont know what to do if(!(phpNamed instanceof PhpClass)) { result.addAll(phpNamedElements); return result; } for (PhpNamedElement phpNamedElement : phpNamedElements) { if(phpNamedElement == null) { continue; } // nothing found if(!(phpNamedElement instanceof Method)) { result.add(phpNamedElement); continue; } // type are equal if(isPhpTypeEqual(phpNamedElement.getType(), (PhpClass) phpNamed)) { continue; } result.add(phpNamedElement); } return result; } private static boolean isPhpTypeEqual(@NotNull PhpType phpType, @NotNull PhpClass phpClass) { for (String s : phpType.getTypes()) { if(PhpElementsUtil.isInstanceOf(phpClass, s)) { return true; } } return false; } /** * We can have multiple types inside a TypeProvider; split them on "|" so that we dont get empty types * * #M#x#M#C\FooBar.get?doctrine.odm.mongodb.document_manager.getRepository| * #M#x#M#C\FooBar.get?doctrine.odm.mongodb.document_manager.getRepository */ @NotNull public static Collection<? extends PhpNamedElement> getTypeSignature(@NotNull PhpIndex phpIndex, @NotNull String signature) { if (!signature.contains("|")) { return phpIndex.getBySignature(signature, null, 0); } Collection<PhpNamedElement> elements = new ArrayList<>(); for (String s : signature.split("\\|")) { elements.addAll(phpIndex.getBySignature(s, null, 0)); } return elements; } }