Java: support safe delete for record components (IDEA-245145)

GitOrigin-RevId: 5eccf46c5e0cce0d63c5ac480ad8d180b1ef0fed
This commit is contained in:
Bas Leijdekkers
2024-10-07 16:49:22 +02:00
committed by intellij-monorepo-bot
parent 1ce2fdbcb5
commit 81a6c7d02c
7 changed files with 171 additions and 28 deletions

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.lang.java;
import com.intellij.lang.LanguageNamesValidation;
@@ -38,7 +38,7 @@ public class JavaRefactoringSupportProvider extends JavaBaseRefactoringSupportPr
public boolean isSafeDeleteAvailable(@NotNull PsiElement element) {
return element instanceof PsiClass || element instanceof PsiMethod || element instanceof PsiField ||
(element instanceof PsiParameter && ((PsiParameter)element).getDeclarationScope() instanceof PsiMethod) ||
element instanceof PsiPackage || element instanceof PsiLocalVariable;
element instanceof PsiPackage || element instanceof PsiLocalVariable || element instanceof PsiRecordComponent;
}
@Override

View File

@@ -25,6 +25,7 @@ import com.intellij.psi.codeStyle.JavaCodeStyleSettings;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.impl.FindSuperElementsHelper;
import com.intellij.psi.impl.light.LightRecordMethod;
import com.intellij.psi.impl.source.javadoc.PsiDocParamRef;
import com.intellij.psi.javadoc.PsiDocTag;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.LocalSearchScope;
@@ -94,9 +95,12 @@ public class JavaSafeDeleteProcessor extends SafeDeleteProcessorDelegateBase {
else if (element instanceof PsiField field) {
insideDeletedCondition = findFieldUsages(field, usages, allElementsToDelete);
}
else if (element instanceof PsiRecordComponent component) {
insideDeletedCondition = findRecordComponentUsages(component, allElementsToDelete, usages);
}
else if (element instanceof PsiParameter parameter) {
LOG.assertTrue(parameter.getDeclarationScope() instanceof PsiMethod);
findParameterUsages(parameter, usages);
findParameterUsages(parameter, allElementsToDelete, usages);
}
else if (element instanceof PsiLocalVariable) {
for (PsiReference reference : ReferencesSearch.search(element)) {
@@ -230,6 +234,26 @@ public class JavaSafeDeleteProcessor extends SafeDeleteProcessorDelegateBase {
public Collection<PsiElement> getAdditionalElementsToDelete(@NotNull PsiElement element,
@NotNull Collection<? extends PsiElement> allElementsToDelete,
boolean askUser) {
if (element instanceof PsiRecordComponent component) {
PsiMethod method = JavaPsiRecordUtil.getAccessorForRecordComponent(component);
List<PsiElement> additional = new ArrayList<>();
if (method != null && !(method instanceof SyntheticElement) && !allElementsToDelete.contains(method)) {
additional.add(method);
}
PsiClass recordClass = component.getContainingClass();
assert recordClass != null;
PsiMethod constructor = JavaPsiRecordUtil.findCanonicalConstructor(recordClass);
if (constructor == null || constructor instanceof SyntheticElement) return additional;
PsiRecordHeader header = recordClass.getRecordHeader();
assert header != null;
int index = ArrayUtil.indexOf(header.getRecordComponents(), component);
if (index < 0) return additional;
PsiParameter parameter = constructor.getParameterList().getParameter(index);
if (parameter != null) {
additional.add(parameter);
}
return additional;
}
if (element instanceof PsiField field) {
Project project = element.getProject();
String propertyName = JavaCodeStyleManager.getInstance(project).variableNameToPropertyName(field.getName(), VariableKind.FIELD);
@@ -328,6 +352,22 @@ public class JavaSafeDeleteProcessor extends SafeDeleteProcessorDelegateBase {
return conflicts.values();
}
}
else if (element instanceof PsiRecordComponent component) {
PsiClass recordClass = component.getContainingClass();
assert recordClass != null;
PsiMethod constructor = JavaPsiRecordUtil.findCanonicalConstructor(recordClass);
if (constructor != null) {
PsiRecordHeader header = recordClass.getRecordHeader();
assert header != null;
int index = ArrayUtil.indexOf(header.getRecordComponents(), component);
if (index >= 0) {
PsiParameter parameter = constructor.getParameterList().getParameter(index);
if(parameter != null) {
return findConflicts(parameter, allElementsToDelete);
}
}
}
}
return null;
}
@@ -947,8 +987,42 @@ public class JavaSafeDeleteProcessor extends SafeDeleteProcessorDelegateBase {
: new SafeDeleteParameterCallHierarchyUsageInfo(called, calledParameter, caller, parameterInCaller);
}
private static Condition<PsiElement> findRecordComponentUsages(PsiRecordComponent component,
PsiElement @NotNull [] allElementsToDelete,
List<? super UsageInfo> usages) {
Condition <PsiElement> isInsideDeleted = getUsageInsideDeletedFilter(allElementsToDelete);
PsiClass recordClass = component.getContainingClass();
assert recordClass != null;
PsiMethod constructor = JavaPsiRecordUtil.findCanonicalConstructor(recordClass);
if (constructor != null) {
PsiRecordHeader header = recordClass.getRecordHeader();
assert header != null;
int index = ArrayUtil.indexOf(header.getRecordComponents(), component);
if (index < 0) return isInsideDeleted;
ReferencesSearch.search(constructor).forEach(ref -> {
PsiElement element = ref.getElement();
if (!isInsideDeleted.test(element)) {
JavaSafeDeleteDelegate safeDeleteDelegate = JavaSafeDeleteDelegate.EP.forLanguage(element.getLanguage());
if (safeDeleteDelegate != null) {
safeDeleteDelegate.createUsageInfoForParameter(ref, usages, component, index, component.isVarArgs());
}
}
});
}
ReferencesSearch.search(component).forEach(ref -> {
PsiElement element = ref.getElement();
if (!isInsideDeleted.test(element)) {
boolean javadoc = element instanceof PsiDocParamRef;
PsiElement parent = element.getParent();
usages.add(parent instanceof PsiAssignmentExpression assignment && element == assignment.getLExpression()
? new SafeDeleteFieldWriteReference(assignment, component)
: new SafeDeleteReferenceJavaDeleteUsageInfo(javadoc ? parent : element, component, javadoc));
}
});
return isInsideDeleted;
}
private static void findParameterUsages(@NotNull PsiParameter parameter, @NotNull List<? super UsageInfo> usages) {
private static void findParameterUsages(@NotNull PsiParameter parameter, PsiElement @NotNull [] allElementsToDelete, @NotNull List<? super UsageInfo> usages) {
PsiMethod method = (PsiMethod)parameter.getDeclarationScope();
int parameterIndex = method.getParameterList().getParameterIndex(parameter);
if (parameterIndex < 0) return;
@@ -976,6 +1050,17 @@ public class JavaSafeDeleteProcessor extends SafeDeleteProcessorDelegateBase {
usages.add(new SafeDeleteReferenceJavaDeleteUsageInfo(docTag, parameter, true));
return true;
}
PsiElement parent = PsiUtil.skipParenthesizedExprUp(element.getParent());
if (parent instanceof PsiAssignmentExpression assignment && PsiTreeUtil.isAncestor(assignment.getRExpression(), element, false)) {
PsiExpression lhs = assignment.getLExpression();
if (lhs instanceof PsiReferenceExpression ref) {
PsiElement target = ref.resolve();
if (target instanceof PsiField field) {
PsiRecordComponent component = JavaPsiRecordUtil.getComponentForField(field);
if (component != null && isInside(component, allElementsToDelete)) return true;
}
}
}
boolean isSafeDelete = false;
if (element.getParent().getParent() instanceof PsiMethodCallExpression call) {

View File

@@ -1,43 +1,26 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.refactoring.safeDelete.usageInfo;
import com.intellij.psi.*;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.util.IncorrectOperationException;
public class SafeDeleteFieldWriteReference extends SafeDeleteReferenceUsageInfo {
private final PsiAssignmentExpression myExpression;
public SafeDeleteFieldWriteReference(PsiAssignmentExpression expr, PsiField referencedElement) {
public SafeDeleteFieldWriteReference(PsiAssignmentExpression expr, PsiMember referencedElement) {
super(expr, referencedElement, safeRemoveRHS(expr));
myExpression = expr;
}
private static boolean safeRemoveRHS(PsiAssignmentExpression expression) {
final PsiExpression rExpression = expression.getRExpression();
final PsiElement parent = expression.getParent();
return RefactoringUtil.verifySafeCopyExpression(rExpression) == RefactoringUtil.EXPR_COPY_SAFE
&& parent instanceof PsiExpressionStatement
&& ((PsiExpressionStatement) parent).getExpression() == expression;
&& parent instanceof PsiExpressionStatement statement
&& statement.getExpression() == expression;
}
@Override
public void deleteElement() throws IncorrectOperationException {
myExpression.getParent().delete();
public void deleteElement() {
PsiElement element = getElement();
if (element != null) element.getParent().delete();
}
}