From 77bb505f54602fc4df51cf14c1f8479fdb8d8396 Mon Sep 17 00:00:00 2001 From: Bas Leijdekkers Date: Fri, 29 Nov 2024 10:38:20 +0100 Subject: [PATCH] Java: call hierarchy for records (IJPL-3784) GitOrigin-RevId: ec7930d33f3626d7f72b75e7709c9cf141a9f5b7 --- .../hierarchy/call/CallHierarchyBrowser.java | 9 +++----- .../call/CallHierarchyNodeDescriptor.java | 7 ++++-- .../call/CalleeMethodsTreeStructure.java | 4 ++-- .../call/JavaCallHierarchyProvider.java | 8 ++----- .../RecordCanonicalConstructor/Action.java | 22 +++++++++++++++++++ .../verification.xml | 4 ++++ .../Action.java | 22 +++++++++++++++++++ .../verification.xml | 2 ++ .../ide/hierarchy/JavaCallHierarchyTest.java | 16 +++++++++++++- 9 files changed, 77 insertions(+), 17 deletions(-) create mode 100644 java/java-tests/testData/ide/hierarchy/call/RecordCanonicalConstructor/Action.java create mode 100644 java/java-tests/testData/ide/hierarchy/call/RecordCanonicalConstructor/verification.xml create mode 100644 java/java-tests/testData/ide/hierarchy/call/RecordCanonicalConstructorReverse/Action.java create mode 100644 java/java-tests/testData/ide/hierarchy/call/RecordCanonicalConstructorReverse/verification.xml diff --git a/java/java-impl/src/com/intellij/ide/hierarchy/call/CallHierarchyBrowser.java b/java/java-impl/src/com/intellij/ide/hierarchy/call/CallHierarchyBrowser.java index c5a45b3a60ec..ad78a3f65017 100644 --- a/java/java-impl/src/com/intellij/ide/hierarchy/call/CallHierarchyBrowser.java +++ b/java/java-impl/src/com/intellij/ide/hierarchy/call/CallHierarchyBrowser.java @@ -1,4 +1,4 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.ide.hierarchy.call; import com.intellij.ide.hierarchy.CallHierarchyBrowserBase; @@ -11,10 +11,7 @@ import com.intellij.openapi.actionSystem.ActionPlaces; import com.intellij.openapi.actionSystem.IdeActions; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiField; -import com.intellij.psi.PsiMember; -import com.intellij.psi.PsiMethod; +import com.intellij.psi.*; import com.intellij.ui.PopupHandler; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; @@ -64,7 +61,7 @@ public class CallHierarchyBrowser extends CallHierarchyBrowserBase { @Override protected boolean isApplicableElement(@NotNull PsiElement e) { - return e instanceof PsiMethod || e instanceof PsiField; + return e instanceof PsiMethod || e instanceof PsiField || e instanceof PsiClass aClass && aClass.isRecord(); } @Override diff --git a/java/java-impl/src/com/intellij/ide/hierarchy/call/CallHierarchyNodeDescriptor.java b/java/java-impl/src/com/intellij/ide/hierarchy/call/CallHierarchyNodeDescriptor.java index 56e0fc6c8dac..df053221be84 100644 --- a/java/java-impl/src/com/intellij/ide/hierarchy/call/CallHierarchyNodeDescriptor.java +++ b/java/java-impl/src/com/intellij/ide/hierarchy/call/CallHierarchyNodeDescriptor.java @@ -1,4 +1,4 @@ -// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.ide.hierarchy.call; import com.intellij.codeInsight.highlighting.HighlightManager; @@ -46,6 +46,9 @@ public final class CallHierarchyNodeDescriptor extends HierarchyNodeDescriptor i */ public PsiMember getEnclosingElement() { PsiElement element = getPsiElement(); + if (element instanceof PsiClass aClass && aClass.isRecord()) { + return JavaPsiRecordUtil.findCanonicalConstructor(aClass); + } return element == null ? null : getEnclosingElement(element); } @@ -90,7 +93,7 @@ public final class CallHierarchyNodeDescriptor extends HierarchyNodeDescriptor i mainTextAttributes = new TextAttributes(myColor, null, null, null, Font.PLAIN); } if (enclosingElement instanceof PsiMethod || enclosingElement instanceof PsiField) { - if (enclosingElement instanceof SyntheticElement) { + if (FileTypeUtils.isInServerPageFile(enclosingElement)) { PsiFile file = enclosingElement.getContainingFile(); myHighlightedText.getEnding().addText(file != null ? file.getName() : JavaBundle.message("node.call.hierarchy.unknown.jsp"), mainTextAttributes); } diff --git a/java/java-impl/src/com/intellij/ide/hierarchy/call/CalleeMethodsTreeStructure.java b/java/java-impl/src/com/intellij/ide/hierarchy/call/CalleeMethodsTreeStructure.java index 1cb6a5912d46..b351af3943b3 100644 --- a/java/java-impl/src/com/intellij/ide/hierarchy/call/CalleeMethodsTreeStructure.java +++ b/java/java-impl/src/com/intellij/ide/hierarchy/call/CalleeMethodsTreeStructure.java @@ -1,4 +1,4 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.ide.hierarchy.call; import com.intellij.ide.hierarchy.HierarchyNodeDescriptor; @@ -30,7 +30,7 @@ public final class CalleeMethodsTreeStructure extends HierarchyTreeStructure { @Override protected Object @NotNull [] buildChildren(@NotNull HierarchyNodeDescriptor descriptor) { PsiMember enclosingElement = ((CallHierarchyNodeDescriptor)descriptor).getEnclosingElement(); - if (!(enclosingElement instanceof PsiMethod method)) { + if (!(enclosingElement instanceof PsiMethod method) || enclosingElement instanceof SyntheticElement) { return ArrayUtilRt.EMPTY_OBJECT_ARRAY; } diff --git a/java/java-impl/src/com/intellij/ide/hierarchy/call/JavaCallHierarchyProvider.java b/java/java-impl/src/com/intellij/ide/hierarchy/call/JavaCallHierarchyProvider.java index b8c952eb5125..25044d7b386c 100644 --- a/java/java-impl/src/com/intellij/ide/hierarchy/call/JavaCallHierarchyProvider.java +++ b/java/java-impl/src/com/intellij/ide/hierarchy/call/JavaCallHierarchyProvider.java @@ -7,21 +7,17 @@ import com.intellij.ide.hierarchy.HierarchyProvider; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiField; -import com.intellij.psi.PsiMember; -import com.intellij.psi.PsiMethod; +import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.annotations.NotNull; - public class JavaCallHierarchyProvider implements HierarchyProvider { @Override public PsiElement getTarget(@NotNull DataContext dataContext) { Project project = CommonDataKeys.PROJECT.getData(dataContext); if (project == null) return null; PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(dataContext); - if (element instanceof PsiField) return element; + if (element instanceof PsiField || element instanceof PsiClass aClass && aClass.isRecord()) return element; return PsiTreeUtil.getParentOfType(element, PsiMethod.class, false); } diff --git a/java/java-tests/testData/ide/hierarchy/call/RecordCanonicalConstructor/Action.java b/java/java-tests/testData/ide/hierarchy/call/RecordCanonicalConstructor/Action.java new file mode 100644 index 000000000000..9bcab8547b57 --- /dev/null +++ b/java/java-tests/testData/ide/hierarchy/call/RecordCanonicalConstructor/Action.java @@ -0,0 +1,22 @@ +class Action { + public static void main(String[] args) { + var a = new Person("sdf", "sdf"); + var b = new PersonB("sdf", "sdf"); + } + + public static class PersonB { + + private final String name; + private final String address; + + public PersonB(String name, String address) { + this.name = name; + this.address = address; + } + } +} +record Person(String name, String address) { + public Person(String name) { + this(name, ""); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/ide/hierarchy/call/RecordCanonicalConstructor/verification.xml b/java/java-tests/testData/ide/hierarchy/call/RecordCanonicalConstructor/verification.xml new file mode 100644 index 000000000000..d640718473f8 --- /dev/null +++ b/java/java-tests/testData/ide/hierarchy/call/RecordCanonicalConstructor/verification.xml @@ -0,0 +1,4 @@ + + + + diff --git a/java/java-tests/testData/ide/hierarchy/call/RecordCanonicalConstructorReverse/Action.java b/java/java-tests/testData/ide/hierarchy/call/RecordCanonicalConstructorReverse/Action.java new file mode 100644 index 000000000000..9bcab8547b57 --- /dev/null +++ b/java/java-tests/testData/ide/hierarchy/call/RecordCanonicalConstructorReverse/Action.java @@ -0,0 +1,22 @@ +class Action { + public static void main(String[] args) { + var a = new Person("sdf", "sdf"); + var b = new PersonB("sdf", "sdf"); + } + + public static class PersonB { + + private final String name; + private final String address; + + public PersonB(String name, String address) { + this.name = name; + this.address = address; + } + } +} +record Person(String name, String address) { + public Person(String name) { + this(name, ""); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/ide/hierarchy/call/RecordCanonicalConstructorReverse/verification.xml b/java/java-tests/testData/ide/hierarchy/call/RecordCanonicalConstructorReverse/verification.xml new file mode 100644 index 000000000000..548cf09bb048 --- /dev/null +++ b/java/java-tests/testData/ide/hierarchy/call/RecordCanonicalConstructorReverse/verification.xml @@ -0,0 +1,2 @@ + + diff --git a/java/java-tests/testSrc/com/intellij/java/ide/hierarchy/JavaCallHierarchyTest.java b/java/java-tests/testSrc/com/intellij/java/ide/hierarchy/JavaCallHierarchyTest.java index b18b8206c3a3..1dd905e8d3ba 100644 --- a/java/java-tests/testSrc/com/intellij/java/ide/hierarchy/JavaCallHierarchyTest.java +++ b/java/java-tests/testSrc/com/intellij/java/ide/hierarchy/JavaCallHierarchyTest.java @@ -27,7 +27,7 @@ import java.util.Arrays; public class JavaCallHierarchyTest extends HierarchyViewTestBase { @Override protected @NotNull LanguageLevel getProjectLanguageLevel() { - return LanguageLevel.JDK_1_8; // method refs are needed + return LanguageLevel.JDK_16; // records are needed } @NotNull @@ -95,6 +95,20 @@ public class JavaCallHierarchyTest extends HierarchyViewTestBase { doJavaCallerTypeHierarchyTest("A", "A", "A.java"); } + public void testRecordCanonicalConstructor() throws Exception { + doHierarchyTest(() -> { + PsiClass aClass = JavaPsiFacade.getInstance(getProject()).findClass("Person", ProjectScope.getProjectScope(getProject())); + return new CallerMethodsTreeStructure(getProject(), aClass, HierarchyBrowserBaseEx.SCOPE_PROJECT); + }, JavaHierarchyUtil.getComparator(myProject), "Action.java"); + } + + public void testRecordCanonicalConstructorReverse() throws Exception { + doHierarchyTest(() -> { + PsiClass aClass = JavaPsiFacade.getInstance(getProject()).findClass("Person", ProjectScope.getProjectScope(getProject())); + return new CalleeMethodsTreeStructure(getProject(), aClass, HierarchyBrowserBaseEx.SCOPE_PROJECT); + }, JavaHierarchyUtil.getComparator(myProject), "Action.java"); + } + public void testMethodRef() throws Exception { doJavaCalleeTypeHierarchyTest("A", "testMethod", "A.java"); }