mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java-inspections] ClassCanBeRecord: support case when field names and constructor param names differ
#IDEA-265154 fixed Merge-request: IJ-MR-158642 Merged-by: Bartek Pacia <bartek.pacia@jetbrains.com> GitOrigin-RevId: 7a04d4830e1f76ee3ad965390f28168834dca9e9
This commit is contained in:
committed by
intellij-monorepo-bot
parent
84ed145470
commit
f619cad1d0
+117
-78
@@ -15,10 +15,7 @@ import com.intellij.psi.*;
|
||||
import com.intellij.psi.PsiAnnotation.TargetType;
|
||||
import com.intellij.psi.controlFlow.ControlFlowUtil;
|
||||
import com.intellij.psi.search.searches.ClassInheritorsSearch;
|
||||
import com.intellij.psi.util.JavaPsiRecordUtil;
|
||||
import com.intellij.psi.util.PropertyUtil;
|
||||
import com.intellij.psi.util.PropertyUtilBase;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.*;
|
||||
import com.intellij.usageView.UsageInfo;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.SmartList;
|
||||
@@ -28,9 +25,9 @@ import com.siyeh.ig.InspectionGadgetsFix;
|
||||
import com.siyeh.ig.callMatcher.CallMatcher;
|
||||
import com.siyeh.ig.memory.InnerClassReferenceVisitor;
|
||||
import com.siyeh.ig.psiutils.MethodUtils;
|
||||
import com.siyeh.ig.psiutils.TypeUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.UnmodifiableView;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -61,11 +58,26 @@ public class ConvertToRecordFix extends InspectionGadgetsFix {
|
||||
protected void doFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
final ConvertToRecordProcessor processor = getRecordProcessor(descriptor);
|
||||
if (processor == null) return;
|
||||
// Without the next line, the conflicts view is not shown
|
||||
processor.setPrepareSuccessfulSwingThreadCallback(() -> {
|
||||
});
|
||||
processor.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull IntentionPreviewInfo generatePreview(@NotNull Project project, @NotNull ProblemDescriptor previewDescriptor) {
|
||||
final ConvertToRecordProcessor processor = getRecordProcessor(previewDescriptor);
|
||||
if (processor == null) return IntentionPreviewInfo.EMPTY;
|
||||
|
||||
// We can't use the below here, because BaseRefactoringProcessor#doRun calls PsiDocumentManager#commitAllDocumentsUnderProgress,
|
||||
// and its Javadoc says "must be called on UI thread".
|
||||
// processor.run();
|
||||
|
||||
processor.performRefactoring(UsageInfo.EMPTY_ARRAY);
|
||||
|
||||
return IntentionPreviewInfo.DIFF;
|
||||
}
|
||||
|
||||
private @Nullable ConvertToRecordProcessor getRecordProcessor(ProblemDescriptor descriptor) {
|
||||
PsiElement psiElement = descriptor.getPsiElement();
|
||||
if (psiElement == null) return null;
|
||||
@@ -78,14 +90,6 @@ public class ConvertToRecordFix extends InspectionGadgetsFix {
|
||||
return new ConvertToRecordProcessor(recordCandidate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull IntentionPreviewInfo generatePreview(@NotNull Project project, @NotNull ProblemDescriptor previewDescriptor) {
|
||||
final ConvertToRecordProcessor processor = getRecordProcessor(previewDescriptor);
|
||||
if (processor == null) return IntentionPreviewInfo.EMPTY;
|
||||
processor.performRefactoring(UsageInfo.EMPTY_ARRAY);
|
||||
return IntentionPreviewInfo.DIFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* There are some restrictions for records:
|
||||
* <a href="https://docs.oracle.com/javase/specs/jls/se15/preview/specs/records-jls.html">see the specification</a>.
|
||||
@@ -93,8 +97,11 @@ public class ConvertToRecordFix extends InspectionGadgetsFix {
|
||||
static RecordCandidate getClassDefinition(@NotNull PsiClass psiClass,
|
||||
boolean suggestAccessorsRenaming,
|
||||
@NotNull List<String> ignoredAnnotations) {
|
||||
boolean isNotAppropriatePsiClass = psiClass.isEnum() || psiClass.isAnnotationType() || psiClass instanceof PsiAnonymousClass ||
|
||||
psiClass.isInterface() || psiClass.isRecord();
|
||||
boolean isNotAppropriatePsiClass = psiClass.isEnum() ||
|
||||
psiClass.isAnnotationType() ||
|
||||
psiClass instanceof PsiAnonymousClass ||
|
||||
psiClass.isInterface() ||
|
||||
psiClass.isRecord();
|
||||
if (isNotAppropriatePsiClass) return null;
|
||||
|
||||
PsiModifierList psiClassModifiers = psiClass.getModifierList();
|
||||
@@ -133,15 +140,14 @@ public class ConvertToRecordFix extends InspectionGadgetsFix {
|
||||
* It helps to validate whether a class will be a well-formed record and supports performing a refactoring.
|
||||
*/
|
||||
static class RecordCandidate {
|
||||
private static final CallMatcher OBJECT_METHOD_CALLS = CallMatcher.anyOf(
|
||||
CallMatcher.exactInstanceCall(JAVA_LANG_OBJECT, "equals").parameterCount(1),
|
||||
CallMatcher.exactInstanceCall(JAVA_LANG_OBJECT, "hashCode", "toString").parameterCount(0)
|
||||
);
|
||||
private static final CallMatcher OBJECT_METHOD_CALLS =
|
||||
CallMatcher.anyOf(CallMatcher.exactInstanceCall(JAVA_LANG_OBJECT, "equals").parameterCount(1),
|
||||
CallMatcher.exactInstanceCall(JAVA_LANG_OBJECT, "hashCode", "toString").parameterCount(0));
|
||||
private final PsiClass myClass;
|
||||
private final boolean mySuggestAccessorsRenaming;
|
||||
private final MultiMap<PsiField, FieldAccessorCandidate> myFieldAccessors = new MultiMap<>(new LinkedHashMap<>());
|
||||
private final MultiMap<PsiField, FieldAccessorCandidate> myFieldsToAccessorCandidates = new MultiMap<>(new LinkedHashMap<>());
|
||||
private final List<PsiMethod> myOrdinaryMethods = new SmartList<>();
|
||||
private final List<RecordConstructorCandidate> myConstructors = new SmartList<>();
|
||||
final List<RecordConstructorCandidate> myConstructorCandidates = new SmartList<>();
|
||||
|
||||
private PsiMethod myEqualsMethod;
|
||||
private PsiMethod myHashCodeMethod;
|
||||
@@ -162,46 +168,43 @@ public class ConvertToRecordFix extends InspectionGadgetsFix {
|
||||
return myClass;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
Map<PsiField, @Nullable FieldAccessorCandidate> getFieldAccessors() {
|
||||
@UnmodifiableView
|
||||
@NotNull Map<PsiField, @Nullable FieldAccessorCandidate> getFieldsToAccessorCandidates() {
|
||||
if (myFieldAccessorsCache != null) return myFieldAccessorsCache;
|
||||
|
||||
Map<PsiField, FieldAccessorCandidate> result = new LinkedHashMap<>();
|
||||
for (var entry : myFieldAccessors.entrySet()) {
|
||||
for (var entry : myFieldsToAccessorCandidates.entrySet()) {
|
||||
PsiField newKey = entry.getKey();
|
||||
Collection<FieldAccessorCandidate> oldValue = entry.getValue();
|
||||
FieldAccessorCandidate newValue = ContainerUtil.getOnlyItem(oldValue);
|
||||
result.put(newKey, newValue);
|
||||
}
|
||||
myFieldAccessorsCache = result;
|
||||
myFieldAccessorsCache = Collections.unmodifiableMap(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
PsiMethod getCanonicalConstructor() {
|
||||
return myConstructors.size() == 1 ? myConstructors.get(0).myConstructor : null;
|
||||
@Nullable RecordConstructorCandidate getCanonicalConstructorCandidate() {
|
||||
return myConstructorCandidates.size() == 1 ? myConstructorCandidates.get(0) : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
PsiMethod getEqualsMethod() {
|
||||
@Nullable PsiMethod getEqualsMethod() {
|
||||
return myEqualsMethod;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
PsiMethod getHashCodeMethod() {
|
||||
@Nullable PsiMethod getHashCodeMethod() {
|
||||
return myHashCodeMethod;
|
||||
}
|
||||
|
||||
private boolean isValid() {
|
||||
if (myConstructors.size() > 1) return false;
|
||||
if (myConstructors.size() == 1) {
|
||||
RecordConstructorCandidate ctorCandidate = myConstructors.get(0);
|
||||
boolean isCanonical = ctorCandidate.myCanonical && throwsOnlyUncheckedExceptions(ctorCandidate.myConstructor);
|
||||
if (myConstructorCandidates.size() > 1) return false;
|
||||
if (myConstructorCandidates.size() == 1) {
|
||||
RecordConstructorCandidate ctorCandidate = myConstructorCandidates.get(0);
|
||||
boolean isCanonical = ctorCandidate.canonical && throwsOnlyUncheckedExceptions(ctorCandidate.constructorMethod);
|
||||
if (!isCanonical) return false;
|
||||
if (containsObjectMethodCalls(ctorCandidate.myConstructor)) return false;
|
||||
if (containsObjectMethodCalls(ctorCandidate.constructorMethod)) return false;
|
||||
}
|
||||
if (myFieldAccessors.size() == 0) return false;
|
||||
for (var entry : myFieldAccessors.entrySet()) {
|
||||
if (myFieldsToAccessorCandidates.size() == 0) return false;
|
||||
for (var entry : myFieldsToAccessorCandidates.entrySet()) {
|
||||
PsiField field = entry.getKey();
|
||||
if (!field.hasModifierProperty(FINAL) || field.hasInitializer()) return false;
|
||||
if (JavaPsiRecordUtil.ILLEGAL_RECORD_COMPONENT_NAMES.contains(field.getName())) return false;
|
||||
@@ -213,7 +216,7 @@ public class ConvertToRecordFix extends InspectionGadgetsFix {
|
||||
for (PsiMethod ordinaryMethod : myOrdinaryMethods) {
|
||||
if (ordinaryMethod.hasModifierProperty(NATIVE)) return false;
|
||||
boolean conflictsWithPotentialAccessor = ordinaryMethod.getParameterList().isEmpty() &&
|
||||
ContainerUtil.exists(myFieldAccessors.keySet(),
|
||||
ContainerUtil.exists(myFieldsToAccessorCandidates.keySet(),
|
||||
field -> field.getName().equals(ordinaryMethod.getName()));
|
||||
if (conflictsWithPotentialAccessor) return false;
|
||||
if (containsObjectMethodCalls(ordinaryMethod)) return false;
|
||||
@@ -223,10 +226,12 @@ public class ConvertToRecordFix extends InspectionGadgetsFix {
|
||||
|
||||
private void prepare() {
|
||||
Arrays.stream(myClass.getFields()).filter(field -> !field.hasModifierProperty(STATIC))
|
||||
.forEach(field -> myFieldAccessors.put(field, new ArrayList<>()));
|
||||
.forEach(field -> myFieldsToAccessorCandidates.put(field, new ArrayList<>()));
|
||||
|
||||
for (PsiMethod method : myClass.getMethods()) {
|
||||
if (method.isConstructor()) {
|
||||
myConstructors.add(new RecordConstructorCandidate(method, myFieldAccessors.keySet()));
|
||||
Set<PsiField> instanceFields = myFieldsToAccessorCandidates.keySet();
|
||||
myConstructorCandidates.add(new RecordConstructorCandidate(method, instanceFields));
|
||||
continue;
|
||||
}
|
||||
if (MethodUtils.isEquals(method)) {
|
||||
@@ -241,12 +246,12 @@ public class ConvertToRecordFix extends InspectionGadgetsFix {
|
||||
myOrdinaryMethods.add(method);
|
||||
continue;
|
||||
}
|
||||
FieldAccessorCandidate fieldAccessorCandidate = createFieldAccessor(method);
|
||||
FieldAccessorCandidate fieldAccessorCandidate = tryCreateFieldAccessorCandidate(method);
|
||||
if (fieldAccessorCandidate == null) {
|
||||
myOrdinaryMethods.add(method);
|
||||
}
|
||||
else {
|
||||
myFieldAccessors.putValue(fieldAccessorCandidate.myBackingField, fieldAccessorCandidate);
|
||||
myFieldsToAccessorCandidates.putValue(fieldAccessorCandidate.myBackingField, fieldAccessorCandidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -292,13 +297,13 @@ public class ConvertToRecordFix extends InspectionGadgetsFix {
|
||||
return visitor.existsSuperMethodCalls;
|
||||
}
|
||||
|
||||
private @Nullable FieldAccessorCandidate createFieldAccessor(@NotNull PsiMethod psiMethod) {
|
||||
private @Nullable FieldAccessorCandidate tryCreateFieldAccessorCandidate(@NotNull PsiMethod psiMethod) {
|
||||
if (psiMethod.hasModifier(JvmModifier.STATIC)) return null;
|
||||
if (!psiMethod.getParameterList().isEmpty()) return null;
|
||||
String methodName = psiMethod.getName();
|
||||
PsiField backingField = null;
|
||||
boolean recordStyleNaming = false;
|
||||
for (PsiField field : myFieldAccessors.keySet()) {
|
||||
for (PsiField field : myFieldsToAccessorCandidates.keySet()) {
|
||||
if (!field.getType().equals(psiMethod.getReturnType())) continue;
|
||||
String fieldName = field.getName();
|
||||
if (fieldName.equals(methodName)) {
|
||||
@@ -306,7 +311,8 @@ public class ConvertToRecordFix extends InspectionGadgetsFix {
|
||||
recordStyleNaming = true;
|
||||
break;
|
||||
}
|
||||
if (mySuggestAccessorsRenaming && fieldName.equals(PropertyUtilBase.getPropertyNameByGetter(psiMethod)) &&
|
||||
if (mySuggestAccessorsRenaming &&
|
||||
fieldName.equals(PropertyUtilBase.getPropertyNameByGetter(psiMethod)) &&
|
||||
!ContainerUtil.exists(psiMethod.findDeepestSuperMethods(),
|
||||
superMethod -> superMethod instanceof PsiCompiledElement || superMethod instanceof SyntheticElement)) {
|
||||
backingField = field;
|
||||
@@ -318,57 +324,84 @@ public class ConvertToRecordFix extends InspectionGadgetsFix {
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates information about the converting constructor e.g whether its canonical or not.
|
||||
* Encapsulates information about converting of a single constructor, for example, whether it is canonical or not.
|
||||
*/
|
||||
private static class RecordConstructorCandidate {
|
||||
private final PsiMethod myConstructor;
|
||||
private final boolean myCanonical;
|
||||
static class RecordConstructorCandidate {
|
||||
private final @NotNull PsiMethod constructorMethod;
|
||||
private final boolean canonical;
|
||||
private final @NotNull Map<PsiParameter, PsiField> ctorParamsToFields = new HashMap<>();
|
||||
|
||||
private RecordConstructorCandidate(@NotNull PsiMethod constructor, @NotNull Set<PsiField> instanceFields) {
|
||||
myConstructor = constructor;
|
||||
|
||||
if (myConstructor.getTypeParameters().length > 0) {
|
||||
myCanonical = false;
|
||||
constructorMethod = constructor;
|
||||
if (constructorMethod.getTypeParameters().length > 0) {
|
||||
canonical = false;
|
||||
return;
|
||||
}
|
||||
Set<String> instanceFieldNames = instanceFields.stream().map(PsiField::getName).collect(Collectors.toSet());
|
||||
if (instanceFieldNames.size() != instanceFields.size()) {
|
||||
myCanonical = false;
|
||||
canonical = false;
|
||||
return;
|
||||
}
|
||||
PsiParameter[] ctorParams = myConstructor.getParameterList().getParameters();
|
||||
PsiParameter[] ctorParams = constructorMethod.getParameterList().getParameters();
|
||||
if (instanceFields.size() != ctorParams.length) {
|
||||
myCanonical = false;
|
||||
canonical = false;
|
||||
return;
|
||||
}
|
||||
PsiCodeBlock ctorBody = myConstructor.getBody();
|
||||
PsiCodeBlock ctorBody = constructorMethod.getBody();
|
||||
if (ctorBody == null) {
|
||||
myCanonical = false;
|
||||
canonical = false;
|
||||
return;
|
||||
}
|
||||
Map<String, PsiType> ctorParamsWithType = Arrays.stream(ctorParams)
|
||||
.collect(Collectors.toMap(param -> param.getName(), param -> param.getType(), (first, second) -> first));
|
||||
|
||||
final boolean allProcessed = PsiTreeUtil.processElements(ctorBody, PsiAssignmentExpression.class, (assignExpr) -> {
|
||||
if (!(assignExpr.getLExpression() instanceof PsiReferenceExpression leftRefExpr)) return true;
|
||||
if (!(leftRefExpr.resolve() instanceof PsiField field)) return true;
|
||||
|
||||
if (!(assignExpr.getRExpression() instanceof PsiReferenceExpression rightRefExpr)) return true;
|
||||
final PsiElement assignmentValue = rightRefExpr.resolve();
|
||||
|
||||
if (assignmentValue == null) return false; // using 'false' as a sentinel value
|
||||
if (!(assignmentValue instanceof PsiParameter parameter)) return true;
|
||||
ctorParamsToFields.put(parameter, field);
|
||||
return true;
|
||||
});
|
||||
if (!allProcessed) {
|
||||
canonical = false;
|
||||
return;
|
||||
}
|
||||
|
||||
for (PsiField instanceField : instanceFields) {
|
||||
PsiType ctorParamType = ctorParamsWithType.get(instanceField.getName());
|
||||
if (ctorParamType instanceof PsiEllipsisType) {
|
||||
ctorParamType = ((PsiEllipsisType)ctorParamType).toArrayType();
|
||||
}
|
||||
if (ctorParamType == null || !TypeUtils.typeEquals(ctorParamType.getCanonicalText(), instanceField.getType())) {
|
||||
myCanonical = false;
|
||||
return;
|
||||
}
|
||||
if (!ControlFlowUtil.variableDefinitelyAssignedIn(instanceField, ctorBody)) {
|
||||
myCanonical = false;
|
||||
canonical = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
myCanonical = true;
|
||||
|
||||
canonical = true;
|
||||
}
|
||||
|
||||
public @NotNull @UnmodifiableView Map<PsiParameter, PsiField> getCtorParamsToFields() {
|
||||
return Collections.unmodifiableMap(ctorParamsToFields);
|
||||
}
|
||||
|
||||
@NotNull PsiMethod getConstructorMethod() {
|
||||
return constructorMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RecordConstructorCandidate{" +
|
||||
"constructorMethod=" + constructorMethod +
|
||||
", canonical=" + canonical +
|
||||
", ctorParamToFieldMap=" + ctorParamsToFields +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates information about the converting of field accessors.
|
||||
* For instance an existing default accessor may be removed during further record creation.
|
||||
* Encapsulates information about converting of a single field accessor.
|
||||
* <p>
|
||||
* For instance, an existing default accessor may be removed during further record creation.
|
||||
*/
|
||||
static class FieldAccessorCandidate {
|
||||
private final PsiMethod myFieldAccessor;
|
||||
@@ -395,13 +428,11 @@ public class ConvertToRecordFix extends InspectionGadgetsFix {
|
||||
!hasAnnotationConflict(backingField, accessor, TargetType.METHOD);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
PsiMethod getAccessor() {
|
||||
@NotNull PsiMethod getAccessor() {
|
||||
return myFieldAccessor;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
PsiField getBackingField() {
|
||||
@NotNull PsiField getBackingField() {
|
||||
return myBackingField;
|
||||
}
|
||||
|
||||
@@ -412,6 +443,14 @@ public class ConvertToRecordFix extends InspectionGadgetsFix {
|
||||
boolean isRecordStyleNaming() {
|
||||
return myRecordStyleNaming;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FieldAccessorCandidate{" +
|
||||
"myFieldAccessor=" + myFieldAccessor +
|
||||
", myBackingField=" + myBackingField +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+63
-29
@@ -22,6 +22,8 @@ import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.refactoring.BaseRefactoringProcessor;
|
||||
import com.intellij.refactoring.RefactoringFactory;
|
||||
import com.intellij.refactoring.RenameRefactoring;
|
||||
import com.intellij.refactoring.rename.RenameProcessor;
|
||||
import com.intellij.refactoring.rename.RenamePsiElementProcessor;
|
||||
import com.intellij.refactoring.rename.RenameUtil;
|
||||
@@ -38,14 +40,24 @@ import com.siyeh.ig.callMatcher.CallMatcher;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static com.intellij.codeInspection.classCanBeRecord.ConvertToRecordFix.RecordConstructorCandidate;
|
||||
|
||||
/**
|
||||
* Responsible for converting a single {@link RecordCandidate} that is {@link RecordCandidate#isValid valid}.
|
||||
*/
|
||||
final class ConvertToRecordProcessor extends BaseRefactoringProcessor {
|
||||
private static final CallMatcher OBJECT_EQUALS = CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_OBJECT, "equals")
|
||||
private static final CallMatcher OBJECT_EQUALS = CallMatcher
|
||||
.instanceCall(CommonClassNames.JAVA_LANG_OBJECT, "equals")
|
||||
.parameterTypes(CommonClassNames.JAVA_LANG_OBJECT);
|
||||
private static final CallMatcher OBJECT_HASHCODE =
|
||||
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_OBJECT, "hashCode").parameterCount(0);
|
||||
|
||||
private static final CallMatcher OBJECT_HASHCODE = CallMatcher
|
||||
.instanceCall(CommonClassNames.JAVA_LANG_OBJECT, "hashCode")
|
||||
.parameterCount(0);
|
||||
|
||||
private final RecordCandidate myRecordCandidate;
|
||||
|
||||
private final Map<PsiElement, String> myAllRenames = new LinkedHashMap<>();
|
||||
@@ -73,10 +85,28 @@ final class ConvertToRecordProcessor extends BaseRefactoringProcessor {
|
||||
@Override
|
||||
protected void doRun() {
|
||||
prepareRenameOfAccessors();
|
||||
prepareRenameOfConstructorParameters();
|
||||
|
||||
super.doRun();
|
||||
}
|
||||
|
||||
private void prepareRenameOfConstructorParameters() {
|
||||
RecordConstructorCandidate ctorCandidate = myRecordCandidate.getCanonicalConstructorCandidate();
|
||||
if (ctorCandidate == null) return;
|
||||
|
||||
ctorCandidate.getCtorParamsToFields().forEach((ctorParam, field) -> {
|
||||
if (!ctorParam.getName().equals(field.getName())) {
|
||||
RenameRefactoring renameRefactoring = RefactoringFactory.getInstance(myProject).createRename(ctorParam, field.getName());
|
||||
renameRefactoring.setPreviewUsages(false);
|
||||
renameRefactoring.setSearchInComments(false);
|
||||
// The below line is required to not show conflicts midway and break the refactoring flow.
|
||||
renameRefactoring.setSearchInNonJavaFiles(false);
|
||||
renameRefactoring.setInteractive(null);
|
||||
renameRefactoring.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void prepareRenameOfAccessors() {
|
||||
List<FieldAccessorCandidate> accessorsToRename = getAccessorsToRename();
|
||||
|
||||
@@ -96,9 +126,9 @@ final class ConvertToRecordProcessor extends BaseRefactoringProcessor {
|
||||
@Override
|
||||
protected UsageInfo @NotNull [] findUsages() {
|
||||
List<UsageInfo> usages = new SmartList<>();
|
||||
for (var psiField : myRecordCandidate.getFieldAccessors().keySet()) {
|
||||
for (var psiField : myRecordCandidate.getFieldsToAccessorCandidates().keySet()) {
|
||||
if (!psiField.hasModifierProperty(PsiModifier.PRIVATE)) {
|
||||
for (PsiReference reference : ReferencesSearch.search(psiField).asIterable()) {
|
||||
for (PsiReference reference : ReferencesSearch.search(psiField).findAll()) {
|
||||
usages.add(new FieldUsageInfo(psiField, reference));
|
||||
}
|
||||
}
|
||||
@@ -126,11 +156,11 @@ final class ConvertToRecordProcessor extends BaseRefactoringProcessor {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list of accessors which have not record-compatible names and need to be renamed separately.
|
||||
* @return list of accessors whose names aren't record-compatible and need to be renamed separately.
|
||||
*/
|
||||
private @NotNull List<@NotNull FieldAccessorCandidate> getAccessorsToRename() {
|
||||
private @NotNull @Unmodifiable List<@NotNull FieldAccessorCandidate> getAccessorsToRename() {
|
||||
List<FieldAccessorCandidate> list = ContainerUtil.filter(
|
||||
myRecordCandidate.getFieldAccessors().values(),
|
||||
myRecordCandidate.getFieldsToAccessorCandidates().values(),
|
||||
fieldAccessorCandidate -> fieldAccessorCandidate != null && !fieldAccessorCandidate.isRecordStyleNaming()
|
||||
);
|
||||
return list;
|
||||
@@ -140,7 +170,7 @@ final class ConvertToRecordProcessor extends BaseRefactoringProcessor {
|
||||
* @param accessor a declaration to find supers methods for
|
||||
* @return a list of direct super methods, or the declaration itself if no super methods are found
|
||||
*/
|
||||
private static @NotNull List<@NotNull PsiMethod> substituteWithSuperMethodsIfPossible(@NotNull PsiMethod accessor) {
|
||||
private static @NotNull @Unmodifiable List<@NotNull PsiMethod> substituteWithSuperMethodsIfPossible(@NotNull PsiMethod accessor) {
|
||||
PsiMethod[] superMethods = accessor.findSuperMethods();
|
||||
if (superMethods.length == 0) {
|
||||
return List.of(accessor);
|
||||
@@ -152,7 +182,7 @@ final class ConvertToRecordProcessor extends BaseRefactoringProcessor {
|
||||
|
||||
static @NotNull List<UsageInfo> findConflicts(@NotNull RecordCandidate recordCandidate) {
|
||||
List<UsageInfo> result = new SmartList<>();
|
||||
for (var entry : recordCandidate.getFieldAccessors().entrySet()) {
|
||||
for (var entry : recordCandidate.getFieldsToAccessorCandidates().entrySet()) {
|
||||
PsiField psiField = entry.getKey();
|
||||
FieldAccessorCandidate fieldAccessorCandidate = entry.getValue();
|
||||
if (fieldAccessorCandidate == null) {
|
||||
@@ -175,13 +205,16 @@ final class ConvertToRecordProcessor extends BaseRefactoringProcessor {
|
||||
}
|
||||
}
|
||||
}
|
||||
PsiMethod canonicalCtor = recordCandidate.getCanonicalConstructor();
|
||||
if (canonicalCtor != null && firstHasWeakerAccess(recordCandidate.getPsiClass(), canonicalCtor)) {
|
||||
result.add(new BrokenEncapsulationUsageInfo(canonicalCtor, JavaRefactoringBundle
|
||||
.message("convert.to.record.ctor.more.accessible",
|
||||
StringUtil.capitalize(RefactoringUIUtil.getDescription(canonicalCtor, false)),
|
||||
VisibilityUtil.getVisibilityStringToDisplay(canonicalCtor),
|
||||
VisibilityUtil.getVisibilityStringToDisplay(recordCandidate.getPsiClass()))));
|
||||
RecordConstructorCandidate canonicalCtorCandidate = recordCandidate.getCanonicalConstructorCandidate();
|
||||
if (canonicalCtorCandidate != null) {
|
||||
PsiMethod canonicalCtor = canonicalCtorCandidate.getConstructorMethod();
|
||||
if (firstHasWeakerAccess(recordCandidate.getPsiClass(), canonicalCtor)) {
|
||||
result.add(new BrokenEncapsulationUsageInfo(canonicalCtor, JavaRefactoringBundle
|
||||
.message("convert.to.record.ctor.more.accessible",
|
||||
StringUtil.capitalize(RefactoringUIUtil.getDescription(canonicalCtor, false)),
|
||||
VisibilityUtil.getVisibilityStringToDisplay(canonicalCtor),
|
||||
VisibilityUtil.getVisibilityStringToDisplay(recordCandidate.getPsiClass()))));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -226,11 +259,12 @@ final class ConvertToRecordProcessor extends BaseRefactoringProcessor {
|
||||
|
||||
@Override
|
||||
protected void performRefactoring(UsageInfo @NotNull [] usages) {
|
||||
prepareRenameOfConstructorParameters();
|
||||
renameMembers(usages);
|
||||
|
||||
PsiClass psiClass = myRecordCandidate.getPsiClass();
|
||||
PsiMethod canonicalCtor = myRecordCandidate.getCanonicalConstructor();
|
||||
Map<PsiField, FieldAccessorCandidate> fieldAccessors = myRecordCandidate.getFieldAccessors();
|
||||
final PsiClass psiClass = myRecordCandidate.getPsiClass();
|
||||
final RecordConstructorCandidate canonicalCtorCandidate = myRecordCandidate.getCanonicalConstructorCandidate();
|
||||
final Map<PsiField, FieldAccessorCandidate> fieldToAccessorCandidateMap = myRecordCandidate.getFieldsToAccessorCandidates();
|
||||
RecordBuilder recordBuilder = new RecordBuilder(psiClass);
|
||||
PsiIdentifier classIdentifier = null;
|
||||
PsiElement nextElement = psiClass.getFirstChild();
|
||||
@@ -245,7 +279,7 @@ final class ConvertToRecordProcessor extends BaseRefactoringProcessor {
|
||||
else if (nextElement instanceof PsiTypeParameterList) {
|
||||
recordBuilder.addPsiElement(nextElement);
|
||||
if (PsiTreeUtil.skipWhitespacesAndCommentsBackward(nextElement) == classIdentifier) {
|
||||
recordBuilder.addRecordHeader(canonicalCtor, fieldAccessors);
|
||||
recordBuilder.addRecordHeader(canonicalCtorCandidate, fieldToAccessorCandidateMap);
|
||||
classIdentifier = null;
|
||||
}
|
||||
}
|
||||
@@ -254,18 +288,18 @@ final class ConvertToRecordProcessor extends BaseRefactoringProcessor {
|
||||
}
|
||||
else if (nextElement instanceof PsiField psiField) {
|
||||
psiField.normalizeDeclaration();
|
||||
if (fieldAccessors.containsKey(psiField)) {
|
||||
if (fieldToAccessorCandidateMap.containsKey(psiField)) {
|
||||
nextElement = PsiTreeUtil.skipWhitespacesForward(nextElement);
|
||||
continue;
|
||||
}
|
||||
recordBuilder.addPsiElement(nextElement);
|
||||
}
|
||||
else if (nextElement instanceof PsiMethod) {
|
||||
if (nextElement == canonicalCtor) {
|
||||
recordBuilder.addCanonicalCtor(canonicalCtor);
|
||||
if (canonicalCtorCandidate != null && nextElement == canonicalCtorCandidate.getConstructorMethod()) {
|
||||
recordBuilder.addCanonicalCtor(canonicalCtorCandidate.getConstructorMethod());
|
||||
}
|
||||
else {
|
||||
FieldAccessorCandidate fieldAccessorCandidate = getFieldAccessorCandidate(fieldAccessors, (PsiMethod)nextElement);
|
||||
FieldAccessorCandidate fieldAccessorCandidate = getFieldAccessorCandidate(fieldToAccessorCandidateMap, (PsiMethod)nextElement);
|
||||
if (fieldAccessorCandidate == null) {
|
||||
recordBuilder.addPsiElement(nextElement);
|
||||
}
|
||||
@@ -326,7 +360,7 @@ final class ConvertToRecordProcessor extends BaseRefactoringProcessor {
|
||||
PsiMethod hashCodeMethod = myRecordCandidate.getHashCodeMethod();
|
||||
if (equalsMethod == null && hashCodeMethod == null) return CallMatcher.none();
|
||||
List<CallMatcher> result = new SmartList<>();
|
||||
Set<PsiField> fields = myRecordCandidate.getFieldAccessors().keySet();
|
||||
Set<PsiField> fields = myRecordCandidate.getFieldsToAccessorCandidates().keySet();
|
||||
if (EqualsChecker.isStandardEqualsMethod(equalsMethod, fields)) {
|
||||
result.add(OBJECT_EQUALS);
|
||||
}
|
||||
@@ -451,14 +485,14 @@ final class ConvertToRecordProcessor extends BaseRefactoringProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
private static void removeRedundantObjectMethods(@NotNull PsiClass record, CallMatcher redundantObjectMethods) {
|
||||
private static void removeRedundantObjectMethods(@NotNull PsiClass record, @NotNull CallMatcher redundantObjectMethods) {
|
||||
ContainerUtil.filter(record.getMethods(), redundantObjectMethods::methodMatches)
|
||||
.forEach(PsiMethod::delete);
|
||||
.forEach(PsiMethod::delete);
|
||||
}
|
||||
|
||||
private void generateJavaDocForDocumentedFields(@NotNull PsiClass record) {
|
||||
Map<String, String> comments = new LinkedHashMap<>();
|
||||
for (PsiField field : myRecordCandidate.getFieldAccessors().keySet()) {
|
||||
for (PsiField field : myRecordCandidate.getFieldsToAccessorCandidates().keySet()) {
|
||||
StringBuilder fieldComment = new StringBuilder();
|
||||
for (PsiComment comment : ObjectUtils.notNull(PsiTreeUtil.getChildrenOfType(field, PsiComment.class), new PsiComment[0])) {
|
||||
if (comment instanceof PsiDocComment) {
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@ interface ConvertToRecordUsageInfo {
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates the field which will be narrowed its visibility as the record introduces a private final field.
|
||||
* Encapsulates the field which will become less accessible the record introduces a private final field.
|
||||
*/
|
||||
class FieldUsageInfo extends UsageInfo implements ConvertToRecordUsageInfo {
|
||||
final PsiField myField;
|
||||
@@ -56,4 +56,4 @@ class BrokenEncapsulationUsageInfo extends UsageInfo implements ConvertToRecordU
|
||||
super(psiMethod);
|
||||
myErrMsg = errMsg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+31
-19
@@ -6,6 +6,7 @@ import com.intellij.codeInsight.AnnotationUtil;
|
||||
import com.intellij.codeInsight.intention.AddAnnotationPsiFix;
|
||||
import com.intellij.codeInsight.javadoc.JavaDocUtil;
|
||||
import com.intellij.codeInspection.classCanBeRecord.ConvertToRecordFix.FieldAccessorCandidate;
|
||||
import com.intellij.codeInspection.classCanBeRecord.ConvertToRecordFix.RecordConstructorCandidate;
|
||||
import com.intellij.java.syntax.parser.DeclarationParser;
|
||||
import com.intellij.java.syntax.parser.JavaParser;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
@@ -40,16 +41,29 @@ class RecordBuilder {
|
||||
myRecordText.append("record");
|
||||
}
|
||||
|
||||
void addRecordHeader(@Nullable PsiMethod canonicalCtor, @NotNull Map<PsiField, @Nullable FieldAccessorCandidate> fieldAccessors) {
|
||||
void addRecordHeader(@Nullable RecordConstructorCandidate canonicalCtorCandidate,
|
||||
@NotNull Map<PsiField, @Nullable FieldAccessorCandidate> fieldToAccessorCandidateMap) {
|
||||
myRecordText.append("(");
|
||||
StringJoiner recordComponentsJoiner = new StringJoiner(",");
|
||||
if (canonicalCtor == null) {
|
||||
fieldAccessors.forEach(
|
||||
(field, fieldAccessor) -> recordComponentsJoiner.add(generateComponentText(field, field.getType(), fieldAccessor)));
|
||||
if (canonicalCtorCandidate == null) {
|
||||
fieldToAccessorCandidateMap.forEach((field, fieldAccessor) -> {
|
||||
recordComponentsJoiner.add(generateComponentText(field, field.getType(), fieldAccessor));
|
||||
});
|
||||
}
|
||||
else {
|
||||
PsiMethod canonicalCtor = canonicalCtorCandidate.getConstructorMethod();
|
||||
Arrays.stream(canonicalCtor.getParameterList().getParameters())
|
||||
.map(parameter -> generateComponentText(parameter, fieldAccessors))
|
||||
.map(parameter -> {
|
||||
PsiField field = canonicalCtorCandidate.getCtorParamsToFields().get(parameter);
|
||||
if (field == null) {
|
||||
field = ContainerUtil.find(myOriginClass.getFields(), f -> f.getName().equals(parameter.getName()));
|
||||
if (field == null) {
|
||||
throw new IllegalStateException("no field found corresponding to constructor parameter '" + parameter.getName() + "'");
|
||||
}
|
||||
}
|
||||
|
||||
return generateComponentText(field, parameter, fieldToAccessorCandidateMap.get(field));
|
||||
})
|
||||
.forEach(recordComponentsJoiner::add);
|
||||
}
|
||||
myRecordText.append(recordComponentsJoiner);
|
||||
@@ -95,27 +109,25 @@ class RecordBuilder {
|
||||
return (PsiClass)Objects.requireNonNull(SourceTreeToPsiMap.treeElementToPsi(holder.getTreeElement().getFirstChildNode()));
|
||||
}
|
||||
|
||||
private static @NotNull String generateComponentText(@NotNull PsiParameter parameter,
|
||||
@NotNull Map<PsiField, @Nullable FieldAccessorCandidate> fieldAccessors) {
|
||||
PsiField field = null;
|
||||
FieldAccessorCandidate fieldAccessorCandidate = null;
|
||||
for (var entry : fieldAccessors.entrySet()) {
|
||||
if (entry.getKey().getName().equals(parameter.getName())) {
|
||||
field = entry.getKey();
|
||||
fieldAccessorCandidate = entry.getValue();
|
||||
break;
|
||||
}
|
||||
private static @NotNull String generateComponentText(@NotNull PsiField field,
|
||||
@NotNull PsiParameter ctorParameter,
|
||||
@Nullable FieldAccessorCandidate fieldAccessorCandidate) {
|
||||
if (field == null) {
|
||||
throw new IllegalStateException("no field found to which the constructor parameter '" +
|
||||
ctorParameter.getType().toString() +
|
||||
ctorParameter.getName() +
|
||||
"' is assigned");
|
||||
}
|
||||
assert field != null;
|
||||
// Do not use parameter.getType() directly, as type annotations may differ; prefer type annotations on the field
|
||||
// Don't use parameter.getType() directly, as type annotations may differ; prefer type annotations on the field
|
||||
PsiType componentType = field.getType();
|
||||
if (parameter.getType() instanceof PsiEllipsisType && componentType instanceof PsiArrayType arrayType) {
|
||||
if (ctorParameter.getType() instanceof PsiEllipsisType && componentType instanceof PsiArrayType arrayType) {
|
||||
componentType = new PsiEllipsisType(arrayType.getComponentType(), arrayType.getAnnotationProvider());
|
||||
}
|
||||
return generateComponentText(field, componentType, fieldAccessorCandidate);
|
||||
}
|
||||
|
||||
private static @NotNull String generateComponentText(@NotNull PsiField field, @NotNull PsiType componentType,
|
||||
private static @NotNull String generateComponentText(@NotNull PsiField field,
|
||||
@NotNull PsiType componentType,
|
||||
@Nullable FieldAccessorCandidate fieldAccessorCandidate) {
|
||||
PsiAnnotation[] fieldAnnotations = field.getAnnotations();
|
||||
String fieldAnnotationsText = Arrays.stream(fieldAnnotations)
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
record R(int first, String second, int... third, boolean fourth) {
|
||||
|
||||
private int getFirst() {
|
||||
@@ -16,4 +16,4 @@ record R(int first, String second, int... third, boolean fourth) {
|
||||
boolean isFourth() {
|
||||
return fourth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
class <caret>R {
|
||||
final int first;
|
||||
final String second;
|
||||
@@ -27,4 +27,4 @@ class <caret>R {
|
||||
boolean isFourth() {
|
||||
return fourth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public record Box(@NotNull Object object) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
package my.annotation2;
|
||||
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
record R(int first) {
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// no "true-preview" above because of IDEA-369873
|
||||
record R(int myFirst) {
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// "Convert to record class" "true"
|
||||
// no "true-preview" above because of IDEA-369873
|
||||
record R(int myFirst) {
|
||||
R(int myFirst) {
|
||||
this.myFirst = myFirst;
|
||||
System.out.println("hello there, myFirst: " + myFirst + ", first: " + myFirst);
|
||||
// Renaming usages in strings can be enabled with 'RenameRefactoring.setSearchInComments(false)', but it always prompts, so it's a no-go
|
||||
}
|
||||
|
||||
// first
|
||||
// myFirst
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// "Convert to record class" "true"
|
||||
// no "true-preview" above because of IDEA-369873
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
class SomeClass {
|
||||
public record Problem(int myLine, int myColumn, @NotNull String code) {
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// "Convert to record class" "true"
|
||||
// no "true-preview" above because of IDEA-369873
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
class SomeClass {
|
||||
record Problem(int myLine, int myColumn, @NotNull String code) {
|
||||
|
||||
static Problem make() {
|
||||
int lineArg = 0;
|
||||
int columnArg = 42;
|
||||
return new Problem(lineArg, columnArg, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// "Convert to record class" "true"
|
||||
// no "true-preview" above because of IDEA-369873
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
class SomeClass {
|
||||
record Problem(int myColumn, int myLine, @NotNull String code) {
|
||||
// Notice: order of parameters differs from order of instance fields
|
||||
|
||||
static Problem make() {
|
||||
int columnArg = 42;
|
||||
int lineArg = 0;
|
||||
return new Problem(columnArg, lineArg, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
record R(String second, int first) {
|
||||
R(String second, int first) {
|
||||
this.second = second;
|
||||
this.first = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
record R(int first, int second) {
|
||||
// third field
|
||||
static int third = 1;
|
||||
// fourth field
|
||||
private static final String fourth = "fourth";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
/**
|
||||
* Some class doc
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
/**
|
||||
* @param x head doc for x
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
// "Convert to record class" "true"
|
||||
// no "true-preview" above because of IDEA-369873
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target({ElementType.FIELD})
|
||||
@@ -52,4 +53,4 @@ record R<T extends Number>(@Method T a, @Field(value = 1) @Field(value = 2) int
|
||||
public int e() {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
import java.io.*;
|
||||
|
||||
record R(int first) {
|
||||
@@ -6,4 +6,4 @@ record R(int first) {
|
||||
int getFirst() throws FileNotFoundException {
|
||||
return first;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
record R(int first, int second, int third) {
|
||||
/**
|
||||
* @throws NullPointerException
|
||||
@@ -26,4 +26,4 @@ record R(int first, int second, int third) {
|
||||
public int third() {
|
||||
return third > 0 ? third : -third;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// "Convert to record class" "true"
|
||||
// no "true-preview" above because of IDEA-369873
|
||||
record R(int first, String second, int... third) {
|
||||
|
||||
@Override
|
||||
@@ -10,4 +11,4 @@ record R(int first, String second, int... third) {
|
||||
public String second() {
|
||||
return second.length() > 1 ? second : "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
class Foo {
|
||||
void test() {
|
||||
record Point(double x, double y) {
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
class Foo {
|
||||
static final double staticVar = 1.0;
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
class Foo {
|
||||
static double delta = 1;
|
||||
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
record R(int first) {
|
||||
|
||||
void print() {
|
||||
System.out.println(toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
package com.example;
|
||||
|
||||
interface Runnable {}
|
||||
|
||||
record X(int field) implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
package com.example;
|
||||
|
||||
record X(int field) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
package com.example;
|
||||
|
||||
record X(boolean field) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
package org.qw;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
class A {
|
||||
public record Nested(int j) { // convert to the record
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
import java.util.Objects;
|
||||
|
||||
record Test(boolean booleanValue, char charValue, String stringValue, long longValue, float floatValue,
|
||||
@@ -7,4 +7,4 @@ record Test(boolean booleanValue, char charValue, String stringValue, long longV
|
||||
public int hashCode() {
|
||||
return Objects.hash(booleanValue, stringValue, longValue, floatValue, doubleValue, arrayValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// "Convert to record class" "true"
|
||||
// no "true-preview" above because of IDEA-369873
|
||||
|
||||
record Test(boolean booleanValue, char charValue, String stringValue, long longValue, float floatValue,
|
||||
double doubleValue, double[] arrayValue) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
record Test(double[] arrayValue) {
|
||||
@Override
|
||||
public int hashCode() {
|
||||
@@ -8,4 +8,4 @@ record Test(double[] arrayValue) {
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
record Test(boolean booleanValue, char charValue, String stringValue, long longValue, float floatValue,
|
||||
double doubleValue, double[] arrayValue) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
// "Convert to record class" "true"
|
||||
// no "true-preview" above because of IDEA-369873
|
||||
interface IR1 {
|
||||
int first();
|
||||
}
|
||||
@@ -20,4 +21,4 @@ class R2 implements IR1 {
|
||||
public int first() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
// "Convert to record class" "true-preview"
|
||||
record R(int x, int y) {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Convert to record class" "true-preview"
|
||||
record R(int x, int y) {
|
||||
R(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = 10;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
import java.lang.annotation.*;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// "Convert to record class" "true"
|
||||
// no "true-preview" above because of IDEA-369873
|
||||
public record SpreadSymbol(String symbol, int legs) {
|
||||
|
||||
void fromClass() {
|
||||
@@ -18,4 +19,4 @@ class Use {
|
||||
System.out.println(ss.symbol());
|
||||
System.out.println(ss.legs());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class <caret>Box {
|
||||
@@ -12,4 +12,4 @@ public final class <caret>Box {
|
||||
return object;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
package my.annotation2;
|
||||
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
class <caret>R {
|
||||
final int first;
|
||||
|
||||
@@ -6,4 +6,4 @@ class <caret>R {
|
||||
super();
|
||||
this.first = first;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
// "Convert to record class" "false"
|
||||
class <caret>R {
|
||||
final int myFirst;
|
||||
|
||||
R(int first) {
|
||||
myFirst = first;
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// "Convert to record class" "true"
|
||||
// no "true-preview" above because of IDEA-369873
|
||||
class <caret>R {
|
||||
final int myFirst;
|
||||
|
||||
R(int first) {
|
||||
myFirst = first;
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// "Convert to record class" "true"
|
||||
// no "true-preview" above because of IDEA-369873
|
||||
class <caret>R {
|
||||
final int myFirst;
|
||||
|
||||
R(int first) {
|
||||
myFirst = first;
|
||||
System.out.println("hello there, myFirst: " + first + ", first: " + first);
|
||||
// Renaming usages in strings can be enabled with 'RenameRefactoring.setSearchInComments(false)', but it always prompts, so it's a no-go
|
||||
}
|
||||
|
||||
// first
|
||||
// myFirst
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// "Convert to record class" "false"
|
||||
class R2<caret> {
|
||||
final int myFirst;
|
||||
final int mySecond;
|
||||
final int myThird;
|
||||
|
||||
R2(int first, int nothing, int third) {
|
||||
myFirst = first;
|
||||
mySecond = nothing;
|
||||
myThird = idk; // 2 problems here:
|
||||
// 1. unresolved symbol 'idk'
|
||||
// 2. no connection between 'myThird' field and 'third' parameter
|
||||
// So we can't do conversion reliably.
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// "Convert to record class" "true"
|
||||
// no "true-preview" above because of IDEA-369873
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
class SomeClass {
|
||||
public static class Prob<caret>lem {
|
||||
private final int myLine;
|
||||
private final int myColumn;
|
||||
private final @NotNull String code;
|
||||
|
||||
public Problem(int line, int column, @NotNull String code) {
|
||||
myLine = line;
|
||||
myColumn = column;
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// "Convert to record class" "true"
|
||||
// no "true-preview" above because of IDEA-369873
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
class SomeClass {
|
||||
static class Prob<caret>lem {
|
||||
private final int myLine;
|
||||
private final @NotNull String code;
|
||||
private final int myColumn;
|
||||
|
||||
Problem(int line, int column, @NotNull String code) {
|
||||
myLine = line;
|
||||
this.code = code;
|
||||
myColumn = column;
|
||||
}
|
||||
|
||||
static Problem make() {
|
||||
int lineArg = 0;
|
||||
int columnArg = 42;
|
||||
return new Problem(lineArg, columnArg, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
// "Convert to record class" "true"
|
||||
// no "true-preview" above because of IDEA-369873
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
class SomeClass {
|
||||
static class Prob<caret>lem {
|
||||
private final int myLine;
|
||||
private final @NotNull String code;
|
||||
private final int myColumn;
|
||||
|
||||
// Notice: order of parameters differs from order of instance fields
|
||||
Problem(int column, int line, @NotNull String code) {
|
||||
myLine = line;
|
||||
this.code = code;
|
||||
myColumn = column;
|
||||
}
|
||||
|
||||
static Problem make() {
|
||||
int columnArg = 42;
|
||||
int lineArg = 0;
|
||||
return new Problem(columnArg, lineArg, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
class <caret>R {
|
||||
final int first;
|
||||
final String second;
|
||||
@@ -7,4 +7,4 @@ class <caret>R {
|
||||
this.second = second;
|
||||
this.first = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
class <caret>R {
|
||||
// third field
|
||||
static int third = 1;
|
||||
@@ -11,4 +11,4 @@ class <caret>R {
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
/**
|
||||
* Some class doc
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
class <caret>Foo {
|
||||
/**
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
// "Convert to record class" "true"
|
||||
// no "true-preview" above because of IDEA-369873
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target({ElementType.FIELD})
|
||||
@@ -71,4 +72,4 @@ class <caret>R<T extends Number> implements IR<T> {
|
||||
int getE() {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
import java.io.*;
|
||||
|
||||
class <caret>R {
|
||||
@@ -11,4 +11,4 @@ class <caret>R {
|
||||
int getFirst() throws FileNotFoundException {
|
||||
return first;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
class <caret>R {
|
||||
final int first;
|
||||
final int second;
|
||||
@@ -27,4 +27,4 @@ class <caret>R {
|
||||
int third() throws NullPointerException, ArithmeticException {
|
||||
return third > 0 ? third : -third;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// "Convert to record class" "true"
|
||||
// no "true-preview" above because of IDEA-369873
|
||||
final class <caret>R {
|
||||
final int first;
|
||||
final String second;
|
||||
@@ -21,4 +22,4 @@ final class <caret>R {
|
||||
private int[] getThird() {
|
||||
return third;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
class Foo {
|
||||
void test() {
|
||||
class Poi<caret>nt {
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
class Foo {
|
||||
static final double staticVar = 1.0;
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
class Foo {
|
||||
static double delta = 1;
|
||||
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
class <caret>R {
|
||||
final int first;
|
||||
|
||||
@@ -9,4 +9,4 @@ class <caret>R {
|
||||
void print() {
|
||||
System.out.println(toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
package com.example;
|
||||
|
||||
interface Runnable {}
|
||||
@@ -13,4 +13,4 @@ class <caret>X implements Runnable {
|
||||
public int field() {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
package com.example;
|
||||
|
||||
class <caret>X {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
package com.example;
|
||||
|
||||
class <caret>X {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
package org.qw;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
class A {
|
||||
public static final class <caret>Nested { // convert to the record
|
||||
private final int j;
|
||||
@@ -7,4 +7,4 @@ class A {
|
||||
this.j = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
import java.util.Objects;
|
||||
|
||||
class <caret>Test {
|
||||
@@ -14,4 +14,4 @@ class <caret>Test {
|
||||
public int hashCode() {
|
||||
return Objects.hash(booleanValue, stringValue, longValue, floatValue, doubleValue, arrayValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// "Convert to record class" "true"
|
||||
// no "true-preview" above because of IDEA-369873
|
||||
import java.util.Objects;
|
||||
|
||||
class <caret>Test {
|
||||
@@ -14,4 +15,4 @@ class <caret>Test {
|
||||
public int hashCode() {
|
||||
return Objects.hash(booleanValue, charValue, stringValue, longValue, floatValue, doubleValue, arrayValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
class <caret>Test {
|
||||
final double[] arrayValue;
|
||||
|
||||
@@ -10,4 +10,4 @@ class <caret>Test {
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
class <caret>Test {
|
||||
final boolean booleanValue;
|
||||
final char charValue;
|
||||
@@ -23,4 +23,4 @@ class <caret>Test {
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
// "Convert to record class" "true"
|
||||
// no "true-preview" above because of IDEA-369873
|
||||
interface IR1 {
|
||||
int getFirst();
|
||||
}
|
||||
@@ -25,4 +26,4 @@ class R2 implements IR1 {
|
||||
public int getFirst() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// "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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// "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 = 10;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// "Convert to record class" "false"
|
||||
class R<caret> {
|
||||
final int x;
|
||||
final int y;
|
||||
final int z;
|
||||
|
||||
R(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = 10;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Convert to record class" "true"
|
||||
// "Convert to record class" "true-preview"
|
||||
import java.lang.annotation.*;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// "Convert to record class" "true"
|
||||
// no "true-preview" above because of IDEA-369873
|
||||
public class SpreadSymb<caret>ol {
|
||||
public final String symbol;
|
||||
public final int legs;
|
||||
@@ -25,4 +26,4 @@ class Use {
|
||||
System.out.println(ss.symbol);
|
||||
System.out.println(ss.legs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -827,4 +827,4 @@ inline.popup.highlight=Highlight {0} conflicting {0, choice, 1#write|2#writes}
|
||||
inline.popup.ignore.conflicts=Ignore writes and continue
|
||||
inline.warning.variables.used.in.initializer.are.updated=Unsafe Inline: Variables Used in Initializer Are Updated
|
||||
dialog.title.resolving.method.implementation=Resolving Method Implementation
|
||||
dialog.message.confirmation.to.process.only.implementation=An implementation of abstract method is found:<br><br><b>{0}</b><br><br>Do you want to inline this implementation?
|
||||
dialog.message.confirmation.to.process.only.implementation=An implementation of abstract method is found:<br><br><b>{0}</b><br><br>Do you want to inline this implementation?
|
||||
|
||||
Reference in New Issue
Block a user