From d6698abecb9c3bb6560889ef24e78ce5a8ca8d0e Mon Sep 17 00:00:00 2001 From: Michail Plushnikov Date: Sun, 18 Jan 2026 12:57:32 +0100 Subject: [PATCH] [lombok] IDEA-310810 IDEA-380110 Show Lombok-generated methods in 'Caller-Hierarchy' tool window, apply review suggestions GitOrigin-RevId: d3cc45468756d840bec3f5e9c974efb391d9708c --- .../call/CallerMethodsTreeStructure.java | 26 ++++++---- .../call/CallHierarchyElementProvider.java | 25 +++++++-- .../LombokFieldHierarchyElementProvider.java | 51 ++++++++++--------- 3 files changed, 65 insertions(+), 37 deletions(-) diff --git a/java/java-impl/src/com/intellij/ide/hierarchy/call/CallerMethodsTreeStructure.java b/java/java-impl/src/com/intellij/ide/hierarchy/call/CallerMethodsTreeStructure.java index 3deee1646f35..98d7821c02bd 100644 --- a/java/java-impl/src/com/intellij/ide/hierarchy/call/CallerMethodsTreeStructure.java +++ b/java/java-impl/src/com/intellij/ide/hierarchy/call/CallerMethodsTreeStructure.java @@ -71,9 +71,9 @@ public final class CallerMethodsTreeStructure extends HierarchyTreeStructure { PsiMember member = enclosingElement; PsiClass originalClass = member.getContainingClass(); - + if (originalClass == null) return ArrayUtilRt.EMPTY_OBJECT_ARRAY; - + PsiClassType originalType = JavaPsiFacade.getElementFactory(myProject).createType(originalClass); Set methodsToFind = new HashSet<>(); @@ -83,7 +83,9 @@ public final class CallerMethodsTreeStructure extends HierarchyTreeStructure { Map> methodToDescriptorMap = new HashMap<>(); for (PsiMethod methodToFind : methodsToFind) { - JavaCallHierarchyData data = new JavaCallHierarchyData(originalClass, methodToFind, originalType, method, methodsToFind, descriptor, methodToDescriptorMap, myProject); + JavaCallHierarchyData data = + new JavaCallHierarchyData(originalClass, methodToFind, originalType, method, methodsToFind, descriptor, methodToDescriptorMap, + myProject); MethodReferencesSearch.search(methodToFind, searchScope, true).forEach(reference -> { // references in javadoc really couldn't "call" anything @@ -118,15 +120,21 @@ public final class CallerMethodsTreeStructure extends HierarchyTreeStructure { return true; }); - // add other method elements without references + // add other methods with augmented references to methodToFind for (CallHierarchyElementProvider provider : CallHierarchyElementProvider.EP_NAME.getExtensionList()) { - provider.appendReferencedMethods(methodToFind, data); + final Collection referencedMethods = provider.provideReferencedMethods(methodToFind); + referencedMethods.forEach(referencedMethod -> { + final Map> nodeDescriptorMap = data.getResultMap(); + nodeDescriptorMap.computeIfAbsent(referencedMethod, + psiMember -> new CallHierarchyNodeDescriptor(myProject, descriptor, psiMember, + false, true)); + }); } } return ArrayUtil.toObjectArray(methodToDescriptorMap.values()); } - + assert enclosingElement instanceof PsiField || enclosingElement instanceof PsiRecordComponent : "Enclosing element should be a field, but was " + enclosingElement.getClass() + ", text: " + enclosingElement.getText(); @@ -134,12 +142,10 @@ public final class CallerMethodsTreeStructure extends HierarchyTreeStructure { .search(enclosingElement, searchScope).findAll().stream() .map(PsiReference::getElement); - // collect other field elements without references + // collect other field elements without direct references Collection<@NotNull PsiElement> providedElements = new ArrayList<>(); for (CallHierarchyElementProvider provider : CallHierarchyElementProvider.EP_NAME.getExtensionList()) { - if(provider.canProvide(enclosingElement)) { - providedElements.addAll(provider.provideReferencedMembers(enclosingElement)); - } + providedElements.addAll(provider.provideReferencedMembers(enclosingElement)); } return Stream.concat(referencedElements, providedElements.stream()) diff --git a/java/openapi/src/com/intellij/ide/hierarchy/call/CallHierarchyElementProvider.java b/java/openapi/src/com/intellij/ide/hierarchy/call/CallHierarchyElementProvider.java index ea4eefce278f..d332738428b8 100644 --- a/java/openapi/src/com/intellij/ide/hierarchy/call/CallHierarchyElementProvider.java +++ b/java/openapi/src/com/intellij/ide/hierarchy/call/CallHierarchyElementProvider.java @@ -5,18 +5,35 @@ import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiMember; import com.intellij.psi.PsiMethod; -import com.intellij.psi.search.SearchScope; import org.jetbrains.annotations.NotNull; import java.util.Collection; +/** + * The CallHierarchyElementProvider ExtensionPoint is designed to augment the Call Hierarchy results (specifically the "Caller Hierarchy" view). + * Its primary purpose is to allow plugins to provide "virtual" or "implicit" relationships between code elements that are not represented + * by standard physical PsiReference objects in the source code. + * This is particularly useful for frameworks that generate code or use bytecode manipulation (like Lombok), + * where a method or field might be logically "called" or "used" by elements that the standard Java reference search cannot find. + */ public interface CallHierarchyElementProvider { ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.hierarchy.elementProvider"); - boolean canProvide(@NotNull PsiMember element); + /** + * Collects augmented elements, which are not really present in code but virtually references to psiMember + * @param psiMember some PsiMember to find referenced elements for + * @return collection of virtually augmented elements references to psiMember + */ + @NotNull + Collection provideReferencedMembers(@NotNull PsiMember psiMember); - Collection provideReferencedMembers(@NotNull PsiMember reference); - void appendReferencedMethods(@NotNull PsiMethod methodToFind, @NotNull JavaCallHierarchyData hierarchyData); + /** + * Collects augmented PsiMethods, which are not really present in code but virtually references to methodToFind + * @param methodToFind some PsiMethod to find virtually referenced other PsiMethods for + ** @return collection of virtually augmented PsiMethods references to methodToFind + */ + @NotNull + Collection provideReferencedMethods(@NotNull PsiMethod methodToFind); } diff --git a/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/hierarchy/LombokFieldHierarchyElementProvider.java b/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/hierarchy/LombokFieldHierarchyElementProvider.java index bf4ec2160b7e..b8189ad13625 100644 --- a/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/hierarchy/LombokFieldHierarchyElementProvider.java +++ b/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/hierarchy/LombokFieldHierarchyElementProvider.java @@ -1,33 +1,39 @@ package de.plushnikov.intellij.plugin.hierarchy; import com.intellij.ide.hierarchy.call.CallHierarchyElementProvider; -import com.intellij.ide.hierarchy.call.CallHierarchyNodeDescriptor; -import com.intellij.ide.hierarchy.call.JavaCallHierarchyData; -import com.intellij.ide.util.treeView.NodeDescriptor; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.psi.*; -import com.intellij.psi.search.SearchScope; import de.plushnikov.intellij.plugin.psi.LombokLightMethodBuilder; +import de.plushnikov.intellij.plugin.util.LombokLibraryUtil; import org.jetbrains.annotations.NotNull; import java.util.*; +import java.util.stream.Collectors; public class LombokFieldHierarchyElementProvider implements CallHierarchyElementProvider { @Override - public boolean canProvide(@NotNull PsiMember element) { - return element instanceof PsiField; - } + public @NotNull Collection provideReferencedMembers(@NotNull PsiMember psiMember) { + // only PsiFields are supported now + if(!(psiMember instanceof PsiField)) { + return Collections.emptyList(); + } - @Override - public Collection provideReferencedMembers(@NotNull PsiMember elementToSearch) { - final PsiClass containingClass = elementToSearch.getContainingClass(); + // skip early if no lombok in current module + final Module module = ModuleUtilCore.findModuleForPsiElement(psiMember); + if (!LombokLibraryUtil.hasLombokClasses(module)) { + return Collections.emptyList(); + } + + final PsiClass containingClass = psiMember.getContainingClass(); if (containingClass != null) { final Collection result = new ArrayList<>(); Arrays.stream(containingClass.getMethods()) .filter(LombokLightMethodBuilder.class::isInstance) .map(LombokLightMethodBuilder.class::cast) - .filter(psiMethod -> psiMethod.getNavigationElement() == elementToSearch || psiMethod.hasRelatedMember(elementToSearch)) + .filter(psiMethod -> psiMethod.getNavigationElement() == psiMember || psiMethod.hasRelatedMember(psiMember)) .forEach(result::add); Arrays.stream(containingClass.getInnerClasses()) @@ -35,7 +41,7 @@ public class LombokFieldHierarchyElementProvider implements CallHierarchyElement .flatMap(Arrays::stream) .filter(LombokLightMethodBuilder.class::isInstance) .map(LombokLightMethodBuilder.class::cast) - .filter(psiMethod -> psiMethod.hasRelatedMember(elementToSearch)) + .filter(psiMethod -> psiMethod.hasRelatedMember(psiMember)) .forEach(result::add); return result; @@ -44,26 +50,25 @@ public class LombokFieldHierarchyElementProvider implements CallHierarchyElement } @Override - public void appendReferencedMethods(@NotNull PsiMethod methodToFind, @NotNull JavaCallHierarchyData hierarchyData) { + public @NotNull List provideReferencedMethods(@NotNull PsiMethod methodToFind) { + // skip early if no lombok in current module + final Module module = ModuleUtilCore.findModuleForPsiElement(methodToFind); + if (!LombokLibraryUtil.hasLombokClasses(module)) { + return Collections.emptyList(); + } + if (methodToFind.isConstructor()) { final PsiClass containingClass = methodToFind.getContainingClass(); if (null != containingClass) { - Arrays.stream(containingClass.getInnerClasses()) + return Arrays.stream(containingClass.getInnerClasses()) .map(PsiClass::getMethods) .flatMap(Arrays::stream) .filter(LombokLightMethodBuilder.class::isInstance) .map(LombokLightMethodBuilder.class::cast) .filter(methodBuilder -> methodBuilder.hasRelatedMember(methodToFind)) - .forEach(methodBuilder -> { - CallHierarchyNodeDescriptor parentDescriptor = (CallHierarchyNodeDescriptor)hierarchyData.getNodeDescriptor(); - final Map> nodeDescriptorMap = hierarchyData.getResultMap(); - CallHierarchyNodeDescriptor d = (CallHierarchyNodeDescriptor)nodeDescriptorMap.get(methodBuilder); - if (d == null) { - d = new CallHierarchyNodeDescriptor(hierarchyData.getProject(), parentDescriptor, methodBuilder, false, true); - nodeDescriptorMap.put(methodBuilder, d); - } - }); + .collect(Collectors.toList()); } } + return Collections.emptyList(); } } \ No newline at end of file