Skip to content

Commit 6282bf9

Browse files
authored
Merge pull request #2092 from Haehnchen/feature/inspection-method-recursive
replace recursive visiting for event methods inspection
2 parents 522a962 + aba5d8b commit 6282bf9

File tree

4 files changed

+121
-87
lines changed

4 files changed

+121
-87
lines changed

src/main/java/fr/adrienbrault/idea/symfony2plugin/config/yaml/inspection/EventMethodCallInspection.java

Lines changed: 59 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@
44
import com.intellij.codeInspection.ProblemDescriptor;
55
import com.intellij.codeInspection.ProblemHighlightType;
66
import com.intellij.codeInspection.ProblemsHolder;
7+
import com.intellij.lang.Language;
8+
import com.intellij.lang.xml.XMLLanguage;
9+
import com.intellij.openapi.util.NotNullLazyValue;
710
import com.intellij.patterns.PlatformPatterns;
811
import com.intellij.patterns.StandardPatterns;
912
import com.intellij.psi.PsiElement;
1013
import com.intellij.psi.PsiElementVisitor;
11-
import com.intellij.psi.PsiFile;
12-
import com.intellij.psi.PsiRecursiveElementWalkingVisitor;
1314
import com.intellij.psi.util.PsiTreeUtil;
14-
import com.intellij.psi.xml.XmlFile;
15+
import com.jetbrains.php.lang.PhpLanguage;
1516
import com.jetbrains.php.lang.parser.PhpElementTypes;
16-
import com.jetbrains.php.lang.psi.PhpFile;
1717
import com.jetbrains.php.lang.psi.elements.Method;
1818
import com.jetbrains.php.lang.psi.elements.PhpClass;
1919
import com.jetbrains.php.lang.psi.elements.PhpReturn;
@@ -33,8 +33,9 @@
3333
import org.apache.commons.lang.StringUtils;
3434
import org.jetbrains.annotations.NotNull;
3535
import org.jetbrains.annotations.Nullable;
36+
import org.jetbrains.yaml.YAMLLanguage;
3637
import org.jetbrains.yaml.YAMLTokenTypes;
37-
import org.jetbrains.yaml.psi.*;
38+
import org.jetbrains.yaml.psi.YAMLScalar;
3839

3940
import java.util.Collection;
4041
import java.util.stream.Collectors;
@@ -52,66 +53,55 @@ public PsiElementVisitor buildVisitor(final @NotNull ProblemsHolder holder, bool
5253
}
5354

5455
return new PsiElementVisitor() {
55-
@Override
56-
public void visitFile(@NotNull PsiFile psiFile) {
57-
if(psiFile instanceof XmlFile) {
58-
visitXmlFile(psiFile, holder, new ContainerCollectionResolver.LazyServiceCollector(holder.getProject()));
59-
} else if(psiFile instanceof YAMLFile) {
60-
visitYamlFile(psiFile, holder, new ContainerCollectionResolver.LazyServiceCollector(holder.getProject()));
61-
} else if(psiFile instanceof PhpFile) {
62-
visitPhpFile((PhpFile) psiFile, holder);
63-
}
64-
}
65-
};
66-
}
56+
private NotNullLazyValue<ContainerCollectionResolver.LazyServiceCollector> serviceCollector;
6757

68-
private void visitPhpFile(PhpFile psiFile, final ProblemsHolder holder) {
69-
psiFile.acceptChildren(new PhpSubscriberRecursiveElementWalkingVisitor(holder));
70-
}
71-
72-
private void visitYamlFile(PsiFile psiFile, final ProblemsHolder holder, @NotNull final ContainerCollectionResolver.LazyServiceCollector lazyServiceCollector) {
73-
74-
psiFile.acceptChildren(new PsiRecursiveElementWalkingVisitor() {
7558
@Override
7659
public void visitElement(@NotNull PsiElement element) {
77-
annotateCallMethod(element, holder, lazyServiceCollector);
78-
super.visitElement(element);
79-
}
80-
});
60+
Language language = element.getLanguage();
8161

82-
}
83-
84-
private void visitXmlFile(@NotNull PsiFile psiFile, @NotNull final ProblemsHolder holder, @NotNull final ContainerCollectionResolver.LazyServiceCollector lazyServiceCollector) {
85-
86-
psiFile.acceptChildren(new PsiRecursiveElementWalkingVisitor() {
87-
@Override
88-
public void visitElement(@NotNull PsiElement element) {
89-
90-
if(XmlHelper.getTagAttributePattern("tag", "method").inside(XmlHelper.getInsideTagPattern("services")).inFile(XmlHelper.getXmlFilePattern()).accepts(element) ||
91-
XmlHelper.getTagAttributePattern("call", "method").inside(XmlHelper.getInsideTagPattern("services")).inFile(XmlHelper.getXmlFilePattern()).accepts(element)
92-
)
93-
{
94-
95-
// attach to text child only
96-
PsiElement[] psiElements = element.getChildren();
97-
if(psiElements.length < 2) {
98-
return;
62+
if (language == YAMLLanguage.INSTANCE) {
63+
if (this.serviceCollector == null) {
64+
this.serviceCollector = NotNullLazyValue.lazy(() -> new ContainerCollectionResolver.LazyServiceCollector(holder.getProject()));
9965
}
10066

101-
String serviceClassValue = XmlHelper.getServiceDefinitionClass(element);
102-
if(StringUtils.isNotBlank(serviceClassValue)) {
103-
registerMethodProblem(psiElements[1], holder, serviceClassValue, lazyServiceCollector);
67+
visitYmlElement(element, holder, this.serviceCollector);
68+
} else if (language == XMLLanguage.INSTANCE) {
69+
if (this.serviceCollector == null) {
70+
this.serviceCollector = NotNullLazyValue.lazy(() -> new ContainerCollectionResolver.LazyServiceCollector(holder.getProject()));
10471
}
10572

73+
visitXmlElement(element, holder, this.serviceCollector);
74+
} else if (language == PhpLanguage.INSTANCE) {
75+
if (element instanceof StringLiteralExpression stringLiteralExpression) {
76+
visitPhpElement(stringLiteralExpression, holder);
77+
}
10678
}
10779

10880
super.visitElement(element);
10981
}
110-
});
82+
};
83+
}
84+
85+
public void visitXmlElement(@NotNull PsiElement element, @NotNull final ProblemsHolder holder, @NotNull NotNullLazyValue<ContainerCollectionResolver.LazyServiceCollector> collector) {
86+
boolean isSupportedTag = XmlHelper.getTagAttributePattern("tag", "method").inside(XmlHelper.getInsideTagPattern("services")).inFile(XmlHelper.getXmlFilePattern()).accepts(element)
87+
|| XmlHelper.getTagAttributePattern("call", "method").inside(XmlHelper.getInsideTagPattern("services")).inFile(XmlHelper.getXmlFilePattern()).accepts(element);
88+
89+
if (isSupportedTag) {
90+
// attach to text child only
91+
PsiElement[] psiElements = element.getChildren();
92+
if (psiElements.length < 2) {
93+
return;
94+
}
11195

96+
String serviceClassValue = XmlHelper.getServiceDefinitionClass(element);
97+
if (StringUtils.isNotBlank(serviceClassValue)) {
98+
registerMethodProblem(psiElements[1], holder, serviceClassValue, collector);
99+
}
100+
101+
}
112102
}
113103

114-
private void visitYamlMethodTagKey(@NotNull final PsiElement psiElement, @NotNull ProblemsHolder holder, ContainerCollectionResolver.LazyServiceCollector collector) {
104+
private void visitYamlMethodTagKey(@NotNull final PsiElement psiElement, @NotNull ProblemsHolder holder, @NotNull NotNullLazyValue<ContainerCollectionResolver.LazyServiceCollector> collector) {
115105

116106
String methodName = PsiElementUtils.trimQuote(psiElement.getText());
117107
if(StringUtils.isBlank(methodName)) {
@@ -126,8 +116,7 @@ private void visitYamlMethodTagKey(@NotNull final PsiElement psiElement, @NotNul
126116
registerMethodProblem(psiElement, holder, classValue, collector);
127117
}
128118

129-
private void annotateCallMethod(@NotNull final PsiElement psiElement, @NotNull ProblemsHolder holder, ContainerCollectionResolver.LazyServiceCollector collector) {
130-
119+
private void visitYmlElement(@NotNull final PsiElement psiElement, @NotNull ProblemsHolder holder, @NotNull NotNullLazyValue<ContainerCollectionResolver.LazyServiceCollector> collector) {
131120
if(StandardPatterns.and(
132121
YamlElementPatternHelper.getInsideKeyValue("tags"),
133122
YamlElementPatternHelper.getSingleLineScalarKey("method")
@@ -140,10 +129,9 @@ private void annotateCallMethod(@NotNull final PsiElement psiElement, @NotNull P
140129
{
141130
visitYamlMethod(psiElement, holder, collector);
142131
}
143-
144132
}
145133

146-
private void visitYamlMethod(PsiElement psiElement, ProblemsHolder holder, ContainerCollectionResolver.LazyServiceCollector collector) {
134+
private void visitYamlMethod(@NotNull PsiElement psiElement, @NotNull ProblemsHolder holder, @NotNull NotNullLazyValue<ContainerCollectionResolver.LazyServiceCollector> collector) {
147135
if(YamlElementPatternHelper.getInsideKeyValue("calls").accepts(psiElement)) {
148136
PsiElement parent = psiElement.getParent();
149137
if ((parent instanceof YAMLScalar)) {
@@ -154,11 +142,11 @@ private void visitYamlMethod(PsiElement psiElement, ProblemsHolder holder, Conta
154142
}
155143
}
156144

157-
private void registerMethodProblem(final @NotNull PsiElement psiElement, @NotNull ProblemsHolder holder, @NotNull String classKeyValue, ContainerCollectionResolver.LazyServiceCollector collector) {
158-
registerMethodProblem(psiElement, holder, ServiceUtil.getResolvedClassDefinition(psiElement.getProject(), classKeyValue, collector));
145+
private void registerMethodProblem(final @NotNull PsiElement psiElement, @NotNull ProblemsHolder holder, @NotNull String classKeyValue, @NotNull NotNullLazyValue<ContainerCollectionResolver.LazyServiceCollector> collector) {
146+
registerMethodProblem(psiElement, holder, ServiceUtil.getResolvedClassDefinition(psiElement.getProject(), classKeyValue, collector.get()));
159147
}
160148

161-
private void registerMethodProblem(final @NotNull PsiElement psiElement, @NotNull ProblemsHolder holder, @Nullable PhpClass phpClass) {
149+
private static void registerMethodProblem(final @NotNull PsiElement psiElement, @NotNull ProblemsHolder holder, @Nullable PhpClass phpClass) {
162150
if(phpClass == null) {
163151
return;
164152
}
@@ -232,35 +220,20 @@ private String importIfNecessary(@NotNull PhpClass phpClass, String fqn) {
232220
* );
233221
*
234222
*/
235-
private class PhpSubscriberRecursiveElementWalkingVisitor extends PsiRecursiveElementWalkingVisitor {
236-
private final ProblemsHolder holder;
237-
238-
PhpSubscriberRecursiveElementWalkingVisitor(ProblemsHolder holder) {
239-
this.holder = holder;
240-
}
241-
242-
@Override
243-
public void visitElement(@NotNull PsiElement element) {
244-
super.visitElement(element);
245-
246-
if(!(element instanceof StringLiteralExpression)) {
247-
return;
248-
}
249-
250-
PsiElement arrayValue = element.getParent();
251-
if(arrayValue != null && arrayValue.getNode().getElementType() == PhpElementTypes.ARRAY_VALUE) {
252-
PhpReturn phpReturn = PsiTreeUtil.getParentOfType(arrayValue, PhpReturn.class);
253-
if(phpReturn != null) {
254-
Method method = PsiTreeUtil.getParentOfType(arrayValue, Method.class);
255-
if(method != null) {
256-
String name = method.getName();
257-
if("getSubscribedEvents".equals(name)) {
258-
PhpClass containingClass = method.getContainingClass();
259-
if(containingClass != null && PhpElementsUtil.isInstanceOf(containingClass, "\\Symfony\\Component\\EventDispatcher\\EventSubscriberInterface")) {
260-
String contents = ((StringLiteralExpression) element).getContents();
261-
if(StringUtils.isNotBlank(contents) && containingClass.findMethodByName(contents) == null) {
262-
registerMethodProblem(element, holder, containingClass);
263-
}
223+
private static void visitPhpElement(@NotNull StringLiteralExpression element, @NotNull ProblemsHolder holder) {
224+
PsiElement arrayValue = element.getParent();
225+
if (arrayValue != null && arrayValue.getNode().getElementType() == PhpElementTypes.ARRAY_VALUE) {
226+
PhpReturn phpReturn = PsiTreeUtil.getParentOfType(arrayValue, PhpReturn.class);
227+
if (phpReturn != null) {
228+
Method method = PsiTreeUtil.getParentOfType(arrayValue, Method.class);
229+
if (method != null) {
230+
String name = method.getName();
231+
if ("getSubscribedEvents".equals(name)) {
232+
PhpClass containingClass = method.getContainingClass();
233+
if (containingClass != null && PhpElementsUtil.isInstanceOf(containingClass, "\\Symfony\\Component\\EventDispatcher\\EventSubscriberInterface")) {
234+
String contents = element.getContents();
235+
if (StringUtils.isNotBlank(contents) && containingClass.findMethodByName(contents) == null) {
236+
registerMethodProblem(element, holder, containingClass);
264237
}
265238
}
266239
}

src/main/resources/META-INF/plugin.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,8 @@
333333
<localInspection groupPath="Symfony" shortName="EventMethodCall" displayName="Create Method"
334334
groupName="Service"
335335
enabledByDefault="true" level="WARNING"
336-
implementationClass="fr.adrienbrault.idea.symfony2plugin.config.yaml.inspection.EventMethodCallInspection"/>
336+
implementationClass="fr.adrienbrault.idea.symfony2plugin.config.yaml.inspection.EventMethodCallInspection"
337+
language=""/>
337338

338339
<localInspection groupPath="Symfony" shortName="XmlServiceArgumentInspection" displayName="Symfony: XML Arguments"
339340
groupName="Service"

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,59 @@ public void testThatYmlCallsProvidesMethodExistsCheck() {
4141
, "Missing Method");
4242
}
4343

44+
public void testThatPhpCallsProvidesMethodExistsCheck() {
45+
assertLocalInspectionContains("test.php", "<?php\n" +
46+
"" +
47+
"use Symfony\\Component\\EventDispatcher\\EventSubscriberInterface;\n" +
48+
"" +
49+
"class ExceptionSubscriber implements EventSubscriberInterface\n" +
50+
"{\n" +
51+
" public static function getSubscribedEvents(): array\n" +
52+
" {\n" +
53+
" // return the subscribed events, their methods and priorities\n" +
54+
" return [\n" +
55+
" KernelEvents::EXCEPTION => [\n" +
56+
" ['proces<caret>sException', 10],\n" +
57+
" ],\n" +
58+
" ];\n" +
59+
" }\n" +
60+
"\n" +
61+
"}",
62+
"Missing Method"
63+
);
64+
}
65+
66+
public void testThatXmlCallsProvidesMethodExistsCheck() {
67+
assertLocalInspectionContains("test.xml", "" +
68+
"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" +
69+
"<container xmlns=\"http://symfony.com/schema/dic/services\"\n" +
70+
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
71+
" xsi:schemaLocation=\"http://symfony.com/schema/dic/services\n" +
72+
" https://symfony.com/schema/dic/services/services-1.0.xsd\">\n" +
73+
"\n" +
74+
" <services>\n" +
75+
" <service id=\"Foo\\Service\\Method\\MyFoo\">\n" +
76+
" <call method=\"get<caret>Foos\"></call>\n" +
77+
" </service>\n" +
78+
" </services>\n" +
79+
"</container>",
80+
"Missing Method"
81+
);
82+
83+
assertLocalInspectionContains("test.xml", "" +
84+
"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" +
85+
"<container xmlns=\"http://symfony.com/schema/dic/services\"\n" +
86+
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
87+
" xsi:schemaLocation=\"http://symfony.com/schema/dic/services\n" +
88+
" https://symfony.com/schema/dic/services/services-1.0.xsd\">\n" +
89+
"\n" +
90+
" <services>\n" +
91+
" <service id=\"Foo\\Service\\Method\\MyFoo\">\n" +
92+
" <tag name=\"kernel.event_listener\" method=\"get<caret>Foos\"></call>\n" +
93+
" </service>\n" +
94+
" </services>\n" +
95+
"</container>",
96+
"Missing Method"
97+
);
98+
}
4499
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,8 @@ public function getFoo() {}
88
}
99
}
1010

11+
namespace Symfony\Component\EventDispatcher
12+
{
13+
interface EventSubscriberInterface {}
14+
}
15+

0 commit comments

Comments
 (0)