Skip to content

Commit c7ee02d

Browse files
committed
named argument completion can now reflect the "bind" or "service" scope
1 parent 63aecd6 commit c7ee02d

File tree

5 files changed

+157
-31
lines changed

5 files changed

+157
-31
lines changed

src/main/java/fr/adrienbrault/idea/symfony2plugin/config/yaml/YamlCompletionContributor.java

Lines changed: 121 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,10 @@
5252
import org.apache.commons.lang.StringUtils;
5353
import org.jetbrains.annotations.NotNull;
5454
import org.jetbrains.annotations.Nullable;
55+
import org.jetbrains.yaml.YAMLTokenTypes;
5556
import org.jetbrains.yaml.psi.YAMLCompoundValue;
5657
import org.jetbrains.yaml.psi.YAMLKeyValue;
58+
import org.jetbrains.yaml.psi.YAMLMapping;
5759
import org.jetbrains.yaml.psi.YAMLScalar;
5860

5961
import java.util.*;
@@ -265,6 +267,9 @@ public void addCompletions(@NotNull CompletionParameters parameters,
265267
// _defaults:
266268
// bind:
267269
// $<caret>: ''
270+
// service:
271+
// arguments:
272+
// $<caret>: ''
268273
extend(
269274
CompletionType.BASIC,
270275
YamlElementPatternHelper.getNamedDefaultBindPattern(),
@@ -408,56 +413,142 @@ protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull
408413
* _defaults:
409414
* bind:
410415
* $projectDir: '%kernel.project_dir%'
416+
* foobar:
417+
* arguments:
418+
* $projectDir: '%kernel.project_dir%'
411419
*/
412420
private static class NamedArgumentCompletionProvider extends CompletionProvider<CompletionParameters> {
413421
@Override
414422
protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull ProcessingContext context, @NotNull CompletionResultSet result) {
415-
HashSet<String> uniqueParameters = new HashSet<>();
416-
417423
PsiElement position = parameters.getPosition();
418424
boolean hasEmptyNextElement = position.getNextSibling() == null;
419425

420-
ServiceContainerUtil.visitNamedArguments(position.getContainingFile(), pair -> {
421-
Parameter parameter = pair.getFirst();
422-
String parameterName = parameter.getName();
423-
if (uniqueParameters.contains(parameterName)) {
426+
YAMLMapping serviceDefintion = null;
427+
428+
if (position.getNode().getElementType() == YAMLTokenTypes.SCALAR_KEY) {
429+
YAMLKeyValue parentOfType = PsiTreeUtil.getParentOfType(position, YAMLKeyValue.class);
430+
YAMLKeyValue parentOfType2 = PsiTreeUtil.getParentOfType(parentOfType, YAMLKeyValue.class);
431+
if (parentOfType2 == null) {
424432
return;
425433
}
426434

427-
uniqueParameters.add(parameterName);
435+
String keyText2 = parentOfType2.getKeyText();
436+
if (!"bind".equals(keyText2)) {
437+
serviceDefintion = parentOfType2.getParentMapping();
438+
}
439+
} else {
440+
YAMLKeyValue parentOfType = PsiTreeUtil.getParentOfType(position, YAMLKeyValue.class);
441+
if (parentOfType == null) {
442+
return;
443+
}
428444

429-
// create argument for yaml: $parameter
430-
result.addElement(
431-
LookupElementBuilder.create("$" + parameterName)
432-
.withIcon(parameter.getIcon())
433-
.withTypeText(StringUtils.stripStart(parameter.getType().toString(), "\\"))
434-
);
445+
if (!"bind".equals(parentOfType.getKeyText())) {
446+
serviceDefintion = parentOfType.getParentMapping();
447+
}
448+
}
449+
450+
if (serviceDefintion == null) {
451+
// "defaults => bind": scope
452+
HashSet<String> uniqueParameters = new HashSet<>();
435453

436-
if (hasEmptyNextElement) {
437-
// iterable $handlers => can also provide "!tagged_iterator"
438-
if (parameter.getType().getTypes().stream().anyMatch(s -> s.equalsIgnoreCase(PhpType._ITERABLE))) {
439-
LookupElementBuilder element = LookupElementBuilder.create("$" + parameterName + ": !tagged_iterator")
454+
ServiceContainerUtil.visitNamedArguments(position.getContainingFile(), pair -> {
455+
Parameter parameter = pair.getFirst();
456+
String parameterName = parameter.getName();
457+
if (uniqueParameters.contains(parameterName)) {
458+
return;
459+
}
460+
461+
uniqueParameters.add(parameterName);
462+
463+
// create argument for yaml: $parameter
464+
result.addElement(
465+
LookupElementBuilder.create("$" + parameterName)
440466
.withIcon(parameter.getIcon())
441-
.withTypeText(StringUtils.stripStart(parameter.getType().toString(), "\\"), true);
467+
.withTypeText(StringUtils.stripStart(parameter.getType().toString(), "\\"))
468+
);
442469

443-
result.addElement(PrioritizedLookupElement.withPriority(element, -1000));
470+
if (hasEmptyNextElement) {
471+
// iterable $handlers => can also provide "!tagged_iterator"
472+
if (parameter.getType().getTypes().stream().anyMatch(s -> s.equalsIgnoreCase(PhpType._ITERABLE))) {
473+
LookupElementBuilder element = LookupElementBuilder.create("$" + parameterName + ": !tagged_iterator")
474+
.withIcon(parameter.getIcon())
475+
.withTypeText(StringUtils.stripStart(parameter.getType().toString(), "\\"), true);
476+
477+
result.addElement(PrioritizedLookupElement.withPriority(element, -1000));
478+
}
479+
480+
if (!parameter.getType().getTypes().stream().allMatch(PhpType::isPrimitiveType)) {
481+
// $foobar: '@service'
482+
result.addAllElements(getServiceSuggestion(position, pair, parameterName, new ContainerCollectionResolver.LazyServiceCollector(position.getProject())));
483+
} else {
484+
String parameterNormalized = parameterName.toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9]", "");
485+
if (parameterNormalized.length() > 5) {
486+
// $projectDir: '%kernel.project_dir%'
487+
result.addAllElements(getParameterSuggestion(parameter, parameterName, parameterNormalized));
488+
489+
// $kernelClass: '%env(KERNEL_CLASS)%'
490+
result.addAllElements(getDotEnvSuggestion(parameter, parameterName, parameterNormalized));
491+
}
492+
}
444493
}
494+
});
445495

446-
if (!parameter.getType().getTypes().stream().allMatch(PhpType::isPrimitiveType)) {
447-
// $foobar: '@service'
448-
result.addAllElements(getServiceSuggestion(position, pair, parameterName, new ContainerCollectionResolver.LazyServiceCollector(position.getProject())));
449-
} else {
450-
String parameterNormalized = parameterName.toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9]", "");
451-
if (parameterNormalized.length() > 5) {
452-
// $projectDir: '%kernel.project_dir%'
453-
result.addAllElements(getParameterSuggestion(parameter, parameterName, parameterNormalized));
496+
return;
497+
}
498+
499+
// service
500+
ContainerCollectionResolver.LazyServiceCollector lazyServiceCollector = new ContainerCollectionResolver.LazyServiceCollector(position.getProject());
454501

455-
// $kernelClass: '%env(KERNEL_CLASS)%'
456-
result.addAllElements(getDotEnvSuggestion(parameter, parameterName, parameterNormalized));
502+
@Nullable PhpClass yamlNamedArgumentPhpClass = ServiceContainerUtil.getServicePhpClassFromServiceMapping(serviceDefintion, lazyServiceCollector);
503+
if (yamlNamedArgumentPhpClass != null) {
504+
Method constructor = yamlNamedArgumentPhpClass.getConstructor();
505+
if (constructor != null) {
506+
HashSet<String> uniqueParameters = new HashSet<>();
507+
508+
Parameter @NotNull [] constructorParameters = constructor.getParameters();
509+
for (int i = 0; i < constructorParameters.length; i++) {
510+
Parameter parameter = constructorParameters[i];
511+
String parameterName = parameter.getName();
512+
if (uniqueParameters.contains(parameterName)) {
513+
return;
514+
}
515+
516+
uniqueParameters.add(parameterName);
517+
518+
// create argument for yaml: $parameter
519+
result.addElement(
520+
LookupElementBuilder.create("$" + parameterName)
521+
.withIcon(parameter.getIcon())
522+
.withTypeText(StringUtils.stripStart(parameter.getType().toString(), "\\"))
523+
);
524+
525+
if (hasEmptyNextElement) {
526+
// iterable $handlers => can also provide "!tagged_iterator"
527+
if (parameter.getType().getTypes().stream().anyMatch(s -> s.equalsIgnoreCase(PhpType._ITERABLE))) {
528+
LookupElementBuilder element = LookupElementBuilder.create("$" + parameterName + ": !tagged_iterator")
529+
.withIcon(parameter.getIcon())
530+
.withTypeText(StringUtils.stripStart(parameter.getType().toString(), "\\"), true);
531+
532+
result.addElement(PrioritizedLookupElement.withPriority(element, -1000));
533+
}
534+
535+
if (!parameter.getType().getTypes().stream().allMatch(PhpType::isPrimitiveType)) {
536+
// $foobar: '@service'
537+
result.addAllElements(getServiceSuggestion(position, Pair.create(parameter, i), parameterName, lazyServiceCollector));
538+
} else {
539+
String parameterNormalized = parameterName.toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9]", "");
540+
if (parameterNormalized.length() > 5) {
541+
// $projectDir: '%kernel.project_dir%'
542+
result.addAllElements(getParameterSuggestion(parameter, parameterName, parameterNormalized));
543+
544+
// $kernelClass: '%env(KERNEL_CLASS)%'
545+
result.addAllElements(getDotEnvSuggestion(parameter, parameterName, parameterNormalized));
546+
}
547+
}
457548
}
458549
}
459550
}
460-
});
551+
}
461552
}
462553

463554
@NotNull

src/main/java/fr/adrienbrault/idea/symfony2plugin/config/yaml/YamlElementPatternHelper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -804,7 +804,7 @@ public static ElementPattern<PsiElement> getPhpConstPattern() {
804804
* arguments:
805805
* $<caret>
806806
*/
807-
static ElementPattern<PsiElement> getNamedDefaultBindPattern() {
807+
public static ElementPattern<PsiElement> getNamedDefaultBindPattern() {
808808
// "__defaults" key
809809
PsiElementPattern.Capture<YAMLMapping> defaultsKey = PlatformPatterns.psiElement(YAMLMapping.class).withParent(PlatformPatterns.psiElement(YAMLKeyValue.class).with(new PatternCondition<YAMLKeyValue>("KeyText") {
810810
@Override

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,17 @@ public static String getServiceClassFromServiceMapping(@NotNull YAMLMapping yaml
672672
return null;
673673
}
674674

675+
@Nullable
676+
public static PhpClass getServicePhpClassFromServiceMapping(@NotNull YAMLMapping yamlMapping, @NotNull ContainerCollectionResolver.LazyServiceCollector lazyServiceCollector) {
677+
String serviceId = getServiceClassFromServiceMapping(yamlMapping);
678+
if (StringUtils.isNotBlank(serviceId)) {
679+
return ServiceUtil.getResolvedClassDefinition(yamlMapping.getProject(), serviceId, lazyServiceCollector);
680+
}
681+
682+
return null;
683+
}
684+
685+
675686
/**
676687
* <services>
677688
* <service class="Foo\\Bar\\Car">

src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/config/yaml/YamlCompletionContributorTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,15 @@ public void testNamedArgumentCompletionForDefaultsBinding() {
308308
);
309309
}
310310

311+
public void testNamedArgumentCompletionForServiceArguments2() {
312+
assertCompletionContains(YAMLFileType.YML, "" +
313+
"services:\n" +
314+
" Foo\\Car:\n" +
315+
" arguments:\n" +
316+
" $myDateT<caret>ime\n",
317+
"$myDateTime: '@foo'"
318+
);
319+
}
311320
public void testNamedArgumentCompletionForServiceArguments() {
312321
assertCompletionContains(YAMLFileType.YML, "" +
313322
"services:\n" +
@@ -333,6 +342,16 @@ public void testNamedArgumentCompletionForServiceArguments() {
333342
"$myDateTime: '@foo'"
334343
);
335344

345+
assertCompletionNotContains(YAMLFileType.YML, "" +
346+
"services:\n" +
347+
" Foo\\Bus: ~\n" +
348+
"\n" +
349+
" Foo\\Car:\n" +
350+
" arguments:\n" +
351+
" $<caret>\n",
352+
"$myBus"
353+
);
354+
336355
assertCompletionContains(YAMLFileType.YML, "" +
337356
"services:\n" +
338357
" Foo\\Car:\n" +

src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/config/yaml/fixtures/classes.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,9 @@ function __construct(\string $projectDir, \string $foobarEnv, \MyDateTime $myDat
2626
{
2727
}
2828
}
29+
30+
class Bus
31+
{
32+
function __construct($myBus) { }
33+
}
2934
}

0 commit comments

Comments
 (0)