diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/codeInspection/service/TaggedExtendsInterfaceClassInspection.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/codeInspection/service/TaggedExtendsInterfaceClassInspection.java index 393fdfcb3..0e6404ad9 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/codeInspection/service/TaggedExtendsInterfaceClassInspection.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/codeInspection/service/TaggedExtendsInterfaceClassInspection.java @@ -115,20 +115,16 @@ public void visitElement(PsiElement element) { PsiElement serviceKeyValue = yamlCompoundValue.getParent(); if(serviceKeyValue instanceof YAMLKeyValue) { Set tags = YamlHelper.collectServiceTags((YAMLKeyValue) serviceKeyValue); - if(tags != null && tags.size() > 0) { + if(tags.size() > 0) { registerTaggedProblems(element, tags, text, holder, this.lazyServiceCollector); } } - } } - } - super.visitElement(element); } - } private void registerTaggedProblems(@NotNull PsiElement source, @NotNull Set tags, @NotNull String serviceClass, @NotNull ProblemsHolder holder, @NotNull ContainerCollectionResolver.LazyServiceCollector lazyServiceCollector) { diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/dic/container/util/ServiceContainerUtil.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/dic/container/util/ServiceContainerUtil.java index 6da47740d..0b8f1d129 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/dic/container/util/ServiceContainerUtil.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/dic/container/util/ServiceContainerUtil.java @@ -19,6 +19,7 @@ import com.jetbrains.php.lang.psi.elements.Parameter; import com.jetbrains.php.lang.psi.elements.PhpClass; import fr.adrienbrault.idea.symfony2plugin.config.xml.XmlHelper; +import fr.adrienbrault.idea.symfony2plugin.config.yaml.YamlElementPatternHelper; import fr.adrienbrault.idea.symfony2plugin.dic.attribute.value.AttributeValueInterface; import fr.adrienbrault.idea.symfony2plugin.dic.attribute.value.XmlTagAttributeValue; import fr.adrienbrault.idea.symfony2plugin.dic.attribute.value.YamlKeyValueAttributeValue; @@ -29,10 +30,7 @@ import fr.adrienbrault.idea.symfony2plugin.dic.container.visitor.ServiceConsumer; import fr.adrienbrault.idea.symfony2plugin.stubs.ContainerCollectionResolver; import fr.adrienbrault.idea.symfony2plugin.stubs.indexes.ContainerIdUsagesStubIndex; -import fr.adrienbrault.idea.symfony2plugin.util.MethodMatcher; -import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil; -import fr.adrienbrault.idea.symfony2plugin.util.PsiElementUtils; -import fr.adrienbrault.idea.symfony2plugin.util.TimeSecondModificationTracker; +import fr.adrienbrault.idea.symfony2plugin.util.*; import fr.adrienbrault.idea.symfony2plugin.util.dict.ServiceUtil; import fr.adrienbrault.idea.symfony2plugin.util.psi.PsiElementAssertUtil; import fr.adrienbrault.idea.symfony2plugin.util.yaml.YamlHelper; @@ -43,6 +41,7 @@ import org.jetbrains.yaml.psi.*; import java.util.*; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -421,18 +420,46 @@ public static Parameter getYamlNamedArgument(@NotNull PsiElement psiElement, @No */ public static void visitNamedArguments(@NotNull PsiFile psiFile, @NotNull Consumer processor) { if (psiFile instanceof YAMLFile) { + Collection parameters = new HashSet<>(); + + // direct service definition for (PhpClass phpClass : YamlHelper.getPhpClassesInYamlFile((YAMLFile) psiFile, new ContainerCollectionResolver.LazyServiceCollector(psiFile.getProject()))) { Method constructor = phpClass.getConstructor(); if (constructor == null) { continue; } - Arrays.stream(constructor.getParameters()).forEach(processor::consume); + parameters.addAll(Arrays.asList(constructor.getParameters())); } + + for (YAMLKeyValue taggedService : YamlHelper.getTaggedServices((YAMLFile) psiFile, "controller.service_arguments")) { + PsiElement key = taggedService.getKey(); + if (key == null) { + continue; + } + + String keyText = key.getText(); + if (StringUtils.isBlank(keyText)) { + continue; + } + + // App\Controller\ => \App\Controller + String namespace = StringUtils.strip(keyText, "\\"); + for (PhpClass phpClass : PhpIndexUtil.getPhpClassInsideNamespace(psiFile.getProject(), "\\" + namespace)) { + // find all parameters on public methods; this are possible actions + + // maybe filter actions and public methods in a suitable way? + phpClass.getMethods().stream() + .filter(method -> method.getAccess().isPublic() && !method.getName().startsWith("set")) + .forEach(method -> Collections.addAll(parameters, method.getParameters())); + } + } + + parameters.forEach(processor::consume); } } - /** + /* * Symfony 3.3: "class" is optional; use service name for its it * * Foo\Bar: diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/form/util/FormUtil.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/form/util/FormUtil.java index 9e3284278..3b55f3d02 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/form/util/FormUtil.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/form/util/FormUtil.java @@ -222,12 +222,12 @@ public static void attachFormAliasesCompletions(@NotNull PhpClass phpClass, @Not */ @NotNull public static Map> getTags(@NotNull YAMLFile yamlFile) { - Map> map = new HashMap<>(); + for(YAMLKeyValue yamlServiceKeyValue : YamlHelper.getQualifiedKeyValuesInFile(yamlFile, "services")) { String serviceName = yamlServiceKeyValue.getName(); Set serviceTagMap = YamlHelper.collectServiceTags(yamlServiceKeyValue); - if(serviceTagMap != null && serviceTagMap.size() > 0) { + if(serviceTagMap.size() > 0) { map.put(serviceName, serviceTagMap); } } @@ -235,8 +235,7 @@ public static Map> getTags(@NotNull YAMLFile yamlFile) { return map; } - public static Map> getTags(XmlFile psiFile) { - + public static Map> getTags(@NotNull XmlFile psiFile) { Map> map = new HashMap<>(); XmlDocumentImpl document = PsiTreeUtil.getChildOfType(psiFile, XmlDocumentImpl.class); diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/intentions/yaml/YamlServiceTagIntention.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/intentions/yaml/YamlServiceTagIntention.java index 5932b7934..f3296fef7 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/intentions/yaml/YamlServiceTagIntention.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/intentions/yaml/YamlServiceTagIntention.java @@ -161,7 +161,7 @@ private static Pair> invoke(@NotNull Project project, @Not Set phpClassServiceTags = ServiceUtil.getPhpClassServiceTags(resolvedClassDefinition); Set strings = YamlHelper.collectServiceTags(serviceKeyValue); - if(strings != null && strings.size() > 0) { + if(strings.size() > 0) { for (String s : strings) { if(phpClassServiceTags.contains(s)) { phpClassServiceTags.remove(s); diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/util/yaml/YamlHelper.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/util/yaml/YamlHelper.java index bb36728ce..3ac83a466 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/util/yaml/YamlHelper.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/util/yaml/YamlHelper.java @@ -625,23 +625,21 @@ public static YAMLKeyValue findServiceInContext(@NotNull PsiElement psiElement) * @param yamlKeyValue the service key value to find the "tags" key on * @return tag names */ - @Nullable + @NotNull public static Set collectServiceTags(@NotNull YAMLKeyValue yamlKeyValue) { - YAMLKeyValue tagsKeyValue = YamlHelper.getYamlKeyValue(yamlKeyValue, "tags"); if(tagsKeyValue == null) { - return null; + return Collections.emptySet(); } PsiElement tagsCompound = tagsKeyValue.getValue(); if(!(tagsCompound instanceof YAMLSequence)) { - return null; + return Collections.emptySet(); } Set tags = new HashSet<>(); for (YAMLSequenceItem yamlSequenceItem : ((YAMLSequence) tagsCompound).getItems()) { - YAMLValue value = yamlSequenceItem.getValue(); if(value instanceof YAMLMapping) { // tags: @@ -664,6 +662,26 @@ public static Set collectServiceTags(@NotNull YAMLKeyValue yamlKeyValue) return tags; } + /** + * acme_demo.form.type.gender: + * class: espend\Form\TypeBundle\Form\FooType + * tags: + * - { name: foo } + */ + @NotNull + public static Collection getTaggedServices(@NotNull YAMLFile yamlFile, @NotNull String tag) { + Collection yamlKeyValues = new HashSet<>(); + + for (YAMLKeyValue yamlServiceKeyValue : YamlHelper.getQualifiedKeyValuesInFile(yamlFile, "services")) { + Set serviceTagMap = YamlHelper.collectServiceTags(yamlServiceKeyValue); + if (serviceTagMap.contains(tag)) { + yamlKeyValues.add(yamlServiceKeyValue); + } + } + + return yamlKeyValues; + } + /** * TODO: use visitor pattern for all tags, we are using them to often */ diff --git a/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/dic/container/util/ServiceContainerUtilTest.java b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/dic/container/util/ServiceContainerUtilTest.java index c533f7ad1..64a07746b 100644 --- a/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/dic/container/util/ServiceContainerUtilTest.java +++ b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/dic/container/util/ServiceContainerUtilTest.java @@ -4,7 +4,9 @@ import com.intellij.openapi.util.Condition; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; +import com.intellij.util.Consumer; import com.intellij.util.containers.ContainerUtil; +import com.jetbrains.php.lang.psi.elements.Parameter; import fr.adrienbrault.idea.symfony2plugin.dic.container.ServiceInterface; import fr.adrienbrault.idea.symfony2plugin.dic.container.ServiceSerializable; import fr.adrienbrault.idea.symfony2plugin.dic.container.dict.ServiceTypeHint; @@ -12,10 +14,12 @@ import fr.adrienbrault.idea.symfony2plugin.stubs.ContainerCollectionResolver; import fr.adrienbrault.idea.symfony2plugin.tests.SymfonyLightCodeInsightFixtureTestCase; import org.jetbrains.annotations.NotNull; +import org.jetbrains.yaml.psi.YAMLFile; import org.jetbrains.yaml.psi.YAMLScalar; import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; import java.util.List; /** @@ -299,6 +303,26 @@ public void testGetXmlCallTypeHint() { assertEquals("setFoo", typeHint.getMethod().getName()); } + public void testVisitNamedArguments() { + PsiFile psiFile = myFixture.configureByText("test.yml", "" + + "services:\n" + + " NamedArgument\\Foobar:\n" + + " arguments: []\n" + + "" + + " App\\Controller\\:\n" + + " resource: '../src/Controller'\n" + + " tags: ['controller.service_arguments']\n" + ); + + Collection arguments = new HashSet<>(); + ServiceContainerUtil.visitNamedArguments(psiFile, parameter -> arguments.add(parameter.getName())); + + assertTrue(arguments.contains("foobar")); + + assertTrue(arguments.contains("foobarString")); + assertFalse(arguments.contains("private")); + } + private static class MyStringServiceInterfaceCondition implements Condition { @NotNull diff --git a/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/dic/container/util/fixtures/classes.php b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/dic/container/util/fixtures/classes.php index 552fd5ceb..f85927012 100644 --- a/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/dic/container/util/fixtures/classes.php +++ b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/dic/container/util/fixtures/classes.php @@ -12,4 +12,18 @@ public function setFoo($foo, Foobar $foobar) { } } +} + +namespace App\Controller +{ + class FoobarController + { + public function fooAction($fooEntity, string $foobarString) + { + } + + private function fooPrivate($private) + { + } + } } \ No newline at end of file diff --git a/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/util/yaml/YamlHelperLightTest.java b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/util/yaml/YamlHelperLightTest.java index ff4031054..e077eb97c 100644 --- a/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/util/yaml/YamlHelperLightTest.java +++ b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/util/yaml/YamlHelperLightTest.java @@ -2,6 +2,7 @@ import com.intellij.openapi.application.ApplicationInfo; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import com.jetbrains.php.lang.psi.elements.Parameter; @@ -192,7 +193,7 @@ public void testCollectServiceTagsForSymfony33TagsShortcut() { public void testCollectServiceTagsForSymfony33TagsShortcutInline() { YAMLKeyValue fromText = YamlPsiElementFactory.createFromText(getProject(), YAMLKeyValue.class, "" + "foo:\n" + - " tags: [routing.loader_tags_3, routing.loader_tags_4]\n" + " tags: [routing.loader_tags_3, routing.loader_tags_4, 'routing.loader_tags_5']\n" ); assertNotNull(fromText); @@ -200,6 +201,7 @@ public void testCollectServiceTagsForSymfony33TagsShortcutInline() { assertContainsElements(collection, "routing.loader_tags_3"); assertContainsElements(collection, "routing.loader_tags_4"); + assertContainsElements(collection, "routing.loader_tags_5"); } /** @@ -551,6 +553,25 @@ public void testGetServiceDefinitionClassFromTagMethod() { assertEquals("ClassName\\Foo", YamlHelper.getServiceDefinitionClassFromTagMethod(psiElement)); } + public void testGetTaggedServices() { + PsiFile psiFile = myFixture.configureByText(YAMLFileType.YML, "" + + "services:\n" + + " foobar:\n" + + " class: ClassName\\Foo\n" + + " tags:\n" + + " - { name: crossHint.test_222 }\n" + + " foobar2:\n" + + " class: ClassName\\Foo\n" + + " tags: [ 'test.11' ]\n" + ); + + Collection taggedServices1 = YamlHelper.getTaggedServices((YAMLFile) psiFile, "crossHint.test_222"); + assertTrue(taggedServices1.stream().anyMatch(yamlKeyValue -> "foobar".equals(yamlKeyValue.getKey().getText()))); + + Collection taggedServices2 = YamlHelper.getTaggedServices((YAMLFile) psiFile, "test.11"); + assertTrue(taggedServices2.stream().anyMatch(yamlKeyValue -> "foobar2".equals(yamlKeyValue.getKey().getText()))); + } + private int getIndentForTextContent(@NotNull String content) { return YamlHelper.getIndentSpaceForFile((YAMLFile) YamlPsiElementFactory.createDummyFile( getProject(),