Skip to content

Commit 930a8c4

Browse files
authored
Merge pull request #1987 from Haehnchen/feature/1984-autowire-attribute-service
#1984 support service inside "Autowire" attribute
2 parents 1cb02e8 + ae68004 commit 930a8c4

File tree

9 files changed

+208
-9
lines changed

9 files changed

+208
-9
lines changed

src/main/java/fr/adrienbrault/idea/symfony2plugin/dic/ServiceCompletionProvider.java

+4-5
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@
66
import com.intellij.psi.PsiElement;
77
import com.intellij.util.ProcessingContext;
88
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent;
9-
import fr.adrienbrault.idea.symfony2plugin.dic.container.suggestion.ServiceSuggestionCollector;
10-
import fr.adrienbrault.idea.symfony2plugin.dic.container.suggestion.XmlCallServiceSuggestionCollector;
11-
import fr.adrienbrault.idea.symfony2plugin.dic.container.suggestion.XmlConstructServiceSuggestionCollector;
12-
import fr.adrienbrault.idea.symfony2plugin.dic.container.suggestion.YamlConstructServiceSuggestionCollector;
9+
import fr.adrienbrault.idea.symfony2plugin.dic.container.suggestion.*;
1310
import fr.adrienbrault.idea.symfony2plugin.dic.container.util.ServiceContainerUtil;
1411
import fr.adrienbrault.idea.symfony2plugin.stubs.ContainerCollectionResolver;
1512
import org.jetbrains.annotations.NotNull;
@@ -28,6 +25,7 @@ public class ServiceCompletionProvider extends CompletionProvider<CompletionPara
2825
new XmlConstructServiceSuggestionCollector(),
2926
new YamlConstructServiceSuggestionCollector(),
3027
new XmlCallServiceSuggestionCollector(),
28+
new PhpAttributeServiceSuggestionCollector(),
3129
};
3230

3331
public void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet resultSet) {
@@ -39,7 +37,8 @@ public void addCompletions(@NotNull CompletionParameters parameters, ProcessingC
3937
PsiElement element = parameters.getPosition();
4038

4139
PrioritizedLookupResult result = getLookupElements(
42-
element, ContainerCollectionResolver.getServices(element.getProject()).values()
40+
element,
41+
ContainerCollectionResolver.getServices(element.getProject()).values()
4342
);
4443

4544
addPrioritizedServiceLookupElements(parameters, resultSet, result);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package fr.adrienbrault.idea.symfony2plugin.dic.container.suggestion;
2+
3+
import com.intellij.psi.PsiElement;
4+
import fr.adrienbrault.idea.symfony2plugin.dic.ContainerService;
5+
import fr.adrienbrault.idea.symfony2plugin.dic.container.suggestion.utils.ServiceSuggestionUtil;
6+
import fr.adrienbrault.idea.symfony2plugin.dic.container.util.ServiceContainerUtil;
7+
import org.jetbrains.annotations.NotNull;
8+
9+
import java.util.Collection;
10+
11+
/**
12+
* @author Daniel Espendiller <daniel@espendiller.net>
13+
*/
14+
public class PhpAttributeServiceSuggestionCollector implements ServiceSuggestionCollector {
15+
16+
@NotNull
17+
public Collection<String> collect(@NotNull PsiElement psiElement, @NotNull Collection<ContainerService> serviceMap) {
18+
return ServiceSuggestionUtil.createSuggestions(ServiceContainerUtil.getPhpAttributeConstructorTypeHint(psiElement), serviceMap);
19+
}
20+
}

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

+53
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import com.intellij.util.Consumer;
1515
import com.intellij.util.indexing.FileBasedIndex;
1616
import com.jetbrains.php.PhpIndex;
17+
import com.jetbrains.php.lang.lexer.PhpTokenTypes;
1718
import com.jetbrains.php.lang.psi.PhpFile;
1819
import com.jetbrains.php.lang.psi.elements.*;
1920
import com.jetbrains.php.refactoring.PhpNamespaceBraceConverter;
@@ -436,6 +437,58 @@ public static ServiceTypeHint getYamlConstructorTypeHint(@NotNull PsiElement psi
436437
return getYamlConstructorTypeHint((YAMLScalar) yamlScalar, lazyServiceCollector);
437438
}
438439

440+
/**
441+
* Resolve class for attribute and its parameter index
442+
*
443+
* class OurClass {
444+
* public function __construct(#[Autowire(service: 'some_service')] private $service1) {}
445+
* public function setFoo(#[Autowire(service: 'some_service')] private $service1) {}
446+
* }
447+
*/
448+
public static ServiceTypeHint getPhpAttributeConstructorTypeHint(@NotNull PsiElement psiElement) {
449+
if (!(psiElement instanceof StringLiteralExpression)) {
450+
return null;
451+
}
452+
453+
PsiElement parameterList = psiElement.getContext();
454+
if (!(parameterList instanceof ParameterList)) {
455+
return null;
456+
}
457+
458+
PsiElement colon = PsiTreeUtil.prevCodeLeaf(psiElement);
459+
if (colon == null || colon.getNode().getElementType() != PhpTokenTypes.opCOLON) {
460+
return null;
461+
}
462+
463+
PsiElement argumentName = PsiTreeUtil.prevCodeLeaf(colon);
464+
if (argumentName == null || argumentName.getNode().getElementType() != PhpTokenTypes.IDENTIFIER) {
465+
return null;
466+
}
467+
468+
// now resolve method attribute: its class and parameter index
469+
PsiElement phpAttribute = parameterList.getParent();
470+
if (phpAttribute instanceof PhpAttribute) {
471+
PsiElement phpAttributesList = phpAttribute.getParent();
472+
if (phpAttributesList instanceof PhpAttributesList) {
473+
PsiElement parameter = phpAttributesList.getParent();
474+
if (parameter instanceof Parameter) {
475+
PsiElement parameterListMethod = parameter.getParent();
476+
if (parameterListMethod instanceof ParameterList) {
477+
PsiElement method = parameterListMethod.getParent();
478+
if (method instanceof Method) {
479+
ParameterBag currentParameterIndex = PsiElementUtils.getCurrentParameterIndex((Parameter) parameter);
480+
if (currentParameterIndex != null) {
481+
return new ServiceTypeHint((Method) method, currentParameterIndex.getIndex(), psiElement);
482+
}
483+
}
484+
}
485+
}
486+
}
487+
}
488+
489+
return null;
490+
}
491+
439492
/**
440493
* foo:
441494
* class: Foo

src/main/java/fr/adrienbrault/idea/symfony2plugin/dic/registrar/DicGotoCompletionRegistrar.java

+47-4
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77
import com.intellij.psi.ResolveResult;
88
import com.intellij.psi.util.PsiTreeUtil;
99
import com.jetbrains.php.lang.psi.elements.PhpAttribute;
10+
import com.jetbrains.php.lang.psi.elements.PhpClass;
1011
import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
1112
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionProvider;
1213
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionRegistrar;
1314
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionRegistrarParameter;
1415
import fr.adrienbrault.idea.symfony2plugin.codeInsight.utils.GotoCompletionUtil;
1516
import fr.adrienbrault.idea.symfony2plugin.config.component.ParameterLookupElement;
1617
import fr.adrienbrault.idea.symfony2plugin.dic.ContainerParameter;
18+
import fr.adrienbrault.idea.symfony2plugin.dic.ServiceCompletionProvider;
1719
import fr.adrienbrault.idea.symfony2plugin.dic.container.util.ServiceContainerUtil;
1820
import fr.adrienbrault.idea.symfony2plugin.stubs.ContainerCollectionResolver;
1921
import fr.adrienbrault.idea.symfony2plugin.util.MethodMatcher;
@@ -22,10 +24,7 @@
2224
import fr.adrienbrault.idea.symfony2plugin.util.dict.ServiceUtil;
2325
import org.jetbrains.annotations.NotNull;
2426

25-
import java.util.ArrayList;
26-
import java.util.Collection;
27-
import java.util.Collections;
28-
import java.util.Map;
27+
import java.util.*;
2928

3029
/**
3130
* @author Daniel Espendiller <daniel@espendiller.net>
@@ -111,6 +110,24 @@ public void register(@NotNull GotoCompletionRegistrarParameter registrar) {
111110
return null;
112111
}
113112
);
113+
114+
// #[Autowire(service: 'some_service')]
115+
registrar.register(
116+
PhpElementsUtil.getAttributeNamedArgumentStringPattern(ServiceContainerUtil.AUTOWIRE_ATTRIBUTE_CLASS, "service"),
117+
psiElement -> {
118+
PsiElement context = psiElement.getContext();
119+
if (!(context instanceof StringLiteralExpression)) {
120+
return null;
121+
}
122+
123+
PhpAttribute phpAttribute = PsiTreeUtil.getParentOfType(context, PhpAttribute.class);
124+
if (phpAttribute != null) {
125+
return new ServiceContributor((StringLiteralExpression) context);
126+
}
127+
128+
return null;
129+
}
130+
);
114131
}
115132

116133
private static class ParameterContributor extends GotoCompletionProvider {
@@ -165,4 +182,30 @@ public Collection<PsiElement> getPsiTargets(PsiElement element) {
165182
return new ArrayList<>(ServiceUtil.getTaggedClasses(getElement().getProject(), contents));
166183
}
167184
}
185+
186+
private static class ServiceContributor extends GotoCompletionProvider {
187+
public ServiceContributor(@NotNull StringLiteralExpression element) {
188+
super(element);
189+
}
190+
191+
@Override
192+
public @NotNull Collection<LookupElement> getLookupElements() {
193+
return ServiceCompletionProvider.getLookupElements(this.getElement(), ContainerCollectionResolver.getServices(getProject()).values()).getLookupElements();
194+
}
195+
196+
@Override
197+
public @NotNull Collection<PsiElement> getPsiTargets(PsiElement element) {
198+
String contents = GotoCompletionUtil.getStringLiteralValue(element);
199+
if (contents == null) {
200+
return Collections.emptyList();
201+
}
202+
203+
PhpClass phpClass = ServiceUtil.getResolvedClassDefinition(element.getProject(), contents);
204+
if (phpClass == null) {
205+
return Collections.emptyList();
206+
}
207+
208+
return List.of(phpClass);
209+
}
210+
}
168211
}

src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/dic/registrar/DicGotoCompletionRegistrarTest.java

+26
Original file line numberDiff line numberDiff line change
@@ -166,4 +166,30 @@ public void testTagContributorForTaggedIterator() {
166166
PlatformPatterns.psiElement()
167167
);
168168
}
169+
170+
public void testServiceContributorForNamedAttribute() {
171+
assertCompletionContains(PhpFileType.INSTANCE, "<?php\n" +
172+
"use Symfony\\Component\\DependencyInjection\\Attribute\\Autowire;\n" +
173+
"\n" +
174+
"class HandlerCollection\n" +
175+
"{\n" +
176+
" public function __construct(\n" +
177+
" #[Autowire(service: '<caret>')]" +
178+
" ) {}\n" +
179+
"}",
180+
"foo_bar_service"
181+
);
182+
183+
assertNavigationMatch(PhpFileType.INSTANCE, "<?php\n" +
184+
"use Symfony\\Component\\DependencyInjection\\Attribute\\Autowire;\n" +
185+
"\n" +
186+
"class HandlerCollection\n" +
187+
"{\n" +
188+
" public function __construct(\n" +
189+
" #[Autowire(service: 'foo_bar<caret>_service')] $handlers\n" +
190+
" ) {}\n" +
191+
"}",
192+
PlatformPatterns.psiElement()
193+
);
194+
}
169195
}

src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/dic/registrar/fixtures/services.yml

+3
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@ services:
55
Foo\Bar:
66
tags:
77
- { name: yaml_type_tag }
8+
9+
foo_bar_service:
10+
class: Foo\Bar
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package fr.adrienbrault.idea.symfony2plugin.tests.dic.suggestion;
2+
3+
import com.jetbrains.php.lang.psi.PhpPsiElementFactory;
4+
import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
5+
import fr.adrienbrault.idea.symfony2plugin.dic.container.suggestion.PhpAttributeServiceSuggestionCollector;
6+
import fr.adrienbrault.idea.symfony2plugin.stubs.ContainerCollectionResolver;
7+
import fr.adrienbrault.idea.symfony2plugin.tests.SymfonyLightCodeInsightFixtureTestCase;
8+
import org.jetbrains.annotations.Nullable;
9+
10+
import java.util.Collection;
11+
12+
/**
13+
* @author Daniel Espendiller <daniel@espendiller.net>
14+
*/
15+
public class PhpAttributeServiceSuggestionCollectorTest extends SymfonyLightCodeInsightFixtureTestCase {
16+
public void setUp() throws Exception {
17+
super.setUp();
18+
myFixture.configureFromExistingVirtualFile(myFixture.copyFileToProject("classes.php"));
19+
myFixture.copyFileToProject("services.yml");
20+
}
21+
22+
public String getTestDataPath() {
23+
return "src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/dic/suggestion/fixtures";
24+
}
25+
26+
public void testParameterContributor() {
27+
@Nullable StringLiteralExpression stringLiteralExpression = PhpPsiElementFactory.createFromText(getProject(), StringLiteralExpression.class,
28+
"<?php\n" +
29+
"use Symfony\\Component\\DependencyInjection\\Attribute\\Autowire;\n" +
30+
"\n" +
31+
"class HandlerCollection\n" +
32+
"{\n" +
33+
" public function __construct(\n" +
34+
" #[Autowire(service: '<caret>')] \\Foo\\Bar $test\n" +
35+
" ) {}\n" +
36+
"}"
37+
);
38+
39+
assert stringLiteralExpression != null;
40+
41+
Collection<String> suggestions = new PhpAttributeServiceSuggestionCollector().collect(stringLiteralExpression, ContainerCollectionResolver.getServices(getProject()).values());
42+
assertContainsElements(suggestions, "foo_bar_service");
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Foo
4+
{
5+
class Bar
6+
{
7+
}
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
services:
2+
foo_bar_service:
3+
class: Foo\Bar

0 commit comments

Comments
 (0)