Skip to content

Commit 8ae5672

Browse files
authored
Merge pull request #1985 from Haehnchen/feature/1984-attributes
#1984 support parameters inside "Autowire" attribute
2 parents a91c959 + a73032f commit 8ae5672

File tree

5 files changed

+183
-1
lines changed

5 files changed

+183
-1
lines changed

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

+2
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ public class ServiceContainerUtil {
6969
"debug", "default", "abstract", "inner", "chain", "decorate", "delegat"
7070
};
7171

72+
public static final String AUTOWIRE_ATTRIBUTE_CLASS = "\\Symfony\\Component\\DependencyInjection\\Attribute\\Autowire";
73+
7274
@NotNull
7375
public static Collection<ServiceSerializable> getServicesInFile(@NotNull PsiFile psiFile) {
7476
final Collection<ServiceSerializable> services = new ArrayList<>();

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

+27-1
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@
33
import com.intellij.codeInsight.lookup.LookupElement;
44
import com.intellij.patterns.PlatformPatterns;
55
import com.intellij.psi.PsiElement;
6+
import com.intellij.psi.util.PsiTreeUtil;
7+
import com.jetbrains.php.lang.psi.elements.PhpAttribute;
68
import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
79
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionProvider;
810
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionRegistrar;
911
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionRegistrarParameter;
1012
import fr.adrienbrault.idea.symfony2plugin.codeInsight.utils.GotoCompletionUtil;
1113
import fr.adrienbrault.idea.symfony2plugin.config.component.ParameterLookupElement;
1214
import fr.adrienbrault.idea.symfony2plugin.dic.ContainerParameter;
15+
import fr.adrienbrault.idea.symfony2plugin.dic.container.util.ServiceContainerUtil;
1316
import fr.adrienbrault.idea.symfony2plugin.stubs.ContainerCollectionResolver;
1417
import fr.adrienbrault.idea.symfony2plugin.util.MethodMatcher;
1518
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
@@ -25,7 +28,6 @@
2528
* @author Daniel Espendiller <daniel@espendiller.net>
2629
*/
2730
public class DicGotoCompletionRegistrar implements GotoCompletionRegistrar {
28-
2931
@Override
3032
public void register(@NotNull GotoCompletionRegistrarParameter registrar) {
3133
// getParameter('FOO')
@@ -65,6 +67,30 @@ public void register(@NotNull GotoCompletionRegistrarParameter registrar) {
6567
return new ParameterContributor((StringLiteralExpression) context);
6668
}
6769
);
70+
71+
// #[Autowire('<caret>')]
72+
// #[Autowire(value: '<caret>')]
73+
registrar.register(
74+
PlatformPatterns.or(
75+
PhpElementsUtil.getFirstAttributeStringPattern(ServiceContainerUtil.AUTOWIRE_ATTRIBUTE_CLASS),
76+
PhpElementsUtil.getAttributeNamedArgumentStringPattern(ServiceContainerUtil.AUTOWIRE_ATTRIBUTE_CLASS, "value")
77+
), psiElement -> {
78+
PsiElement context = psiElement.getContext();
79+
if (!(context instanceof StringLiteralExpression)) {
80+
return null;
81+
}
82+
83+
PhpAttribute phpAttribute = PsiTreeUtil.getParentOfType(context, PhpAttribute.class);
84+
if (phpAttribute != null) {
85+
String fqn = phpAttribute.getFQN();
86+
if (fqn != null && PhpElementsUtil.isInstanceOf(psiElement.getProject(), fqn, ServiceContainerUtil.AUTOWIRE_ATTRIBUTE_CLASS)) {
87+
return new ParameterContributor((StringLiteralExpression) context);
88+
}
89+
}
90+
91+
return null;
92+
}
93+
);
6894
}
6995

7096
private static class ParameterContributor extends GotoCompletionProvider {

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

+68
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,74 @@ public boolean accepts(@NotNull FunctionReference functionReference, ProcessingC
382382
.withLanguage(PhpLanguage.INSTANCE);
383383
}
384384

385+
private static final PatternCondition<StringLiteralExpression> EMPTY_PREVIOUS_LEAF = new PatternCondition<>("previous leaf empty") {
386+
@Override
387+
public boolean accepts(@NotNull StringLiteralExpression stringLiteralExpression, ProcessingContext context) {
388+
return stringLiteralExpression.getPrevSibling() == null;
389+
}
390+
};
391+
392+
/**
393+
* #[Security("is_granted('POST_SHOW')")]
394+
*/
395+
@NotNull
396+
public static PsiElementPattern.Capture<PsiElement> getFirstAttributeStringPattern(@NotNull String clazz) {
397+
return PlatformPatterns.psiElement().withElementType(PlatformPatterns.elementType().or(
398+
PhpTokenTypes.STRING_LITERAL_SINGLE_QUOTE,
399+
PhpTokenTypes.STRING_LITERAL
400+
))
401+
.withParent(PlatformPatterns.psiElement(StringLiteralExpression.class)
402+
.with(EMPTY_PREVIOUS_LEAF)
403+
.withParent(PlatformPatterns.psiElement(ParameterList.class)
404+
.withParent(PlatformPatterns.psiElement(PhpAttribute.class)
405+
.with(new AttributeInstancePatternCondition(clazz))
406+
)
407+
)
408+
);
409+
}
410+
411+
/**
412+
* #[Security(foobar: "is_granted('POST_SHOW')")]
413+
*/
414+
@NotNull
415+
public static PsiElementPattern.Capture<PsiElement> getAttributeNamedArgumentStringPattern(@NotNull String clazz, @NotNull String namedArgument) {
416+
return PlatformPatterns.psiElement().withElementType(PlatformPatterns.elementType().or(
417+
PhpTokenTypes.STRING_LITERAL_SINGLE_QUOTE,
418+
PhpTokenTypes.STRING_LITERAL
419+
))
420+
.withParent(PlatformPatterns.psiElement(StringLiteralExpression.class)
421+
.afterLeafSkipping(
422+
PlatformPatterns.psiElement(PsiWhiteSpace.class),
423+
PlatformPatterns.psiElement(PhpTokenTypes.opCOLON).afterLeafSkipping(
424+
PlatformPatterns.psiElement(PsiWhiteSpace.class),
425+
PlatformPatterns.psiElement(PhpTokenTypes.IDENTIFIER).withText(namedArgument)
426+
)
427+
)
428+
.withParent(PlatformPatterns.psiElement(ParameterList.class)
429+
.withParent(PlatformPatterns.psiElement(PhpAttribute.class)
430+
.with(new AttributeInstancePatternCondition(clazz))
431+
)
432+
)
433+
);
434+
}
435+
436+
/**
437+
* Check if given Attribute
438+
*/
439+
private static class AttributeInstancePatternCondition extends PatternCondition<PsiElement> {
440+
private final String clazz;
441+
442+
AttributeInstancePatternCondition(@NotNull String clazz) {
443+
super("Attribute Instance");
444+
this.clazz = clazz;
445+
}
446+
447+
@Override
448+
public boolean accepts(@NotNull PsiElement psiElement, ProcessingContext processingContext) {
449+
return clazz.equals(((PhpAttribute) psiElement).getFQN());
450+
}
451+
}
452+
385453
/**
386454
* $foo->bar('<caret>')
387455
*/

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

+69
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,73 @@ public void testParameterContributorFor() {
6363
PlatformPatterns.psiElement()
6464
);
6565
}
66+
67+
public void testParameterContributorForDefaultAttribute() {
68+
assertCompletionContains(PhpFileType.INSTANCE, "<?php\n" +
69+
"use Symfony\\Component\\DependencyInjection\\Attribute\\Autowire;\n" +
70+
"\n" +
71+
"class MyService\n" +
72+
"{\n" +
73+
" public function __construct(\n" +
74+
" #[Autowire('<caret>')]\n" +
75+
" private $parameter2" +
76+
" ) {}\n" +
77+
"}",
78+
"foo"
79+
);
80+
81+
assertCompletionContains(PhpFileType.INSTANCE, "<?php\n" +
82+
"use Symfony\\Component\\DependencyInjection\\Attribute\\Autowire;\n" +
83+
"\n" +
84+
"class MyService\n" +
85+
"{\n" +
86+
" public function __construct(\n" +
87+
" #[Autowire(\"<caret>\")]\n" +
88+
" private $parameter2" +
89+
" ) {}\n" +
90+
"}",
91+
"foo"
92+
);
93+
94+
assertNavigationMatch(PhpFileType.INSTANCE, "<?php\n" +
95+
"use Symfony\\Component\\DependencyInjection\\Attribute\\Autowire;\n" +
96+
"\n" +
97+
"class MyService\n" +
98+
"{\n" +
99+
" public function __construct(\n" +
100+
" #[Autowire('fo<caret>o')]\n" +
101+
" private $parameter2" +
102+
" ) {}\n" +
103+
"}",
104+
PlatformPatterns.psiElement()
105+
);
106+
}
107+
108+
public void testParameterContributorForNamedAttribute() {
109+
assertCompletionContains(PhpFileType.INSTANCE, "<?php\n" +
110+
"use Symfony\\Component\\DependencyInjection\\Attribute\\Autowire;\n" +
111+
"\n" +
112+
"class MyService\n" +
113+
"{\n" +
114+
" public function __construct(\n" +
115+
" #[Autowire(value: '<caret>')]\n" +
116+
" private $parameter2" +
117+
" ) {}\n" +
118+
"}",
119+
"foo"
120+
);
121+
122+
assertNavigationMatch(PhpFileType.INSTANCE, "<?php\n" +
123+
"use Symfony\\Component\\DependencyInjection\\Attribute\\Autowire;\n" +
124+
"\n" +
125+
"class MyService\n" +
126+
"{\n" +
127+
" public function __construct(\n" +
128+
" #[Autowire(value: 'fo<caret>o')]\n" +
129+
" private $parameter2" +
130+
" ) {}\n" +
131+
"}",
132+
PlatformPatterns.psiElement()
133+
);
134+
}
66135
}

src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/dic/registrar/fixtures/classes.php

+17
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,23 @@ function hasParameter($parameter);
99
}
1010
}
1111

12+
namespace Symfony\Component\DependencyInjection\Attribute
13+
{
14+
class Autowire
15+
{
16+
/**
17+
* @param string|null $value Parameter value (ie "%kernel.project_dir%/some/path")
18+
* @param string|null $service Service ID (ie "some.service")
19+
* @param string|null $expression Expression (ie 'service("some.service").someMethod()')
20+
*/
21+
public function __construct(
22+
string $value = null,
23+
string $service = null,
24+
string $expression = null,
25+
) {}
26+
}
27+
}
28+
1229
namespace
1330
{
1431
class Foo

0 commit comments

Comments
 (0)