Skip to content

Commit 123d2c1

Browse files
committed
- Add support for #[AutowireServiceClosure], #[AutowireMethodOf], and #[AutowireCallable] attributes with completion and navigation
- Strip processor prefixes (e.g., `bool:`, `int:`) from `#[Autowire(env:)]` attributes for navigation and completion - Add `%parameter%` wrapping support in `#[Autowire()]` attributes with completion and navigation - Add support for `#[Autowire(param:)]` and `#[Autowire(env:)]` attributes with completion and navigation
1 parent 24f9e57 commit 123d2c1

File tree

5 files changed

+556
-9
lines changed

5 files changed

+556
-9
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ public class ServiceContainerUtil {
7979
public static final String DECORATOR_ATTRIBUTE_CLASS = "\\Symfony\\Component\\DependencyInjection\\Attribute\\AsDecorator";
8080
public static final String AUTOCONFIGURE_ATTRIBUTE_CLASS = "\\Symfony\\Component\\DependencyInjection\\Attribute\\Autoconfigure";
8181
public static final String AUTOWIRE_LOCATOR_ATTRIBUTE_CLASS = "\\Symfony\\Component\\DependencyInjection\\Attribute\\AutowireLocator";
82+
public static final String AUTOWIRE_SERVICE_CLOSURE_ATTRIBUTE_CLASS = "\\Symfony\\Component\\DependencyInjection\\Attribute\\AutowireServiceClosure";
83+
public static final String AUTOWIRE_CALLABLE_ATTRIBUTE_CLASS = "\\Symfony\\Component\\DependencyInjection\\Attribute\\AutowireCallable";
84+
public static final String AUTOWIRE_METHOD_OF_ATTRIBUTE_CLASS = "\\Symfony\\Component\\DependencyInjection\\Attribute\\AutowireMethodOf";
8285

8386
@NotNull
8487
public static Collection<ServiceSerializable> getServicesInFile(@NotNull PsiFile psiFile) {

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

Lines changed: 210 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,30 @@
55
import com.intellij.patterns.PlatformPatterns;
66
import com.intellij.psi.PsiElement;
77
import com.intellij.psi.util.PsiTreeUtil;
8-
import com.jetbrains.php.lang.psi.elements.PhpAttribute;
9-
import com.jetbrains.php.lang.psi.elements.PhpClass;
10-
import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
8+
import com.jetbrains.php.lang.psi.elements.*;
119
import fr.adrienbrault.idea.symfony2plugin.Symfony2Icons;
1210
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionProvider;
1311
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionRegistrar;
1412
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionRegistrarParameter;
1513
import fr.adrienbrault.idea.symfony2plugin.codeInsight.utils.GotoCompletionUtil;
1614
import fr.adrienbrault.idea.symfony2plugin.config.component.ParameterLookupElement;
15+
import fr.adrienbrault.idea.symfony2plugin.config.yaml.ParameterPercentWrapInsertHandler;
1716
import fr.adrienbrault.idea.symfony2plugin.dic.ContainerParameter;
1817
import fr.adrienbrault.idea.symfony2plugin.dic.ServiceCompletionProvider;
18+
import fr.adrienbrault.idea.symfony2plugin.dic.container.util.DotEnvUtil;
1919
import fr.adrienbrault.idea.symfony2plugin.dic.container.util.ServiceContainerUtil;
2020
import fr.adrienbrault.idea.symfony2plugin.stubs.ContainerCollectionResolver;
2121
import fr.adrienbrault.idea.symfony2plugin.util.MethodMatcher;
2222
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
2323
import fr.adrienbrault.idea.symfony2plugin.util.completion.TagNameCompletionProvider;
2424
import fr.adrienbrault.idea.symfony2plugin.util.dict.ServiceUtil;
25+
import org.apache.commons.lang3.StringUtils;
2526
import org.jetbrains.annotations.NotNull;
2627

2728
import java.util.*;
2829
import java.util.function.Function;
30+
import java.util.regex.Matcher;
31+
import java.util.regex.Pattern;
2932
import java.util.stream.Collectors;
3033

3134
/**
@@ -72,8 +75,8 @@ public void register(@NotNull GotoCompletionRegistrarParameter registrar) {
7275
}
7376
);
7477

75-
// #[Autowire('<caret>')]
76-
// #[Autowire(value: '<caret>')]
78+
// #[Autowire('%kernel.project_dir%')]
79+
// #[Autowire(value: '%kernel.project_dir%')]
7780
registrar.register(
7881
PlatformPatterns.or(
7982
PhpElementsUtil.getFirstAttributeStringPattern(ServiceContainerUtil.AUTOWIRE_ATTRIBUTE_CLASS),
@@ -86,7 +89,7 @@ public void register(@NotNull GotoCompletionRegistrarParameter registrar) {
8689

8790
PhpAttribute phpAttribute = PsiTreeUtil.getParentOfType(context, PhpAttribute.class);
8891
if (phpAttribute != null) {
89-
return new ParameterContributor((StringLiteralExpression) context);
92+
return new ParameterWithPercentWrapContributor((StringLiteralExpression) context);
9093
}
9194

9295
return null;
@@ -124,8 +127,49 @@ public void register(@NotNull GotoCompletionRegistrarParameter registrar) {
124127
}
125128
);
126129

130+
// #[Autowire(param: 'kernel.debug')]
131+
registrar.register(
132+
PhpElementsUtil.getAttributeNamedArgumentStringPattern(ServiceContainerUtil.AUTOWIRE_ATTRIBUTE_CLASS, "param"),
133+
psiElement -> {
134+
PsiElement context = psiElement.getContext();
135+
if (!(context instanceof StringLiteralExpression)) {
136+
return null;
137+
}
138+
139+
PhpAttribute phpAttribute = PsiTreeUtil.getParentOfType(context, PhpAttribute.class);
140+
if (phpAttribute != null) {
141+
return new ParameterContributor((StringLiteralExpression) context);
142+
}
143+
144+
return null;
145+
}
146+
);
147+
148+
// #[Autowire(env: 'SOME_ENV_VAR')]
149+
registrar.register(
150+
PhpElementsUtil.getAttributeNamedArgumentStringPattern(ServiceContainerUtil.AUTOWIRE_ATTRIBUTE_CLASS, "env"),
151+
psiElement -> {
152+
PsiElement context = psiElement.getContext();
153+
if (!(context instanceof StringLiteralExpression)) {
154+
return null;
155+
}
156+
157+
PhpAttribute phpAttribute = PsiTreeUtil.getParentOfType(context, PhpAttribute.class);
158+
if (phpAttribute != null) {
159+
return new EnvironmentVariableContributor((StringLiteralExpression) context);
160+
}
161+
162+
return null;
163+
}
164+
);
165+
127166
// #[Autowire(service: 'some_service')]
128167
// #[AsDecorator(decorates: 'some_service')]
168+
// #[AutowireServiceClosure('some_service')]
169+
// #[AutowireServiceClosure(service: 'some_service')]
170+
// #[AutowireMethodOf('some_service')]
171+
// #[AutowireMethodOf(service: 'some_service')]
172+
// #[AutowireCallable(service: 'some_service')]
129173
registrar.register(
130174
PlatformPatterns.or(
131175
PhpElementsUtil.getAttributeNamedArgumentStringPattern(ServiceContainerUtil.AUTOWIRE_ATTRIBUTE_CLASS, "service"),
@@ -145,7 +189,20 @@ public void register(@NotNull GotoCompletionRegistrarParameter registrar) {
145189
// #[AutowireLocator(exclude: ['app.some_tag'])]
146190
// #[AutowireLocator(exclude: 'app.some_tag')]
147191
PhpElementsUtil.getAttributeNamedArgumentArrayStringPattern(ServiceContainerUtil.AUTOWIRE_LOCATOR_ATTRIBUTE_CLASS, "exclude"),
148-
PhpElementsUtil.getAttributeNamedArgumentStringPattern(ServiceContainerUtil.AUTOWIRE_LOCATOR_ATTRIBUTE_CLASS, "exclude")
192+
PhpElementsUtil.getAttributeNamedArgumentStringPattern(ServiceContainerUtil.AUTOWIRE_LOCATOR_ATTRIBUTE_CLASS, "exclude"),
193+
194+
// #[AutowireServiceClosure('some_service')]
195+
// #[AutowireServiceClosure(service: 'some_service')]
196+
PhpElementsUtil.getFirstAttributeStringPattern(ServiceContainerUtil.AUTOWIRE_SERVICE_CLOSURE_ATTRIBUTE_CLASS),
197+
PhpElementsUtil.getAttributeNamedArgumentStringPattern(ServiceContainerUtil.AUTOWIRE_SERVICE_CLOSURE_ATTRIBUTE_CLASS, "service"),
198+
199+
// #[AutowireMethodOf('some_service')]
200+
// #[AutowireMethodOf(service: 'some_service')]
201+
PhpElementsUtil.getFirstAttributeStringPattern(ServiceContainerUtil.AUTOWIRE_METHOD_OF_ATTRIBUTE_CLASS),
202+
PhpElementsUtil.getAttributeNamedArgumentStringPattern(ServiceContainerUtil.AUTOWIRE_METHOD_OF_ATTRIBUTE_CLASS, "service"),
203+
204+
// #[AutowireCallable(service: 'some_service')]
205+
PhpElementsUtil.getAttributeNamedArgumentStringPattern(ServiceContainerUtil.AUTOWIRE_CALLABLE_ATTRIBUTE_CLASS, "service")
149206
),
150207
psiElement -> {
151208
PsiElement context = psiElement.getContext();
@@ -162,6 +219,24 @@ public void register(@NotNull GotoCompletionRegistrarParameter registrar) {
162219
}
163220
);
164221

222+
// #[AutowireCallable(service: 'some_service', method: 'methodName')]
223+
registrar.register(
224+
PhpElementsUtil.getAttributeNamedArgumentStringPattern(ServiceContainerUtil.AUTOWIRE_CALLABLE_ATTRIBUTE_CLASS, "method"),
225+
psiElement -> {
226+
PsiElement context = psiElement.getContext();
227+
if (!(context instanceof StringLiteralExpression)) {
228+
return null;
229+
}
230+
231+
PhpAttribute phpAttribute = PsiTreeUtil.getParentOfType(context, PhpAttribute.class);
232+
if (phpAttribute != null) {
233+
return new AutowireCallableMethodContributor((StringLiteralExpression) context, phpAttribute);
234+
}
235+
236+
return null;
237+
}
238+
);
239+
165240
// #[When('dev')]
166241
registrar.register(
167242
PlatformPatterns.or(
@@ -255,4 +330,132 @@ public ServiceContributor(@NotNull StringLiteralExpression element) {
255330
return List.of(phpClass);
256331
}
257332
}
333+
334+
private static class EnvironmentVariableContributor extends GotoCompletionProvider {
335+
public EnvironmentVariableContributor(@NotNull StringLiteralExpression element) {
336+
super(element);
337+
}
338+
339+
@Override
340+
public @NotNull Collection<LookupElement> getLookupElements() {
341+
Collection<LookupElement> results = new ArrayList<>();
342+
343+
for (String envVar : DotEnvUtil.getEnvironmentVariables(getProject())) {
344+
results.add(LookupElementBuilder.create(envVar).withIcon(Symfony2Icons.SYMFONY));
345+
}
346+
347+
return results;
348+
}
349+
350+
@Override
351+
public @NotNull Collection<PsiElement> getPsiTargets(PsiElement element) {
352+
String contents = GotoCompletionUtil.getStringLiteralValue(element);
353+
if (contents == null) {
354+
return Collections.emptyList();
355+
}
356+
357+
// Strip processor prefix if present: bool:SOME_ENV_VAR -> SOME_ENV_VAR
358+
// Supports: int:, bool:, string:, json:, resolve:, etc.
359+
Matcher matcher = Pattern.compile("^[\\w-_^:]+:(.*)$", Pattern.MULTILINE).matcher(contents);
360+
if (matcher.find()) {
361+
contents = matcher.group(1);
362+
}
363+
364+
return DotEnvUtil.getEnvironmentVariableTargets(getProject(), contents);
365+
}
366+
}
367+
368+
private static class ParameterWithPercentWrapContributor extends GotoCompletionProvider {
369+
public ParameterWithPercentWrapContributor(@NotNull StringLiteralExpression element) {
370+
super(element);
371+
}
372+
373+
@Override
374+
public @NotNull Collection<LookupElement> getLookupElements() {
375+
Collection<LookupElement> results = new ArrayList<>();
376+
377+
for (Map.Entry<String, ContainerParameter> entry : ContainerCollectionResolver.getParameters(getProject()).entrySet()) {
378+
results.add(new ParameterLookupElement(
379+
entry.getValue(),
380+
ParameterPercentWrapInsertHandler.getInstance(),
381+
getElement().getText()
382+
));
383+
}
384+
385+
return results;
386+
}
387+
388+
@Override
389+
public @NotNull Collection<PsiElement> getPsiTargets(PsiElement element) {
390+
String contents = GotoCompletionUtil.getStringLiteralValue(element);
391+
if (contents == null) {
392+
return Collections.emptyList();
393+
}
394+
395+
// Strip % characters if present: %kernel.debug% -> kernel.debug
396+
contents = StringUtils.strip(contents, "%");
397+
398+
return ServiceUtil.getParameterDefinition(getProject(), contents);
399+
}
400+
}
401+
402+
private static class AutowireCallableMethodContributor extends GotoCompletionProvider {
403+
private final PhpAttribute phpAttribute;
404+
405+
public AutowireCallableMethodContributor(@NotNull StringLiteralExpression element, @NotNull PhpAttribute phpAttribute) {
406+
super(element);
407+
this.phpAttribute = phpAttribute;
408+
}
409+
410+
@Override
411+
public @NotNull Collection<LookupElement> getLookupElements() {
412+
Collection<LookupElement> results = new ArrayList<>();
413+
414+
PhpClass phpClass = getServiceClass();
415+
if (phpClass == null) {
416+
return results;
417+
}
418+
419+
for (Method method : phpClass.getMethods()) {
420+
// Only public non-static methods
421+
if (method.getAccess().isPublic() && !method.isStatic()) {
422+
results.add(LookupElementBuilder.create(method.getName())
423+
.withIcon(method.getIcon())
424+
.withTypeText(phpClass.getName(), true));
425+
}
426+
}
427+
428+
return results;
429+
}
430+
431+
@Override
432+
public @NotNull Collection<PsiElement> getPsiTargets(PsiElement element) {
433+
String methodName = GotoCompletionUtil.getStringLiteralValue(element);
434+
if (methodName == null) {
435+
return Collections.emptyList();
436+
}
437+
438+
PhpClass phpClass = getServiceClass();
439+
if (phpClass == null) {
440+
return Collections.emptyList();
441+
}
442+
443+
Method method = phpClass.findMethodByName(methodName);
444+
if (method != null && method.getAccess().isPublic() && !method.isStatic()) {
445+
return List.of(method);
446+
}
447+
448+
return Collections.emptyList();
449+
}
450+
451+
private PhpClass getServiceClass() {
452+
// Find the 'service' argument in the same attribute using the utility method
453+
String serviceId = PhpElementsUtil.getAttributeArgumentStringByName(phpAttribute, "service");
454+
if (serviceId != null && !serviceId.isEmpty()) {
455+
return ServiceUtil.getResolvedClassDefinition(getProject(), serviceId);
456+
}
457+
458+
return null;
459+
}
460+
}
258461
}

0 commit comments

Comments
 (0)