Skip to content

Commit 4f31dc3

Browse files
authored
Merge pull request #439 from drpayyne/issue-423
Added reference navigation for observer name
2 parents 4d7dddd + 94cac05 commit 4f31dc3

File tree

11 files changed

+208
-34
lines changed

11 files changed

+208
-34
lines changed

src/com/magento/idea/magento2plugin/indexes/EventIndex.java

+47-20
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* Copyright © Magento, Inc. All rights reserved.
33
* See COPYING.txt for license details.
44
*/
5+
56
package com.magento.idea.magento2plugin.indexes;
67

78
import com.intellij.openapi.project.Project;
@@ -13,41 +14,67 @@
1314
import com.intellij.psi.xml.XmlFile;
1415
import com.intellij.util.indexing.FileBasedIndex;
1516
import com.magento.idea.magento2plugin.stubs.indexes.EventNameIndex;
17+
import com.magento.idea.magento2plugin.stubs.indexes.EventObserverIndex;
1618
import com.magento.idea.magento2plugin.util.xml.XmlPsiTreeUtil;
17-
1819
import java.util.ArrayList;
1920
import java.util.Collection;
2021

2122
public class EventIndex {
2223

23-
private static EventIndex INSTANCE;
24-
25-
private Project project;
26-
27-
private EventIndex() {
28-
}
29-
30-
public static EventIndex getInstance(final Project project) {
31-
if (null == INSTANCE) {
32-
INSTANCE = new EventIndex();
33-
}
34-
INSTANCE.project = project;
24+
private final Project project;
3525

36-
return INSTANCE;
26+
/**
27+
* Constructor.
28+
*/
29+
public EventIndex(final Project project) {
30+
this.project = project;
3731
}
3832

39-
public Collection<PsiElement> getEventElements(final String name, final GlobalSearchScope scope) {
40-
Collection<PsiElement> result = new ArrayList<>();
33+
/**
34+
* Gets event elements by event name.
35+
*/
36+
public Collection<PsiElement> getEventElements(
37+
final String name,
38+
final GlobalSearchScope scope
39+
) {
40+
final Collection<PsiElement> result = new ArrayList<>();
4141

42-
Collection<VirtualFile> virtualFiles =
42+
final Collection<VirtualFile> virtualFiles =
4343
FileBasedIndex.getInstance().getContainingFiles(EventNameIndex.KEY, name, scope);
4444

45-
for (VirtualFile virtualFile : virtualFiles) {
46-
XmlFile xmlFile = (XmlFile) PsiManager.getInstance(project).findFile(virtualFile);
47-
Collection<XmlAttributeValue> valueElements = XmlPsiTreeUtil
45+
for (final VirtualFile virtualFile : virtualFiles) {
46+
final XmlFile xmlFile = (XmlFile) PsiManager.getInstance(project).findFile(virtualFile);
47+
final Collection<XmlAttributeValue> valueElements = XmlPsiTreeUtil
4848
.findAttributeValueElements(xmlFile, "event", "name", name);
4949
result.addAll(valueElements);
5050
}
5151
return result;
5252
}
53+
54+
/**
55+
* Gets observers by event-observer name combination.
56+
*/
57+
public Collection<PsiElement> getObservers(
58+
final String eventName,
59+
final String observerName,
60+
final GlobalSearchScope scope
61+
) {
62+
final Collection<PsiElement> result = new ArrayList<>();
63+
final Collection<VirtualFile> virtualFiles
64+
= FileBasedIndex.getInstance().getContainingFiles(
65+
EventObserverIndex.KEY, eventName, scope
66+
);
67+
68+
for (final VirtualFile virtualFile: virtualFiles) {
69+
final XmlFile eventsXmlFile
70+
= (XmlFile) PsiManager.getInstance(project).findFile(virtualFile);
71+
if (eventsXmlFile != null) {
72+
result.addAll(
73+
XmlPsiTreeUtil.findObserverTags(eventsXmlFile, eventName, observerName)
74+
);
75+
}
76+
}
77+
78+
return result;
79+
}
5380
}

src/com/magento/idea/magento2plugin/inspections/xml/ObserverDeclarationInspection.java

+7-5
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public class ObserverDeclarationInspection extends PhpInspection {
4444

4545
@NotNull
4646
@Override
47+
@SuppressWarnings({"PMD.AvoidInstantiatingObjectsInLoops"})
4748
public PsiElementVisitor buildVisitor(
4849
final @NotNull ProblemsHolder problemsHolder,
4950
final boolean isOnTheFly
@@ -66,12 +67,12 @@ public void visitFile(final PsiFile file) {
6667
return;
6768
}
6869

69-
final EventIndex eventIndex = EventIndex.getInstance(file.getProject());
70+
final EventIndex eventIndex = new EventIndex(file.getProject());
7071
final HashMap<String, XmlTag> targetObserversHash = new HashMap<>();
7172

7273
for (final XmlTag eventXmlTag: xmlTags) {
7374
final HashMap<String, XmlTag> eventProblems = new HashMap<>();
74-
if (!eventXmlTag.getName().equals("event")) {
75+
if (!eventXmlTag.getName().equals(ModuleEventsXml.EVENT_TAG)) {
7576
continue;
7677
}
7778

@@ -236,7 +237,7 @@ private List<XmlTag> fetchObserverTagsFromEventTag(final XmlTag eventXmlTag) {
236237
}
237238

238239
for (final XmlTag observerXmlTag: observerXmlTags) {
239-
if (!observerXmlTag.getName().equals("observer")) {
240+
if (!observerXmlTag.getName().equals(ModuleEventsXml.OBSERVER_TAG)) {
240241
continue;
241242
}
242243

@@ -257,10 +258,11 @@ private void addModuleNameWhereSameObserverUsed(
257258
return;
258259
}
259260

260-
if (!moduleDeclarationTag.getName().equals("module")) {
261+
if (!moduleDeclarationTag.getName().equals(ModuleEventsXml.MODULE_TAG)) {
261262
return;
262263
}
263-
final XmlAttribute moduleNameAttribute = moduleDeclarationTag.getAttribute("name");
264+
final XmlAttribute moduleNameAttribute
265+
= moduleDeclarationTag.getAttribute(ModuleEventsXml.NAME_ATTRIBUTE);
264266
if (moduleNameAttribute == null) {
265267
return;
266268
}

src/com/magento/idea/magento2plugin/magento/files/ModuleEventsXml.java

+4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ public class ModuleEventsXml implements ModuleFileInterface {
1919
public static final String OBSERVER_TAG = "observer";
2020
public static final String INSTANCE_ATTRIBUTE = "instance";
2121
public static final String EVENT_TAG = "event";
22+
public static final String MODULE_TAG = "module";
23+
24+
//attributes
25+
public static final String NAME_ATTRIBUTE = "name";
2226

2327
private static final ModuleEventsXml INSTANCE = new ModuleEventsXml();
2428

src/com/magento/idea/magento2plugin/reference/provider/EventDispatchReferenceProvider.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public PsiReference[] getReferencesByElement(
2929
) {
3030
final String value = StringUtil.unquoteString(element.getText());
3131
final List<PsiReference> psiReferences = new ArrayList<>();
32-
final Collection<PsiElement> targets = EventIndex.getInstance(element.getProject())
32+
final Collection<PsiElement> targets = new EventIndex(element.getProject())
3333
.getEventElements(
3434
value,
3535
GlobalSearchScope.getScopeRestrictedByFileTypes(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.magento.idea.magento2plugin.reference.provider;
2+
3+
import com.intellij.psi.PsiElement;
4+
import com.intellij.psi.PsiReference;
5+
import com.intellij.psi.PsiReferenceProvider;
6+
import com.intellij.psi.impl.source.xml.XmlAttributeValueImpl;
7+
import com.intellij.psi.impl.source.xml.XmlTagImpl;
8+
import com.intellij.psi.search.GlobalSearchScope;
9+
import com.intellij.util.ProcessingContext;
10+
import com.magento.idea.magento2plugin.indexes.EventIndex;
11+
import com.magento.idea.magento2plugin.reference.xml.PolyVariantReferenceBase;
12+
import java.util.ArrayList;
13+
import java.util.Collection;
14+
import java.util.List;
15+
import org.jetbrains.annotations.NotNull;
16+
17+
public class ObserverNameReferenceProvider extends PsiReferenceProvider {
18+
@Override
19+
public PsiReference @NotNull [] getReferencesByElement(
20+
@NotNull final PsiElement element,
21+
@NotNull final ProcessingContext context
22+
) {
23+
final List<PsiReference> psiReferences = new ArrayList<>();
24+
final XmlTagImpl eventTag = (XmlTagImpl) element.getParent().getParent().getParent();
25+
final String eventName = eventTag.getAttributeValue("name");
26+
27+
if (eventName == null) {
28+
return psiReferences.toArray(new PsiReference[0]);
29+
}
30+
31+
final String observerName = ((XmlAttributeValueImpl) element).getValue();
32+
final Collection<PsiElement> observers
33+
= new EventIndex(element.getProject()).getObservers(
34+
eventName, observerName, GlobalSearchScope.allScope(element.getProject())
35+
);
36+
37+
if (!observers.isEmpty()) {
38+
psiReferences.add(new PolyVariantReferenceBase(element, observers));
39+
}
40+
41+
return psiReferences.toArray(new PsiReference[0]);
42+
}
43+
}

src/com/magento/idea/magento2plugin/reference/xml/XmlReferenceContributor.java

+10
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,16 @@ public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar)
153153
new EventNameReferenceProvider()
154154
);
155155

156+
// <observer name="reference" />
157+
registrar.registerReferenceProvider(
158+
XmlPatterns.xmlAttributeValue().withParent(
159+
XmlPatterns.xmlAttribute().withName("name").withParent(
160+
XmlPatterns.xmlTag().withName("observer")
161+
)
162+
),
163+
new ObserverNameReferenceProvider()
164+
);
165+
156166
// <someXmlTag someAttribute="Module_Name[.*]" />
157167
registrar.registerReferenceProvider(
158168
XmlPatterns.xmlAttributeValue().withValue(string().matches(".*[A-Z][a-zA-Z0-9]+_[A-Z][a-zA-Z0-9]+.*")),

src/com/magento/idea/magento2plugin/util/xml/XmlPsiTreeUtil.java

+50
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
"PMD.UseObjectForClearerAPI"
3030
})
3131
public class XmlPsiTreeUtil {
32+
public static final String EVENT_TAG_NAME = "event";
33+
public static final String OBSERVER_TAG_NAME = "observer";
34+
public static final String NAME_ATTRIBUTE = "name";
35+
3236
/**
3337
* Get type tag of an argument.
3438
*
@@ -47,6 +51,52 @@ public static XmlTag getTypeTagOfArgument(final XmlElement psiArgumentValueEleme
4751
return PsiTreeUtil.getParentOfType(argumentsTag, XmlTag.class);
4852
}
4953

54+
/**
55+
* Finds observer tags by event-observer name combination.
56+
*/
57+
@SuppressWarnings({
58+
"PMD.CyclomaticComplexity"
59+
})
60+
public static Collection<XmlAttributeValue> findObserverTags(
61+
final XmlFile xmlFile,
62+
final String eventName,
63+
final String observerName
64+
) {
65+
Collection<XmlAttributeValue> psiElements = new THashSet<>();
66+
final XmlTag configTag = xmlFile.getRootTag();
67+
68+
if (configTag == null) {
69+
return psiElements;
70+
}
71+
72+
/* Loop through event tags */
73+
for (XmlTag eventTag: configTag.getSubTags()) {
74+
XmlAttribute eventNameAttribute = eventTag.getAttribute(NAME_ATTRIBUTE);
75+
76+
/* Check if event tag and name matches */
77+
if (EVENT_TAG_NAME.equals(eventTag.getName())
78+
&& eventNameAttribute != null
79+
&& eventName.equals(eventNameAttribute.getValue())
80+
) {
81+
/* Loop through observer tags under matched event tag */
82+
for (XmlTag observerTag: eventTag.getSubTags()) {
83+
XmlAttribute observerNameAttribute = observerTag.getAttribute(NAME_ATTRIBUTE);
84+
85+
/* Check if observer tag and name matches */
86+
if (OBSERVER_TAG_NAME.equals(observerTag.getName())
87+
&& observerNameAttribute != null
88+
&& observerNameAttribute.getValueElement() != null
89+
&& observerName.equals(observerNameAttribute.getValue())
90+
) {
91+
psiElements.add(observerNameAttribute.getValueElement());
92+
}
93+
}
94+
}
95+
}
96+
97+
return psiElements;
98+
}
99+
50100
/**
51101
* Find attribute value elements based on the tag name.
52102
*

testData/project/magento2/vendor/magento/module-catalog/etc/events.xml

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
* See COPYING.txt for license details.
66
*/
77
-->
8-
9-
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
8+
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
9+
xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
1010
<event name="test_event_in_test_class">
1111
<observer name="test_observer" instance="Magento\Catalog\Observer\TestObserver" />
1212
</event>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
9+
xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
10+
<event name="test_event_in_test_class">
11+
<observer name="test_observer<caret>" disabled="true"/>
12+
</event>
13+
</config>

tests/com/magento/idea/magento2plugin/reference/BaseReferenceTestCase.java

+22-6
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,29 @@ protected void setUp() throws Exception {
3939
protected void assertHasReferenceToXmlAttributeValue(final String reference) {
4040
final PsiElement element = getElementFromCaret();
4141
for (final PsiReference psiReference: element.getReferences()) {
42-
final PsiElement resolved = psiReference.resolve();
43-
if (!(resolved instanceof XmlAttributeValue)) {
44-
continue;
45-
}
42+
if (psiReference instanceof PolyVariantReferenceBase) {
43+
final ResolveResult[] resolveResults
44+
= ((PolyVariantReferenceBase) psiReference).multiResolve(true);
4645

47-
if (((XmlAttributeValue) resolved).getValue().equals(reference)) {
48-
return;
46+
for (final ResolveResult resolveResult : resolveResults) {
47+
final PsiElement resolved = resolveResult.getElement();
48+
if (!(resolved instanceof XmlAttributeValue)) {
49+
continue;
50+
}
51+
52+
if (((XmlAttributeValue) resolved).getValue().equals(reference)) {
53+
return;
54+
}
55+
}
56+
} else {
57+
final PsiElement resolved = psiReference.resolve();
58+
if (!(resolved instanceof XmlAttributeValue)) {
59+
continue;
60+
}
61+
62+
if (((XmlAttributeValue) resolved).getValue().equals(reference)) {
63+
return;
64+
}
4965
}
5066
}
5167
final String referenceNotFound =

tests/com/magento/idea/magento2plugin/reference/xml/ObserverReferenceRegistrarTest.java

+9
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ public void testObserverInstanceDirectorySnakeCaseMustHaveReference() {
2727
assertHasReferencePhpClass("Magento\\Catalog\\test_event\\TestObserver");
2828
}
2929

30+
/**
31+
* Tests for observer name reference in events.xml.
32+
*/
33+
public void testObserverNameMustHaveReference() {
34+
myFixture.configureByFile(this.getFixturePath(ModuleEventsXml.FILE_NAME));
35+
36+
assertHasReferenceToXmlAttributeValue("test_observer");
37+
}
38+
3039
/**
3140
* Tests for event name reference in events.xml.
3241
*/

0 commit comments

Comments
 (0)