[lombok] IDEA-310810 IDEA-380110 Show Lombok-generated methods in 'Caller-Hierarchy' tool window, apply review suggestions

GitOrigin-RevId: d3cc45468756d840bec3f5e9c974efb391d9708c
This commit is contained in:
Michail Plushnikov
2026-01-18 14:46:53 +00:00
committed by intellij-monorepo-bot
parent 55dfc2580c
commit d6698abecb
3 changed files with 65 additions and 37 deletions
@@ -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<PsiMethod> methodsToFind = new HashSet<>();
@@ -83,7 +83,9 @@ public final class CallerMethodsTreeStructure extends HierarchyTreeStructure {
Map<PsiMember, NodeDescriptor<?>> 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<PsiMethod> referencedMethods = provider.provideReferencedMethods(methodToFind);
referencedMethods.forEach(referencedMethod -> {
final Map<PsiMember, NodeDescriptor<?>> 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())
@@ -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<CallHierarchyElementProvider> 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<PsiElement> provideReferencedMembers(@NotNull PsiMember psiMember);
Collection<PsiElement> 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<PsiMethod> provideReferencedMethods(@NotNull PsiMethod methodToFind);
}
@@ -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<PsiElement> provideReferencedMembers(@NotNull PsiMember psiMember) {
// only PsiFields are supported now
if(!(psiMember instanceof PsiField)) {
return Collections.emptyList();
}
@Override
public Collection<PsiElement> 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<PsiElement> 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<PsiMethod> 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<PsiMember, NodeDescriptor<?>> 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();
}
}