[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
This commit is contained in:
Michail Plushnikov
2022-12-04 13:24:02 +00:00
committed by intellij-monorepo-bot
parent b62f68a267
commit 50788430bd
7 changed files with 234 additions and 18 deletions
@@ -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<PropertyAccessorDetector> 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);
}
}
@@ -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;
}
}
}
@@ -28,6 +28,7 @@
<extensionPoint qualifiedName="com.intellij.jvm.elementProvider" area="IDEA_PROJECT" interface="com.intellij.lang.jvm.facade.JvmElementProvider" dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.java.elementFinder" area="IDEA_PROJECT" interface="com.intellij.psi.PsiElementFinder" dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.classTypePointerFactory" interface="com.intellij.psi.ClassTypePointerFactory" dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.propertyAccessorDetector" interface="com.intellij.psi.util.PropertyAccessorDetector" dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.customJavadocTagProvider" interface="com.intellij.psi.javadoc.CustomJavadocTagProvider" dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.javadocTagInfo" area="IDEA_PROJECT" interface="com.intellij.psi.javadoc.JavadocTagInfo" dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.importFilter" interface="com.intellij.codeInsight.ImportFilter" dynamic="true"/>
@@ -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() {
@@ -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)));
}
}
@@ -136,6 +136,7 @@
<treeGenerator implementation="de.plushnikov.intellij.plugin.extension.LombokLightMethodTreeGenerator"/>
<lang.structureViewExtension implementation="de.plushnikov.intellij.plugin.extension.LombokStructureViewExtension"/>
<propertyAccessorDetector implementation="de.plushnikov.intellij.plugin.extension.LombokPropertyAccessorDetector"/>
<daemon.highlightInfoFilter implementation="de.plushnikov.intellij.plugin.extension.LombokHighlightErrorFilter"/>
@@ -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);
});
}
}