1
1
package fr .adrienbrault .idea .symfony2plugin .templating .util ;
2
2
3
+ import com .intellij .lang .ASTNode ;
3
4
import com .intellij .openapi .extensions .ExtensionPointName ;
4
5
import com .intellij .openapi .project .Project ;
5
6
import com .intellij .openapi .vfs .VirtualFile ;
6
7
import com .intellij .patterns .PlatformPatterns ;
7
8
import com .intellij .psi .PsiComment ;
8
9
import com .intellij .psi .PsiElement ;
9
10
import com .intellij .psi .PsiWhiteSpace ;
11
+ import com .intellij .psi .formatter .FormatterUtil ;
10
12
import com .intellij .psi .tree .IElementType ;
11
13
import com .intellij .psi .util .PsiTreeUtil ;
12
14
import com .jetbrains .php .PhpIndex ;
35
37
import fr .adrienbrault .idea .symfony2plugin .util .yaml .YamlHelper ;
36
38
import org .apache .commons .lang3 .StringUtils ;
37
39
import org .jetbrains .annotations .NotNull ;
40
+ import org .jetbrains .annotations .Nullable ;
38
41
39
42
import java .util .*;
40
43
import java .util .regex .Matcher ;
@@ -182,7 +185,10 @@ private static Map<String, String> findInlineStatementVariableDocBlock(@NotNull
182
185
return variables ;
183
186
}
184
187
185
- Map <String , String > inlineCommentDocsVars = getInlineCommentDocsVars (twigCompositeElement );
188
+ Map <String , String > inlineCommentDocsVars = new HashMap <>() {{
189
+ putAll (getInlineCommentDocsVars (twigCompositeElement ));
190
+ putAll (getTypesTagVars (twigCompositeElement ));
191
+ }};
186
192
187
193
// visit parent elements for extending scope
188
194
if (nextParent ) {
@@ -196,14 +202,21 @@ private static Map<String, String> findInlineStatementVariableDocBlock(@NotNull
196
202
}
197
203
198
204
/**
199
- * Find file related doc blocks:
205
+ * Find file related doc blocks or "types" tags :
200
206
*
201
- * "@var foo \Foo"
207
+ * - "@var foo \Foo"
208
+ * - "{% types {...} %}"
202
209
*/
203
210
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
+ }};
205
215
}
206
216
217
+ /**
218
+ * "@var foo \Foo"
219
+ */
207
220
private static Map <String , String > getInlineCommentDocsVars (@ NotNull PsiElement twigCompositeElement ) {
208
221
Map <String , String > variables = new HashMap <>();
209
222
@@ -228,6 +241,86 @@ private static Map<String, String> getInlineCommentDocsVars(@NotNull PsiElement
228
241
return variables ;
229
242
}
230
243
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
+
231
324
@ NotNull
232
325
public static Map <String , PsiVariable > collectScopeVariables (@ NotNull PsiElement psiElement ) {
233
326
return collectScopeVariables (psiElement , new HashSet <>());
0 commit comments