Skip to content

Commit a1e8e40

Browse files
committedOct 31, 2017
add support for named arguments #998
1 parent e6c2e24 commit a1e8e40

File tree

10 files changed

+294
-43
lines changed

10 files changed

+294
-43
lines changed
 

‎src/fr/adrienbrault/idea/symfony2plugin/config/xml/XmlHelper.java

+44-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package fr.adrienbrault.idea.symfony2plugin.config.xml;
22

3+
import com.intellij.openapi.util.Pair;
34
import com.intellij.patterns.*;
45
import com.intellij.psi.PsiElement;
56
import com.intellij.psi.PsiFile;
@@ -662,6 +663,46 @@ public static void visitServiceCallArgumentMethodIndex(@NotNull XmlAttributeValu
662663
visitServiceCallArgument(xmlAttribute, new ParameterResolverConsumer(xmlAttribute.getProject(), consumer));
663664
}
664665

666+
667+
/**
668+
* Find argument of given service method scope
669+
*
670+
* <service>
671+
* <argument key="$foobar"/>
672+
* <argument index="0"/>
673+
* <argument/>
674+
* </service>
675+
*/
676+
public static int getArgumentIndex(@NotNull XmlTag argumentTag) {
677+
String indexAttr = argumentTag.getAttributeValue("index");
678+
if(indexAttr != null) {
679+
try {
680+
return Integer.valueOf(indexAttr);
681+
} catch (NumberFormatException e) {
682+
return -1;
683+
}
684+
}
685+
686+
String keyAttr = argumentTag.getAttributeValue("key");
687+
if(keyAttr != null) {
688+
PsiElement parentTag = argumentTag.getParent();
689+
if(parentTag instanceof XmlTag && "service".equalsIgnoreCase(((XmlTag) parentTag).getName())) {
690+
String aClass = XmlHelper.getClassFromServiceDefinition((XmlTag) parentTag);
691+
if(aClass != null) {
692+
PhpClass phpClass = ServiceUtil.getResolvedClassDefinition(argumentTag.getProject(), aClass);
693+
if(phpClass != null) {
694+
Pair<Parameter, Integer> parameter = PhpElementsUtil.getConstructorArgumentByName(phpClass, StringUtils.stripStart(keyAttr, "$"));
695+
if(parameter != null) {
696+
return parameter.getSecond();
697+
}
698+
}
699+
}
700+
}
701+
}
702+
703+
return getArgumentIndexByCount(argumentTag);
704+
}
705+
665706
/**
666707
* Returns current index of parent tag
667708
*
@@ -670,14 +711,14 @@ public static void visitServiceCallArgumentMethodIndex(@NotNull XmlAttributeValu
670711
* <arg<caret>ument/>
671712
* </foo>
672713
*/
673-
public static int getArgumentIndex(@NotNull XmlTag xmlTag) {
674-
714+
private static int getArgumentIndexByCount(@NotNull XmlTag xmlTag) {
675715
PsiElement psiElement = xmlTag;
676716
int index = 0;
677717

678718
while (psiElement != null) {
679719
psiElement = psiElement.getPrevSibling();
680-
if(psiElement instanceof XmlTag && "argument".equalsIgnoreCase(((XmlTag) psiElement).getName())) {
720+
// ignore: <argument index="0"/>, <argument key="$foobar"/>
721+
if(psiElement instanceof XmlTag && "argument".equalsIgnoreCase(((XmlTag) psiElement).getName()) && ((XmlTag) psiElement).getAttribute("key") == null && ((XmlTag) psiElement).getAttribute("index") == null) {
681722
index++;
682723
}
683724
}

‎src/fr/adrienbrault/idea/symfony2plugin/dic/container/util/ServiceContainerUtil.java

+66-40
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import com.jetbrains.php.PhpIndex;
1313
import com.jetbrains.php.lang.psi.elements.Field;
1414
import com.jetbrains.php.lang.psi.elements.Method;
15+
import com.jetbrains.php.lang.psi.elements.Parameter;
1516
import com.jetbrains.php.lang.psi.elements.PhpClass;
1617
import fr.adrienbrault.idea.symfony2plugin.config.xml.XmlHelper;
1718
import fr.adrienbrault.idea.symfony2plugin.dic.attribute.value.AttributeValueInterface;
@@ -289,51 +290,76 @@ public static ServiceTypeHint getYamlConstructorTypeHint(@NotNull PsiElement psi
289290
*/
290291
@Nullable
291292
public static ServiceTypeHint getYamlConstructorTypeHint(@NotNull YAMLScalar yamlScalar, @NotNull ContainerCollectionResolver.LazyServiceCollector lazyServiceCollector) {
292-
PsiElement context = yamlScalar.getContext();
293-
if(!(context instanceof YAMLSequenceItem)) {
294-
return null;
295-
}
296-
297-
final YAMLSequenceItem sequenceItem = (YAMLSequenceItem) context;
298-
if (!(sequenceItem.getContext() instanceof YAMLSequence)) {
299-
return null;
300-
}
301-
302-
final YAMLSequence yamlArray = (YAMLSequence) sequenceItem.getContext();
303-
if(!(yamlArray.getContext() instanceof YAMLKeyValue)) {
304-
return null;
305-
}
306-
307-
final YAMLKeyValue yamlKeyValue = (YAMLKeyValue) yamlArray.getContext();
308-
if(!yamlKeyValue.getKeyText().equals("arguments")) {
309-
return null;
310-
}
311-
312-
YAMLMapping parentMapping = yamlKeyValue.getParentMapping();
313-
if(parentMapping == null) {
314-
return null;
315-
}
316-
317-
String serviceId = getServiceClassFromServiceMapping(parentMapping);
318-
if(StringUtils.isBlank(serviceId)) {
319-
return null;
320-
}
293+
// arguments: ['$foobar': '@foo']
321294

322-
PhpClass serviceClass = ServiceUtil.getResolvedClassDefinition(yamlScalar.getProject(), serviceId, lazyServiceCollector);
323-
if(serviceClass == null) {
324-
return null;
295+
PsiElement context = yamlScalar.getContext();
296+
if(context instanceof YAMLKeyValue) {
297+
String key = ((YAMLKeyValue) context).getKeyText();
298+
if(key.startsWith("$")) {
299+
PsiElement yamlMapping = context.getParent();
300+
if(yamlMapping instanceof YAMLMapping) {
301+
PsiElement yamlKeyValue = yamlMapping.getParent();
302+
if(yamlKeyValue instanceof YAMLKeyValue) {
303+
String keyText = ((YAMLKeyValue) yamlKeyValue).getKeyText();
304+
if(keyText.equals("arguments")) {
305+
YAMLMapping parentMapping = ((YAMLKeyValue) yamlKeyValue).getParentMapping();
306+
if(parentMapping != null) {
307+
String serviceId = getServiceClassFromServiceMapping(parentMapping);
308+
if(StringUtils.isNotBlank(serviceId)) {
309+
PhpClass serviceClass = ServiceUtil.getResolvedClassDefinition(yamlScalar.getProject(), serviceId, lazyServiceCollector);
310+
if(serviceClass != null) {
311+
Method constructor = serviceClass.getConstructor();
312+
if(constructor != null) {
313+
Parameter[] parameters = constructor.getParameters();
314+
for (int i = 0; i < parameters.length; i++) {
315+
Parameter parameter = parameters[i];
316+
if (key.equals("$" + parameter.getName())) {
317+
return new ServiceTypeHint(constructor, i, yamlScalar);
318+
}
319+
}
320+
}
321+
}
322+
}
323+
}
324+
}
325+
}
326+
}
327+
}
325328
}
326329

327-
Method constructor = serviceClass.getConstructor();
328-
if(constructor == null) {
329-
return null;
330+
// arguments: ['@foobar']
331+
if(context instanceof YAMLSequenceItem) {
332+
YAMLSequenceItem sequenceItem = (YAMLSequenceItem) context;
333+
PsiElement yamlSequenceItem = sequenceItem.getContext();
334+
if (yamlSequenceItem instanceof YAMLSequence) {
335+
YAMLSequence yamlArray = (YAMLSequence) sequenceItem.getContext();
336+
PsiElement yamlKeyValue = yamlArray.getContext();
337+
if(yamlKeyValue instanceof YAMLKeyValue) {
338+
YAMLKeyValue yamlKeyValueArguments = (YAMLKeyValue) yamlKeyValue;
339+
if(yamlKeyValueArguments.getKeyText().equals("arguments")) {
340+
YAMLMapping parentMapping = yamlKeyValueArguments.getParentMapping();
341+
if(parentMapping != null) {
342+
String serviceId = getServiceClassFromServiceMapping(parentMapping);
343+
if(StringUtils.isNotBlank(serviceId)) {
344+
PhpClass serviceClass = ServiceUtil.getResolvedClassDefinition(yamlScalar.getProject(), serviceId, lazyServiceCollector);
345+
if(serviceClass != null) {
346+
Method constructor = serviceClass.getConstructor();
347+
if(constructor != null) {
348+
return new ServiceTypeHint(
349+
constructor,
350+
PsiElementUtils.getPrevSiblingsOfType(sequenceItem, PlatformPatterns.psiElement(YAMLSequenceItem.class)).size(),
351+
yamlScalar
352+
);
353+
}
354+
}
355+
}
356+
}
357+
}
358+
}
359+
}
330360
}
331361

332-
return new ServiceTypeHint(
333-
constructor,
334-
PsiElementUtils.getPrevSiblingsOfType(sequenceItem, PlatformPatterns.psiElement(YAMLSequenceItem.class)).size(),
335-
yamlScalar
336-
);
362+
return null;
337363
}
338364

339365
/**

‎src/fr/adrienbrault/idea/symfony2plugin/util/PhpElementsUtil.java

+18
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.intellij.codeInsight.completion.CompletionResultSet;
44
import com.intellij.openapi.project.Project;
5+
import com.intellij.openapi.util.Pair;
56
import com.intellij.patterns.ElementPattern;
67
import com.intellij.patterns.PlatformPatterns;
78
import com.intellij.patterns.PsiElementPattern;
@@ -1386,6 +1387,23 @@ public static Collection<String> getTernaryExpressionConditionStrings(@NotNull T
13861387
return types;
13871388
}
13881389

1390+
@Nullable
1391+
public static Pair<Parameter, Integer> getConstructorArgumentByName(@NotNull PhpClass phpClass, @NotNull String argumentName) {
1392+
Method constructor = phpClass.getConstructor();
1393+
1394+
if(constructor != null) {
1395+
Parameter[] parameters = constructor.getParameters();
1396+
for (int i = 0; i < parameters.length; i++) {
1397+
Parameter parameter = parameters[i];
1398+
if (argumentName.equals(parameter.getName())) {
1399+
return Pair.create(parameter, i);
1400+
}
1401+
}
1402+
}
1403+
1404+
return null;
1405+
}
1406+
13891407
/**
13901408
* Visit and collect all variables in given scope
13911409
*/

‎tests/fr/adrienbrault/idea/symfony2plugin/tests/config/xml/XmlHelperTest.java

+55
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.intellij.ide.highlighter.XmlFileType;
44
import com.intellij.psi.PsiElement;
55
import com.intellij.psi.xml.XmlAttributeValue;
6+
import com.intellij.psi.xml.XmlTag;
67
import com.intellij.util.containers.ContainerUtil;
78
import com.jetbrains.php.lang.psi.elements.Parameter;
89
import fr.adrienbrault.idea.symfony2plugin.config.xml.XmlHelper;
@@ -99,4 +100,58 @@ public void testGetServiceDefinitionClass() {
99100
psiElement = myFixture.getFile().findElementAt(myFixture.getCaretOffset());
100101
assertEquals("Foo\\Bar", XmlHelper.getServiceDefinitionClass(psiElement));
101102
}
103+
104+
/**
105+
* @see XmlHelper#getArgumentIndex
106+
*/
107+
public void testGetArgumentIndex() {
108+
myFixture.configureByText(XmlFileType.INSTANCE, "" +
109+
"<service class=\"Foo\\Bar\">\n" +
110+
" <argum<caret>ent key=\"$foobar1\" />\n" +
111+
"</service>"
112+
);
113+
114+
PsiElement psiElement = myFixture.getFile().findElementAt(myFixture.getCaretOffset());
115+
assertEquals(1, XmlHelper.getArgumentIndex((XmlTag) psiElement.getParent()));
116+
}
117+
118+
/**
119+
* @see XmlHelper#getArgumentIndex
120+
*/
121+
public void testGetArgumentIndexOnIndex() {
122+
myFixture.configureByText(XmlFileType.INSTANCE, "" +
123+
"<service class=\"Foo\\Bar\">\n" +
124+
" <argum<caret>ent index=\"2\" />\n" +
125+
"</service>"
126+
);
127+
128+
PsiElement psiElement = myFixture.getFile().findElementAt(myFixture.getCaretOffset());
129+
assertEquals(2, XmlHelper.getArgumentIndex((XmlTag) psiElement.getParent()));
130+
131+
myFixture.configureByText(XmlFileType.INSTANCE, "" +
132+
"<service class=\"Foo\\Bar\">\n" +
133+
" <argum<caret>ent index=\"foobar\" />\n" +
134+
"</service>"
135+
);
136+
137+
psiElement = myFixture.getFile().findElementAt(myFixture.getCaretOffset());
138+
assertEquals(-1, XmlHelper.getArgumentIndex((XmlTag) psiElement.getParent()));
139+
}
140+
141+
/**
142+
* @see XmlHelper#getArgumentIndex
143+
*/
144+
public void testGetArgumentIndexOnArgumentCount() {
145+
myFixture.configureByText(XmlFileType.INSTANCE, "" +
146+
"<service class=\"Foo\\Bar\">\n" +
147+
" <argument/>\n" +
148+
" <argument index=\"\"/>\n" +
149+
" <argument key=\"\"/>\n" +
150+
" <argum<caret>ent/>\n" +
151+
"</service>"
152+
);
153+
154+
PsiElement psiElement = myFixture.getFile().findElementAt(myFixture.getCaretOffset());
155+
assertEquals(1, XmlHelper.getArgumentIndex((XmlTag) psiElement.getParent()));
156+
}
102157
}

‎tests/fr/adrienbrault/idea/symfony2plugin/tests/config/xml/fixtures/XmlHelper.php

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
{
55
class Bar
66
{
7+
public function __construct(Bar $foobar, Bar $foobar1)
8+
{
9+
}
10+
711
public function setBar($arg1, $arg2)
812
{
913
}

‎tests/fr/adrienbrault/idea/symfony2plugin/tests/dic/ServiceArgumentParameterHintsProviderTest.java

+25
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,20 @@ public void testXmlParameterTypeHintWithoutTypeHintMustFallbackToParameterNameOn
8787
assertNotNull(ContainerUtil.find(parameterHints, inlayInfo -> "foobar".equals(inlayInfo.getText())));
8888
}
8989

90+
public void testXmlParameterTypeHintForNamedArguments() {
91+
List<InlayInfo> parameterHints = getInlayInfo("" +
92+
"<container>\n" +
93+
" <services>\n" +
94+
" <service id=\"Foobar\\MyFoobarNamed\">\n" +
95+
" <argument key=\"$foo\" id=\"a<caret>a\"/>\n" +
96+
" </service>\n" +
97+
" </services>\n" +
98+
"</container>\n"
99+
);
100+
101+
assertNotNull(ContainerUtil.find(parameterHints, inlayInfo -> "FooInterface".equals(inlayInfo.getText())));
102+
}
103+
90104
public void testXmlParameterTypeForNestedArgumentsMustNotProvideHint() {
91105
List<InlayInfo> parameterHints = getInlayInfo("" +
92106
"<container>\n" +
@@ -153,6 +167,17 @@ public void testYamlParameterTypeForServiceIdShortcut() {
153167
assertNotNull(ContainerUtil.find(parameterHints, inlayInfo -> "FooInterface".equals(inlayInfo.getText())));
154168
}
155169

170+
public void testYamlParameterTypeForServiceIdShortcutWithNamedArgument() {
171+
List<InlayInfo> parameterHints = getInlayInfo(YAMLFileType.YML,"" +
172+
"services:\n" +
173+
" Foobar\\MyFoobar:\n" +
174+
" arguments:\n" +
175+
" $foo: @fo<caret>obar\n"
176+
);
177+
178+
assertNotNull(ContainerUtil.find(parameterHints, inlayInfo -> "FooInterface".equals(inlayInfo.getText())));
179+
}
180+
156181
public void testYamlParameterTypeForCallMethodParameter() {
157182
List<InlayInfo> parameterHints = getInlayInfo(YAMLFileType.YML,"" +
158183
"services:\n" +

0 commit comments

Comments
 (0)