Skip to content

Commit b1c2589

Browse files
committed
Refactor Twig type containers to store cacheable type strings instead of PSI elements
1 parent d1c4fd7 commit b1c2589

9 files changed

Lines changed: 116 additions & 121 deletions

File tree

src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/TwigTemplateCompletionContributor.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -735,15 +735,14 @@ protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull
735735

736736
// find core function for that
737737
for(TwigTypeContainer twigTypeContainer: TwigTypeResolveUtil.resolveTwigMethodName(psiElement, possibleTypes)) {
738-
if(twigTypeContainer.getPhpNamedElement() instanceof PhpClass) {
739-
740-
for(Method method: ((PhpClass) twigTypeContainer.getPhpNamedElement()).getMethods()) {
738+
for (PhpClass phpClass : TwigTypeResolveUtil.resolveTwigTypeClasses(psiElement.getProject(), twigTypeContainer)) {
739+
for(Method method: phpClass.getMethods()) {
741740
if(TwigTypeResolveUtil.isTwigAccessibleMethod(method)) {
742741
resultSet.addElement(new PhpTwigMethodLookupElement(method));
743742
}
744743
}
745744

746-
for(Field field: ((PhpClass) twigTypeContainer.getPhpNamedElement()).getFields()) {
745+
for(Field field: phpClass.getFields()) {
747746
if(field.getModifier().isPublic()) {
748747
resultSet.addElement(new PhpTwigMethodLookupElement(field));
749748
}

src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/TwigTemplateGoToDeclarationHandler.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -520,9 +520,7 @@ public static Collection<PsiElement> getTypeGoto(@NotNull PsiElement psiElement)
520520
if(beforeLeaf.isEmpty()) {
521521
Collection<TwigTypeContainer> twigTypeContainers = TwigTypeResolveUtil.resolveTwigMethodName(psiElement, TwigTypeResolveUtil.formatPsiTypeNameWithCurrent(psiElement));
522522
for(TwigTypeContainer twigTypeContainer: twigTypeContainers) {
523-
if(twigTypeContainer.getPhpNamedElement() != null) {
524-
targetPsiElements.add(twigTypeContainer.getPhpNamedElement());
525-
}
523+
targetPsiElements.addAll(TwigTypeResolveUtil.resolveTwigTypeClasses(psiElement.getProject(), twigTypeContainer));
526524
}
527525

528526
} else {
@@ -531,9 +529,7 @@ public static Collection<PsiElement> getTypeGoto(@NotNull PsiElement psiElement)
531529
if(StringUtils.isNotBlank(text)) {
532530
// provide method / field goto
533531
for(TwigTypeContainer twigTypeContainer: types) {
534-
if(twigTypeContainer.getPhpNamedElement() != null) {
535-
targetPsiElements.addAll(TwigTypeResolveUtil.getTwigPhpNameTargets(twigTypeContainer.getPhpNamedElement(), text));
536-
}
532+
targetPsiElements.addAll(TwigTypeResolveUtil.getTwigPhpNameTargets(psiElement.getProject(), twigTypeContainer, text));
537533

538534
// form
539535
// @TODO: provide extension

src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/inspection/TwigVariableDeprecatedInspection.java

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import fr.adrienbrault.idea.symfony2plugin.templating.variable.TwigTypeContainer;
1616
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
1717
import org.jetbrains.annotations.NotNull;
18+
import org.jspecify.annotations.NonNull;
1819

1920
import java.util.Collection;
2021

@@ -43,35 +44,33 @@ private static class MyPsiElementVisitor extends PsiElementVisitor {
4344
}
4445

4546
@Override
46-
public void visitElement(PsiElement element) {
47-
if(getTypeCompletionPattern().accepts(element)) {
47+
public void visitElement(@NonNull PsiElement element) {
48+
if (getTypeCompletionPattern().accepts(element)) {
4849
visit(element);
4950
}
51+
5052
super.visitElement(element);
5153
}
5254

5355
private void visit(@NotNull PsiElement element) {
5456
Collection<String> beforeLeaf = TwigTypeResolveUtil.formatPsiTypeName(element);
55-
if(beforeLeaf.isEmpty()) {
57+
if (beforeLeaf.isEmpty()) {
5658
return;
5759
}
5860

5961
Collection<TwigTypeContainer> types = TwigTypeResolveUtil.resolveTwigMethodName(element, beforeLeaf);
60-
if(types.isEmpty()) {
62+
if (types.isEmpty()) {
6163
return;
6264
}
6365

64-
for(TwigTypeContainer twigTypeContainer: types) {
65-
PhpNamedElement phpClass = twigTypeContainer.getPhpNamedElement();
66-
if(!(phpClass instanceof PhpClass)) {
67-
continue;
68-
}
69-
70-
String text = element.getText();
66+
for (TwigTypeContainer twigTypeContainer: types) {
67+
for (PhpClass phpClass : TwigTypeResolveUtil.resolveTwigTypeClasses(element.getProject(), twigTypeContainer)) {
68+
String text = element.getText();
7169

72-
for (PhpNamedElement namedElement : TwigTypeResolveUtil.getTwigPhpNameTargets(phpClass, text)) {
73-
if(namedElement instanceof Method method && PhpElementsUtil.isClassOrFunctionDeprecated(method)) {
74-
this.holder.registerProblem(element, String.format("Method '%s::%s' is deprecated", phpClass.getName(), namedElement.getName()), ProblemHighlightType.LIKE_DEPRECATED);
70+
for (PhpNamedElement namedElement : TwigTypeResolveUtil.getTwigPhpNameTargets(phpClass, text)) {
71+
if (namedElement instanceof Method method && PhpElementsUtil.isClassOrFunctionDeprecated(method)) {
72+
this.holder.registerProblem(element, String.format("Method '%s::%s' is deprecated", phpClass.getName(), namedElement.getName()), ProblemHighlightType.LIKE_DEPRECATED);
73+
}
7574
}
7675
}
7776
}

src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/inspection/TwigVariablePathInspection.java

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,12 @@
77
import com.intellij.psi.PsiElement;
88
import com.intellij.psi.PsiElementVisitor;
99
import com.jetbrains.php.lang.psi.elements.PhpClass;
10-
import com.jetbrains.php.lang.psi.elements.PhpNamedElement;
1110
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent;
1211
import fr.adrienbrault.idea.symfony2plugin.templating.TwigPattern;
1312
import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigTypeResolveUtil;
1413
import fr.adrienbrault.idea.symfony2plugin.templating.variable.TwigTypeContainer;
15-
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
1614
import org.jetbrains.annotations.NotNull;
15+
import org.jspecify.annotations.NonNull;
1716

1817
import java.util.Collection;
1918

@@ -42,7 +41,7 @@ private static class MyPsiElementVisitor extends PsiElementVisitor {
4241
}
4342

4443
@Override
45-
public void visitElement(PsiElement element) {
44+
public void visitElement(@NonNull PsiElement element) {
4645
if(getTypeCompletionPattern().accepts(element)) {
4746
visit(element);
4847
}
@@ -56,36 +55,31 @@ private void visit(@NotNull PsiElement element) {
5655
}
5756

5857
Collection<TwigTypeContainer> types = TwigTypeResolveUtil.resolveTwigMethodName(element, beforeLeaf);
59-
if(types.isEmpty()) {
58+
if (types.isEmpty()) {
6059
return;
6160
}
6261

63-
for(TwigTypeContainer twigTypeContainer: types) {
64-
PhpNamedElement phpNamedElement = twigTypeContainer.getPhpNamedElement();
65-
if(phpNamedElement == null) {
66-
continue;
67-
}
68-
69-
if(isWeakPhpClass(phpNamedElement)) {
62+
for (TwigTypeContainer twigTypeContainer: types) {
63+
String text = element.getText();
64+
Collection<PhpClass> phpClasses = TwigTypeResolveUtil.resolveTwigTypeClasses(element.getProject(), twigTypeContainer);
65+
if (phpClasses.isEmpty()) {
7066
return;
7167
}
7268

73-
String text = element.getText();
74-
if(!TwigTypeResolveUtil.getTwigPhpNameTargets(phpNamedElement, text).isEmpty()) {
75-
return;
69+
for (PhpClass phpClass : phpClasses) {
70+
if(TwigTypeResolveUtil.isWeakCollectionLikeClass(phpClass)) {
71+
return;
72+
}
73+
74+
if (!TwigTypeResolveUtil.getTwigPhpNameTargets(phpClass, text).isEmpty()) {
75+
return;
76+
}
7677
}
7778
}
7879

7980
this.holder.registerProblem(element, "Field or method not found", ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
8081
}
8182

82-
private boolean isWeakPhpClass(PhpNamedElement phpNamedElement) {
83-
return phpNamedElement instanceof PhpClass && (
84-
PhpElementsUtil.isInstanceOf((PhpClass) phpNamedElement, "ArrayAccess") ||
85-
PhpElementsUtil.isInstanceOf((PhpClass) phpNamedElement, "Iterator")
86-
);
87-
}
88-
8983
private ElementPattern<PsiElement> getTypeCompletionPattern() {
9084
return typeCompletionPattern != null ? typeCompletionPattern : (typeCompletionPattern = TwigPattern.getTypeCompletionPattern());
9185
}

src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/util/TwigTypeResolveUtil.java

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ public static Collection<TwigTypeContainer> resolveTwigMethodName(@NotNull PsiEl
145145
Collection<PsiVariable> rootVariables = getRootVariableByName(psiElement, rootType);
146146
if (types.size() == 1) {
147147
Project project = psiElement.getProject();
148-
Collection<TwigTypeContainer> twigTypeContainers = TwigTypeContainer.fromCollection(project, rootVariables);
148+
Collection<TwigTypeContainer> twigTypeContainers = TwigTypeContainer.fromCollection(rootVariables);
149149
for(TwigTypeResolver twigTypeResolver: TWIG_TYPE_RESOLVERS) {
150150
twigTypeResolver.resolve(project, twigTypeContainers, twigTypeContainers, rootType, new ArrayList<>(), rootVariables);
151151
}
@@ -154,7 +154,7 @@ public static Collection<TwigTypeContainer> resolveTwigMethodName(@NotNull PsiEl
154154
}
155155

156156
Project project = psiElement.getProject();
157-
Collection<TwigTypeContainer> type = TwigTypeContainer.fromCollection(project, rootVariables);
157+
Collection<TwigTypeContainer> type = TwigTypeContainer.fromCollection(rootVariables);
158158
Collection<List<TwigTypeContainer>> previousElements = new ArrayList<>();
159159
previousElements.add(new ArrayList<>(type));
160160

@@ -546,23 +546,18 @@ private static Collection<TwigTypeContainer> resolveTwigMethodName(@NotNull Proj
546546

547547
for(TwigTypeContainer phpNamedElement: previousElement) {
548548

549-
if(phpNamedElement.getPhpNamedElement() != null) {
550-
for(PhpNamedElement target : getTwigPhpNameTargets(phpNamedElement.getPhpNamedElement(), typeName)) {
551-
PhpType phpType = target.getType();
549+
for(PhpNamedElement target : getTwigPhpNameTargets(project, phpNamedElement, typeName)) {
550+
PhpType phpType = target.getType();
552551

553-
// @TODO: provide extension
554-
// custom resolving for Twig here: "app.user" => can also be a general solution just support the "getToken()->getUser()"
555-
if (target instanceof Method && StaticVariableCollector.isUserMethod((Method) target)) {
556-
phpNamedElements.addAll(getApplicationUserImplementations(target.getProject()));
557-
}
552+
// @TODO: provide extension
553+
// custom resolving for Twig here: "app.user" => can also be a general solution just support the "getToken()->getUser()"
554+
if (target instanceof Method && StaticVariableCollector.isUserMethod((Method) target)) {
555+
phpNamedElements.addAll(getApplicationUserImplementations(project));
556+
}
558557

559-
// @TODO: use full resolving for object, that would allow using TypeProviders and core PhpStorm feature
560-
for (String typeString: phpType.filterPrimitives().getTypes()) {
561-
PhpClass phpClass = PhpElementsUtil.getClassInterface(phpNamedElement.getPhpNamedElement().getProject(), typeString);
562-
if(phpClass != null) {
563-
phpNamedElements.add(new TwigTypeContainer(phpClass));
564-
}
565-
}
558+
Set<String> types = phpType.filterPrimitives().getTypes();
559+
if (!types.isEmpty()) {
560+
phpNamedElements.add(new TwigTypeContainer(types));
566561
}
567562
}
568563

@@ -584,7 +579,7 @@ private static Collection<TwigTypeContainer> getApplicationUserImplementations(@
584579
.getAllSubclasses(project, "\\Symfony\\Component\\Security\\Core\\User\\UserInterface")
585580
.stream()
586581
.filter(phpClass -> !phpClass.isInterface()) // filter out implementation like AdvancedUserInterface
587-
.map(TwigTypeContainer::new)
582+
.map(phpClass -> new TwigTypeContainer(Collections.singleton(phpClass.getFQN())))
588583
.collect(Collectors.toList());
589584
}
590585

@@ -635,6 +630,26 @@ public static Collection<? extends PhpNamedElement> getTwigPhpNameTargets(PhpNam
635630
return targets;
636631
}
637632

633+
@NotNull
634+
public static Collection<PhpClass> resolveTwigTypeClasses(@NotNull Project project, @NotNull TwigTypeContainer twigTypeContainer) {
635+
if (twigTypeContainer.getTypes().isEmpty()) {
636+
return Collections.emptyList();
637+
}
638+
639+
return PhpElementsUtil.getClassFromPhpTypeSet(project, twigTypeContainer.getTypes());
640+
}
641+
642+
@NotNull
643+
public static Collection<? extends PhpNamedElement> getTwigPhpNameTargets(@NotNull Project project, @NotNull TwigTypeContainer twigTypeContainer, @NotNull String variableName) {
644+
Collection<PhpNamedElement> targets = new ArrayList<>();
645+
646+
for (PhpClass phpClass : resolveTwigTypeClasses(project, twigTypeContainer)) {
647+
targets.addAll(getTwigPhpNameTargets(phpClass, variableName));
648+
}
649+
650+
return targets;
651+
}
652+
638653

639654
public static String getTypeDisplayName(Project project, Set<String> types) {
640655

@@ -685,6 +700,10 @@ public static boolean isTwigAccessibleMethod(@NotNull Method method) {
685700
return !name.startsWith("set") && !name.startsWith("__");
686701
}
687702

703+
public static boolean isWeakCollectionLikeClass(@NotNull PhpClass phpClass) {
704+
return PhpElementsUtil.isInstanceOf(phpClass, "ArrayAccess") || PhpElementsUtil.isInstanceOf(phpClass, "Iterator");
705+
}
706+
688707
public static boolean isPropertyShortcutMethod(String methodName) {
689708
for (String shortcut: PROPERTY_SHORTCUTS) {
690709
if (methodName.startsWith(shortcut) && methodName.length() > shortcut.length()) {

src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/variable/TwigTypeContainer.java

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,34 @@
11
package fr.adrienbrault.idea.symfony2plugin.templating.variable;
22

3-
import com.intellij.openapi.project.Project;
4-
import com.jetbrains.php.lang.psi.elements.PhpClass;
5-
import com.jetbrains.php.lang.psi.elements.PhpNamedElement;
63
import fr.adrienbrault.idea.symfony2plugin.templating.variable.dict.PsiVariable;
74
import fr.adrienbrault.idea.symfony2plugin.templating.variable.resolver.holder.FormFieldDataHolder;
85
import fr.adrienbrault.idea.symfony2plugin.templating.variable.resolver.holder.FormViewDataHolder;
9-
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
6+
import org.jetbrains.annotations.NotNull;
107
import org.jetbrains.annotations.Nullable;
118

129
import java.util.ArrayList;
1310
import java.util.Collection;
11+
import java.util.Collections;
12+
import java.util.HashSet;
1413
import java.util.List;
14+
import java.util.Set;
1515

1616
/**
1717
* @author Daniel Espendiller <daniel@espendiller.net>
1818
*/
1919
public class TwigTypeContainer {
2020

21-
private final PhpNamedElement phpNamedElement;
21+
private final Set<String> types = new HashSet<>();
2222
private final String stringElement;
2323
private final FormFieldDataHolder formFieldDataHolder;
2424
private final FormViewDataHolder formViewDataHolder;
2525

26-
public TwigTypeContainer(PhpNamedElement phpNamedElement) {
27-
this(phpNamedElement, null);
26+
public TwigTypeContainer(@NotNull Collection<String> types) {
27+
this(types, null);
2828
}
2929

30-
public TwigTypeContainer(PhpNamedElement phpNamedElement, @Nullable FormViewDataHolder formViewDataHolder) {
31-
this.phpNamedElement = phpNamedElement;
30+
private TwigTypeContainer(@NotNull Collection<String> types, @Nullable FormViewDataHolder formViewDataHolder) {
31+
this.types.addAll(types);
3232
this.stringElement = null;
3333
this.formFieldDataHolder = null;
3434
this.formViewDataHolder = formViewDataHolder;
@@ -39,35 +39,35 @@ public TwigTypeContainer(String stringElement) {
3939
}
4040

4141
public TwigTypeContainer(String stringElement, @Nullable FormFieldDataHolder formFieldDataHolder) {
42-
this.phpNamedElement = null;
4342
this.stringElement = stringElement;
4443
this.formFieldDataHolder = formFieldDataHolder;
4544
this.formViewDataHolder = null;
4645
}
4746

48-
@Nullable
49-
public PhpNamedElement getPhpNamedElement() {
50-
return phpNamedElement;
47+
@NotNull
48+
public Set<String> getTypes() {
49+
return Collections.unmodifiableSet(types);
5150
}
5251

5352
@Nullable
5453
public String getStringElement() {
5554
return stringElement;
5655
}
5756

58-
public static Collection<TwigTypeContainer> fromCollection(Project project, Collection<PsiVariable> psiVariables) {
57+
public static Collection<TwigTypeContainer> fromCollection(Collection<PsiVariable> psiVariables) {
5958

6059
List<TwigTypeContainer> twigTypeContainerList = new ArrayList<>();
6160

62-
for(PsiVariable phpNamedElement :psiVariables) {
63-
Collection<PhpClass> phpClass = PhpElementsUtil.getClassFromPhpTypeSet(project, phpNamedElement.getTypes());
64-
if(!phpClass.isEmpty()) {
65-
FormViewDataHolder formViewDataHolder = phpNamedElement.getFormTypeFqns().isEmpty()
66-
? null
67-
: new FormViewDataHolder(phpNamedElement.getFormTypeFqns());
68-
69-
twigTypeContainerList.add(new TwigTypeContainer(phpClass.iterator().next(), formViewDataHolder));
61+
for(PsiVariable psiVariable : psiVariables) {
62+
if (psiVariable.getTypes().isEmpty()) {
63+
continue;
7064
}
65+
66+
FormViewDataHolder formViewDataHolder = psiVariable.getFormTypeFqns().isEmpty()
67+
? null
68+
: new FormViewDataHolder(psiVariable.getFormTypeFqns());
69+
70+
twigTypeContainerList.add(new TwigTypeContainer(psiVariable.getTypes(), formViewDataHolder));
7171
}
7272

7373
return twigTypeContainerList;

0 commit comments

Comments
 (0)