diff --git a/src/com/magento/idea/magento2plugin/indexes/EventIndex.java b/src/com/magento/idea/magento2plugin/indexes/EventIndex.java index f0faf8de6..0641e92e1 100644 --- a/src/com/magento/idea/magento2plugin/indexes/EventIndex.java +++ b/src/com/magento/idea/magento2plugin/indexes/EventIndex.java @@ -2,6 +2,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + package com.magento.idea.magento2plugin.indexes; import com.intellij.openapi.project.Project; @@ -13,41 +14,67 @@ import com.intellij.psi.xml.XmlFile; import com.intellij.util.indexing.FileBasedIndex; import com.magento.idea.magento2plugin.stubs.indexes.EventNameIndex; +import com.magento.idea.magento2plugin.stubs.indexes.EventObserverIndex; import com.magento.idea.magento2plugin.util.xml.XmlPsiTreeUtil; - import java.util.ArrayList; import java.util.Collection; public class EventIndex { - private static EventIndex INSTANCE; - - private Project project; - - private EventIndex() { - } - - public static EventIndex getInstance(final Project project) { - if (null == INSTANCE) { - INSTANCE = new EventIndex(); - } - INSTANCE.project = project; + private final Project project; - return INSTANCE; + /** + * Constructor. + */ + public EventIndex(final Project project) { + this.project = project; } - public Collection getEventElements(final String name, final GlobalSearchScope scope) { - Collection result = new ArrayList<>(); + /** + * Gets event elements by event name. + */ + public Collection getEventElements( + final String name, + final GlobalSearchScope scope + ) { + final Collection result = new ArrayList<>(); - Collection virtualFiles = + final Collection virtualFiles = FileBasedIndex.getInstance().getContainingFiles(EventNameIndex.KEY, name, scope); - for (VirtualFile virtualFile : virtualFiles) { - XmlFile xmlFile = (XmlFile) PsiManager.getInstance(project).findFile(virtualFile); - Collection valueElements = XmlPsiTreeUtil + for (final VirtualFile virtualFile : virtualFiles) { + final XmlFile xmlFile = (XmlFile) PsiManager.getInstance(project).findFile(virtualFile); + final Collection valueElements = XmlPsiTreeUtil .findAttributeValueElements(xmlFile, "event", "name", name); result.addAll(valueElements); } return result; } + + /** + * Gets observers by event-observer name combination. + */ + public Collection getObservers( + final String eventName, + final String observerName, + final GlobalSearchScope scope + ) { + final Collection result = new ArrayList<>(); + final Collection virtualFiles + = FileBasedIndex.getInstance().getContainingFiles( + EventObserverIndex.KEY, eventName, scope + ); + + for (final VirtualFile virtualFile: virtualFiles) { + final XmlFile eventsXmlFile + = (XmlFile) PsiManager.getInstance(project).findFile(virtualFile); + if (eventsXmlFile != null) { + result.addAll( + XmlPsiTreeUtil.findObserverTags(eventsXmlFile, eventName, observerName) + ); + } + } + + return result; + } } diff --git a/src/com/magento/idea/magento2plugin/inspections/xml/ObserverDeclarationInspection.java b/src/com/magento/idea/magento2plugin/inspections/xml/ObserverDeclarationInspection.java index a489bef89..a687e7bbd 100644 --- a/src/com/magento/idea/magento2plugin/inspections/xml/ObserverDeclarationInspection.java +++ b/src/com/magento/idea/magento2plugin/inspections/xml/ObserverDeclarationInspection.java @@ -44,6 +44,7 @@ public class ObserverDeclarationInspection extends PhpInspection { @NotNull @Override + @SuppressWarnings({"PMD.AvoidInstantiatingObjectsInLoops"}) public PsiElementVisitor buildVisitor( final @NotNull ProblemsHolder problemsHolder, final boolean isOnTheFly @@ -66,12 +67,12 @@ public void visitFile(final PsiFile file) { return; } - final EventIndex eventIndex = EventIndex.getInstance(file.getProject()); + final EventIndex eventIndex = new EventIndex(file.getProject()); final HashMap targetObserversHash = new HashMap<>(); for (final XmlTag eventXmlTag: xmlTags) { final HashMap eventProblems = new HashMap<>(); - if (!eventXmlTag.getName().equals("event")) { + if (!eventXmlTag.getName().equals(ModuleEventsXml.EVENT_TAG)) { continue; } @@ -236,7 +237,7 @@ private List fetchObserverTagsFromEventTag(final XmlTag eventXmlTag) { } for (final XmlTag observerXmlTag: observerXmlTags) { - if (!observerXmlTag.getName().equals("observer")) { + if (!observerXmlTag.getName().equals(ModuleEventsXml.OBSERVER_TAG)) { continue; } @@ -257,10 +258,11 @@ private void addModuleNameWhereSameObserverUsed( return; } - if (!moduleDeclarationTag.getName().equals("module")) { + if (!moduleDeclarationTag.getName().equals(ModuleEventsXml.MODULE_TAG)) { return; } - final XmlAttribute moduleNameAttribute = moduleDeclarationTag.getAttribute("name"); + final XmlAttribute moduleNameAttribute + = moduleDeclarationTag.getAttribute(ModuleEventsXml.NAME_ATTRIBUTE); if (moduleNameAttribute == null) { return; } diff --git a/src/com/magento/idea/magento2plugin/magento/files/ModuleEventsXml.java b/src/com/magento/idea/magento2plugin/magento/files/ModuleEventsXml.java index ee87f3d6a..81df22cff 100644 --- a/src/com/magento/idea/magento2plugin/magento/files/ModuleEventsXml.java +++ b/src/com/magento/idea/magento2plugin/magento/files/ModuleEventsXml.java @@ -19,6 +19,10 @@ public class ModuleEventsXml implements ModuleFileInterface { public static final String OBSERVER_TAG = "observer"; public static final String INSTANCE_ATTRIBUTE = "instance"; public static final String EVENT_TAG = "event"; + public static final String MODULE_TAG = "module"; + + //attributes + public static final String NAME_ATTRIBUTE = "name"; private static final ModuleEventsXml INSTANCE = new ModuleEventsXml(); diff --git a/src/com/magento/idea/magento2plugin/reference/provider/EventDispatchReferenceProvider.java b/src/com/magento/idea/magento2plugin/reference/provider/EventDispatchReferenceProvider.java index 59ee575af..dc1d5a292 100644 --- a/src/com/magento/idea/magento2plugin/reference/provider/EventDispatchReferenceProvider.java +++ b/src/com/magento/idea/magento2plugin/reference/provider/EventDispatchReferenceProvider.java @@ -29,7 +29,7 @@ public PsiReference[] getReferencesByElement( ) { final String value = StringUtil.unquoteString(element.getText()); final List psiReferences = new ArrayList<>(); - final Collection targets = EventIndex.getInstance(element.getProject()) + final Collection targets = new EventIndex(element.getProject()) .getEventElements( value, GlobalSearchScope.getScopeRestrictedByFileTypes( diff --git a/src/com/magento/idea/magento2plugin/reference/provider/ObserverNameReferenceProvider.java b/src/com/magento/idea/magento2plugin/reference/provider/ObserverNameReferenceProvider.java new file mode 100644 index 000000000..94e34c555 --- /dev/null +++ b/src/com/magento/idea/magento2plugin/reference/provider/ObserverNameReferenceProvider.java @@ -0,0 +1,43 @@ +package com.magento.idea.magento2plugin.reference.provider; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiReference; +import com.intellij.psi.PsiReferenceProvider; +import com.intellij.psi.impl.source.xml.XmlAttributeValueImpl; +import com.intellij.psi.impl.source.xml.XmlTagImpl; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.util.ProcessingContext; +import com.magento.idea.magento2plugin.indexes.EventIndex; +import com.magento.idea.magento2plugin.reference.xml.PolyVariantReferenceBase; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import org.jetbrains.annotations.NotNull; + +public class ObserverNameReferenceProvider extends PsiReferenceProvider { + @Override + public PsiReference @NotNull [] getReferencesByElement( + @NotNull final PsiElement element, + @NotNull final ProcessingContext context + ) { + final List psiReferences = new ArrayList<>(); + final XmlTagImpl eventTag = (XmlTagImpl) element.getParent().getParent().getParent(); + final String eventName = eventTag.getAttributeValue("name"); + + if (eventName == null) { + return psiReferences.toArray(new PsiReference[0]); + } + + final String observerName = ((XmlAttributeValueImpl) element).getValue(); + final Collection observers + = new EventIndex(element.getProject()).getObservers( + eventName, observerName, GlobalSearchScope.allScope(element.getProject()) + ); + + if (!observers.isEmpty()) { + psiReferences.add(new PolyVariantReferenceBase(element, observers)); + } + + return psiReferences.toArray(new PsiReference[0]); + } +} diff --git a/src/com/magento/idea/magento2plugin/reference/xml/XmlReferenceContributor.java b/src/com/magento/idea/magento2plugin/reference/xml/XmlReferenceContributor.java index e255e8704..e1eb7471a 100644 --- a/src/com/magento/idea/magento2plugin/reference/xml/XmlReferenceContributor.java +++ b/src/com/magento/idea/magento2plugin/reference/xml/XmlReferenceContributor.java @@ -153,6 +153,16 @@ public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) new EventNameReferenceProvider() ); + // + registrar.registerReferenceProvider( + XmlPatterns.xmlAttributeValue().withParent( + XmlPatterns.xmlAttribute().withName("name").withParent( + XmlPatterns.xmlTag().withName("observer") + ) + ), + new ObserverNameReferenceProvider() + ); + // registrar.registerReferenceProvider( XmlPatterns.xmlAttributeValue().withValue(string().matches(".*[A-Z][a-zA-Z0-9]+_[A-Z][a-zA-Z0-9]+.*")), diff --git a/src/com/magento/idea/magento2plugin/util/xml/XmlPsiTreeUtil.java b/src/com/magento/idea/magento2plugin/util/xml/XmlPsiTreeUtil.java index 870c823fd..8e3273a90 100644 --- a/src/com/magento/idea/magento2plugin/util/xml/XmlPsiTreeUtil.java +++ b/src/com/magento/idea/magento2plugin/util/xml/XmlPsiTreeUtil.java @@ -29,6 +29,10 @@ "PMD.UseObjectForClearerAPI" }) public class XmlPsiTreeUtil { + public static final String EVENT_TAG_NAME = "event"; + public static final String OBSERVER_TAG_NAME = "observer"; + public static final String NAME_ATTRIBUTE = "name"; + /** * Get type tag of an argument. * @@ -47,6 +51,52 @@ public static XmlTag getTypeTagOfArgument(final XmlElement psiArgumentValueEleme return PsiTreeUtil.getParentOfType(argumentsTag, XmlTag.class); } + /** + * Finds observer tags by event-observer name combination. + */ + @SuppressWarnings({ + "PMD.CyclomaticComplexity" + }) + public static Collection findObserverTags( + final XmlFile xmlFile, + final String eventName, + final String observerName + ) { + Collection psiElements = new THashSet<>(); + final XmlTag configTag = xmlFile.getRootTag(); + + if (configTag == null) { + return psiElements; + } + + /* Loop through event tags */ + for (XmlTag eventTag: configTag.getSubTags()) { + XmlAttribute eventNameAttribute = eventTag.getAttribute(NAME_ATTRIBUTE); + + /* Check if event tag and name matches */ + if (EVENT_TAG_NAME.equals(eventTag.getName()) + && eventNameAttribute != null + && eventName.equals(eventNameAttribute.getValue()) + ) { + /* Loop through observer tags under matched event tag */ + for (XmlTag observerTag: eventTag.getSubTags()) { + XmlAttribute observerNameAttribute = observerTag.getAttribute(NAME_ATTRIBUTE); + + /* Check if observer tag and name matches */ + if (OBSERVER_TAG_NAME.equals(observerTag.getName()) + && observerNameAttribute != null + && observerNameAttribute.getValueElement() != null + && observerName.equals(observerNameAttribute.getValue()) + ) { + psiElements.add(observerNameAttribute.getValueElement()); + } + } + } + } + + return psiElements; + } + /** * Find attribute value elements based on the tag name. * diff --git a/testData/project/magento2/vendor/magento/module-catalog/etc/events.xml b/testData/project/magento2/vendor/magento/module-catalog/etc/events.xml index aa2f68cd4..992a5dcde 100644 --- a/testData/project/magento2/vendor/magento/module-catalog/etc/events.xml +++ b/testData/project/magento2/vendor/magento/module-catalog/etc/events.xml @@ -5,8 +5,8 @@ * See COPYING.txt for license details. */ --> - - + diff --git a/testData/reference/xml/ObserverReferenceRegistrar/observerNameMustHaveReference/events.xml b/testData/reference/xml/ObserverReferenceRegistrar/observerNameMustHaveReference/events.xml new file mode 100644 index 000000000..a7872e70f --- /dev/null +++ b/testData/reference/xml/ObserverReferenceRegistrar/observerNameMustHaveReference/events.xml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/tests/com/magento/idea/magento2plugin/reference/BaseReferenceTestCase.java b/tests/com/magento/idea/magento2plugin/reference/BaseReferenceTestCase.java index b48c8b720..560e685bb 100644 --- a/tests/com/magento/idea/magento2plugin/reference/BaseReferenceTestCase.java +++ b/tests/com/magento/idea/magento2plugin/reference/BaseReferenceTestCase.java @@ -39,13 +39,29 @@ protected void setUp() throws Exception { protected void assertHasReferenceToXmlAttributeValue(final String reference) { final PsiElement element = getElementFromCaret(); for (final PsiReference psiReference: element.getReferences()) { - final PsiElement resolved = psiReference.resolve(); - if (!(resolved instanceof XmlAttributeValue)) { - continue; - } + if (psiReference instanceof PolyVariantReferenceBase) { + final ResolveResult[] resolveResults + = ((PolyVariantReferenceBase) psiReference).multiResolve(true); - if (((XmlAttributeValue) resolved).getValue().equals(reference)) { - return; + for (final ResolveResult resolveResult : resolveResults) { + final PsiElement resolved = resolveResult.getElement(); + if (!(resolved instanceof XmlAttributeValue)) { + continue; + } + + if (((XmlAttributeValue) resolved).getValue().equals(reference)) { + return; + } + } + } else { + final PsiElement resolved = psiReference.resolve(); + if (!(resolved instanceof XmlAttributeValue)) { + continue; + } + + if (((XmlAttributeValue) resolved).getValue().equals(reference)) { + return; + } } } final String referenceNotFound = diff --git a/tests/com/magento/idea/magento2plugin/reference/xml/ObserverReferenceRegistrarTest.java b/tests/com/magento/idea/magento2plugin/reference/xml/ObserverReferenceRegistrarTest.java index 5364376ce..155852a18 100644 --- a/tests/com/magento/idea/magento2plugin/reference/xml/ObserverReferenceRegistrarTest.java +++ b/tests/com/magento/idea/magento2plugin/reference/xml/ObserverReferenceRegistrarTest.java @@ -27,6 +27,15 @@ public void testObserverInstanceDirectorySnakeCaseMustHaveReference() { assertHasReferencePhpClass("Magento\\Catalog\\test_event\\TestObserver"); } + /** + * Tests for observer name reference in events.xml. + */ + public void testObserverNameMustHaveReference() { + myFixture.configureByFile(this.getFixturePath(ModuleEventsXml.FILE_NAME)); + + assertHasReferenceToXmlAttributeValue("test_observer"); + } + /** * Tests for event name reference in events.xml. */