diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/TwigLineMarkerProvider.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/TwigLineMarkerProvider.java index 1bd3ad835..ac5570046 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/TwigLineMarkerProvider.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/TwigLineMarkerProvider.java @@ -38,7 +38,7 @@ import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigTypeResolveUtil; import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigUtil; import fr.adrienbrault.idea.symfony2plugin.templating.variable.TwigTypeContainer; -import fr.adrienbrault.idea.symfony2plugin.templating.variable.resolver.holder.FormDataHolder; +import fr.adrienbrault.idea.symfony2plugin.templating.variable.resolver.holder.FormFieldDataHolder; import fr.adrienbrault.idea.symfony2plugin.twig.loader.FileImplementsLazyLoader; import fr.adrienbrault.idea.symfony2plugin.twig.loader.FileOverwritesLazyLoader; import fr.adrienbrault.idea.symfony2plugin.twig.utils.TwigBlockUtil; @@ -332,9 +332,9 @@ private LineMarkerInfo attachFormType(@NotNull PsiElement psiElement) { Collection phpClasses = new HashSet<>(); for (TwigTypeContainer twigTypeContainer : twigTypeContainers) { - Object dataHolder = twigTypeContainer.getDataHolder(); - if (dataHolder instanceof FormDataHolder formDataHolder) { - PhpClass phpClass = PhpElementsUtil.getClassInterface(psiElement.getProject(), formDataHolder.ownerFormTypeFqn()); + FormFieldDataHolder formFieldDataHolder = twigTypeContainer.getFormFieldDataHolder(); + if (formFieldDataHolder != null) { + PhpClass phpClass = PhpElementsUtil.getClassInterface(psiElement.getProject(), formFieldDataHolder.ownerFormTypeFqn()); if (phpClass != null && PhpElementsUtil.isInstanceOf(phpClass, "\\Symfony\\Component\\Form\\FormTypeInterface")) { phpClasses.add(phpClass); } diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/TwigTemplateCompletionContributor.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/TwigTemplateCompletionContributor.java index fbebbf601..57f0b980e 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/TwigTemplateCompletionContributor.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/TwigTemplateCompletionContributor.java @@ -41,7 +41,7 @@ import fr.adrienbrault.idea.symfony2plugin.templating.variable.TwigTypeContainer; import fr.adrienbrault.idea.symfony2plugin.templating.variable.dict.PsiVariable; import fr.adrienbrault.idea.symfony2plugin.templating.variable.resolver.FormFieldResolver; -import fr.adrienbrault.idea.symfony2plugin.templating.variable.resolver.holder.FormDataHolder; +import fr.adrienbrault.idea.symfony2plugin.templating.variable.resolver.holder.FormFieldDataHolder; import fr.adrienbrault.idea.symfony2plugin.translation.dict.TranslationUtil; import fr.adrienbrault.idea.symfony2plugin.twig.utils.TwigFileUtil; import fr.adrienbrault.idea.symfony2plugin.twig.variable.collector.ControllerDocVariableCollector; @@ -755,9 +755,9 @@ protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull LookupElementBuilder lookupElement = LookupElementBuilder.create(twigTypeContainer.getStringElement()); // form - Object dataHolder = twigTypeContainer.getDataHolder(); - if (dataHolder instanceof FormDataHolder formDataHolder) { - lookupElement = decorateFormFieldLookupElement(lookupElement, formDataHolder); + FormFieldDataHolder formFieldDataHolder = twigTypeContainer.getFormFieldDataHolder(); + if (formFieldDataHolder != null) { + lookupElement = decorateFormFieldLookupElement(lookupElement, formFieldDataHolder); } resultSet.addElement(lookupElement); @@ -973,8 +973,9 @@ public boolean accepts(@NotNull String s, ProcessingContext processingContext) { FormFieldResolver.visitFormReferencesFields(element1, twigTypeContainers -> { String typeText = null; - if (twigTypeContainers.getDataHolder() instanceof FormDataHolder formDataHolder) { - typeText = getFormTypeShortName(formDataHolder.fieldTypeFqn()); + FormFieldDataHolder formFieldDataHolder = twigTypeContainers.getFormFieldDataHolder(); + if (formFieldDataHolder != null) { + typeText = getFormTypeShortName(formFieldDataHolder.fieldTypeFqn()); } for (String s : new String[]{"form_row", "form_widget", "form_label", "form_errors", "form_help"}) { @@ -1001,7 +1002,7 @@ private static String getFormTypeShortName(@Nullable String fqn) { } @NotNull - public static LookupElementBuilder decorateFormFieldLookupElement(@NotNull LookupElementBuilder lookupElement, @NotNull FormDataHolder formDataHolder) { + public static LookupElementBuilder decorateFormFieldLookupElement(@NotNull LookupElementBuilder lookupElement, @NotNull FormFieldDataHolder formDataHolder) { lookupElement = lookupElement.withIcon(Symfony2Icons.FORM_TYPE); String fieldTypeShortName = getFormTypeShortName(formDataHolder.fieldTypeFqn()); diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/TwigTemplateGoToDeclarationHandler.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/TwigTemplateGoToDeclarationHandler.java index b2418cb7d..6c94df099 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/TwigTemplateGoToDeclarationHandler.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/TwigTemplateGoToDeclarationHandler.java @@ -28,7 +28,7 @@ import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigTypeResolveUtil; import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigUtil; import fr.adrienbrault.idea.symfony2plugin.templating.variable.TwigTypeContainer; -import fr.adrienbrault.idea.symfony2plugin.templating.variable.resolver.holder.FormDataHolder; +import fr.adrienbrault.idea.symfony2plugin.templating.variable.resolver.holder.FormFieldDataHolder; import fr.adrienbrault.idea.symfony2plugin.translation.dict.TranslationUtil; import fr.adrienbrault.idea.symfony2plugin.twig.utils.TwigBlockUtil; import fr.adrienbrault.idea.symfony2plugin.twig.variable.collector.ControllerDocVariableCollector; @@ -538,10 +538,10 @@ public static Collection getTypeGoto(@NotNull PsiElement psiElement) // form // @TODO: provide extension if (text.equals(twigTypeContainer.getStringElement())) { - Object dataHolder = twigTypeContainer.getDataHolder(); - if (dataHolder instanceof FormDataHolder formDataHolder) { + FormFieldDataHolder formFieldDataHolder = twigTypeContainer.getFormFieldDataHolder(); + if (formFieldDataHolder != null) { // @TODO: resolve the to field itself - PhpClass phpClass = PhpElementsUtil.getClassInterface(psiElement.getProject(), formDataHolder.ownerFormTypeFqn()); + PhpClass phpClass = PhpElementsUtil.getClassInterface(psiElement.getProject(), formFieldDataHolder.ownerFormTypeFqn()); if (phpClass != null) { targetPsiElements.add(phpClass); } diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/util/PhpMethodVariableResolveUtil.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/util/PhpMethodVariableResolveUtil.java index ec0e93771..5049e62a5 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/util/PhpMethodVariableResolveUtil.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/util/PhpMethodVariableResolveUtil.java @@ -17,6 +17,7 @@ import fr.adrienbrault.idea.symfony2plugin.extension.PluginConfigurationExtension; import fr.adrienbrault.idea.symfony2plugin.extension.PluginConfigurationExtensionParameter; import fr.adrienbrault.idea.symfony2plugin.templating.variable.dict.PsiVariable; +import fr.adrienbrault.idea.symfony2plugin.templating.variable.resolver.FormFieldResolver; import fr.adrienbrault.idea.symfony2plugin.util.AnnotationBackportUtil; import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil; import fr.adrienbrault.idea.symfony2plugin.util.PhpPsiAttributesUtil; @@ -242,7 +243,11 @@ private static Pair getTypesOnArrayIndex(@NotNull ArrayAcce variableTypes.addAll(((PhpTypedElement) arrayValue).getType().getTypes()); } - return Pair.create(variableName, new PsiVariable(variableTypes, ((AssignmentExpression) parent).getValue())); + return Pair.create(variableName, new PsiVariable( + variableTypes, + arrayValue, + arrayValue == null ? Collections.emptySet() : FormFieldResolver.getFormTypeFqnsFromFormFactory(arrayValue) + )); } else { return Pair.create(variableName, new PsiVariable(variableTypes)); } @@ -261,12 +266,17 @@ public static Map getTypesOnArrayHash(@NotNull ArrayCreatio if(arrayHashElement.getKey() instanceof StringLiteralExpression) { String variableName = ((StringLiteralExpression) arrayHashElement.getKey()).getContents(); Set variableTypes = new HashSet<>(); + PsiElement arrayValue = arrayHashElement.getValue(); - if(arrayHashElement.getValue() instanceof PhpTypedElement) { - variableTypes.addAll(((PhpTypedElement) arrayHashElement.getValue()).getType().getTypes()); + if(arrayValue instanceof PhpTypedElement) { + variableTypes.addAll(((PhpTypedElement) arrayValue).getType().getTypes()); } - collectedTypes.put(variableName, new PsiVariable(variableTypes, arrayHashElement.getValue())); + collectedTypes.put(variableName, new PsiVariable( + variableTypes, + arrayValue, + arrayValue == null ? Collections.emptySet() : FormFieldResolver.getFormTypeFqnsFromFormFactory(arrayValue) + )); } } diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/util/TwigTypeResolveUtil.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/util/TwigTypeResolveUtil.java index 8a7ddd727..140f1e454 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/util/TwigTypeResolveUtil.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/util/TwigTypeResolveUtil.java @@ -355,6 +355,7 @@ public static Map collectScopeVariables(@NotNull PsiElement controllerVars1.forEach((s, psiVariable) -> { controllerVars.putIfAbsent(s, new PsiVariable()); controllerVars.get(s).addTypes(psiVariable.getTypes()); + controllerVars.get(s).addFormTypeFqns(psiVariable.getFormTypeFqns()); PsiElement context = psiVariable.getElement(); if (context != null) { @@ -363,13 +364,14 @@ public static Map collectScopeVariables(@NotNull PsiElement }); } - // globals first + // globals first @var first Collection> vars = Arrays.asList( findInlineStatementVariableDocBlock(psiElement, TwigElementTypes.BLOCK_STATEMENT, true), findInlineStatementVariableDocBlock(psiElement, TwigElementTypes.MACRO_STATEMENT, false), findInlineStatementVariableDocBlock(psiElement, TwigElementTypes.FOR_STATEMENT, false) ); + // Inline Twig docs only provide type strings, e.g. "{# @var form \Symfony\Component\Form\FormView #}". for (Map entry : vars) { entry.forEach((s, s2) -> { controllerVars.putIfAbsent(s, new PsiVariable()); @@ -769,4 +771,3 @@ private static Collection getForTagIdentifierAsString(PsiElement forTag) return TwigTypeResolveUtil.formatPsiTypeName(afterInVarPsiElement); } } - diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/variable/TwigTypeContainer.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/variable/TwigTypeContainer.java index 6c6388eab..cb935f2b0 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/variable/TwigTypeContainer.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/variable/TwigTypeContainer.java @@ -4,6 +4,8 @@ import com.jetbrains.php.lang.psi.elements.PhpClass; import com.jetbrains.php.lang.psi.elements.PhpNamedElement; import fr.adrienbrault.idea.symfony2plugin.templating.variable.dict.PsiVariable; +import fr.adrienbrault.idea.symfony2plugin.templating.variable.resolver.holder.FormFieldDataHolder; +import fr.adrienbrault.idea.symfony2plugin.templating.variable.resolver.holder.FormViewDataHolder; import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil; import org.jetbrains.annotations.Nullable; @@ -16,16 +18,31 @@ */ public class TwigTypeContainer { - private PhpNamedElement phpNamedElement; - private String stringElement; - private Object dataHolder; + private final PhpNamedElement phpNamedElement; + private final String stringElement; + private final FormFieldDataHolder formFieldDataHolder; + private final FormViewDataHolder formViewDataHolder; public TwigTypeContainer(PhpNamedElement phpNamedElement) { + this(phpNamedElement, null); + } + + public TwigTypeContainer(PhpNamedElement phpNamedElement, @Nullable FormViewDataHolder formViewDataHolder) { this.phpNamedElement = phpNamedElement; + this.stringElement = null; + this.formFieldDataHolder = null; + this.formViewDataHolder = formViewDataHolder; } public TwigTypeContainer(String stringElement) { + this(stringElement, null); + } + + public TwigTypeContainer(String stringElement, @Nullable FormFieldDataHolder formFieldDataHolder) { + this.phpNamedElement = null; this.stringElement = stringElement; + this.formFieldDataHolder = formFieldDataHolder; + this.formViewDataHolder = null; } @Nullable @@ -45,19 +62,25 @@ public static Collection fromCollection(Project project, Coll for(PsiVariable phpNamedElement :psiVariables) { Collection phpClass = PhpElementsUtil.getClassFromPhpTypeSet(project, phpNamedElement.getTypes()); if(!phpClass.isEmpty()) { - twigTypeContainerList.add(new TwigTypeContainer(phpClass.iterator().next())); + FormViewDataHolder formViewDataHolder = phpNamedElement.getFormTypeFqns().isEmpty() + ? null + : new FormViewDataHolder(phpNamedElement.getFormTypeFqns()); + + twigTypeContainerList.add(new TwigTypeContainer(phpClass.iterator().next(), formViewDataHolder)); } } return twigTypeContainerList; } - public TwigTypeContainer withDataHolder(Object object) { - this.dataHolder = object; - return this; + @Nullable + public FormFieldDataHolder getFormFieldDataHolder() { + return formFieldDataHolder; } - public Object getDataHolder() { - return dataHolder; + @Nullable + public FormViewDataHolder getFormViewDataHolder() { + return formViewDataHolder; } -} \ No newline at end of file + +} diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/variable/dict/PsiVariable.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/variable/dict/PsiVariable.java index bbdb77afd..f86f6077c 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/variable/dict/PsiVariable.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/variable/dict/PsiVariable.java @@ -18,9 +18,17 @@ public class PsiVariable { @NotNull final private Collection psiElements = new HashSet<>(); + @NotNull + final private Set formTypeFqns = new HashSet<>(); + public PsiVariable(@NotNull Set types, @Nullable PsiElement psiElement) { + this(types, psiElement, Set.of()); + } + + public PsiVariable(@NotNull Set types, @Nullable PsiElement psiElement, @NotNull Collection formTypeFqns) { this.types.addAll(types); this.psiElements.add(psiElement); + this.formTypeFqns.addAll(formTypeFqns); } public PsiVariable(@NotNull Set types) { @@ -39,6 +47,11 @@ public Set getTypes() { return types; } + @NotNull + public Set getFormTypeFqns() { + return formTypeFqns; + } + @Nullable public PsiElement getElement() { if (!psiElements.isEmpty()) { @@ -59,4 +72,8 @@ public void addTypes(@NotNull Collection types) { public void addType(@NotNull String type) { this.types.add(type); } + + public void addFormTypeFqns(@NotNull Collection formTypeFqns) { + this.formTypeFqns.addAll(formTypeFqns); + } } diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/variable/resolver/FormFieldResolver.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/variable/resolver/FormFieldResolver.java index 59e15c15a..6da5cd77c 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/variable/resolver/FormFieldResolver.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/variable/resolver/FormFieldResolver.java @@ -12,7 +12,8 @@ import fr.adrienbrault.idea.symfony2plugin.form.util.FormUtil; import fr.adrienbrault.idea.symfony2plugin.templating.variable.TwigTypeContainer; import fr.adrienbrault.idea.symfony2plugin.templating.variable.dict.PsiVariable; -import fr.adrienbrault.idea.symfony2plugin.templating.variable.resolver.holder.FormDataHolder; +import fr.adrienbrault.idea.symfony2plugin.templating.variable.resolver.holder.FormFieldDataHolder; +import fr.adrienbrault.idea.symfony2plugin.templating.variable.resolver.holder.FormViewDataHolder; import fr.adrienbrault.idea.symfony2plugin.util.MethodMatcher; import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil; import fr.adrienbrault.idea.symfony2plugin.util.PsiElementUtils; @@ -28,16 +29,18 @@ public class FormFieldResolver implements TwigTypeResolver { public void resolve(Collection targets, Collection previousElement, String typeName, Collection> previousElements, @Nullable Collection psiVariables) { - if (targets.isEmpty() || psiVariables == null || psiVariables.isEmpty() || previousElements == null || !previousElements.isEmpty()) { + if (targets.isEmpty() || previousElements == null || !previousElements.isEmpty()) { return; } TwigTypeContainer twigTypeContainer = targets.iterator().next(); - if (twigTypeContainer.getPhpNamedElement() instanceof PhpClass phpClass && isFormView(phpClass)) { - PsiElement element = psiVariables.iterator().next().getElement(); - if (element != null) { - visitFormReferencesFields(element, targets::add); - } + if ( + twigTypeContainer.getPhpNamedElement() instanceof PhpClass phpClass && + isFormView(phpClass) && + twigTypeContainer.getFormViewDataHolder() instanceof FormViewDataHolder formViewDataHolder && + !formViewDataHolder.formTypeFqns().isEmpty() + ) { + visitFormFields(phpClass.getProject(), formViewDataHolder.formTypeFqns(), field -> targets.add(toTwigTypeContainer(field))); } } @@ -250,7 +253,7 @@ public static void visitFormFields(@NotNull Project project, @NotNull Collection @NotNull private static TwigTypeContainer toTwigTypeContainer(@NotNull TwigFormField field) { - return new TwigTypeContainer(field.name()).withDataHolder(new FormDataHolder(field.fieldTypeFqn(), field.ownerFormTypeFqn())); + return new TwigTypeContainer(field.name(), new FormFieldDataHolder(field.fieldTypeFqn(), field.ownerFormTypeFqn())); } private static void consumeFieldType(@NotNull PhpClass phpClass, @NotNull Consumer consumer) { diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/variable/resolver/holder/FormDataHolder.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/variable/resolver/holder/FormFieldDataHolder.java similarity index 85% rename from src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/variable/resolver/holder/FormDataHolder.java rename to src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/variable/resolver/holder/FormFieldDataHolder.java index 4f8039ebe..80c979c91 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/variable/resolver/holder/FormDataHolder.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/variable/resolver/holder/FormFieldDataHolder.java @@ -4,18 +4,18 @@ import org.jetbrains.annotations.Nullable; /** - * UI metadata for a Symfony form field without holding PSI objects. + * Metadata for a Symfony form field exposed in Twig, for example {@code form.title}. * * @param fieldTypeFqn explicit normalized field form type FQN, or {@code null} when the builder call has no type parameter * @param ownerFormTypeFqn normalized FQN of the form type whose {@code buildForm()} contributed this field * * @author Daniel Espendiller */ -public record FormDataHolder( +public record FormFieldDataHolder( @Nullable String fieldTypeFqn, @NotNull String ownerFormTypeFqn ) { - public FormDataHolder { + public FormFieldDataHolder { if (fieldTypeFqn != null && !fieldTypeFqn.startsWith("\\")) { throw new IllegalArgumentException("fieldTypeFqn must be normalized with a leading backslash"); } diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/variable/resolver/holder/FormViewDataHolder.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/variable/resolver/holder/FormViewDataHolder.java new file mode 100644 index 000000000..31485f1de --- /dev/null +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/variable/resolver/holder/FormViewDataHolder.java @@ -0,0 +1,38 @@ +package fr.adrienbrault.idea.symfony2plugin.templating.variable.resolver.holder; + +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Metadata for a Twig root form view variable, for example {@code form}. + * Example: {@code $this->render('edit.html.twig', ['form' => $form->createView()])} + * exposes the root Twig variable {@code form}; its {@code formTypeFqns} point to the + * Symfony form type classes that built that view. + * + * @param formTypeFqns normalized FQNs of Symfony form types that created the form view + * + * @author Daniel Espendiller + */ +public record FormViewDataHolder(@NotNull Set formTypeFqns) { + public FormViewDataHolder { + formTypeFqns = toImmutableNormalizedSet(formTypeFqns); + } + + @NotNull + private static Set toImmutableNormalizedSet(@NotNull Set formTypeFqns) { + Set normalized = new LinkedHashSet<>(); + + for (String formTypeFqn : formTypeFqns) { + if (!formTypeFqn.startsWith("\\")) { + throw new IllegalArgumentException("formTypeFqn must be normalized with a leading backslash"); + } + + normalized.add(formTypeFqn); + } + + return Collections.unmodifiableSet(normalized); + } +} diff --git a/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/templating/TwigTemplateCompletionContributorTest.java b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/templating/TwigTemplateCompletionContributorTest.java index 93abeeb63..1b2ce0537 100644 --- a/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/templating/TwigTemplateCompletionContributorTest.java +++ b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/templating/TwigTemplateCompletionContributorTest.java @@ -6,7 +6,7 @@ import com.intellij.patterns.PlatformPatterns; import com.jetbrains.twig.TwigFileType; import fr.adrienbrault.idea.symfony2plugin.templating.TwigTemplateCompletionContributor; -import fr.adrienbrault.idea.symfony2plugin.templating.variable.resolver.holder.FormDataHolder; +import fr.adrienbrault.idea.symfony2plugin.templating.variable.resolver.holder.FormFieldDataHolder; import fr.adrienbrault.idea.symfony2plugin.tests.SymfonyLightCodeInsightFixtureTestCase; /** @@ -68,7 +68,7 @@ public void testThatTypesTagProvidesIncompleteForStatementCompletion() { public void testFormFieldCompletionUsesPrimitiveFormDataHolderPresentation() { LookupElement lookupElement = TwigTemplateCompletionContributor.decorateFormFieldLookupElement( LookupElementBuilder.create("title"), - new FormDataHolder("\\Symfony\\Component\\Form\\Extension\\Core\\Type\\TextType", "\\App\\Form\\ProductType") + new FormFieldDataHolder("\\Symfony\\Component\\Form\\Extension\\Core\\Type\\TextType", "\\App\\Form\\ProductType") ); LookupElementPresentation presentation = new LookupElementPresentation(); diff --git a/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/templating/util/PhpMethodVariableResolveUtilTest.java b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/templating/util/PhpMethodVariableResolveUtilTest.java index 48fb52620..23d7bdb91 100644 --- a/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/templating/util/PhpMethodVariableResolveUtilTest.java +++ b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/templating/util/PhpMethodVariableResolveUtilTest.java @@ -1,10 +1,14 @@ package fr.adrienbrault.idea.symfony2plugin.tests.templating.util; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.php.lang.PhpFileType; import com.jetbrains.php.lang.psi.PhpPsiElementFactory; import com.jetbrains.php.lang.psi.elements.Function; import fr.adrienbrault.idea.symfony2plugin.templating.util.PhpMethodVariableResolveUtil; import fr.adrienbrault.idea.symfony2plugin.templating.variable.dict.PsiVariable; import fr.adrienbrault.idea.symfony2plugin.tests.SymfonyLightCodeInsightFixtureTestCase; +import org.jetbrains.annotations.NotNull; import java.util.Map; @@ -63,6 +67,30 @@ public void testCollectMethodVariablesForArrayCreation() { assertContainsElements(vars.keySet(), "foobar"); } + /** + * @see PhpMethodVariableResolveUtil#collectMethodVariables + */ + public void testCollectMethodVariablesCarriesPrimitiveFormTypeFqns() { + myFixture.configureByText(PhpFileType.INSTANCE, "createForm(\\App\\Form\\ProductType::class);\n" + + " return $this->render('product.html.twig', ['form' => $form->createView()]);\n" + + " }\n" + + " }\n" + + "}\n" + ); + + Map vars = PhpMethodVariableResolveUtil.collectMethodVariables(findFunction("index")); + + assertContainsElements(vars.keySet(), "form"); + assertContainsElements(vars.get("form").getFormTypeFqns(), "\\App\\Form\\ProductType"); + } + /** * @see PhpMethodVariableResolveUtil#collectMethodVariables */ @@ -83,6 +111,19 @@ public void testCollectMethodVariablesForArrayMerge() { assertContainsElements(vars.keySet(), "foobar1"); } + @NotNull + private Function findFunction(@NotNull String name) { + for (PsiElement psiElement : PsiTreeUtil.collectElementsOfType(myFixture.getFile(), Function.class)) { + Function function = (Function) psiElement; + if (name.equals(function.getName())) { + return function; + } + } + + fail("Function not found: " + name); + throw new IllegalStateException(name); + } + /** * @see PhpMethodVariableResolveUtil#collectMethodVariables */ diff --git a/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/templating/variable/resolver/FormFieldResolverTest.java b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/templating/variable/resolver/FormFieldResolverTest.java index a0cb3edc4..a86276e2f 100644 --- a/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/templating/variable/resolver/FormFieldResolverTest.java +++ b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/templating/variable/resolver/FormFieldResolverTest.java @@ -4,8 +4,12 @@ import com.intellij.psi.util.PsiTreeUtil; import com.jetbrains.php.lang.PhpFileType; import com.jetbrains.php.lang.psi.elements.MethodReference; +import fr.adrienbrault.idea.symfony2plugin.templating.variable.TwigTypeContainer; +import fr.adrienbrault.idea.symfony2plugin.templating.variable.dict.PsiVariable; import fr.adrienbrault.idea.symfony2plugin.templating.variable.resolver.FormFieldResolver; import fr.adrienbrault.idea.symfony2plugin.templating.variable.resolver.TwigFormField; +import fr.adrienbrault.idea.symfony2plugin.templating.variable.resolver.holder.FormFieldDataHolder; +import fr.adrienbrault.idea.symfony2plugin.templating.variable.resolver.holder.FormViewDataHolder; import fr.adrienbrault.idea.symfony2plugin.tests.SymfonyLightCodeInsightFixtureTestCase; import org.jetbrains.annotations.NotNull; @@ -76,6 +80,43 @@ public void testVisitFormFieldsProvidesPrimitiveFieldMetadata() { assertEquals("\\App\\Form\\ProductType", plain.ownerFormTypeFqn()); } + public void testResolveUsesPrimitiveFormTypeFqnsWithoutPsiElement() { + myFixture.configureByText(PhpFileType.INSTANCE, "add('title', \\Symfony\\Component\\Form\\Extension\\Core\\Type\\TextType::class);\n" + + " }\n" + + " }\n" + + "}\n" + ); + + PsiVariable rootVariable = new PsiVariable("\\Symfony\\Component\\Form\\FormView"); + rootVariable.addFormTypeFqns(Collections.singleton("\\App\\Form\\ProductType")); + + Collection targets = TwigTypeContainer.fromCollection(getProject(), Collections.singleton(rootVariable)); + assertSize(1, targets); + + TwigTypeContainer rootContainer = targets.iterator().next(); + FormViewDataHolder rootFormDataHolder = rootContainer.getFormViewDataHolder(); + assertNotNull(rootFormDataHolder); + assertContainsElements(rootFormDataHolder.formTypeFqns(), "\\App\\Form\\ProductType"); + + new FormFieldResolver().resolve(targets, targets, "form", new ArrayList<>(), null); + + TwigTypeContainer title = targets.stream() + .filter(twigTypeContainer -> "title".equals(twigTypeContainer.getStringElement())) + .findFirst() + .orElseThrow(); + + FormFieldDataHolder formFieldDataHolder = title.getFormFieldDataHolder(); + assertNotNull(formFieldDataHolder); + assertEquals("\\Symfony\\Component\\Form\\Extension\\Core\\Type\\TextType", formFieldDataHolder.fieldTypeFqn()); + assertEquals("\\App\\Form\\ProductType", formFieldDataHolder.ownerFormTypeFqn()); + } + @NotNull private MethodReference findMethodReference(@NotNull String name) { for (PsiElement psiElement : PsiTreeUtil.collectElementsOfType(myFixture.getFile(), MethodReference.class)) {