package fr.adrienbrault.idea.symfony2plugin.form; import com.intellij.patterns.PlatformPatterns; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.ProcessingContext; import com.jetbrains.php.lang.parser.PhpElementTypes; import com.jetbrains.php.lang.psi.elements.*; import fr.adrienbrault.idea.symfony2plugin.Symfony2InterfacesUtil; import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent; import fr.adrienbrault.idea.symfony2plugin.doctrine.EntityReference; import fr.adrienbrault.idea.symfony2plugin.form.util.FormFieldNameReference; import fr.adrienbrault.idea.symfony2plugin.form.util.FormOptionsUtil; import fr.adrienbrault.idea.symfony2plugin.form.util.FormUtil; import fr.adrienbrault.idea.symfony2plugin.translation.TranslationReference; import fr.adrienbrault.idea.symfony2plugin.util.MethodMatcher; import fr.adrienbrault.idea.symfony2plugin.util.ParameterBag; import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil; import fr.adrienbrault.idea.symfony2plugin.util.PsiElementUtils; import org.jetbrains.annotations.NotNull; /** * @author Daniel Espendiller <daniel@espendiller.net> */ public class FormTypeReferenceContributor extends PsiReferenceContributor { @Override public void registerReferenceProviders(PsiReferenceRegistrar psiReferenceRegistrar) { psiReferenceRegistrar.registerReferenceProvider( PlatformPatterns.psiElement(StringLiteralExpression.class) .withParent( PlatformPatterns.psiElement(PhpElementTypes.ARRAY_VALUE).inside(ParameterList.class) ), new PsiReferenceProvider() { @NotNull @Override public PsiReference[] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext processingContext) { if (!Symfony2ProjectComponent.isEnabled(psiElement)) { return new PsiReference[0]; } ParameterList parameterList = PsiTreeUtil.getParentOfType(psiElement, ParameterList.class); if (parameterList == null) { return new PsiReference[0]; } if(!(parameterList.getContext() instanceof MethodReference)) { return new PsiReference[0]; } MethodReference method = (MethodReference) parameterList.getContext(); Symfony2InterfacesUtil interfacesUtil = new Symfony2InterfacesUtil(); if (!interfacesUtil.isFormBuilderFormTypeCall(method)) { return new PsiReference[0]; } ArrayHashElement arrayHash = PsiTreeUtil.getParentOfType(psiElement, ArrayHashElement.class); if(arrayHash != null && arrayHash.getKey() instanceof StringLiteralExpression) { ArrayCreationExpression arrayCreation = PsiTreeUtil.getParentOfType(psiElement, ArrayCreationExpression.class); if(arrayCreation == null) { return new PsiReference[0]; } // old 3 parameter hold valid array data ParameterBag currentIndex = PsiElementUtils.getCurrentParameterIndex(arrayCreation); if(currentIndex == null || currentIndex.getIndex() != 2) { return new PsiReference[0]; } StringLiteralExpression key = (StringLiteralExpression) arrayHash.getKey(); if(key == null) { return new PsiReference[0]; } String keyString = key.getContents(); // @TODO: how to handle custom bundle fields like help_block if(keyString.equals("label") || keyString.equals("help_block") || keyString.equals("help_inline") || keyString.equals("placeholder")) { // translation_domain in current array block String translationDomain = FormOptionsUtil.getTranslationFromScope(arrayCreation); if(translationDomain == null) { translationDomain = "messages"; } return new PsiReference[]{ new TranslationReference((StringLiteralExpression) psiElement, translationDomain) }; } if(keyString.equals("class")) { return new PsiReference[]{ new EntityReference((StringLiteralExpression) psiElement, true)}; } } return new PsiReference[0]; } } ); /** * support form type alias references; * we dont use completion here, form type resolving depends on container, which is slow stuff */ psiReferenceRegistrar.registerReferenceProvider( PlatformPatterns.psiElement(StringLiteralExpression.class), new PsiReferenceProvider() { @NotNull @Override public PsiReference[] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext processingContext) { // match add('foo', 'type name') MethodMatcher.MethodMatchParameter methodMatchParameter = new MethodMatcher.StringParameterMatcher(psiElement, 1) .withSignature(Symfony2InterfacesUtil.getFormBuilderInterface()) .match(); if(methodMatchParameter == null) { methodMatchParameter = new MethodMatcher.StringParameterMatcher(psiElement, 1) .withSignature("\\Symfony\\Component\\Form\\FormFactoryInterface", "createNamedBuilder") .withSignature("\\Symfony\\Component\\Form\\FormFactoryInterface", "createNamed") .match(); } if(methodMatchParameter == null) { return new PsiReference[0]; } return new PsiReference[]{ new FormTypeReferenceRef((StringLiteralExpression) psiElement) }; } } ); // FormBuilderInterface::add('underscore_method') psiReferenceRegistrar.registerReferenceProvider( PlatformPatterns.psiElement(StringLiteralExpression.class), new PsiReferenceProvider() { @NotNull @Override public PsiReference[] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext processingContext) { if (!Symfony2ProjectComponent.isEnabled(psiElement) || !(psiElement.getContext() instanceof ParameterList)) { return new PsiReference[0]; } ParameterList parameterList = (ParameterList) psiElement.getContext(); if (parameterList == null || !(parameterList.getContext() instanceof MethodReference)) { return new PsiReference[0]; } MethodReference method = (MethodReference) parameterList.getContext(); if (method == null || !new Symfony2InterfacesUtil().isFormBuilderFormTypeCall(method)) { return new PsiReference[0]; } // only use second parameter ParameterBag currentIndex = PsiElementUtils.getCurrentParameterIndex(psiElement); if (currentIndex == null || currentIndex.getIndex() != 0) { return new PsiReference[0]; } PsiElement className = PhpElementsUtil.getArrayKeyValueInsideSignaturePsi(psiElement, FormOptionsUtil.FORM_OPTION_METHODS, "setDefaults", "data_class"); if(className == null) { return new PsiReference[0]; } PhpClass phpClass = PhpElementsUtil.resolvePhpClassOnPsiElement(className); if (phpClass == null) { return new PsiReference[0]; } return new PsiReference[]{new FormUnderscoreMethodReference((StringLiteralExpression) psiElement, phpClass)}; } } ); // TODO: migrate to FormGotoCompletionRegistrar for better performance as lazy condition // $resolver->setDefaults(['csrf_protection<caret>' => 'foobar']); psiReferenceRegistrar.registerReferenceProvider( PlatformPatterns.psiElement(StringLiteralExpression.class), new PsiReferenceProvider() { @NotNull @Override public PsiReference[] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext processingContext) { if (!Symfony2ProjectComponent.isEnabled(psiElement)) { return new PsiReference[0]; } ParameterList parameterList = PsiTreeUtil.getParentOfType(psiElement, ParameterList.class); if (parameterList == null) { return new PsiReference[0]; } if(!(parameterList.getContext() instanceof MethodReference)) { return new PsiReference[0]; } MethodReference method = (MethodReference) parameterList.getContext(); // Symfony 2 and 3 BC fix if(!(PhpElementsUtil.isMethodReferenceInstanceOf(method, "\\Symfony\\Component\\OptionsResolver\\OptionsResolverInterface", "setDefaults") || PhpElementsUtil.isMethodReferenceInstanceOf(method, "\\Symfony\\Component\\OptionsResolver\\OptionsResolver", "setDefaults")) ) { return new PsiReference[0]; } // only use second parameter ArrayCreationExpression arrayHash = PsiTreeUtil.getParentOfType(psiElement, ArrayCreationExpression.class); if(arrayHash == null) { return new PsiReference[0]; } ParameterBag currentIndex = PsiElementUtils.getCurrentParameterIndex(arrayHash); if(currentIndex == null || currentIndex.getIndex() != 0) { return new PsiReference[0]; } if(PhpElementsUtil.getCompletableArrayCreationElement(psiElement) != null) { return new PsiReference[]{ new FormExtensionKeyReference((StringLiteralExpression) psiElement), new FormDefaultOptionsKeyReference((StringLiteralExpression) psiElement, "form"), new FormDefaultOptionsKeyReference((StringLiteralExpression) psiElement, "Symfony\\Component\\Form\\Extension\\Core\\Type\\FormType"), }; } return new PsiReference[0]; } } ); // $form->get('field') psiReferenceRegistrar.registerReferenceProvider( PlatformPatterns.psiElement(StringLiteralExpression.class), new PsiReferenceProvider() { @NotNull @Override public PsiReference[] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext processingContext) { MethodMatcher.MethodMatchParameter methodMatchParameter = new MethodMatcher.StringParameterMatcher(psiElement, 0) .withSignature("\\Symfony\\Component\\Form\\FormInterface", "get") .withSignature("\\Symfony\\Component\\Form\\FormInterface", "has") .match(); if(methodMatchParameter == null) { return new PsiReference[0]; } Method method = FormUtil.resolveFormGetterCallMethod(methodMatchParameter.getMethodReference()); if(method == null) { return new PsiReference[0]; } return new PsiReference[] { new FormFieldNameReference((StringLiteralExpression) psiElement, method) }; } } ); /** * $options * public function buildForm(FormBuilderInterface $builder, array $options) { * $options['foo'] * } * * public function setDefaultOptions(OptionsResolverInterface $resolver) { * $resolver->setDefaults(array( * 'foo' => 'bar', * )); } */ psiReferenceRegistrar.registerReferenceProvider( PlatformPatterns.psiElement(StringLiteralExpression.class), new PsiReferenceProvider() { @NotNull @Override public PsiReference[] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext processingContext) { if (!Symfony2ProjectComponent.isEnabled(psiElement)) { return new PsiReference[0]; } PsiElement context = psiElement.getContext(); if(!(context instanceof ArrayIndex)) { return new PsiReference[0]; } PhpPsiElement variable = ((ArrayIndex) context).getPrevPsiSibling(); if(!(variable instanceof Variable)) { return new PsiReference[0]; } PsiElement parameter = ((Variable) variable).resolve(); if(!(parameter instanceof Parameter)) { return new PsiReference[0]; } // all options keys are at parameter = 1 by now ParameterBag parameterBag = PsiElementUtils.getCurrentParameterIndex((Parameter) parameter); if(parameterBag == null || parameterBag.getIndex() != 1) { return new PsiReference[0]; } Method method = PsiTreeUtil.getParentOfType(parameter, Method.class); if(method == null) { return new PsiReference[0]; } Symfony2InterfacesUtil symfony2InterfacesUtil = new Symfony2InterfacesUtil(); if(!symfony2InterfacesUtil.isCallTo(method, "\\Symfony\\Component\\Form\\FormTypeInterface", "buildForm") && !symfony2InterfacesUtil.isCallTo(method, "\\Symfony\\Component\\Form\\FormTypeInterface", "buildView") && !symfony2InterfacesUtil.isCallTo(method, "\\Symfony\\Component\\Form\\FormTypeInterface", "finishView")) { return new PsiReference[0]; } PhpClass phpClass = method.getContainingClass(); if(phpClass == null) { return new PsiReference[0]; } return new PsiReference[]{ new FormExtensionKeyReference((StringLiteralExpression) psiElement), new FormDefaultOptionsKeyReference((StringLiteralExpression) psiElement, phpClass.getPresentableFQN()) }; } } ); } private static class FormTypeReferenceRef extends FormTypeReference { public FormTypeReferenceRef(@NotNull StringLiteralExpression element) { super(element); } @NotNull @Override public Object[] getVariants() { return new Object[0]; } } }