From 50788430bd3c55dee17fc8773412aaa0e5ae99e6 Mon Sep 17 00:00:00 2001 From: Michail Plushnikov Date: Sat, 3 Dec 2022 20:44:45 +0100 Subject: [PATCH] [lombok] IDEA-305806 IDEA-301687 provided new extension point for custom property group handling : added support to "structure tool tab" to show lombok's @With methods and @Accessors(fluent = true) in properties (together with getters and setter) GitOrigin-RevId: f7baae1c9c5690c2b0002d3b1276b7d25ec83040 --- .../util/DefaultPropertyAccessorDetector.java | 42 ++++++++++ .../psi/util/PropertyAccessorDetector.java | 43 ++++++++++ .../src/META-INF/JavaPsiPlugin.xml | 1 + .../impl/java/PropertyGroup.java | 36 ++++----- .../LombokPropertyAccessorDetector.java | 49 ++++++++++++ .../src/main/resources/META-INF/plugin.xml | 1 + .../LombokStructureViewExtensionTest.java | 80 +++++++++++++++++++ 7 files changed, 234 insertions(+), 18 deletions(-) create mode 100644 java/java-psi-api/src/com/intellij/psi/util/DefaultPropertyAccessorDetector.java create mode 100644 java/java-psi-api/src/com/intellij/psi/util/PropertyAccessorDetector.java create mode 100644 plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/extension/LombokPropertyAccessorDetector.java create mode 100644 plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/extension/LombokStructureViewExtensionTest.java diff --git a/java/java-psi-api/src/com/intellij/psi/util/DefaultPropertyAccessorDetector.java b/java/java-psi-api/src/com/intellij/psi/util/DefaultPropertyAccessorDetector.java new file mode 100644 index 000000000000..ed4968b091ff --- /dev/null +++ b/java/java-psi-api/src/com/intellij/psi/util/DefaultPropertyAccessorDetector.java @@ -0,0 +1,42 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.psi.util; + +import com.intellij.lang.java.beans.PropertyKind; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.psi.PsiMethod; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class DefaultPropertyAccessorDetector implements PropertyAccessorDetector { + private static final ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.propertyAccessorDetector"); + + public static @Nullable PropertyAccessorInfo detectFrom(@NotNull PsiMethod method) { + for (PropertyAccessorDetector detector : EP_NAME.getExtensions()) { + PropertyAccessorInfo accessorInfo = detector.detectPropertyAccessor(method); + if (accessorInfo != null) { + return accessorInfo; + } + } + return getAccessorInfo(method); + } + + @Nullable + private static PropertyAccessorInfo getAccessorInfo(@NotNull PsiMethod method) { + if (PropertyUtilBase.isSimplePropertyGetter(method)) { + return new PropertyAccessorInfo(PropertyUtilBase.getPropertyNameByGetter(method), + method.getReturnType(), + PropertyKind.GETTER); + } + else if (PropertyUtilBase.isSimplePropertySetter(method)) { + return new PropertyAccessorInfo(PropertyUtilBase.getPropertyNameBySetter(method), + method.getParameterList().getParameters()[0].getType(), + PropertyKind.SETTER); + } + return null; + } + + @Override + public @Nullable PropertyAccessorInfo detectPropertyAccessor(@NotNull PsiMethod method) { + return getAccessorInfo(method); + } +} diff --git a/java/java-psi-api/src/com/intellij/psi/util/PropertyAccessorDetector.java b/java/java-psi-api/src/com/intellij/psi/util/PropertyAccessorDetector.java new file mode 100644 index 000000000000..7afeed7bf78d --- /dev/null +++ b/java/java-psi-api/src/com/intellij/psi/util/PropertyAccessorDetector.java @@ -0,0 +1,43 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.psi.util; + +import com.intellij.lang.java.beans.PropertyKind; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface PropertyAccessorDetector { + /** + * Detects property access information if any, or results to null + */ + @Nullable PropertyAccessorInfo detectPropertyAccessor(@NotNull PsiMethod method); + + class PropertyAccessorInfo { + private final @NotNull String propertyName; + private final @NotNull PsiType propertyType; + private final @NotNull PropertyKind kind; + + public PropertyAccessorInfo(@NotNull String propertyName, @NotNull PsiType propertyType, @NotNull PropertyKind kind) { + this.propertyName = propertyName; + this.propertyType = propertyType; + this.kind = kind; + } + + public @NotNull String getPropertyName() { + return propertyName; + } + + public @NotNull PsiType getPropertyType() { + return propertyType; + } + + public @NotNull PropertyKind getKind() { + return kind; + } + + public boolean isKindOf(PropertyKind other) { + return this.kind == other; + } + } +} \ No newline at end of file diff --git a/java/java-psi-impl/src/META-INF/JavaPsiPlugin.xml b/java/java-psi-impl/src/META-INF/JavaPsiPlugin.xml index 6eec44669025..858ef44bdb28 100644 --- a/java/java-psi-impl/src/META-INF/JavaPsiPlugin.xml +++ b/java/java-psi-impl/src/META-INF/JavaPsiPlugin.xml @@ -28,6 +28,7 @@ + diff --git a/java/java-structure-view/src/com/intellij/ide/structureView/impl/java/PropertyGroup.java b/java/java-structure-view/src/com/intellij/ide/structureView/impl/java/PropertyGroup.java index 3f6d0a0cdd4f..2f1208550aef 100644 --- a/java/java-structure-view/src/com/intellij/ide/structureView/impl/java/PropertyGroup.java +++ b/java/java-structure-view/src/com/intellij/ide/structureView/impl/java/PropertyGroup.java @@ -5,6 +5,7 @@ import com.intellij.icons.AllIcons; import com.intellij.ide.util.treeView.WeighedItem; import com.intellij.ide.util.treeView.smartTree.Group; import com.intellij.ide.util.treeView.smartTree.TreeElement; +import com.intellij.lang.java.beans.PropertyKind; import com.intellij.navigation.ColoredItemPresentation; import com.intellij.navigation.ItemPresentation; import com.intellij.openapi.editor.colors.CodeInsightColors; @@ -12,6 +13,8 @@ import com.intellij.openapi.editor.colors.TextAttributesKey; import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.project.Project; import com.intellij.psi.*; +import com.intellij.psi.util.DefaultPropertyAccessorDetector; +import com.intellij.psi.util.PropertyAccessorDetector; import com.intellij.psi.util.PropertyUtilBase; import com.intellij.psi.util.PsiUtil; import org.jetbrains.annotations.NotNull; @@ -48,28 +51,26 @@ public final class PropertyGroup implements Group, ColoredItemPresentation, Acce } public static PropertyGroup createOn(PsiElement object, final TreeElement treeElement) { - if (object instanceof PsiField) { - PsiField field = (PsiField)object; + if (object instanceof PsiField field) { PropertyGroup group = new PropertyGroup(PropertyUtilBase.suggestPropertyName(field), field.getType(), - field.hasModifierProperty(PsiModifier.STATIC), object.getProject()); + field.hasModifierProperty(PsiModifier.STATIC), field.getProject()); group.setField(field); group.myChildren.add(treeElement); return group; } - else if (object instanceof PsiMethod) { - PsiMethod method = (PsiMethod)object; - if (PropertyUtilBase.isSimplePropertyGetter(method)) { - PropertyGroup group = new PropertyGroup(PropertyUtilBase.getPropertyNameByGetter(method), method.getReturnType(), - method.hasModifierProperty(PsiModifier.STATIC), object.getProject()); - group.setGetter(method); - group.myChildren.add(treeElement); - return group; - } - else if (PropertyUtilBase.isSimplePropertySetter(method)) { - PropertyGroup group = - new PropertyGroup(PropertyUtilBase.getPropertyNameBySetter(method), method.getParameterList().getParameters()[0].getType(), - method.hasModifierProperty(PsiModifier.STATIC), object.getProject()); - group.setSetter(method); + else if (object instanceof PsiMethod method) { + final PropertyAccessorDetector.PropertyAccessorInfo accessorInfo = DefaultPropertyAccessorDetector.detectFrom(method); + if (null != accessorInfo && + (accessorInfo.isKindOf(PropertyKind.GETTER) || accessorInfo.isKindOf(PropertyKind.SETTER))) { + + PropertyGroup group = new PropertyGroup(accessorInfo.getPropertyName(), accessorInfo.getPropertyType(), + method.hasModifierProperty(PsiModifier.STATIC), method.getProject()); + if (accessorInfo.isKindOf(PropertyKind.GETTER)) { + group.setGetter(method); + } + else { + group.setSetter(method); + } group.myChildren.add(treeElement); return group; } @@ -113,7 +114,6 @@ public final class PropertyGroup implements Group, ColoredItemPresentation, Acce return PROPERTY_WRITE_ICON; } } - } private boolean isStatic() { diff --git a/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/extension/LombokPropertyAccessorDetector.java b/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/extension/LombokPropertyAccessorDetector.java new file mode 100644 index 000000000000..e7c6988d2e68 --- /dev/null +++ b/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/extension/LombokPropertyAccessorDetector.java @@ -0,0 +1,49 @@ +package de.plushnikov.intellij.plugin.extension; + +import com.intellij.lang.java.beans.PropertyKind; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiField; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.util.PropertyAccessorDetector; +import com.intellij.psi.util.PropertyUtilBase; +import de.plushnikov.intellij.plugin.processor.field.AccessorsInfo; +import de.plushnikov.intellij.plugin.psi.LombokLightMethodBuilder; +import de.plushnikov.intellij.plugin.thirdparty.LombokUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class LombokPropertyAccessorDetector implements PropertyAccessorDetector { + + @Override + public @Nullable PropertyAccessorInfo detectPropertyAccessor(@NotNull PsiMethod method) { + if (method instanceof LombokLightMethodBuilder methodBuilder) { + final PsiElement navigationElement = methodBuilder.getNavigationElement(); + if (navigationElement instanceof PsiField originalField) { + final AccessorsInfo accessorsInfo = AccessorsInfo.buildFor(originalField); + + final boolean lombokPropertySetterOrWither = isLombokPropertySetterOrWither(methodBuilder, originalField, accessorsInfo); + final boolean lombokPropertyGetter = isLombokPropertyGetter(methodBuilder, originalField, accessorsInfo); + + + if (lombokPropertySetterOrWither || lombokPropertyGetter) { + return new PropertyAccessorInfo(PropertyUtilBase.suggestPropertyName(originalField), + originalField.getType(), + lombokPropertyGetter?PropertyKind.GETTER:PropertyKind.SETTER); + } + } + } + return null; + } + + private static boolean isLombokPropertyGetter(LombokLightMethodBuilder method, PsiField originalField, AccessorsInfo accessorsInfo) { + return !method.hasParameters() && method.getName().equals(LombokUtils.getGetterName(originalField, accessorsInfo)); + } + + private static boolean isLombokPropertySetterOrWither(LombokLightMethodBuilder method, + PsiField originalField, + AccessorsInfo accessorsInfo) { + return method.getParameterList().getParameters().length == 1 && + (method.getName().equals(LombokUtils.getSetterName(originalField, accessorsInfo)) || + method.getName().equals(LombokUtils.getWitherName(originalField, accessorsInfo))); + } +} diff --git a/plugins/lombok/src/main/resources/META-INF/plugin.xml b/plugins/lombok/src/main/resources/META-INF/plugin.xml index 205ec260764a..9d249fb3e787 100644 --- a/plugins/lombok/src/main/resources/META-INF/plugin.xml +++ b/plugins/lombok/src/main/resources/META-INF/plugin.xml @@ -136,6 +136,7 @@ + diff --git a/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/extension/LombokStructureViewExtensionTest.java b/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/extension/LombokStructureViewExtensionTest.java new file mode 100644 index 000000000000..da795228c7f4 --- /dev/null +++ b/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/extension/LombokStructureViewExtensionTest.java @@ -0,0 +1,80 @@ +package de.plushnikov.intellij.plugin.extension; + +import com.intellij.ide.structureView.impl.java.JavaAnonymousClassesNodeProvider; +import com.intellij.ide.structureView.impl.java.PropertiesGrouper; +import com.intellij.ide.structureView.impl.java.SuperTypesGrouper; +import com.intellij.testFramework.PlatformTestUtil; +import de.plushnikov.intellij.plugin.AbstractLombokLightCodeInsightTestCase; +import org.intellij.lang.annotations.Language; + +import javax.swing.*; + +public class LombokStructureViewExtensionTest extends AbstractLombokLightCodeInsightTestCase { + + @Language("JAVA") + private static final String LOMBOKED_TEST_CLASS = """ + @lombok.Data + @lombok.NoArgsConstructor + @lombok.AllArgsConstructor + @lombok.experimental.Accessors(fluent = true, prefix = "my") + public class Test { + private float fff; + private String myString; + @lombok.With + private Boolean myActive; + @lombok.experimental.Accessors(fluent = false) + private int myX; + }"""; + + + public void testLombokPropertiesGrouping() { + doPropertiesTest(LOMBOKED_TEST_CLASS, + """ + -Test.java + -Test + Test(float, String, Boolean, int) + Test() + equals(Object): boolean + canEqual(Object): boolean + hashCode(): int + toString(): String + -fff: float + fff(): float + fff(float): Test + fff: float + -myString: String + string(): String + string(String): Test + myString: String + -myActive: Boolean + active(): Boolean + active(Boolean): Test + withActive(Boolean): Test + myActive: Boolean + -myX: int + getX(): int + setX(int): void + myX: int + """); + } + + private void doPropertiesTest(String classText, String expected) { + doTest(classText, expected, false, true); + } + + private void doTest(String classText, + String expected, + boolean showInterfaces, + boolean showProperties) { + myFixture.configureByText("Test.java", classText); + myFixture.testStructureView(svc -> { + svc.setActionActive(SuperTypesGrouper.ID, showInterfaces); + svc.setActionActive(PropertiesGrouper.ID, showProperties); + svc.setActionActive(JavaAnonymousClassesNodeProvider.ID, true); + JTree tree = svc.getTree(); + PlatformTestUtil.waitWhileBusy(tree); + PlatformTestUtil.expandAll(tree); + PlatformTestUtil.assertTreeEqual(tree, expected); + }); + } +}