Skip to content

Commit 7078b7c

Browse files
committed
2020.3 support: handle a new psi structure for twig variables and fields
Twig variables and fields used to be parsed as PsiIdentifier; now they are wrapped into new PSI elements: TwigVariableReference and TwigFieldReference. TwigFieldReference may be nested and contain a variable or other fields inside. https://jetbrains.org/intellij/sdk/docs/products/phpstorm/php_open_api_breaking_changes_203.html#twig-support-changes
1 parent 2c507c1 commit 7078b7c

File tree

5 files changed

+150
-100
lines changed

5 files changed

+150
-100
lines changed

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

Lines changed: 38 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,11 @@
99
import com.intellij.psi.PsiErrorElement;
1010
import com.intellij.psi.PsiWhiteSpace;
1111
import com.intellij.psi.tree.IElementType;
12+
import com.intellij.psi.tree.TokenSet;
1213
import com.intellij.util.ProcessingContext;
1314
import com.jetbrains.twig.TwigLanguage;
1415
import com.jetbrains.twig.TwigTokenTypes;
15-
import com.jetbrains.twig.elements.TwigBlockTag;
16-
import com.jetbrains.twig.elements.TwigCompositeElement;
17-
import com.jetbrains.twig.elements.TwigElementTypes;
16+
import com.jetbrains.twig.elements.*;
1817
import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigTypeResolveUtil;
1918
import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigUtil;
2019
import org.jetbrains.annotations.NotNull;
@@ -414,16 +413,18 @@ public static ElementPattern<PsiElement> getAfterIsTokenWithOneIdentifierLeafPat
414413
//noinspection unchecked
415414
return PlatformPatterns
416415
.psiElement()
417-
.afterLeafSkipping(
418-
PlatformPatterns.or(
419-
PlatformPatterns.psiElement(PsiWhiteSpace.class),
420-
PlatformPatterns.psiElement(TwigTokenTypes.WHITE_SPACE)
421-
),
422-
PlatformPatterns.psiElement(TwigTokenTypes.IDENTIFIER).afterLeafSkipping(PlatformPatterns.psiElement(PsiWhiteSpace.class), PlatformPatterns.or(
423-
PlatformPatterns.psiElement(TwigTokenTypes.IS),
424-
PlatformPatterns.psiElement(TwigTokenTypes.NOT)
416+
.withParent(PlatformPatterns
417+
.psiElement()
418+
.afterLeafSkipping(
419+
PlatformPatterns.or(
420+
PlatformPatterns.psiElement(PsiWhiteSpace.class),
421+
PlatformPatterns.psiElement(TwigTokenTypes.WHITE_SPACE)
422+
),
423+
PlatformPatterns.psiElement(TwigTokenTypes.IDENTIFIER).afterLeafSkipping(PlatformPatterns.psiElement(PsiWhiteSpace.class), PlatformPatterns.or(
424+
PlatformPatterns.psiElement(TwigTokenTypes.IS),
425+
PlatformPatterns.psiElement(TwigTokenTypes.NOT)
426+
))
425427
))
426-
)
427428
.withLanguage(TwigLanguage.INSTANCE);
428429
}
429430

@@ -445,41 +446,10 @@ public static ElementPattern<PsiElement> getAfterIsTokenTextPattern() {
445446
* {% if 'foo.bar' <carpet> %}
446447
*/
447448
public static ElementPattern<PsiElement> getAfterOperatorPattern() {
448-
// @TODO: make it some nicer. can wrap it with whitespace
449-
450-
//noinspection unchecked
451-
ElementPattern<PsiElement> or = PlatformPatterns.or(
452-
PlatformPatterns.psiElement(PsiWhiteSpace.class),
453-
PlatformPatterns.psiElement(TwigTokenTypes.WHITE_SPACE),
454-
PlatformPatterns.psiElement(TwigTokenTypes.IDENTIFIER),
455-
PlatformPatterns.psiElement(TwigTokenTypes.SINGLE_QUOTE),
456-
PlatformPatterns.psiElement(TwigTokenTypes.STRING_TEXT),
457-
PlatformPatterns.psiElement(TwigTokenTypes.DOT),
458-
PlatformPatterns.psiElement(TwigTokenTypes.DOUBLE_QUOTE),
459-
PlatformPatterns.psiElement(TwigTokenTypes.LBRACE),
460-
PlatformPatterns.psiElement(TwigTokenTypes.RBRACE),
461-
PlatformPatterns.psiElement(TwigTokenTypes.LBRACE_SQ),
462-
PlatformPatterns.psiElement(TwigTokenTypes.RBRACE_SQ),
463-
PlatformPatterns.psiElement(TwigTokenTypes.NUMBER),
464-
PlatformPatterns.psiElement(TwigTokenTypes.FILTER)
465-
);
466-
467-
//noinspection unchecked
468-
ElementPattern<PsiElement> anIf = PlatformPatterns.or(
469-
PlatformPatterns.psiElement(TwigTokenTypes.TAG_NAME).withText("if"),
470-
PlatformPatterns.psiElement(TwigTokenTypes.AND),
471-
PlatformPatterns.psiElement(TwigTokenTypes.OR)
472-
);
473-
474449
return PlatformPatterns
475450
.psiElement(TwigTokenTypes.IDENTIFIER)
476-
.afterLeaf(PlatformPatterns.not(
477-
PlatformPatterns.psiElement(TwigTokenTypes.DOT)
478-
))
479-
.withParent(
480-
PlatformPatterns.psiElement(TwigElementTypes.IF_TAG)
481-
)
482-
.afterLeafSkipping(or, anIf)
451+
.inside(PlatformPatterns.psiElement(TwigElementTypes.IF_TAG))
452+
.andNot(PlatformPatterns.psiElement().inside(PlatformPatterns.psiElement(TwigElementTypes.FIELD_REFERENCE)))
483453
.withLanguage(TwigLanguage.INSTANCE);
484454
}
485455

@@ -555,7 +525,12 @@ public static ElementPattern<PsiElement> getEmbedPattern() {
555525
}
556526

557527
static ElementPattern<PsiElement> getPrintBlockFunctionPattern() {
558-
return PlatformPatterns.psiElement(TwigTokenTypes.IDENTIFIER).withParent(getFunctionCallScopePattern()).withLanguage(TwigLanguage.INSTANCE);
528+
return PlatformPatterns.psiElement(TwigTokenTypes.IDENTIFIER)
529+
.inside(PlatformPatterns.or(
530+
PlatformPatterns.psiElement(TwigPsiReference.class),
531+
PlatformPatterns.psiElement(TwigElementTypes.FUNCTION_CALL)))
532+
.inside(getFunctionCallScopePattern())
533+
.withLanguage(TwigLanguage.INSTANCE);
559534
}
560535

561536
public static ElementPattern<PsiElement> getFunctionPattern(@NotNull String ...functionName) {
@@ -601,25 +576,16 @@ private static ElementPattern<PsiElement> getFunctionCallScopePattern() {
601576
*/
602577
public static ElementPattern<PsiElement> getCompletablePattern() {
603578
//noinspection unchecked
604-
return PlatformPatterns.psiElement()
579+
return PlatformPatterns.psiElement(TwigTokenTypes.IDENTIFIER)
580+
.inside(PlatformPatterns.psiElement(TwigElementTypes.VARIABLE_REFERENCE))
605581
.andNot(
582+
PlatformPatterns.psiElement().inside(PlatformPatterns.psiElement(TwigElementTypes.FIELD_REFERENCE))
583+
).inside(
606584
PlatformPatterns.or(
607-
PlatformPatterns.psiElement().afterLeaf(PlatformPatterns.psiElement(TwigTokenTypes.DOT)),
608-
PlatformPatterns.psiElement().afterLeaf(PlatformPatterns.psiElement(TwigTokenTypes.SINGLE_QUOTE)),
609-
PlatformPatterns.psiElement().afterLeaf(PlatformPatterns.psiElement(TwigTokenTypes.DOUBLE_QUOTE))
585+
PlatformPatterns.psiElement(TwigElementTypes.PRINT_BLOCK),
586+
PlatformPatterns.psiElement(TwigElementTypes.SET_TAG)
610587
)
611588
)
612-
.afterLeafSkipping(
613-
PlatformPatterns.or(
614-
PlatformPatterns.psiElement(TwigTokenTypes.LBRACE),
615-
PlatformPatterns.psiElement(PsiWhiteSpace.class)
616-
),
617-
PlatformPatterns.psiElement()
618-
)
619-
.withParent(PlatformPatterns.or(
620-
PlatformPatterns.psiElement(TwigElementTypes.PRINT_BLOCK),
621-
PlatformPatterns.psiElement(TwigElementTypes.SET_TAG)
622-
))
623589
.withLanguage(TwigLanguage.INSTANCE);
624590
}
625591

@@ -873,9 +839,7 @@ public static ElementPattern<PsiElement> getParentFunctionPattern() {
873839
public static ElementPattern<PsiElement> getTypeCompletionPattern() {
874840
return PlatformPatterns
875841
.psiElement(TwigTokenTypes.IDENTIFIER)
876-
.afterLeaf(
877-
PlatformPatterns.psiElement(TwigTokenTypes.DOT)
878-
)
842+
.withParent(PlatformPatterns.psiElement(TwigElementTypes.FIELD_REFERENCE))
879843
.withLanguage(TwigLanguage.INSTANCE);
880844
}
881845

@@ -1080,9 +1044,7 @@ public static ElementPattern<PsiElement> getForTagVariablePattern() {
10801044
// {% for "user" %}
10811045

10821046
//noinspection unchecked
1083-
return PlatformPatterns
1084-
.psiElement(TwigTokenTypes.IDENTIFIER)
1085-
.beforeLeafSkipping(
1047+
return captureVariableOrField().beforeLeafSkipping(
10861048
PlatformPatterns.or(
10871049
PlatformPatterns.psiElement(PsiWhiteSpace.class),
10881050
PlatformPatterns.psiElement(TwigTokenTypes.WHITE_SPACE)
@@ -1129,16 +1091,19 @@ public boolean accepts(@NotNull PsiElement psiElement, ProcessingContext process
11291091
.withLanguage(TwigLanguage.INSTANCE);
11301092
}
11311093

1094+
public static PsiElementPattern.Capture<PsiElement> captureVariableOrField() {
1095+
return PlatformPatterns.psiElement().withElementType(TokenSet.create(TwigElementTypes.VARIABLE_REFERENCE,
1096+
TwigElementTypes.FIELD_REFERENCE));
1097+
}
1098+
11321099
public static ElementPattern<PsiElement> getForTagInVariablePattern() {
11331100

11341101
// {% for key, user in "users" %}
11351102
// {% for user in "users" %}
11361103
// {% for user in "users"|slice(0, 10) %}
11371104

11381105
//noinspection unchecked
1139-
return PlatformPatterns
1140-
.psiElement(TwigTokenTypes.IDENTIFIER)
1141-
.afterLeafSkipping(
1106+
return captureVariableOrField().afterLeafSkipping(
11421107
PlatformPatterns.or(
11431108
PlatformPatterns.psiElement(PsiWhiteSpace.class),
11441109
PlatformPatterns.psiElement(TwigTokenTypes.WHITE_SPACE)
@@ -1153,9 +1118,7 @@ public static ElementPattern<PsiElement> getIfVariablePattern() {
11531118
// {% if "var" %}
11541119

11551120
//noinspection unchecked
1156-
return PlatformPatterns
1157-
.psiElement(TwigTokenTypes.IDENTIFIER)
1158-
.afterLeafSkipping(
1121+
return captureVariableOrField().afterLeafSkipping(
11591122
PlatformPatterns.or(
11601123
PlatformPatterns.psiElement(PsiWhiteSpace.class),
11611124
PlatformPatterns.psiElement(TwigTokenTypes.WHITE_SPACE)
@@ -1177,9 +1140,7 @@ public static ElementPattern<PsiElement> getIfConditionVariablePattern() {
11771140
// and so on
11781141

11791142
//noinspection unchecked
1180-
return PlatformPatterns
1181-
.psiElement(TwigTokenTypes.IDENTIFIER)
1182-
.afterLeafSkipping(
1143+
return captureVariableOrField().afterLeafSkipping(
11831144
PlatformPatterns.or(
11841145
PlatformPatterns.psiElement(PsiWhiteSpace.class),
11851146
PlatformPatterns.psiElement(TwigTokenTypes.WHITE_SPACE)
@@ -1246,9 +1207,7 @@ public static ElementPattern<PsiElement> getSetVariablePattern() {
12461207
// {% set count1 = "var" %}
12471208

12481209
//noinspection unchecked
1249-
return PlatformPatterns
1250-
.psiElement(TwigTokenTypes.IDENTIFIER)
1251-
.afterLeafSkipping(
1210+
return captureVariableOrField().afterLeafSkipping(
12521211
PlatformPatterns.or(
12531212
PlatformPatterns.psiElement(PsiWhiteSpace.class),
12541213
PlatformPatterns.psiElement(TwigTokenTypes.WHITE_SPACE)

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

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import com.intellij.psi.PsiElement;
1111
import com.intellij.psi.PsiManager;
1212
import com.intellij.psi.PsiWhiteSpace;
13-
import com.intellij.psi.util.PsiTreeUtil;
13+
import com.intellij.psi.tree.IElementType;
1414
import com.intellij.util.containers.ContainerUtil;
1515
import com.jetbrains.php.PhpIndex;
1616
import com.jetbrains.php.lang.psi.elements.Field;
@@ -19,7 +19,8 @@
1919
import com.jetbrains.twig.TwigTokenTypes;
2020
import com.jetbrains.twig.elements.TwigBlockTag;
2121
import com.jetbrains.twig.elements.TwigElementTypes;
22-
import com.jetbrains.twig.elements.TwigTagWithFileReference;
22+
import com.jetbrains.twig.elements.TwigPsiReference;
23+
import com.jetbrains.twig.elements.TwigVariableReference;
2324
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent;
2425
import fr.adrienbrault.idea.symfony2plugin.routing.RouteHelper;
2526
import fr.adrienbrault.idea.symfony2plugin.templating.dict.TwigExtension;
@@ -174,8 +175,9 @@ public PsiElement[] getGotoDeclarationTargets(PsiElement psiElement, int offset,
174175
@NotNull
175176
private Collection<PsiElement> getAfterIsToken(@NotNull PsiElement psiElement) {
176177
// find text after if statement
178+
PsiElement actualElement = psiElement.getParent() instanceof TwigVariableReference ? psiElement.getParent() : psiElement;
177179
String text = StringUtils.trim(
178-
PhpElementsUtil.getPrevSiblingAsTextUntil(psiElement, TwigPattern.getAfterIsTokenTextPattern(), false) + psiElement.getText()
180+
PhpElementsUtil.getPrevSiblingAsTextUntil(actualElement, TwigPattern.getAfterIsTokenTextPattern(), false) + actualElement.getText()
179181
);
180182

181183
if(StringUtils.isBlank(text)) {
@@ -191,12 +193,13 @@ private Collection<PsiElement> getAfterIsToken(@NotNull PsiElement psiElement) {
191193
PsiElement whitespace = psiElement.getNextSibling();
192194
if(whitespace instanceof PsiWhiteSpace) {
193195
PsiElement nextSibling = whitespace.getNextSibling();
194-
if(nextSibling != null && nextSibling.getNode().getElementType() == TwigTokenTypes.IDENTIFIER) {
195-
String identifier = nextSibling.getText();
196-
if(StringUtils.isNotBlank(identifier)) {
197-
items.add(text + " " + identifier);
196+
IElementType elementType = nextSibling == null ? null : nextSibling.getNode().getElementType();
197+
if (elementType == TwigTokenTypes.IDENTIFIER || elementType == TwigElementTypes.VARIABLE_REFERENCE) {
198+
String identifier = nextSibling.getText();
199+
if (StringUtils.isNotBlank(identifier)) {
200+
items.add(text + " " + identifier);
201+
}
198202
}
199-
}
200203
}
201204

202205
Collection<PsiElement> psiElements = new ArrayList<>();
@@ -405,7 +408,10 @@ private Collection<PsiElement> getSeeDocTagTargets(@NotNull PsiElement psiElemen
405408
@NotNull
406409
public static Collection<PsiElement> getTypeGoto(@NotNull PsiElement psiElement) {
407410
Collection<PsiElement> targetPsiElements = new HashSet<>();
408-
411+
if (psiElement.getParent() instanceof TwigPsiReference) {
412+
PsiElement defaultResult = ((TwigPsiReference) psiElement.getParent()).resolve();
413+
if (defaultResult != null && defaultResult != psiElement.getParent()) return Collections.singleton(defaultResult);
414+
}
409415
// class, class.method, class.method.method
410416
// click on first item is our class name
411417
Collection<String> beforeLeaf = TwigTypeResolveUtil.formatPsiTypeName(psiElement);
@@ -465,7 +471,7 @@ private Collection<PsiElement> getMacros(@NotNull PsiElement psiElement) {
465471
PsiElement prevSibling = psiElement.getPrevSibling();
466472
if(prevSibling != null && prevSibling.getNode().getElementType() == TwigTokenTypes.DOT) {
467473
PsiElement identifier = prevSibling.getPrevSibling();
468-
if(identifier == null || identifier.getNode().getElementType() != TwigTokenTypes.IDENTIFIER) {
474+
if(identifier == null || identifier.getNode().getElementType() != TwigElementTypes.VARIABLE_REFERENCE) {
469475
return Collections.emptyList();
470476
}
471477

@@ -496,7 +502,8 @@ private Collection<PsiElement> getControllerNameGoto(@NotNull PsiElement psiElem
496502
@NotNull
497503
private Collection<PsiElement> getParentGoto(@NotNull PsiElement psiElement) {
498504
// find printblock
499-
PsiElement printBlock = psiElement.getParent();
505+
PsiElement functionCall = psiElement.getParent();
506+
PsiElement printBlock = functionCall != null ? functionCall.getParent() : null;
500507
if(printBlock == null || !PlatformPatterns.psiElement(TwigElementTypes.PRINT_BLOCK).accepts(printBlock)) {
501508
return Collections.emptyList();
502509
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.jetbrains.twig.TwigTokenTypes;
2020
import com.jetbrains.twig.elements.TwigCompositeElement;
2121
import com.jetbrains.twig.elements.TwigElementTypes;
22+
import com.jetbrains.twig.elements.TwigVariableReference;
2223
import fr.adrienbrault.idea.symfony2plugin.templating.TwigPattern;
2324
import fr.adrienbrault.idea.symfony2plugin.templating.variable.TwigFileVariableCollector;
2425
import fr.adrienbrault.idea.symfony2plugin.templating.variable.TwigFileVariableCollectorParameter;
@@ -39,6 +40,8 @@
3940
import java.util.regex.Pattern;
4041
import java.util.stream.Collectors;
4142

43+
import static fr.adrienbrault.idea.symfony2plugin.templating.TwigPattern.captureVariableOrField;
44+
4245
/**
4346
* @author Daniel Espendiller <daniel@espendiller.net>
4447
*/
@@ -369,6 +372,7 @@ private static void collectForArrayScopeVariables(@NotNull PsiElement psiElement
369372
// {% for user in "users" %}
370373
PsiElement forTag = twigCompositeElement.getFirstChild();
371374
PsiElement inVariable = PsiElementUtils.getChildrenOfType(forTag, TwigPattern.getForTagInVariablePattern());
375+
inVariable = inVariable instanceof TwigVariableReference ? inVariable : PsiTreeUtil.getChildOfType(inVariable, TwigVariableReference.class);
372376
if(inVariable == null) {
373377
return;
374378
}
@@ -629,7 +633,7 @@ private static Collection<String> getForTagIdentifierAsString(PsiElement forTag)
629633
}
630634

631635
// find next IDENTIFIER, eg skip whitespaces
632-
PsiElement psiIdentifier = PsiElementUtils.getNextSiblingOfType(psiIn, PlatformPatterns.psiElement(TwigTokenTypes.IDENTIFIER));
636+
PsiElement psiIdentifier = PsiElementUtils.getNextSiblingOfType(psiIn, captureVariableOrField());
633637
if(psiIdentifier == null) {
634638
return Collections.emptyList();
635639
}

0 commit comments

Comments
 (0)