Skip to content

Commit 61136e4

Browse files
authored
Merge pull request #1117 from Haehnchen/feature/1097-twig-iterator-loop
support iterator in twig loop completion #1097 #1035
2 parents 6100558 + 2bcf370 commit 61136e4

File tree

2 files changed

+115
-2
lines changed

2 files changed

+115
-2
lines changed

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

+49-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import java.util.*;
3838
import java.util.regex.Matcher;
3939
import java.util.regex.Pattern;
40+
import java.util.stream.Collectors;
4041

4142
/**
4243
* @author Daniel Espendiller <daniel@espendiller.net>
@@ -259,7 +260,13 @@ public static Map<String, PsiVariable> collectScopeVariables(@NotNull PsiElement
259260
globalVars.putAll(convertHashMapToTypeSet(findInlineStatementVariableDocBlock(psiElement, TwigElementTypes.FOR_STATEMENT)));
260261

261262
for(Map.Entry<String, Set<String>> entry: globalVars.entrySet()) {
262-
controllerVars.put(entry.getKey(), new PsiVariable(entry.getValue(), null));
263+
Set<String> types = entry.getValue();
264+
265+
// collect iterator
266+
types.addAll(collectIteratorReturns(psiElement, entry.getValue()));
267+
268+
// convert to variable model
269+
controllerVars.put(entry.getKey(), new PsiVariable(types, null));
263270
}
264271

265272
// check if we are in "for" scope and resolve types ending with []
@@ -268,6 +275,47 @@ public static Map<String, PsiVariable> collectScopeVariables(@NotNull PsiElement
268275
return controllerVars;
269276
}
270277

278+
/**
279+
* Extract magic iterator implementation like "getIterator" or "__iterator"
280+
*
281+
* "@TODO find core stuff for resolve possible class return values"
282+
*
283+
* "getIterator", "@method Foo __iterator", "@method Foo[] __iterator"
284+
*/
285+
@NotNull
286+
private static Set<String> collectIteratorReturns(@NotNull PsiElement psiElement, @NotNull Set<String> types) {
287+
Set<String> arrayValues = new HashSet<>();
288+
for (String type : types) {
289+
PhpClass phpClass = PhpElementsUtil.getClassInterface(psiElement.getProject(), type);
290+
291+
if(phpClass == null) {
292+
continue;
293+
}
294+
295+
for (String methodName : new String[]{"getIterator", "__iterator", "current"}) {
296+
Method method = phpClass.findMethodByName(methodName);
297+
if(method != null) {
298+
// @method Foo __iterator
299+
// @method Foo[] __iterator
300+
Set<String> iteratorTypes = method.getType().getTypes();
301+
if("__iterator".equals(methodName) || "current".equals(methodName)) {
302+
arrayValues.addAll(iteratorTypes.stream().map(x ->
303+
!x.endsWith("[]") ? x + "[]" : x
304+
).collect(Collectors.toSet()));
305+
} else {
306+
// Foobar[]
307+
for (String iteratorType : iteratorTypes) {
308+
if(iteratorType.endsWith("[]")) {
309+
arrayValues.add(iteratorType);
310+
}
311+
}
312+
}
313+
}
314+
}
315+
}
316+
317+
return arrayValues;
318+
}
271319

272320
private static Collection<String> collectForArrayScopeVariablesFoo(Project project, String[] typeName, PsiVariable psiVariable) {
273321

tests/fr/adrienbrault/idea/symfony2plugin/tests/templating/variable/collector/FileDocVariableCollectorTest.java

+66-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package fr.adrienbrault.idea.symfony2plugin.tests.templating.variable.collector;
22

3+
import com.jetbrains.php.lang.PhpFileType;
34
import com.jetbrains.twig.TwigFileType;
45
import fr.adrienbrault.idea.symfony2plugin.tests.SymfonyLightCodeInsightFixtureTestCase;
56

@@ -25,9 +26,10 @@ public void setUp() throws Exception {
2526
" private function privateBar() {}\n" +
2627
" /** @return FooClass[] */\n" +
2728
" public function getNested() {}\n" +
29+
" /** @return FooClass[] */\n" +
30+
" public function getIterator() {}\n" +
2831
"}"
2932
);
30-
3133
}
3234

3335
public void testFileBasedVarDocPhpTypes() {
@@ -91,7 +93,70 @@ public void testVarArrayIteration() {
9193
"{% endfor %}\n"
9294
, "fooBar"
9395
);
96+
}
97+
98+
public void testVarArrayIterationViaIterationClassImplementations() {
99+
myFixture.configureByText("class1.php", "<?php\n" +
100+
"namespace Bar;\n" +
101+
"/**\n" +
102+
" * @method FooClassIteratorArray[] __iterator\n" +
103+
" */\n" +
104+
"class FooClassIteratorArray {\n" +
105+
" public function getFooBar() {}\n" +
106+
"}"
107+
);
108+
109+
myFixture.configureByText("class2.php", "<?php\n" +
110+
"namespace Bar;\n" +
111+
"/**\n" +
112+
" * @method FooClassIterator __iterator\n" +
113+
" */\n" +
114+
"class FooClassIterator {\n" +
115+
" public function getFooBar() {}\n" +
116+
"}"
117+
);
118+
119+
myFixture.configureByText("class3.php", "<?php\n" +
120+
"namespace Bar;\n" +
121+
"/**\n" +
122+
" * @method FooClassCurrent current\n" +
123+
" */\n" +
124+
"class FooClassCurrent {\n" +
125+
" public function getFooBar() {}\n" +
126+
"}"
127+
);
128+
129+
assertCompletionContains(TwigFileType.INSTANCE, "" +
130+
"{# @var bars \\Bar\\FooClass #}\n" +
131+
"{% for bar in bars %}\n" +
132+
" {{ bar.<caret> }}\n" +
133+
"{% endfor %}\n"
134+
, "fooBar"
135+
);
94136

137+
assertCompletionContains(TwigFileType.INSTANCE, "" +
138+
"{# @var bars \\Bar\\FooClassIteratorArray #}\n" +
139+
"{% for bar in bars %}\n" +
140+
" {{ bar.<caret> }}\n" +
141+
"{% endfor %}\n"
142+
, "fooBar"
143+
);
144+
145+
assertCompletionContains(TwigFileType.INSTANCE, "" +
146+
"{# @var bars \\Bar\\FooClassIterator #}\n" +
147+
"{% for bar in bars %}\n" +
148+
" {{ bar.<caret> }}\n" +
149+
"{% endfor %}\n"
150+
, "fooBar"
151+
);
152+
153+
assertCompletionContains(TwigFileType.INSTANCE, "" +
154+
"{# @var bars \\Bar\\FooClassCurrent #}\n" +
155+
"{% for bar in bars %}\n" +
156+
" {{ bar.<caret> }}\n" +
157+
"{% endfor %}\n"
158+
, "fooBar"
159+
);
95160
}
96161

97162
/**

0 commit comments

Comments
 (0)