Skip to content

Commit 702479c

Browse files
committed
support Twig lexer changes in PhpStorm 2017.3.2 #1123 #1125
1 parent f5b4780 commit 702479c

18 files changed

+106
-71
lines changed

.travis.yml

+2-3
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,11 @@ before_script:
1313
- chmod +x travis.sh
1414

1515
env:
16-
- PHPSTORM_ENV=2017.2
17-
- PHPSTORM_ENV=2017.2.4
16+
#- PHPSTORM_ENV=2017.2 # unsupported api
17+
#- PHPSTORM_ENV=2017.2.4 # unsupported api
1818
- PHPSTORM_ENV=2017.3.2
1919
#- PHPSTORM_ENV=eap # disabled never used
2020

2121
matrix:
2222
allow_failures:
2323
- env: PHPSTORM_ENV=eap
24-
- env: PHPSTORM_ENV=2017.3.2

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ Changelog
22
=========
33

44
# Version names
5+
* 0.16.x: PhpStorm 2017.3.2+
56
* 0.15.x: PhpStorm 2017.2+
67
* 0.14.x: PhpStorm 2017.1+ (no support)
78
* 0.13.x: PhpStorm 2016.3.1 (no support)

META-INF/plugin.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@
113113
</change-notes>
114114

115115
<!-- please see http://confluence.jetbrains.net/display/IDEADEV/Build+Number+Ranges for description -->
116-
<idea-version since-build="172.3317"/>
116+
<idea-version since-build="173.4127"/>
117117

118118
<extensions defaultExtensionNs="com.jetbrains.php">
119119
<typeProvider3 implementation="fr.adrienbrault.idea.symfony2plugin.dic.SymfonyContainerTypeProvider"/>

src/fr/adrienbrault/idea/symfony2plugin/templating/BlockGotoCompletionRegistrar.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
public class BlockGotoCompletionRegistrar implements GotoCompletionRegistrar {
2424
public void register(@NotNull GotoCompletionRegistrarParameter registrar) {
2525
// {{ block('foo_block') }}
26-
registrar.register(TwigPattern.getPrintBlockFunctionPattern("block"), psiElement -> {
26+
registrar.register(TwigPattern.getPrintBlockOrTagFunctionPattern("block"), psiElement -> {
2727
if (!Symfony2ProjectComponent.isEnabled(psiElement)) {
2828
return null;
2929
}

src/fr/adrienbrault/idea/symfony2plugin/templating/TwigLineMarkerProvider.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public void collectSlowLineMarkers(@NotNull List<PsiElement> psiElements, @NotNu
6969
if(overwrites != null) {
7070
results.add(overwrites);
7171
}
72-
} else if (TwigPattern.getBlockTagPattern().accepts(psiElement) || TwigPattern.getPrintBlockFunctionPattern("block").accepts(psiElement)) {
72+
} else if (TwigPattern.getBlockTagPattern().accepts(psiElement) || TwigPattern.getPrintBlockOrTagFunctionPattern("block").accepts(psiElement)) {
7373
// blocks: {% block 'foobar' %}, {{ block('foobar') }}
7474

7575
VirtualFile virtualFile = psiElement.getContainingFile().getVirtualFile();
@@ -240,7 +240,7 @@ protected Collection<? extends PsiElement> compute() {
240240
}
241241

242242
@Nullable
243-
private LineMarkerInfo attachBlockOverwrites(PsiElement psiElement, @NotNull FileOverwritesLazyLoader loader) {
243+
private LineMarkerInfo attachBlockOverwrites(@NotNull PsiElement psiElement, @NotNull FileOverwritesLazyLoader loader) {
244244
if(!TwigBlockUtil.hasBlockOverwrites(psiElement, loader)) {
245245
return null;
246246
}

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

+22-28
Original file line numberDiff line numberDiff line change
@@ -93,31 +93,6 @@ public static ElementPattern<PsiElement> getTranslationTokenTagFromPattern() {
9393
);
9494
}
9595

96-
/**
97-
* Check for {{ include('|') }}
98-
*
99-
* @param functionName twig function name
100-
*/
101-
public static ElementPattern<PsiElement> getPrintBlockFunctionPattern(String... functionName) {
102-
//noinspection unchecked
103-
return PlatformPatterns
104-
.psiElement(TwigTokenTypes.STRING_TEXT)
105-
.withParent(
106-
PlatformPatterns.psiElement(TwigElementTypes.PRINT_BLOCK)
107-
)
108-
.afterLeafSkipping(
109-
PlatformPatterns.or(
110-
PlatformPatterns.psiElement(TwigTokenTypes.LBRACE),
111-
PlatformPatterns.psiElement(PsiWhiteSpace.class),
112-
PlatformPatterns.psiElement(TwigTokenTypes.WHITE_SPACE),
113-
PlatformPatterns.psiElement(TwigTokenTypes.SINGLE_QUOTE),
114-
PlatformPatterns.psiElement(TwigTokenTypes.DOUBLE_QUOTE)
115-
),
116-
PlatformPatterns.psiElement(TwigTokenTypes.IDENTIFIER).withText(PlatformPatterns.string().oneOf(functionName))
117-
)
118-
.withLanguage(TwigLanguage.INSTANCE);
119-
}
120-
12196
/**
12297
* {% include ['', ~ '', ''] %}
12398
*/
@@ -184,12 +159,18 @@ public static ElementPattern<PsiElement> getPrintBlockOrTagFunctionPattern(Strin
184159
.psiElement(TwigTokenTypes.STRING_TEXT)
185160
.withParent(
186161
PlatformPatterns.or(
162+
163+
// old and inconsistently implementations of FUNCTION_CALL:
164+
// eg {% if asset('') %} does not provide a FUNCTION_CALL whereas a print block does
187165
PlatformPatterns.psiElement(TwigElementTypes.PRINT_BLOCK),
188166
PlatformPatterns.psiElement(TwigElementTypes.TAG),
189167
PlatformPatterns.psiElement(TwigElementTypes.IF_TAG),
190168
PlatformPatterns.psiElement(TwigElementTypes.SET_TAG),
191169
PlatformPatterns.psiElement(TwigElementTypes.ELSE_TAG),
192-
PlatformPatterns.psiElement(TwigElementTypes.ELSEIF_TAG)
170+
PlatformPatterns.psiElement(TwigElementTypes.ELSEIF_TAG),
171+
172+
// PhpStorm 2017.3.2: {{ asset('') }}
173+
PlatformPatterns.psiElement(TwigElementTypes.FUNCTION_CALL)
193174
)
194175
)
195176
.afterLeafSkipping(
@@ -587,7 +568,12 @@ public static ElementPattern<PsiElement> getEmbedPattern() {
587568
}
588569

589570
public static ElementPattern<PsiElement> getPrintBlockFunctionPattern() {
590-
return PlatformPatterns.psiElement().withParent(PlatformPatterns.psiElement(TwigElementTypes.PRINT_BLOCK)).withLanguage(TwigLanguage.INSTANCE);
571+
return PlatformPatterns.psiElement().withParent(PlatformPatterns.or(
572+
PlatformPatterns.psiElement(TwigElementTypes.PRINT_BLOCK),
573+
574+
// PhpStorm 2017.3.2
575+
PlatformPatterns.psiElement(TwigElementTypes.FUNCTION_CALL)
576+
)).withLanguage(TwigLanguage.INSTANCE);
591577
}
592578

593579
/**
@@ -951,7 +937,15 @@ public static ElementPattern<PsiElement> getAutocompletableAssetPattern() {
951937
;
952938
}
953939

954-
public static ElementPattern<PsiElement> getTranslationPattern(String... type) {
940+
/**
941+
* Pattern to match given string before "filter" with given function name
942+
*
943+
* {{ 'foo<caret>bar'|trans }}
944+
* {{ 'foo<caret>bar'|trans() }}
945+
* {{ 'foo<caret>bar'|transchoice() }}
946+
*/
947+
@NotNull
948+
public static ElementPattern<PsiElement> getTranslationKeyPattern(@NotNull String... type) {
955949
//noinspection unchecked
956950
return
957951
PlatformPatterns

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public TwigTemplateCompletionContributor() {
6767

6868
// all file template "include" pattern
6969
extend(CompletionType.BASIC, PlatformPatterns.or(
70-
TwigPattern.getPrintBlockFunctionPattern("include", "source"),
70+
TwigPattern.getPrintBlockOrTagFunctionPattern("include", "source"),
7171
TwigPattern.getIncludeTagArrayPattern(),
7272
TwigPattern.getTagTernaryPattern(TwigElementTypes.INCLUDE_TAG)
7373
), new TemplateCompletionProvider());
@@ -76,7 +76,7 @@ public TwigTemplateCompletionContributor() {
7676
// provides support for 'a<xxx>'|transchoice(2, {'%foo%' : bar|default}, 'Domain')
7777
extend(
7878
CompletionType.BASIC,
79-
TwigPattern.getTranslationPattern("trans", "transchoice"),
79+
TwigPattern.getTranslationKeyPattern("trans", "transchoice"),
8080
new CompletionProvider<CompletionParameters>() {
8181
public void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet resultSet) {
8282
if(!Symfony2ProjectComponent.isEnabled(parameters.getPosition())) {

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

+5-3
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public PsiElement[] getGotoDeclarationTargets(PsiElement psiElement, int offset,
6363
targets.addAll(getRouteParameterGoTo(psiElement));
6464
}
6565

66-
if(TwigPattern.getTemplateFileReferenceTagPattern().accepts(psiElement) || TwigPattern.getPrintBlockFunctionPattern("include", "source").accepts(psiElement)) {
66+
if(TwigPattern.getTemplateFileReferenceTagPattern().accepts(psiElement) || TwigPattern.getPrintBlockOrTagFunctionPattern("include", "source").accepts(psiElement)) {
6767
// support: {% include() %}, {{ include() }}
6868
targets.addAll(getTwigFiles(psiElement, offset));
6969
} else if (PlatformPatterns.psiElement(TwigTokenTypes.STRING_TEXT).withText(PlatformPatterns.string().endsWith(".twig")).accepts(psiElement)) {
@@ -91,7 +91,7 @@ public PsiElement[] getGotoDeclarationTargets(PsiElement psiElement, int offset,
9191
targets.addAll(getTranslationDomainGoto(psiElement));
9292
}
9393

94-
if (TwigPattern.getTranslationPattern("trans", "transchoice").accepts(psiElement)) {
94+
if (TwigPattern.getTranslationKeyPattern("trans", "transchoice").accepts(psiElement)) {
9595
targets.addAll(getTranslationKeyGoTo(psiElement));
9696
}
9797

@@ -140,7 +140,9 @@ public PsiElement[] getGotoDeclarationTargets(PsiElement psiElement, int offset,
140140
.psiElement(TwigTokenTypes.IDENTIFIER)
141141
.withParent(PlatformPatterns.or(
142142
PlatformPatterns.psiElement(TwigElementTypes.PRINT_BLOCK),
143-
PlatformPatterns.psiElement(TwigElementTypes.SET_TAG)
143+
PlatformPatterns.psiElement(TwigElementTypes.SET_TAG),
144+
145+
PlatformPatterns.psiElement(TwigElementTypes.FUNCTION_CALL)
144146
)).withLanguage(TwigLanguage.INSTANCE).accepts(psiElement)) {
145147

146148
targets.addAll(this.getFunctions(psiElement));

src/fr/adrienbrault/idea/symfony2plugin/templating/inspection/TwigTemplateMissingInspection.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public PsiElementVisitor buildVisitor(final @NotNull ProblemsHolder holder, bool
3131
return new PsiElementVisitor() {
3232
@Override
3333
public void visitElement(PsiElement element) {
34-
if((TwigPattern.getTemplateFileReferenceTagPattern().accepts(element) || TwigPattern.getPrintBlockFunctionPattern("include", "source").accepts(element)) && TwigUtil.isValidStringWithoutInterpolatedOrConcat(element)) {
34+
if((TwigPattern.getTemplateFileReferenceTagPattern().accepts(element) || TwigPattern.getPrintBlockOrTagFunctionPattern("include", "source").accepts(element)) && TwigUtil.isValidStringWithoutInterpolatedOrConcat(element)) {
3535
invoke(element, holder);
3636
}
3737

src/fr/adrienbrault/idea/symfony2plugin/templating/util/TwigUtil.java

+36-20
Original file line numberDiff line numberDiff line change
@@ -233,14 +233,17 @@ public static Map<String, Collection<PsiElement>> getTemplateAnnotationFilesWith
233233
public static String getTransDefaultDomainOnScope(@NotNull PsiElement position) {
234234
// {% embed 'foo.html.twig' with { foo: '<caret>'|trans } %}
235235
PsiElement parent = position.getParent();
236-
if(parent != null && parent.getNode().getElementType() == TwigElementTypes.EMBED_TAG) {
237-
PsiElement firstParent = PsiTreeUtil.findFirstParent(position, true, psiElement -> {
238-
IElementType elementType = psiElement.getNode().getElementType();
239-
return elementType != TwigElementTypes.EMBED_TAG && elementType != TwigElementTypes.EMBED_STATEMENT;
240-
});
236+
if(parent != null && parent.getNode().getElementType() == TwigElementTypes.LITERAL) {
237+
PsiElement parent2 = parent.getParent();
238+
if(parent2 != null && parent2.getNode().getElementType() == TwigElementTypes.EMBED_TAG) {
239+
PsiElement firstParent = PsiTreeUtil.findFirstParent(parent, true, psiElement -> {
240+
IElementType elementType = psiElement.getNode().getElementType();
241+
return elementType != TwigElementTypes.EMBED_TAG && elementType != TwigElementTypes.EMBED_STATEMENT;
242+
});
241243

242-
if(firstParent != null) {
243-
position = firstParent;
244+
if(firstParent != null) {
245+
position = firstParent;
246+
}
244247
}
245248
}
246249

@@ -1556,23 +1559,27 @@ public static Collection<String> getIncludeTagStrings(@NotNull TwigTagWithFileRe
15561559

15571560
/**
15581561
* Visit string values of given array start brace
1559-
* ["foobar"]
1562+
*
1563+
* ["foobar", "foobar"]
1564+
* {"foobar", "foobar"}
15601565
*/
1561-
public static void visitStringInArray(@NotNull PsiElement arrayStartBrace, @NotNull Consumer<Pair<String, PsiElement>> pair) {
1566+
private static void visitStringInArray(@NotNull PsiElement arrayStartBrace, @NotNull Consumer<Pair<String, PsiElement>> pair) {
15621567
// match: "([,)''(,])"
15631568
Collection<PsiElement> questString = PsiElementUtils.getNextSiblingOfTypes(arrayStartBrace, PlatformPatterns.psiElement(TwigTokenTypes.STRING_TEXT)
15641569
.afterLeafSkipping(
15651570
TwigPattern.STRING_WRAP_PATTERN,
15661571
PlatformPatterns.or(
15671572
PlatformPatterns.psiElement(TwigTokenTypes.COMMA),
1568-
PlatformPatterns.psiElement(TwigTokenTypes.LBRACE_SQ)
1569-
)
1573+
PlatformPatterns.psiElement(TwigTokenTypes.LBRACE_SQ),
1574+
PlatformPatterns.psiElement(TwigTokenTypes.LBRACE_CURL)
1575+
)
15701576
)
15711577
.beforeLeafSkipping(
15721578
TwigPattern.STRING_WRAP_PATTERN,
15731579
PlatformPatterns.or(
15741580
PlatformPatterns.psiElement(TwigTokenTypes.COMMA),
1575-
PlatformPatterns.psiElement(TwigTokenTypes.RBRACE_SQ)
1581+
PlatformPatterns.psiElement(TwigTokenTypes.RBRACE_SQ),
1582+
PlatformPatterns.psiElement(TwigTokenTypes.RBRACE_CURL)
15761583
)
15771584
)
15781585
);
@@ -1620,7 +1627,7 @@ public void visitElement(PsiElement element) {
16201627
psiElement.acceptChildren(new PsiRecursiveElementVisitor() {
16211628
@Override
16221629
public void visitElement(PsiElement element) {
1623-
if(target[0] == null && TwigPattern.getPrintBlockFunctionPattern("block").accepts(element)) {
1630+
if(target[0] == null && TwigPattern.getPrintBlockOrTagFunctionPattern("block").accepts(element)) {
16241631
target[0] = element;
16251632
}
16261633
super.visitElement(element);
@@ -2213,7 +2220,12 @@ public static void visitTemplateIncludes(@NotNull TwigFile twigFile, @NotNull Co
22132220
visitTemplateIncludes(
22142221
twigFile,
22152222
consumer,
2216-
TemplateInclude.TYPE.EMBED, TemplateInclude.TYPE.INCLUDE, TemplateInclude.TYPE.INCLUDE_FUNCTION, TemplateInclude.TYPE.FROM, TemplateInclude.TYPE.IMPORT, TemplateInclude.TYPE.FORM_THEME
2223+
TemplateInclude.TYPE.EMBED,
2224+
TemplateInclude.TYPE.INCLUDE,
2225+
TemplateInclude.TYPE.INCLUDE_FUNCTION,
2226+
TemplateInclude.TYPE.FROM,
2227+
TemplateInclude.TYPE.IMPORT,
2228+
TemplateInclude.TYPE.FORM_THEME
22172229
);
22182230
}
22192231

@@ -2262,7 +2274,7 @@ private static void visitTemplateIncludes(@NotNull TwigFile twigFile, @NotNull C
22622274
// {{ include() }}
22632275
// {{ source() }}
22642276
if(myTypes.contains(TemplateInclude.TYPE.INCLUDE_FUNCTION)) {
2265-
PsiElement includeTag = PsiElementUtils.getChildrenOfType(psiElement, TwigPattern.getPrintBlockFunctionPattern("include", "source"));
2277+
PsiElement includeTag = PsiElementUtils.getChildrenOfType(psiElement, TwigPattern.getPrintBlockOrTagFunctionPattern("include", "source"));
22662278
if(includeTag != null) {
22672279
String templateName = includeTag.getText();
22682280
if(StringUtils.isNotBlank(templateName)) {
@@ -2299,17 +2311,21 @@ private static void visitTemplateIncludes(@NotNull TwigFile twigFile, @NotNull C
22992311
}
23002312
}
23012313

2302-
// {% form_theme form.child 'form/fields_child.html.twig' %}
2314+
// {% form_theme form.child with ['form/fields_child.html.twig'] %}
23032315
PsiElement withElement = PsiElementUtils.getNextSiblingOfType(tagElement, PlatformPatterns.psiElement().withElementType(TwigTokenTypes.IDENTIFIER).withText("with"));
23042316
if(withElement != null) {
2305-
PsiElement arrayStart = PsiElementUtils.getNextSiblingAndSkip(tagElement, TwigTokenTypes.LBRACE_SQ,
2317+
// find LITERAL "[", "{"
2318+
PsiElement arrayStart = PsiElementUtils.getNextSiblingAndSkip(tagElement, TwigElementTypes.LITERAL,
23062319
TwigTokenTypes.IDENTIFIER, TwigTokenTypes.SINGLE_QUOTE, TwigTokenTypes.DOUBLE_QUOTE, TwigTokenTypes.DOT
23072320
);
23082321

23092322
if(arrayStart != null) {
2310-
visitStringInArray(arrayStart, pair ->
2311-
consumer.consume(new TemplateInclude(psiElement, pair.getFirst(), TemplateInclude.TYPE.FORM_THEME))
2312-
);
2323+
PsiElement firstChild = arrayStart.getFirstChild();
2324+
if(firstChild != null) {
2325+
visitStringInArray(firstChild, pair ->
2326+
consumer.consume(new TemplateInclude(psiElement, pair.getFirst(), TemplateInclude.TYPE.FORM_THEME))
2327+
);
2328+
}
23132329
}
23142330
}
23152331
}

src/fr/adrienbrault/idea/symfony2plugin/templating/variable/collector/IncludeVariableCollector.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ public void visitElement(PsiElement element) {
208208

209209
if(element instanceof TwigCompositeElement) {
210210
// {{ include('template.html') }}
211-
PsiElement includeTag = PsiElementUtils.getChildrenOfType(element, TwigPattern.getPrintBlockFunctionPattern("include"));
211+
PsiElement includeTag = PsiElementUtils.getChildrenOfType(element, TwigPattern.getPrintBlockOrTagFunctionPattern("include"));
212212
if(includeTag != null) {
213213
collectContextVars(TwigTokenTypes.IDENTIFIER, element, includeTag);
214214
}

src/fr/adrienbrault/idea/symfony2plugin/translation/TranslationPlaceholderGotoCompletionRegistrar.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ public GotoCompletionProvider getProvider(@NotNull PsiElement psiElement) {
130130
}
131131
}
132132

133+
/**
134+
* {{ 'symfony.great'|trans({'fo<caret>f'}, 'symfony')) }}
135+
*/
133136
private static class MyTwigTransFilterCompletionContributor implements GotoCompletionContributor {
134137
@NotNull
135138
private final String filter;
@@ -146,7 +149,13 @@ public GotoCompletionProvider getProvider(@NotNull PsiElement psiElement) {
146149
return null;
147150
}
148151

149-
PsiElement function = PsiElementUtils.getPrevSiblingOfType(parent, TwigPattern.getTranslationPattern(this.filter));
152+
PsiElement functionCall = parent.getParent();
153+
if(functionCall.getNode().getElementType() != TwigElementTypes.FUNCTION_CALL) {
154+
return null;
155+
}
156+
157+
// find translation key: 'symfony.great'
158+
PsiElement function = PsiElementUtils.getPrevSiblingOfType(functionCall, TwigPattern.getTranslationKeyPattern(this.filter));
150159
if(function == null) {
151160
return null;
152161
}

src/fr/adrienbrault/idea/symfony2plugin/translation/inspection/TwigTranslationKeyInspection.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ private static class MyTranslationKeyPsiElementVisitor extends PsiElementVisitor
3737

3838
@Override
3939
public void visitElement(PsiElement psiElement) {
40-
if(!TwigPattern.getTranslationPattern("trans", "transchoice").accepts(psiElement)) {
40+
if(!TwigPattern.getTranslationKeyPattern("trans", "transchoice").accepts(psiElement)) {
4141
super.visitElement(psiElement);
4242
return;
4343
}

src/fr/adrienbrault/idea/symfony2plugin/translation/intention/TwigTranslationKeyIntention.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public String getText() {
5959

6060
@Nullable
6161
private Pair<String, String> getKeyAndDomain(@NotNull PsiElement psiElement) {
62-
if(!TwigPattern.getTranslationPattern("trans", "transchoice").accepts(psiElement)) {
62+
if(!TwigPattern.getTranslationKeyPattern("trans", "transchoice").accepts(psiElement)) {
6363
return null;
6464
}
6565

0 commit comments

Comments
 (0)