Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 6276151

Browse files
#2396 add experimental support for extracting Twig "types" variables with types
1 parent e1dd5ae commit 6276151

File tree

3 files changed

+137
-6
lines changed

3 files changed

+137
-6
lines changed

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

Lines changed: 97 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package fr.adrienbrault.idea.symfony2plugin.templating.util;
22

3+
import com.intellij.lang.ASTNode;
34
import com.intellij.openapi.extensions.ExtensionPointName;
45
import com.intellij.openapi.project.Project;
56
import com.intellij.openapi.vfs.VirtualFile;
67
import com.intellij.patterns.PlatformPatterns;
78
import com.intellij.psi.PsiComment;
89
import com.intellij.psi.PsiElement;
910
import com.intellij.psi.PsiWhiteSpace;
11+
import com.intellij.psi.formatter.FormatterUtil;
1012
import com.intellij.psi.tree.IElementType;
1113
import com.intellij.psi.util.PsiTreeUtil;
1214
import com.jetbrains.php.PhpIndex;
@@ -35,6 +37,7 @@
3537
import fr.adrienbrault.idea.symfony2plugin.util.yaml.YamlHelper;
3638
import org.apache.commons.lang3.StringUtils;
3739
import org.jetbrains.annotations.NotNull;
40+
import org.jetbrains.annotations.Nullable;
3841

3942
import java.util.*;
4043
import java.util.regex.Matcher;
@@ -182,7 +185,10 @@ private static Map<String, String> findInlineStatementVariableDocBlock(@NotNull
182185
return variables;
183186
}
184187

185-
Map<String, String> inlineCommentDocsVars = getInlineCommentDocsVars(twigCompositeElement);
188+
Map<String, String> inlineCommentDocsVars = new HashMap<>() {{
189+
putAll(getInlineCommentDocsVars(twigCompositeElement));
190+
putAll(getTypesTagVars(twigCompositeElement));
191+
}};
186192

187193
// visit parent elements for extending scope
188194
if(nextParent) {
@@ -196,14 +202,21 @@ private static Map<String, String> findInlineStatementVariableDocBlock(@NotNull
196202
}
197203

198204
/**
199-
* Find file related doc blocks:
205+
* Find file related doc blocks or "types" tags:
200206
*
201-
* "@var foo \Foo"
207+
* - "@var foo \Foo"
208+
* - "{% types {...} %}"
202209
*/
203210
public static Map<String, String> findFileVariableDocBlock(@NotNull TwigFile twigFile) {
204-
return getInlineCommentDocsVars(twigFile);
211+
return new HashMap<>() {{
212+
putAll(getInlineCommentDocsVars(twigFile));
213+
putAll(getTypesTagVars(twigFile));
214+
}};
205215
}
206216

217+
/**
218+
* "@var foo \Foo"
219+
*/
207220
private static Map<String, String> getInlineCommentDocsVars(@NotNull PsiElement twigCompositeElement) {
208221
Map<String, String> variables = new HashMap<>();
209222

@@ -228,6 +241,86 @@ private static Map<String, String> getInlineCommentDocsVars(@NotNull PsiElement
228241
return variables;
229242
}
230243

244+
/**
245+
* {% types {...} %}
246+
*/
247+
private static Map<String, String> getTypesTagVars(@NotNull PsiElement twigFile) {
248+
Map<String, String> variables = new HashMap<>();
249+
250+
for (PsiElement psiComment: YamlHelper.getChildrenFix(twigFile)) {
251+
if (!(psiComment instanceof TwigCompositeElement) || psiComment.getNode().getElementType() != TwigElementTypes.TAG) {
252+
continue;
253+
}
254+
255+
PsiElement firstChild = psiComment.getFirstChild();
256+
if (firstChild == null) {
257+
continue;
258+
}
259+
260+
PsiElement tagName = PsiElementUtils.getNextSiblingAndSkip(firstChild, TwigTokenTypes.TAG_NAME);
261+
if (tagName == null || !"types".equals(tagName.getText())) {
262+
continue;
263+
}
264+
265+
ASTNode lbraceCurlPsi = FormatterUtil.getNextNonWhitespaceLeaf(tagName.getNode());
266+
if (lbraceCurlPsi == null || lbraceCurlPsi.getElementType() != TwigTokenTypes.LBRACE_CURL) {
267+
continue;
268+
}
269+
270+
ASTNode variableNamePsi = FormatterUtil.getNextNonWhitespaceLeaf(lbraceCurlPsi);
271+
if (variableNamePsi == null) {
272+
continue;
273+
}
274+
275+
if (variableNamePsi.getElementType() == TwigTokenTypes.IDENTIFIER) {
276+
String variableName = variableNamePsi.getText();
277+
if (!variableName.isBlank()) {
278+
variables.put(variableName, getTypesTagVarValue(variableNamePsi.getPsi()));
279+
}
280+
}
281+
282+
for (PsiElement commaPsi : PsiElementUtils.getNextSiblingOfTypes(variableNamePsi.getPsi(), PlatformPatterns.psiElement().withElementType(TwigTokenTypes.COMMA))) {
283+
ASTNode commaPsiNext = FormatterUtil.getNextNonWhitespaceLeaf(commaPsi.getNode());
284+
if (commaPsiNext != null && commaPsiNext.getElementType() == TwigTokenTypes.IDENTIFIER) {
285+
String variableName = commaPsiNext.getText();
286+
if (!variableName.isBlank()) {
287+
variables.put(variableName, getTypesTagVarValue(commaPsiNext.getPsi()));
288+
}
289+
}
290+
}
291+
}
292+
293+
return variables;
294+
}
295+
296+
/**
297+
* Find value tarting scope key:
298+
* - : 'foo'
299+
* - : "foo"
300+
*/
301+
@Nullable
302+
private static String getTypesTagVarValue(@NotNull PsiElement psiColon) {
303+
PsiElement filter = PsiElementUtils.getNextSiblingAndSkip(psiColon, TwigTokenTypes.STRING_TEXT, TwigTokenTypes.SINGLE_QUOTE, TwigTokenTypes.COLON, TwigTokenTypes.DOUBLE_QUOTE, TwigTokenTypes.QUESTION);
304+
if (filter == null) {
305+
return null;
306+
}
307+
308+
String type = PsiElementUtils.trimQuote(filter.getText());
309+
if (type.isBlank()) {
310+
return null;
311+
}
312+
313+
// secure value
314+
Matcher matcher = Pattern.compile("^(?<class>[\\w\\\\\\[\\]]+)$").matcher(type);
315+
if (matcher.find()) {
316+
// unescape: see also for Twig 4: https://github.com/twigphp/Twig/pull/4199
317+
return matcher.group("class").replace("\\\\", "\\");
318+
}
319+
320+
// unknown
321+
return "\\mixed";
322+
}
323+
231324
@NotNull
232325
public static Map<String, PsiVariable> collectScopeVariables(@NotNull PsiElement psiElement) {
233326
return collectScopeVariables(psiElement, new HashSet<>());

‎src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/variable/collector/FileDocVariableCollector.java‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,16 @@ public void collect(@NotNull TwigFileVariableCollectorParameter parameter, @NotN
2020
variables.putAll(convertHashMapToTypeSet(TwigTypeResolveUtil.findFileVariableDocBlock((TwigFile) parameter.getElement().getContainingFile())));
2121
}
2222

23-
private static Map<String, Set<String>> convertHashMapToTypeSet(Map<String, String> hashMap) {
23+
private static Map<String, Set<String>> convertHashMapToTypeSet(@NotNullMap<String, String> hashMap) {
2424
HashMap<String, Set<String>> globalVars = new HashMap<>();
2525

2626
for(final Map.Entry<String, String> entry: hashMap.entrySet()) {
27-
globalVars.put(entry.getKey(), new HashSet<>(Collections.singletonList(entry.getValue())));
27+
String value = entry.getValue();
28+
if (value != null) {
29+
globalVars.put(entry.getKey(), new HashSet<>(Collections.singletonList(value)));
30+
} else {
31+
globalVars.put(entry.getKey(), new HashSet<>());
32+
}
2833
}
2934

3035
return globalVars;

‎src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/templating/util/TwigTypeResolveUtilTest.java‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,39 @@ public void testFindFileVariableDocBlock() {
5353
assertEquals("\\AppBundle\\Entity\\MeterValueDTO", fileVariableDocBlock.get("foo_6"));
5454
}
5555

56+
/**
57+
* @see TwigTypeResolveUtil#findFileVariableDocBlock
58+
*/
59+
public void testFindFileTypeTag() {
60+
PsiFile fileFromText = PsiFileFactory.getInstance(getProject()).createFileFromText(TwigLanguage.INSTANCE, "" +
61+
"{% types {\n" +
62+
" is_correct: 'bool',\n" +
63+
" score: 'int',\n" +
64+
" foobar_1: 'array<int, App\\\\User>',\n" +
65+
" foobar_2?: '\\\\App\\\\User'," +
66+
" foobar_3: '\\\\App\\\\User[]'," +
67+
" foobar_4: '\\\\App\\\\User[]|\\User'," +
68+
" foobar_5: '',foobar_6: ''\r\n,\n\tfoobar_7:''\n\t\r," +
69+
"} %}" +
70+
"\n"
71+
);
72+
73+
Map<String, String> fileVariableDocBlock = TwigTypeResolveUtil.findFileVariableDocBlock((TwigFile) fileFromText);
74+
assertEquals("bool", fileVariableDocBlock.get("is_correct"));
75+
assertEquals("int", fileVariableDocBlock.get("score"));
76+
assertNull(fileVariableDocBlock.get("foobar_5"));
77+
78+
assertEquals("\\App\\User", fileVariableDocBlock.get("foobar_2"));
79+
assertEquals("\\App\\User[]", fileVariableDocBlock.get("foobar_3"));
80+
81+
// maybe resolve this
82+
assertEquals("\\mixed", fileVariableDocBlock.get("foobar_1"));
83+
assertEquals("\\mixed", fileVariableDocBlock.get("foobar_4"));
84+
85+
assertNull(fileVariableDocBlock.get("foobar_6"));
86+
assertNull(fileVariableDocBlock.get("foobar_7"));
87+
}
88+
5689
public void testReqExForInlineDocVariables() {
5790
assertMatches("@var foo_1 \\AppBundle\\Entity\\MeterValueDTO", TwigTypeResolveUtil.DOC_TYPE_PATTERN_SINGLE);
5891
assertMatches("@var \\AppBundle\\Entity\\MeterValueDTO foo_1", TwigTypeResolveUtil.DOC_TYPE_PATTERN_SINGLE);

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /