Skip to content

#1984 support parameters inside "Autowire" attribute #1985

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ public class ServiceContainerUtil {
"debug", "default", "abstract", "inner", "chain", "decorate", "delegat"
};

public static final String AUTOWIRE_ATTRIBUTE_CLASS = "\\Symfony\\Component\\DependencyInjection\\Attribute\\Autowire";

@NotNull
public static Collection<ServiceSerializable> getServicesInFile(@NotNull PsiFile psiFile) {
final Collection<ServiceSerializable> services = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.php.lang.psi.elements.PhpAttribute;
import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionProvider;
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionRegistrar;
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionRegistrarParameter;
import fr.adrienbrault.idea.symfony2plugin.codeInsight.utils.GotoCompletionUtil;
import fr.adrienbrault.idea.symfony2plugin.config.component.ParameterLookupElement;
import fr.adrienbrault.idea.symfony2plugin.dic.ContainerParameter;
import fr.adrienbrault.idea.symfony2plugin.dic.container.util.ServiceContainerUtil;
import fr.adrienbrault.idea.symfony2plugin.stubs.ContainerCollectionResolver;
import fr.adrienbrault.idea.symfony2plugin.util.MethodMatcher;
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
Expand All @@ -25,7 +28,6 @@
* @author Daniel Espendiller <daniel@espendiller.net>
*/
public class DicGotoCompletionRegistrar implements GotoCompletionRegistrar {

@Override
public void register(@NotNull GotoCompletionRegistrarParameter registrar) {
// getParameter('FOO')
Expand Down Expand Up @@ -65,6 +67,30 @@ public void register(@NotNull GotoCompletionRegistrarParameter registrar) {
return new ParameterContributor((StringLiteralExpression) context);
}
);

// #[Autowire('<caret>')]
// #[Autowire(value: '<caret>')]
registrar.register(
PlatformPatterns.or(
PhpElementsUtil.getFirstAttributeStringPattern(ServiceContainerUtil.AUTOWIRE_ATTRIBUTE_CLASS),
PhpElementsUtil.getAttributeNamedArgumentStringPattern(ServiceContainerUtil.AUTOWIRE_ATTRIBUTE_CLASS, "value")
), psiElement -> {
PsiElement context = psiElement.getContext();
if (!(context instanceof StringLiteralExpression)) {
return null;
}

PhpAttribute phpAttribute = PsiTreeUtil.getParentOfType(context, PhpAttribute.class);
if (phpAttribute != null) {
String fqn = phpAttribute.getFQN();
if (fqn != null && PhpElementsUtil.isInstanceOf(psiElement.getProject(), fqn, ServiceContainerUtil.AUTOWIRE_ATTRIBUTE_CLASS)) {
return new ParameterContributor((StringLiteralExpression) context);
}
}

return null;
}
);
}

private static class ParameterContributor extends GotoCompletionProvider {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,74 @@ public boolean accepts(@NotNull FunctionReference functionReference, ProcessingC
.withLanguage(PhpLanguage.INSTANCE);
}

private static final PatternCondition<StringLiteralExpression> EMPTY_PREVIOUS_LEAF = new PatternCondition<>("previous leaf empty") {
@Override
public boolean accepts(@NotNull StringLiteralExpression stringLiteralExpression, ProcessingContext context) {
return stringLiteralExpression.getPrevSibling() == null;
}
};

/**
* #[Security("is_granted('POST_SHOW')")]
*/
@NotNull
public static PsiElementPattern.Capture<PsiElement> getFirstAttributeStringPattern(@NotNull String clazz) {
return PlatformPatterns.psiElement().withElementType(PlatformPatterns.elementType().or(
PhpTokenTypes.STRING_LITERAL_SINGLE_QUOTE,
PhpTokenTypes.STRING_LITERAL
))
.withParent(PlatformPatterns.psiElement(StringLiteralExpression.class)
.with(EMPTY_PREVIOUS_LEAF)
.withParent(PlatformPatterns.psiElement(ParameterList.class)
.withParent(PlatformPatterns.psiElement(PhpAttribute.class)
.with(new AttributeInstancePatternCondition(clazz))
)
)
);
}

/**
* #[Security(foobar: "is_granted('POST_SHOW')")]
*/
@NotNull
public static PsiElementPattern.Capture<PsiElement> getAttributeNamedArgumentStringPattern(@NotNull String clazz, @NotNull String namedArgument) {
return PlatformPatterns.psiElement().withElementType(PlatformPatterns.elementType().or(
PhpTokenTypes.STRING_LITERAL_SINGLE_QUOTE,
PhpTokenTypes.STRING_LITERAL
))
.withParent(PlatformPatterns.psiElement(StringLiteralExpression.class)
.afterLeafSkipping(
PlatformPatterns.psiElement(PsiWhiteSpace.class),
PlatformPatterns.psiElement(PhpTokenTypes.opCOLON).afterLeafSkipping(
PlatformPatterns.psiElement(PsiWhiteSpace.class),
PlatformPatterns.psiElement(PhpTokenTypes.IDENTIFIER).withText(namedArgument)
)
)
.withParent(PlatformPatterns.psiElement(ParameterList.class)
.withParent(PlatformPatterns.psiElement(PhpAttribute.class)
.with(new AttributeInstancePatternCondition(clazz))
)
)
);
}

/**
* Check if given Attribute
*/
private static class AttributeInstancePatternCondition extends PatternCondition<PsiElement> {
private final String clazz;

AttributeInstancePatternCondition(@NotNull String clazz) {
super("Attribute Instance");
this.clazz = clazz;
}

@Override
public boolean accepts(@NotNull PsiElement psiElement, ProcessingContext processingContext) {
return clazz.equals(((PhpAttribute) psiElement).getFQN());
}
}

/**
* $foo->bar('<caret>')
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,73 @@ public void testParameterContributorFor() {
PlatformPatterns.psiElement()
);
}

public void testParameterContributorForDefaultAttribute() {
assertCompletionContains(PhpFileType.INSTANCE, "<?php\n" +
"use Symfony\\Component\\DependencyInjection\\Attribute\\Autowire;\n" +
"\n" +
"class MyService\n" +
"{\n" +
" public function __construct(\n" +
" #[Autowire('<caret>')]\n" +
" private $parameter2" +
" ) {}\n" +
"}",
"foo"
);

assertCompletionContains(PhpFileType.INSTANCE, "<?php\n" +
"use Symfony\\Component\\DependencyInjection\\Attribute\\Autowire;\n" +
"\n" +
"class MyService\n" +
"{\n" +
" public function __construct(\n" +
" #[Autowire(\"<caret>\")]\n" +
" private $parameter2" +
" ) {}\n" +
"}",
"foo"
);

assertNavigationMatch(PhpFileType.INSTANCE, "<?php\n" +
"use Symfony\\Component\\DependencyInjection\\Attribute\\Autowire;\n" +
"\n" +
"class MyService\n" +
"{\n" +
" public function __construct(\n" +
" #[Autowire('fo<caret>o')]\n" +
" private $parameter2" +
" ) {}\n" +
"}",
PlatformPatterns.psiElement()
);
}

public void testParameterContributorForNamedAttribute() {
assertCompletionContains(PhpFileType.INSTANCE, "<?php\n" +
"use Symfony\\Component\\DependencyInjection\\Attribute\\Autowire;\n" +
"\n" +
"class MyService\n" +
"{\n" +
" public function __construct(\n" +
" #[Autowire(value: '<caret>')]\n" +
" private $parameter2" +
" ) {}\n" +
"}",
"foo"
);

assertNavigationMatch(PhpFileType.INSTANCE, "<?php\n" +
"use Symfony\\Component\\DependencyInjection\\Attribute\\Autowire;\n" +
"\n" +
"class MyService\n" +
"{\n" +
" public function __construct(\n" +
" #[Autowire(value: 'fo<caret>o')]\n" +
" private $parameter2" +
" ) {}\n" +
"}",
PlatformPatterns.psiElement()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,23 @@ function hasParameter($parameter);
}
}

namespace Symfony\Component\DependencyInjection\Attribute
{
class Autowire
{
/**
* @param string|null $value Parameter value (ie "%kernel.project_dir%/some/path")
* @param string|null $service Service ID (ie "some.service")
* @param string|null $expression Expression (ie 'service("some.service").someMethod()')
*/
public function __construct(
string $value = null,
string $service = null,
string $expression = null,
) {}
}
}

namespace
{
class Foo
Expand Down