mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java-inspections] IDEA-252259 ClassCanBeRecordInspection: delete redundant Lombok annotations after conversion
Merge-request: IJ-MR-159286 Merged-by: Bartek Pacia <bartek.pacia@jetbrains.com> GitOrigin-RevId: 7456109ad3a225dcab41229e3f03c2c99a57c094
This commit is contained in:
committed by
intellij-monorepo-bot
parent
77f4d62b64
commit
98ee3086d0
+4
-3
@@ -17,10 +17,11 @@ public abstract class CanBeFinalHandler {
|
||||
|
||||
/**
|
||||
* @param member a member (field or method) to check whether it's allowed to be final
|
||||
* @return false if a current extension prohibit marking this member as final.
|
||||
* @return false if this extension prohibits marking this member as final.
|
||||
* Must return true for every member that is unknown to the current extension.
|
||||
* Note that if the field is written explicitly, it will be detected by inspection itself,
|
||||
* so no need to specially handle explicitly written fields here.
|
||||
* <p>
|
||||
* If the field is written explicitly, it is detected by the inspection itself,
|
||||
* so implementations of this method shouldn't care about such a case.
|
||||
*/
|
||||
public abstract boolean canBeFinal(@NotNull PsiMember member);
|
||||
|
||||
|
||||
+4
-3
@@ -88,7 +88,7 @@ public final class ConvertToRecordFix implements LocalQuickFix {
|
||||
RecordCandidate recordCandidate = getClassDefinition(psiClass, mySuggestAccessorsRenaming, myIgnoredAnnotations);
|
||||
if (recordCandidate == null) return null;
|
||||
|
||||
return new ConvertToRecordProcessor(recordCandidate);
|
||||
return new ConvertToRecordProcessor(recordCandidate, mySuggestAccessorsRenaming);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,7 +137,7 @@ public final class ConvertToRecordFix implements LocalQuickFix {
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates necessary information about the converting class e.g its existing fields, accessors...
|
||||
* Encapsulates necessary information about the class being converted: its fields, accessors, etc.
|
||||
* It helps to validate whether a class will be a well-formed record and supports performing a refactoring.
|
||||
*/
|
||||
static class RecordCandidate {
|
||||
@@ -207,7 +207,8 @@ public final class ConvertToRecordFix implements LocalQuickFix {
|
||||
if (myFieldsToAccessorCandidates.size() == 0) return false;
|
||||
for (var entry : myFieldsToAccessorCandidates.entrySet()) {
|
||||
PsiField field = entry.getKey();
|
||||
if (!field.hasModifierProperty(FINAL) || field.hasInitializer()) return false;
|
||||
if (!field.hasModifierProperty(FINAL)) return false;
|
||||
if (field.hasInitializer()) return false;
|
||||
if (JavaPsiRecordUtil.ILLEGAL_RECORD_COMPONENT_NAMES.contains(field.getName())) return false;
|
||||
if (entry.getValue().size() > 1) return false;
|
||||
FieldAccessorCandidate firstAccessor = ContainerUtil.getFirstItem(entry.getValue());
|
||||
|
||||
+79
-15
@@ -5,6 +5,7 @@ import com.intellij.codeInspection.RedundantRecordConstructorInspection;
|
||||
import com.intellij.codeInspection.RedundantRecordConstructorInspection.ConstructorSimplifier;
|
||||
import com.intellij.codeInspection.classCanBeRecord.ConvertToRecordFix.FieldAccessorCandidate;
|
||||
import com.intellij.codeInspection.classCanBeRecord.ConvertToRecordFix.RecordCandidate;
|
||||
import com.intellij.java.library.JavaLibraryUtil;
|
||||
import com.intellij.java.refactoring.JavaRefactoringBundle;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.util.NlsContexts;
|
||||
@@ -19,6 +20,7 @@ import com.intellij.psi.javadoc.PsiDocTag;
|
||||
import com.intellij.psi.javadoc.PsiDocTagValue;
|
||||
import com.intellij.psi.search.searches.ReferencesSearch;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.PropertyUtilBase;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.refactoring.BaseRefactoringProcessor;
|
||||
@@ -43,6 +45,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.intellij.codeInspection.classCanBeRecord.ConvertToRecordFix.RecordConstructorCandidate;
|
||||
|
||||
@@ -59,12 +62,14 @@ final class ConvertToRecordProcessor extends BaseRefactoringProcessor {
|
||||
.parameterCount(0);
|
||||
|
||||
private final RecordCandidate myRecordCandidate;
|
||||
private final boolean mySuggestAccessorsRenaming;
|
||||
|
||||
private final Map<PsiElement, String> myAllRenames = new LinkedHashMap<>();
|
||||
|
||||
ConvertToRecordProcessor(@NotNull RecordCandidate recordCandidate) {
|
||||
ConvertToRecordProcessor(@NotNull RecordCandidate recordCandidate, boolean suggestAccessorsRenaming) {
|
||||
super(recordCandidate.getProject());
|
||||
myRecordCandidate = recordCandidate;
|
||||
mySuggestAccessorsRenaming = suggestAccessorsRenaming;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -293,14 +298,14 @@ final class ConvertToRecordProcessor extends BaseRefactoringProcessor {
|
||||
}
|
||||
recordBuilder.addPsiElement(nextElement);
|
||||
}
|
||||
else if (nextElement instanceof PsiMethod) {
|
||||
if (canonicalCtorCandidate != null && nextElement == canonicalCtorCandidate.getConstructorMethod()) {
|
||||
else if (nextElement instanceof PsiMethod psiMethod) {
|
||||
if (canonicalCtorCandidate != null && psiMethod == canonicalCtorCandidate.getConstructorMethod()) {
|
||||
recordBuilder.addCanonicalCtor(canonicalCtorCandidate.getConstructorMethod());
|
||||
}
|
||||
else {
|
||||
FieldAccessorCandidate fieldAccessorCandidate = getFieldAccessorCandidate(fieldToAccessorCandidateMap, (PsiMethod)nextElement);
|
||||
FieldAccessorCandidate fieldAccessorCandidate = getFieldAccessorCandidate(fieldToAccessorCandidateMap, psiMethod);
|
||||
if (fieldAccessorCandidate == null) {
|
||||
recordBuilder.addPsiElement(nextElement);
|
||||
recordBuilder.addPsiElement(psiMethod);
|
||||
}
|
||||
else {
|
||||
recordBuilder.addFieldAccessor(fieldAccessorCandidate);
|
||||
@@ -312,16 +317,34 @@ final class ConvertToRecordProcessor extends BaseRefactoringProcessor {
|
||||
}
|
||||
nextElement = nextElement.getNextSibling();
|
||||
}
|
||||
|
||||
Set<PsiMethod> syntheticGetters = Arrays.stream(psiClass.getMethods())
|
||||
.filter(method -> method instanceof SyntheticElement)
|
||||
.filter(method -> ContainerUtil.exists(fieldToAccessorCandidateMap.keySet(),
|
||||
field -> field.getName().equals(PropertyUtilBase.getPropertyNameByGetter(method))))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
useAccessorsWhenNecessary(usages);
|
||||
CallMatcher redundantObjectMethods = findRedundantObjectMethods();
|
||||
CallMatcher redundantObjectMethods = findRedundantObjectMethods(myRecordCandidate);
|
||||
PsiClass result = (PsiClass)psiClass.replace(recordBuilder.build());
|
||||
tryToCompactCanonicalCtor(result);
|
||||
removeRedundantObjectMethods(result, redundantObjectMethods);
|
||||
generateJavaDocForDocumentedFields(result);
|
||||
addImplicitLombokGetters(result, syntheticGetters.toArray(PsiMethod.EMPTY_ARRAY));
|
||||
removeRedundantLombokAnnotations(result);
|
||||
generateJavaDocForDocumentedFields(result, myRecordCandidate.getFieldsToAccessorCandidates().keySet());
|
||||
CodeStyleManager.getInstance(myProject).reformat(JavaCodeStyleManager.getInstance(myProject).shortenClassReferences(result));
|
||||
}
|
||||
|
||||
private void useAccessorsWhenNecessary(UsageInfo @NotNull [] usages) {
|
||||
private void addImplicitLombokGetters(@NotNull PsiClass record, @NotNull PsiMethod @NotNull [] implicitGetters) {
|
||||
if (!mySuggestAccessorsRenaming) {
|
||||
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(myProject);
|
||||
for (PsiMethod getter : implicitGetters) {
|
||||
record.add(elementFactory.createMethodFromText(getter.getText(), record));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void useAccessorsWhenNecessary(@NotNull UsageInfo @NotNull [] usages) {
|
||||
for (UsageInfo usage : usages) {
|
||||
if (usage instanceof FieldUsageInfo fieldUsageInfo) {
|
||||
PsiField field = fieldUsageInfo.myField;
|
||||
@@ -354,12 +377,15 @@ final class ConvertToRecordProcessor extends BaseRefactoringProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
private CallMatcher findRedundantObjectMethods() {
|
||||
PsiMethod equalsMethod = myRecordCandidate.getEqualsMethod();
|
||||
PsiMethod hashCodeMethod = myRecordCandidate.getHashCodeMethod();
|
||||
/**
|
||||
* Finds methods within {@code recordCandidate} that are redundant implementations of {@link Object#equals} and {@link Object#hashCode}.
|
||||
*/
|
||||
private static CallMatcher findRedundantObjectMethods(@NotNull RecordCandidate recordCandidate) {
|
||||
PsiMethod equalsMethod = recordCandidate.getEqualsMethod();
|
||||
PsiMethod hashCodeMethod = recordCandidate.getHashCodeMethod();
|
||||
if (equalsMethod == null && hashCodeMethod == null) return CallMatcher.none();
|
||||
List<CallMatcher> result = new SmartList<>();
|
||||
Set<PsiField> fields = myRecordCandidate.getFieldsToAccessorCandidates().keySet();
|
||||
Set<PsiField> fields = recordCandidate.getFieldsToAccessorCandidates().keySet();
|
||||
if (EqualsChecker.isStandardEqualsMethod(equalsMethod, fields)) {
|
||||
result.add(OBJECT_EQUALS);
|
||||
}
|
||||
@@ -468,6 +494,8 @@ final class ConvertToRecordProcessor extends BaseRefactoringProcessor {
|
||||
}
|
||||
|
||||
private static void tryToCompactCanonicalCtor(@NotNull PsiClass record) {
|
||||
if (!record.isRecord()) throw new IllegalArgumentException("Not a record: " + record);
|
||||
|
||||
PsiMethod canonicalCtor = ArrayUtil.getFirstElement(record.getConstructors());
|
||||
if (canonicalCtor != null) {
|
||||
PsiCodeBlock ctorBody = canonicalCtor.getBody();
|
||||
@@ -489,9 +517,45 @@ final class ConvertToRecordProcessor extends BaseRefactoringProcessor {
|
||||
.forEach(PsiMethod::delete);
|
||||
}
|
||||
|
||||
private void generateJavaDocForDocumentedFields(@NotNull PsiClass record) {
|
||||
/**
|
||||
* This could be also implemented as a {@link com.intellij.refactoring.RefactoringHelper}.
|
||||
* {@link BaseRefactoringProcessor} automatically calls refactoring helpers after refactoring is done.
|
||||
* <p>
|
||||
* The problem with it is that refactoring helpers don't trivially work in preview, so we would make the bug IDEA-369873 only worse.
|
||||
*/
|
||||
private static void removeRedundantLombokAnnotations(@NotNull PsiClass record) {
|
||||
if (!record.isRecord()) throw new IllegalArgumentException("Not a record: " + record);
|
||||
if (!JavaLibraryUtil.hasLibraryJar(record.getProject(), "org.projectlombok:lombok")) return;
|
||||
|
||||
// Remove annotations from the class
|
||||
for (final PsiAnnotation annotation : record.getAnnotations()) {
|
||||
final String qualifiedName = annotation.getQualifiedName();
|
||||
if (qualifiedName == null) continue;
|
||||
if (Set.of(
|
||||
"lombok.ToString",
|
||||
"lombok.Getter",
|
||||
"lombok.EqualsAndHashCode",
|
||||
"lombok.RequiredArgsConstructor",
|
||||
"lombok.Data",
|
||||
"lombok.Value").contains(qualifiedName)) {
|
||||
annotation.delete();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove annotations from instance fields
|
||||
for (final PsiField field : record.getFields()) {
|
||||
for (final PsiAnnotation annotation : field.getAnnotations()) {
|
||||
final String qualifiedName = annotation.getQualifiedName();
|
||||
if (qualifiedName != null && qualifiedName.equals("lombok.Getter")) {
|
||||
annotation.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void generateJavaDocForDocumentedFields(@NotNull PsiClass record, @NotNull Set<@NotNull PsiField> fields) {
|
||||
Map<String, String> comments = new LinkedHashMap<>();
|
||||
for (PsiField field : myRecordCandidate.getFieldsToAccessorCandidates().keySet()) {
|
||||
for (PsiField field : fields) {
|
||||
StringBuilder fieldComment = new StringBuilder();
|
||||
for (PsiComment comment : ObjectUtils.notNull(PsiTreeUtil.getChildrenOfType(field, PsiComment.class), new PsiComment[0])) {
|
||||
if (comment instanceof PsiDocComment) {
|
||||
@@ -509,7 +573,7 @@ final class ConvertToRecordProcessor extends BaseRefactoringProcessor {
|
||||
}
|
||||
}
|
||||
if (comments.isEmpty()) return;
|
||||
PsiJavaParserFacade parserFacade = JavaPsiFacade.getInstance(myProject).getParserFacade();
|
||||
PsiJavaParserFacade parserFacade = JavaPsiFacade.getInstance(record.getProject()).getParserFacade();
|
||||
PsiDocComment recordDoc = record.getDocComment();
|
||||
if (recordDoc == null) {
|
||||
PsiDocComment emptyDoc = parserFacade.createDocCommentFromText("/** */");
|
||||
|
||||
+12
-10
@@ -27,18 +27,20 @@ final class EqualsChecker {
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* Detects standard equals method implementation with identity check, null/class check, and field-by-field comparison, in the form of:
|
||||
* <pre>
|
||||
* public boolean equals(Object obj) {
|
||||
* if (obj == this) return true;
|
||||
* if (obj == null || obj.getClass() != this.getClass()) return false;
|
||||
* var that = (Person)obj;
|
||||
* return Objects.equals(this.name, that.name) &&
|
||||
* this.age == that.age;
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
static boolean isStandardEqualsMethod(@Nullable PsiMethod method, @NotNull Set<PsiField> fields) {
|
||||
if (method == null) return false;
|
||||
/*
|
||||
Finds equals in the form
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (obj == null || obj.getClass() != this.getClass()) return false;
|
||||
var that = (Person)obj;
|
||||
return Objects.equals(this.name, that.name) &&
|
||||
this.age == that.age;
|
||||
}
|
||||
*/
|
||||
PsiCodeBlock body = method.getBody();
|
||||
if (body == null) return false;
|
||||
PsiParameter parameter = method.getParameterList().getParameter(0);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
// "Convert to record class" "true-preview"
|
||||
record R(int x, int y) {
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// "Convert to record class" "true-preview"
|
||||
class R<caret> {
|
||||
final int x;
|
||||
final int y;
|
||||
|
||||
R(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
int getY() {
|
||||
return y;
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// "Convert to record class" "true-preview"
|
||||
record R(int x, int y) {
|
||||
|
||||
int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
int getY() {
|
||||
return y;
|
||||
}
|
||||
}
|
||||
|
||||
class Foo {
|
||||
void foo() {
|
||||
R r = new R(10, 20);
|
||||
System.out.println("x: " + person.getX() + ", y" + r.getY());
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// "Convert to record class" "true-preview"
|
||||
class R<caret> {
|
||||
final int x;
|
||||
final int y;
|
||||
|
||||
R(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
int getY() {
|
||||
return y;
|
||||
}
|
||||
}
|
||||
|
||||
class Foo {
|
||||
void foo() {
|
||||
R r = new R(10, 20);
|
||||
System.out.println("x: " + person.getX() + ", y" + r.getY());
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -8,7 +8,7 @@ import com.intellij.codeInspection.classCanBeRecord.ClassCanBeRecordInspection.C
|
||||
import com.intellij.refactoring.BaseRefactoringProcessor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ClassCanBeRecordInspectionAccessorsRenamingTest extends LightQuickFixParameterizedTestCase {
|
||||
public class ClassCanBeRecordInspectionNoAccessorsRenamingTest extends LightQuickFixParameterizedTestCase {
|
||||
|
||||
@Override
|
||||
protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() {
|
||||
@@ -17,7 +17,7 @@ public class ClassCanBeRecordInspectionAccessorsRenamingTest extends LightQuickF
|
||||
|
||||
@Override
|
||||
protected String getBasePath() {
|
||||
return "/inspection/classCanBeRecord/accessorsRenaming";
|
||||
return "/inspection/classCanBeRecord/noAccessorsRenaming";
|
||||
}
|
||||
|
||||
@Override
|
||||
+3
-1
@@ -22,7 +22,9 @@ public final class LombokCanBeFinalHandler extends CanBeFinalHandler {
|
||||
}
|
||||
|
||||
final PsiClass psiClass = PsiTreeUtil.getParentOfType(member, PsiClass.class);
|
||||
return null == psiClass || !PsiAnnotationSearchUtil.isAnnotatedWith(psiClass, LombokClassNames.SETTER, LombokClassNames.DATA, LombokClassNames.VALUE);
|
||||
if (psiClass == null) return true;
|
||||
return !PsiAnnotationSearchUtil.isAnnotatedWith(psiClass, LombokClassNames.SETTER, LombokClassNames.DATA, LombokClassNames.VALUE);
|
||||
// will return true for our elemnt
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
+1
@@ -29,6 +29,7 @@ import java.util.List;
|
||||
* @author Plushnikov Michail
|
||||
*/
|
||||
public final class GetterFieldProcessor extends AbstractFieldProcessor {
|
||||
|
||||
public GetterFieldProcessor() {
|
||||
super(PsiMethod.class, LombokClassNames.GETTER);
|
||||
}
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
package com.intellij.java.lomboktest;
|
||||
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInspection.classCanBeRecord.ClassCanBeRecordInspection;
|
||||
import com.intellij.refactoring.BaseRefactoringProcessor;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import de.plushnikov.intellij.plugin.AbstractLombokLightCodeInsightTestCase;
|
||||
import de.plushnikov.intellij.plugin.LombokTestUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static com.intellij.codeInspection.classCanBeRecord.ClassCanBeRecordInspection.ConversionStrategy;
|
||||
|
||||
public class AdditionalLombokClassCanBeRecordInspectionTest extends AbstractLombokLightCodeInsightTestCase {
|
||||
private static final String VALUE_CLASS_SAMPLE = """
|
||||
@lombok.Value
|
||||
class Person<caret> {
|
||||
String name;
|
||||
int age;
|
||||
|
||||
String getInfo() {
|
||||
return "Person " + name + " is " + age + " years old";
|
||||
}
|
||||
}
|
||||
|
||||
class Foo {
|
||||
void foo() {
|
||||
Person person = new Person("Charlie", 42);
|
||||
// Renaming getters to accessors requires the Lombok plugin
|
||||
System.out.println("Person " + person.getName() + " is " + person.getAge() + " years old");
|
||||
System.out.println(person.getInfo());
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
@Override
|
||||
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
|
||||
return LombokTestUtil.LOMBOK_NEW_DESCRIPTOR;
|
||||
}
|
||||
|
||||
public void testClassCanBeRecord_renamingAccessors() {
|
||||
myFixture.enableInspections(new ClassCanBeRecordInspection(ConversionStrategy.DO_NOT_SUGGEST, true));
|
||||
myFixture.configureByText("Test.java", VALUE_CLASS_SAMPLE);
|
||||
|
||||
IntentionAction intention = myFixture.findSingleIntention("Convert to record class");
|
||||
// myFixture.checkPreviewAndLaunchAction(intention); // preview differs from the actual result because of IDEA-369873
|
||||
BaseRefactoringProcessor.ConflictsInTestsException.withIgnoredConflicts(() -> myFixture.launchAction(intention));
|
||||
myFixture.checkResult("""
|
||||
record Person(String name, int age) {
|
||||
String getInfo() {
|
||||
return "Person " + name + " is " + age + " years old";
|
||||
}
|
||||
}
|
||||
|
||||
class Foo {
|
||||
void foo() {
|
||||
Person person = new Person("Charlie", 42);
|
||||
// Renaming getters to accessors requires the Lombok plugin
|
||||
System.out.println("Person " + person.name() + " is " + person.age() + " years old");
|
||||
System.out.println(person.getInfo());
|
||||
}
|
||||
}
|
||||
""");
|
||||
myFixture.checkHighlighting(true, false, false); // Ensure again that code is well-formed
|
||||
}
|
||||
|
||||
public void testClassCanBeRecord_notRenamingAccessors() {
|
||||
myFixture.enableInspections(new ClassCanBeRecordInspection(ConversionStrategy.DO_NOT_SUGGEST, false));
|
||||
myFixture.configureByText("Test.java", VALUE_CLASS_SAMPLE);
|
||||
|
||||
IntentionAction intention = myFixture.findSingleIntention("Convert to record class");
|
||||
// myFixture.checkPreviewAndLaunchAction(intention); // preview differs from the actual result because of IDEA-369873
|
||||
BaseRefactoringProcessor.ConflictsInTestsException.withIgnoredConflicts(() -> myFixture.launchAction(intention));
|
||||
myFixture.checkResult("""
|
||||
record Person(String name, int age) {
|
||||
String getInfo() {
|
||||
return "Person " + name + " is " + age + " years old";
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return this.age;
|
||||
}
|
||||
}
|
||||
|
||||
class Foo {
|
||||
void foo() {
|
||||
Person person = new Person("Charlie", 42);
|
||||
// Renaming getters to accessors requires the Lombok plugin
|
||||
System.out.println("Person " + person.getName() + " is " + person.getAge() + " years old");
|
||||
System.out.println(person.getInfo());
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
myFixture.checkHighlighting(true, false, false); // Ensure again that code is well-formed
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
// 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.
|
||||
package com.intellij.java.lomboktest;
|
||||
|
||||
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
|
||||
import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import com.intellij.codeInspection.classCanBeRecord.ClassCanBeRecordInspection;
|
||||
import com.intellij.codeInspection.classCanBeRecord.ClassCanBeRecordInspection.ConversionStrategy;
|
||||
import com.intellij.openapi.application.PathManager;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.refactoring.BaseRefactoringProcessor;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import de.plushnikov.intellij.plugin.LombokTestUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class LombokClassCanBeRecordInspectionTest extends LightQuickFixParameterizedTestCase {
|
||||
|
||||
@Override
|
||||
protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() {
|
||||
return new LocalInspectionTool[]{new ClassCanBeRecordInspection(ConversionStrategy.DO_NOT_SUGGEST, true)};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull String getTestDataPath() {
|
||||
return PathManager.getCommunityHomePath() + "/plugins/lombok/testData";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBasePath() {
|
||||
return "/inspection/classCanBeRecord";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
|
||||
return LombokTestUtil.LOMBOK_NEW_DESCRIPTOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runSingle() throws Throwable {
|
||||
String option = StringUtil.substringAfter(getName(), "__");
|
||||
if (option != null && option.startsWith("ignoreConflicts")) {
|
||||
// TODO(bartekpacia): It'd be good to test that specific conflicts appear. See IDEA-370463
|
||||
BaseRefactoringProcessor.ConflictsInTestsException.withIgnoredConflicts(super::runSingle);
|
||||
}
|
||||
else {
|
||||
super.runSingle();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
record C(int a, int b) {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
record Person(String name, int age) {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
record Person(String name, int age) {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
record Person(String name, int age) {
|
||||
}
|
||||
|
||||
@Getter // another class, just to verify that the import doesn't get deleted too eagerly
|
||||
class Person2 {
|
||||
final String name;
|
||||
final int age;
|
||||
|
||||
Person2(String name, int age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
record Person(String name, int age) {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
record Person(String name, int age) {
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
record Person(String name, int age) {
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
/// Instance fields of this class have redundant modifiers, because @Value automatically:
|
||||
/// 1. marks non-static fields as 'final'
|
||||
/// 2. marks non-static, package-local fields as 'private'
|
||||
///
|
||||
/// @param name 'private' is redundant because of (1)
|
||||
/// @param age 'final' is redundant because of (2)
|
||||
/// @param city 'private' and 'final' are redundant because of (1) and (2)
|
||||
record Person(String name, int age, String city) {
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// "Convert to record class" "true"
|
||||
// no "true-preview" above because of IDEA-369873
|
||||
|
||||
record Person(String name, int age) {
|
||||
}
|
||||
|
||||
class Foo {
|
||||
void foo() {
|
||||
Person person = new Person("Charlie", 42);
|
||||
// Renaming getters to accessors requires the Lombok plugin
|
||||
System.out.println("Person " + person.name() + " is " + person.age() + " years old");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
record Person(String name, int age) {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
@lombok.Data
|
||||
class C<caret> {
|
||||
final int a;
|
||||
final int b;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Convert to record class" "false"
|
||||
|
||||
@lombok.Data
|
||||
class C<caret> {
|
||||
final int a;
|
||||
final int b;
|
||||
int c;
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
@lombok.EqualsAndHashCode
|
||||
class Person<caret> {
|
||||
final String name;
|
||||
final int age;
|
||||
|
||||
Person(String name, int age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
@lombok.Getter
|
||||
class Person<caret> {
|
||||
final String name;
|
||||
final int age;
|
||||
|
||||
Person(String name, int age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
@lombok.Getter
|
||||
class Person<caret> {
|
||||
final String name;
|
||||
final int age;
|
||||
|
||||
Person(String name, int age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
|
||||
@Getter // another class, just to verify that the import doesn't get deleted too eagerly
|
||||
class Person2 {
|
||||
final String name;
|
||||
final int age;
|
||||
|
||||
Person2(String name, int age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
@lombok.RequiredArgsConstructor
|
||||
class Person<caret> {
|
||||
final String name;
|
||||
final int age;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
@lombok.ToString
|
||||
class Person<caret> {
|
||||
final String name;
|
||||
final int age;
|
||||
|
||||
Person(String name, int age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
@lombok.Value
|
||||
class Person<caret> {
|
||||
String name;
|
||||
int age;
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
/// Instance fields of this class have redundant modifiers, because @Value automatically:
|
||||
/// 1. marks non-static fields as 'final'
|
||||
/// 2. marks non-static, package-local fields as 'private'
|
||||
@lombok.Value
|
||||
class Person<caret> {
|
||||
private String name; // 'private' is redundant because of (1)
|
||||
final int age; // 'final' is redundant because of (2)
|
||||
private final String city; // 'private' and 'final' are redundant because of (1) and (2)
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// "Convert to record class" "true"
|
||||
// no "true-preview" above because of IDEA-369873
|
||||
|
||||
@lombok.Value
|
||||
class Person<caret> {
|
||||
String name;
|
||||
int age;
|
||||
}
|
||||
|
||||
class Foo {
|
||||
void foo() {
|
||||
Person person = new Person("Charlie", 42);
|
||||
// Renaming getters to accessors requires the Lombok plugin
|
||||
System.out.println("Person " + person.getName() + " is " + person.getAge() + " years old");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
class Person<caret> {
|
||||
@lombok.Getter final String name;
|
||||
@lombok.Getter final int age;
|
||||
|
||||
Person(String name, int age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// "Convert to record class" "false"
|
||||
|
||||
class Person<caret> {
|
||||
@lombok.Getter final String name;
|
||||
@lombok.Getter int age; // we don't want to suggest converting this class to record, because this field is not final
|
||||
|
||||
Person(String name, int age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// "Convert to record class" "false"
|
||||
|
||||
class Person<caret> {
|
||||
// Java records are immutable, so we don't suggest conversion here
|
||||
@lombok.Setter String name;
|
||||
@lombok.Setter int age;
|
||||
|
||||
Person(String name, int age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user