Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -332,9 +332,9 @@ private LineMarkerInfo<?> attachFormType(@NotNull PsiElement psiElement) {
Collection<PhpClass> 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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"}) {
Expand All @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -538,10 +538,10 @@ public static Collection<PsiElement> 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -242,7 +243,11 @@ private static Pair<String, PsiVariable> 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));
}
Expand All @@ -261,12 +266,17 @@ public static Map<String, PsiVariable> getTypesOnArrayHash(@NotNull ArrayCreatio
if(arrayHashElement.getKey() instanceof StringLiteralExpression) {
String variableName = ((StringLiteralExpression) arrayHashElement.getKey()).getContents();
Set<String> 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)
));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ public static Map<String, PsiVariable> 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) {
Expand All @@ -363,13 +364,14 @@ public static Map<String, PsiVariable> collectScopeVariables(@NotNull PsiElement
});
}

// globals first
// globals first @var first
Collection<Map<String, String>> 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<String, String> entry : vars) {
entry.forEach((s, s2) -> {
controllerVars.putIfAbsent(s, new PsiVariable());
Expand Down Expand Up @@ -769,4 +771,3 @@ private static Collection<String> getForTagIdentifierAsString(PsiElement forTag)
return TwigTypeResolveUtil.formatPsiTypeName(afterInVarPsiElement);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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
Expand All @@ -45,19 +62,25 @@ public static Collection<TwigTypeContainer> fromCollection(Project project, Coll
for(PsiVariable phpNamedElement :psiVariables) {
Collection<PhpClass> 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;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,17 @@ public class PsiVariable {
@NotNull
final private Collection<PsiElement> psiElements = new HashSet<>();

@NotNull
final private Set<String> formTypeFqns = new HashSet<>();

public PsiVariable(@NotNull Set<String> types, @Nullable PsiElement psiElement) {
this(types, psiElement, Set.of());
}

public PsiVariable(@NotNull Set<String> types, @Nullable PsiElement psiElement, @NotNull Collection<String> formTypeFqns) {
this.types.addAll(types);
this.psiElements.add(psiElement);
this.formTypeFqns.addAll(formTypeFqns);
}

public PsiVariable(@NotNull Set<String> types) {
Expand All @@ -39,6 +47,11 @@ public Set<String> getTypes() {
return types;
}

@NotNull
public Set<String> getFormTypeFqns() {
return formTypeFqns;
}

@Nullable
public PsiElement getElement() {
if (!psiElements.isEmpty()) {
Expand All @@ -59,4 +72,8 @@ public void addTypes(@NotNull Collection<String> types) {
public void addType(@NotNull String type) {
this.types.add(type);
}

public void addFormTypeFqns(@NotNull Collection<String> formTypeFqns) {
this.formTypeFqns.addAll(formTypeFqns);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -28,16 +29,18 @@
public class FormFieldResolver implements TwigTypeResolver {

public void resolve(Collection<TwigTypeContainer> targets, Collection<TwigTypeContainer> previousElement, String typeName, Collection<List<TwigTypeContainer>> previousElements, @Nullable Collection<PsiVariable> 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)));
}
}

Expand Down Expand Up @@ -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<TwigFormField> consumer) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <daniel@espendiller.net>
*/
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");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <daniel@espendiller.net>
*/
public record FormViewDataHolder(@NotNull Set<String> formTypeFqns) {
public FormViewDataHolder {
formTypeFqns = toImmutableNormalizedSet(formTypeFqns);
}

@NotNull
private static Set<String> toImmutableNormalizedSet(@NotNull Set<String> formTypeFqns) {
Set<String> 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);
}
}
Loading
Loading