Skip to content

Commit 793219d

Browse files
committed
provide resolving of Twig globals and variables with multiple types and targets
1 parent 6b2de47 commit 793219d

File tree

4 files changed

+88
-65
lines changed

4 files changed

+88
-65
lines changed

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

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

3+
import com.intellij.openapi.util.Pair;
34
import com.intellij.psi.PsiElement;
45
import com.intellij.psi.PsiRecursiveElementWalkingVisitor;
56
import com.intellij.psi.util.PsiTreeUtil;
@@ -171,11 +172,15 @@ private static Map<String, PsiVariable> collectOnVariableReferences(@NotNull Fun
171172
PsiElement parent = scopeVar.getParent();
172173
if (parent instanceof ArrayAccessExpression) {
173174
// $template['variable'] = $foo
174-
collectedTypes.putAll(getTypesOnArrayIndex((ArrayAccessExpression) parent));
175+
Pair<String, PsiVariable> pair = getTypesOnArrayIndex((ArrayAccessExpression) parent);
176+
if (pair != null) {
177+
collectedTypes.put(pair.getFirst(), pair.getSecond());
178+
}
175179
} else if (parent instanceof AssignmentExpression) {
176180
// array('foo' => $var)
177-
if (((AssignmentExpression) parent).getValue() instanceof ArrayCreationExpression) {
178-
collectedTypes.putAll(getTypesOnArrayHash((ArrayCreationExpression) ((AssignmentExpression) parent).getValue()));
181+
PhpPsiElement value = ((AssignmentExpression) parent).getValue();
182+
if (value instanceof ArrayCreationExpression) {
183+
collectedTypes.putAll(getTypesOnArrayHash((ArrayCreationExpression) value));
179184
}
180185
}
181186
}
@@ -186,10 +191,8 @@ private static Map<String, PsiVariable> collectOnVariableReferences(@NotNull Fun
186191
/**
187192
* $template['var'] = $foo
188193
*/
189-
private static Map<String, PsiVariable> getTypesOnArrayIndex(ArrayAccessExpression arrayAccessExpression) {
190-
191-
Map<String, PsiVariable> collectedTypes = new HashMap<>();
192-
194+
@Nullable
195+
private static Pair<String, PsiVariable> getTypesOnArrayIndex(@NotNull ArrayAccessExpression arrayAccessExpression) {
193196
ArrayIndex arrayIndex = arrayAccessExpression.getIndex();
194197
if(arrayIndex != null && arrayIndex.getValue() instanceof StringLiteralExpression) {
195198

@@ -203,28 +206,23 @@ private static Map<String, PsiVariable> getTypesOnArrayIndex(ArrayAccessExpressi
203206
variableTypes.addAll(((PhpTypedElement) arrayValue).getType().getTypes());
204207
}
205208

206-
collectedTypes.put(variableName, new PsiVariable(variableTypes, ((AssignmentExpression) parent).getValue()));
207-
209+
return Pair.create(variableName, new PsiVariable(variableTypes, ((AssignmentExpression) parent).getValue()));
208210
} else {
209-
collectedTypes.put(variableName, new PsiVariable(variableTypes, null));
211+
return Pair.create(variableName, new PsiVariable(variableTypes));
210212
}
211-
212-
213213
}
214214

215-
return collectedTypes;
215+
return null;
216216
}
217217

218218
/**
219219
* array('foo' => $var, 'bar' => $bar)
220220
*/
221-
public static Map<String, PsiVariable> getTypesOnArrayHash(ArrayCreationExpression arrayCreationExpression) {
222-
221+
public static Map<String, PsiVariable> getTypesOnArrayHash(@NotNull ArrayCreationExpression arrayCreationExpression) {
223222
Map<String, PsiVariable> collectedTypes = new HashMap<>();
224223

225224
for(ArrayHashElement arrayHashElement: arrayCreationExpression.getHashElements()) {
226225
if(arrayHashElement.getKey() instanceof StringLiteralExpression) {
227-
228226
String variableName = ((StringLiteralExpression) arrayHashElement.getKey()).getContents();
229227
Set<String> variableTypes = new HashSet<>();
230228

@@ -233,7 +231,6 @@ public static Map<String, PsiVariable> getTypesOnArrayHash(ArrayCreationExpressi
233231
}
234232

235233
collectedTypes.put(variableName, new PsiVariable(variableTypes, arrayHashElement.getValue()));
236-
237234
}
238235
}
239236

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

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -224,61 +224,66 @@ private static Map<String, String> getInlineCommentDocsVars(@NotNull PsiElement
224224
return variables;
225225
}
226226

227-
private static Map<String, Set<String>> convertHashMapToTypeSet(Map<String, String> hashMap) {
228-
Map<String, Set<String>> globalVars = new HashMap<>();
229-
230-
for(final Map.Entry<String, String> entry: hashMap.entrySet()) {
231-
globalVars.put(entry.getKey(), new HashSet<>(Collections.singletonList(entry.getValue())));
232-
}
233-
234-
return globalVars;
235-
}
236-
237227
@NotNull
238228
public static Map<String, PsiVariable> collectScopeVariables(@NotNull PsiElement psiElement) {
239229
return collectScopeVariables(psiElement, new HashSet<>());
240230
}
241231

242232
@NotNull
243233
public static Map<String, PsiVariable> collectScopeVariables(@NotNull PsiElement psiElement, @NotNull Set<VirtualFile> visitedFiles) {
244-
Map<String, Set<String>> globalVars = new HashMap<>();
245-
Map<String, PsiVariable> controllerVars = new HashMap<>();
246-
247234
VirtualFile virtualFile = psiElement.getContainingFile().getVirtualFile();
248235
if(visitedFiles.contains(virtualFile)) {
249-
return controllerVars;
236+
return Collections.emptyMap();
250237
}
251238

252239
visitedFiles.add(virtualFile);
253240

241+
Map<String, PsiVariable> controllerVars = new HashMap<>();
242+
254243
TwigFileVariableCollectorParameter collectorParameter = new TwigFileVariableCollectorParameter(psiElement, visitedFiles);
255244
for(TwigFileVariableCollector collector: TWIG_FILE_VARIABLE_COLLECTORS.getExtensions()) {
256245
Map<String, Set<String>> globalVarsScope = new HashMap<>();
257246
collector.collect(collectorParameter, globalVarsScope);
258247

259248
// @TODO: resolve this in change extension point, so that its only possible to provide data and dont give full scope to break / overwrite other variables
260249
globalVarsScope.forEach((s, strings) -> {
261-
globalVars.putIfAbsent(s, new HashSet<>());
262-
globalVars.get(s).addAll(strings);
250+
controllerVars.putIfAbsent(s, new PsiVariable());
251+
controllerVars.get(s).addTypes(strings);
263252
});
264253

265-
// @TODO: provide merge for multiple matches
266-
collector.collectPsiVariables(collectorParameter, controllerVars);
254+
// merging elements
255+
Map<String, PsiVariable> controllerVars1 = new HashMap<>();
256+
collector.collectPsiVariables(collectorParameter, controllerVars1);
257+
258+
controllerVars1.forEach((s, psiVariable) -> {
259+
controllerVars.putIfAbsent(s, new PsiVariable());
260+
controllerVars.get(s).addTypes(psiVariable.getTypes());
261+
262+
PsiElement context = psiVariable.getElement();
263+
if (context != null) {
264+
controllerVars.get(s).addElements(context);
265+
}
266+
});
267267
}
268268

269269
// globals first
270-
globalVars.putAll(convertHashMapToTypeSet(findInlineStatementVariableDocBlock(psiElement, TwigElementTypes.BLOCK_STATEMENT, true)));
271-
globalVars.putAll(convertHashMapToTypeSet(findInlineStatementVariableDocBlock(psiElement, TwigElementTypes.MACRO_STATEMENT, false)));
272-
globalVars.putAll(convertHashMapToTypeSet(findInlineStatementVariableDocBlock(psiElement, TwigElementTypes.FOR_STATEMENT, false)));
273-
274-
for(Map.Entry<String, Set<String>> entry: globalVars.entrySet()) {
275-
Set<String> types = entry.getValue();
270+
Collection<Map<String, String>> vars = Arrays.asList(
271+
findInlineStatementVariableDocBlock(psiElement, TwigElementTypes.BLOCK_STATEMENT, true),
272+
findInlineStatementVariableDocBlock(psiElement, TwigElementTypes.MACRO_STATEMENT, false),
273+
findInlineStatementVariableDocBlock(psiElement, TwigElementTypes.FOR_STATEMENT, false)
274+
);
276275

277-
// collect iterator
278-
types.addAll(collectIteratorReturns(psiElement, entry.getValue()));
276+
for (Map<String, String> entry : vars) {
277+
entry.forEach((s, s2) -> {
278+
controllerVars.putIfAbsent(s, new PsiVariable());
279+
controllerVars.get(s).addType(s2);
280+
});
281+
}
279282

280-
// convert to variable model
281-
controllerVars.put(entry.getKey(), new PsiVariable(types, null));
283+
// collect iterator
284+
for(Map.Entry<String, PsiVariable> entry: controllerVars.entrySet()) {
285+
PsiVariable psiVariable = entry.getValue();
286+
psiVariable.addTypes(collectIteratorReturns(psiElement, psiVariable.getTypes()));
282287
}
283288

284289
// check if we are in "for" scope and resolve types ending with []
@@ -347,7 +352,7 @@ private static Collection<String> collectForArrayScopeVariablesFoo(@NotNull Proj
347352
return previousElements;
348353
}
349354

350-
private static void collectForArrayScopeVariables(PsiElement psiElement, Map<String, PsiVariable> globalVars) {
355+
private static void collectForArrayScopeVariables(@NotNull PsiElement psiElement, @NotNull Map<String, PsiVariable> globalVars) {
351356
PsiElement twigCompositeElement = PsiTreeUtil.findFirstParent(psiElement, psiElement1 -> {
352357
if (psiElement1 instanceof TwigCompositeElement) {
353358
if (PlatformPatterns.psiElement(TwigElementTypes.FOR_STATEMENT).accepts(psiElement1)) {
@@ -412,12 +417,12 @@ private static void collectForArrayScopeVariables(PsiElement psiElement, Map<Str
412417
}
413418

414419
// we already have same variable in scope, so merge types
415-
if(globalVars.containsKey(scopeVariable)) {
416-
globalVars.get(scopeVariable).getTypes().addAll(types);
420+
PsiVariable psiVariable = globalVars.get(scopeVariable);
421+
if (psiVariable != null) {
422+
psiVariable.addTypes(types);
417423
} else {
418424
globalVars.put(scopeVariable, new PsiVariable(types));
419425
}
420-
421426
}
422427

423428
@NotNull

src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/variable/dict/PsiVariable.java

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,34 @@
44
import org.jetbrains.annotations.NotNull;
55
import org.jetbrains.annotations.Nullable;
66

7-
import java.util.Collections;
7+
import java.util.Collection;
8+
import java.util.HashSet;
89
import java.util.Set;
910

1011
/**
1112
* @author Daniel Espendiller <daniel@espendiller.net>
1213
*/
1314
public class PsiVariable {
1415
@NotNull
15-
final private Set<String> types;
16+
final private Set<String> types = new HashSet<>();
1617

17-
@Nullable
18-
private PsiElement psiElement;
18+
@NotNull
19+
final private Collection<PsiElement> psiElements = new HashSet<>();
1920

2021
public PsiVariable(@NotNull Set<String> types, @Nullable PsiElement psiElement) {
21-
this.types = types;
22-
this.psiElement = psiElement;
22+
this.types.addAll(types);
23+
this.psiElements.add(psiElement);
2324
}
2425

2526
public PsiVariable(@NotNull Set<String> types) {
26-
this.types = types;
27+
this.types.addAll(types);
2728
}
2829

2930
public PsiVariable(@NotNull String type) {
30-
this.types = Collections.singleton(type);
31+
this.types.add(type);
32+
}
33+
34+
public PsiVariable() {
3135
}
3236

3337
@NotNull
@@ -37,6 +41,22 @@ public Set<String> getTypes() {
3741

3842
@Nullable
3943
public PsiElement getElement() {
40-
return psiElement;
44+
if (psiElements.size() > 0) {
45+
return psiElements.iterator().next();
46+
}
47+
48+
return null;
49+
}
50+
51+
public void addElements(@NotNull PsiElement psiElement) {
52+
this.psiElements.add(psiElement);
53+
}
54+
55+
public void addTypes(@NotNull Collection<String> types) {
56+
this.types.addAll(types);
57+
}
58+
59+
public void addType(@NotNull String type) {
60+
this.types.add(type);
4161
}
4262
}

src/main/java/fr/adrienbrault/idea/symfony2plugin/twig/variable/collector/ServiceContainerGlobalVariableCollector.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@
1111
import org.apache.commons.lang.StringUtils;
1212
import org.jetbrains.annotations.NotNull;
1313

14-
import java.util.Collections;
15-
import java.util.HashSet;
16-
import java.util.Map;
17-
import java.util.Set;
14+
import java.util.*;
1815

1916
/**
2017
* @author Daniel Espendiller <daniel@espendiller.net>
@@ -23,17 +20,21 @@ public class ServiceContainerGlobalVariableCollector implements TwigFileVariable
2320

2421
@Override
2522
public void collect(@NotNull TwigFileVariableCollectorParameter parameter, @NotNull Map<String, Set<String>> variables) {
23+
Map<String, Set<String>> map = new HashMap<>();
24+
2625
TwigGlobalsServiceParser twigPathServiceParser = ServiceXmlParserFactory.getInstance(parameter.getProject(), TwigGlobalsServiceParser.class);
2726
for(Map.Entry<String, TwigGlobalVariable> globalVariableEntry: twigPathServiceParser.getTwigGlobals().entrySet()) {
2827
if(globalVariableEntry.getValue().getTwigGlobalEnum() == TwigGlobalEnum.SERVICE) {
2928
String serviceName = globalVariableEntry.getValue().getValue();
3029
PhpClass phpClass = ServiceUtil.getServiceClass(parameter.getProject(), serviceName);
3130
if(phpClass != null) {
32-
variables.put(globalVariableEntry.getKey(), new HashSet<>(Collections.singletonList(
33-
StringUtils.stripStart(phpClass.getFQN(), "\\")
34-
)));
31+
String key = globalVariableEntry.getKey();
32+
map.putIfAbsent(key, new HashSet<>());
33+
map.get(key).add("\\" + StringUtils.stripStart(phpClass.getFQN(), "\\"));
3534
}
3635
}
3736
}
37+
38+
variables.putAll(map);
3839
}
3940
}

0 commit comments

Comments
 (0)