IDEA-229843 Change signature refactoring for records

Known problems: when removing record component getter is not removed; getter/field usages are not reported

GitOrigin-RevId: 3babf3e316209232be416a28b52e77ab470fe2a3
This commit is contained in:
Tagir Valeev
2020-02-11 08:45:32 +00:00
committed by intellij-monorepo-bot
parent 27e8c38041
commit e08eb714bd
21 changed files with 596 additions and 202 deletions
@@ -153,7 +153,7 @@ public class JavaChangeSignatureDialog extends ChangeSignatureDialogBase<Paramet
@Override
protected VisibilityPanelBase<String> createVisibilityControl() {
return new JavaComboBoxVisibilityPanel();
return new JavaComboBoxVisibilityPanel(myMethod.getAllowedModifiers());
}
@Override
@@ -13,6 +13,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.psi.*;
import com.intellij.psi.util.JavaPsiRecordUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.HelpID;
@@ -79,7 +80,9 @@ public class JavaChangeSignatureHandler implements ChangeSignatureHandler {
final PsiClass containingClass = method.getContainingClass();
final PsiReferenceExpression refExpr = editor != null ? JavaTargetElementEvaluator.findReferenceExpression(editor) : null;
final boolean allowDelegation = containingClass != null && (!containingClass.isInterface() || PsiUtil.isLanguageLevel8OrHigher(containingClass));
final boolean allowDelegation = containingClass != null &&
(!containingClass.isInterface() || PsiUtil.isLanguageLevel8OrHigher(containingClass)) &&
!method.equals(JavaPsiRecordUtil.findCanonicalConstructor(containingClass));
InplaceChangeSignature inplaceChangeSignature = editor != null ? InplaceChangeSignature.getCurrentRefactoring(editor) : null;
ChangeInfo initialChange = inplaceChangeSignature != null ? inplaceChangeSignature.getStableChange() : null;
@@ -156,6 +159,14 @@ public class JavaChangeSignatureHandler implements ChangeSignatureHandler {
if (PsiTreeUtil.getParentOfType(element, PsiParameterList.class) != null) {
return PsiTreeUtil.getParentOfType(element, PsiMethod.class);
}
PsiRecordHeader header = PsiTreeUtil.getParentOfType(element, PsiRecordHeader.class);
if (header != null) {
PsiClass aClass = header.getContainingClass();
if (aClass != null) {
return JavaPsiRecordUtil.findCanonicalConstructor(aClass);
}
return null;
}
final PsiTypeParameterList typeParameterList = PsiTreeUtil.getParentOfType(element, PsiTypeParameterList.class);
if (typeParameterList != null) {
@@ -13,6 +13,8 @@ import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.application.ReadActionProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
@@ -181,6 +183,10 @@ public class JavaChangeSignatureUsageProcessor implements ChangeSignatureUsagePr
processParameterUsage((PsiReferenceExpression)element, oldName, newName);
return true;
}
else if (usage instanceof RecordGetterDeclarationUsageInfo) {
processRecordGetter((PsiMethod)element, ((RecordGetterDeclarationUsageInfo)usage).myNewName,
((RecordGetterDeclarationUsageInfo)usage).myNewType);
}
else if (usage instanceof CallReferenceUsageInfo) {
((CallReferenceUsageInfo)usage).getReference().handleChangeSignature(changeInfo);
return true;
@@ -202,6 +208,20 @@ public class JavaChangeSignatureUsageProcessor implements ChangeSignatureUsagePr
return false;
}
private static void processRecordGetter(PsiMethod method, String newName, String type) {
Project project = method.getProject();
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
if (newName != null && !newName.equals(method.getName())) {
final PsiIdentifier nameId = method.getNameIdentifier();
assert nameId != null : method;
nameId.replace(factory.createIdentifier(newName));
}
PsiTypeElement element = method.getReturnTypeElement();
if (element != null && !element.textMatches(type)) {
JavaCodeStyleManager.getInstance(project).shortenClassReferences(element.replace(factory.createTypeElementFromText(type, method)));
}
}
private static void processParameterUsage(PsiReferenceExpression ref, String oldName, String newName)
throws IncorrectOperationException {
@@ -788,7 +808,20 @@ public class JavaChangeSignatureUsageProcessor implements ChangeSignatureUsagePr
}
PsiParameterList list = method.getParameterList();
int newParamsLength = processMethodParams(changeInfo, baseMethod, factory, substitutor, list, method.getBody());
PsiClass aClass = method.getContainingClass();
boolean isRecordCanonicalConstructor = aClass != null && method.equals(JavaPsiRecordUtil.findCanonicalConstructor(aClass));
boolean shouldChangeParameters = !isRecordCanonicalConstructor || JavaPsiRecordUtil.isExplicitCanonicalConstructor(method);
int newParamsLength = getNewParametersCount(changeInfo, baseMethod, list);
if (shouldChangeParameters) {
processMethodParams(changeInfo, baseMethod, factory, substitutor, list, method.getBody());
}
if (isRecordCanonicalConstructor) {
processRecordHeader(factory, Objects.requireNonNull(aClass.getRecordHeader()), changeInfo);
if (method instanceof SyntheticElement) {
method = Objects.requireNonNull(JavaPsiRecordUtil.findCanonicalConstructor(aClass));
}
}
fixJavadocsForChangedMethod(method, changeInfo, newParamsLength);
if (changeInfo.isExceptionSetOrOrderChanged()) {
final PsiClassType[] newExceptions = getPrimaryChangedExceptionInfo(changeInfo);
@@ -805,17 +838,66 @@ public class JavaChangeSignatureUsageProcessor implements ChangeSignatureUsagePr
}
}
private static int processMethodParams(JavaChangeInfo changeInfo,
PsiMethod baseMethod,
PsiElementFactory factory,
PsiSubstitutor substitutor,
PsiParameterList list,
PsiElement methodBody) {
private static void processRecordHeader(@NotNull PsiElementFactory factory,
@NotNull PsiRecordHeader header,
@NotNull JavaChangeInfo changeInfo) {
PsiRecordComponent[] components = header.getRecordComponents();
JavaParameterInfo[] parameterInfos = changeInfo.getNewParameters();
List<PsiRecordComponent> newComponents = new ArrayList<>();
String[] oldParameterNames = changeInfo.getOldParameterNames();
String[] oldParameterTypes = changeInfo.getOldParameterTypes();
for (JavaParameterInfo info : parameterInfos) {
int index = info.getOldIndex();
if (index >= 0) {
PsiRecordComponent component = components[index];
newComponents.add(component);
String oldName = oldParameterNames[index];
if (!oldName.equals(info.getName()) && oldName.equals(component.getName())) {
PsiIdentifier newIdentifier = factory.createIdentifier(info.getName());
Objects.requireNonNull(component.getNameIdentifier()).replace(newIdentifier);
}
PsiTypeElement typeElement = component.getTypeElement();
if (typeElement != null) {
component.normalizeDeclaration();
typeElement = component.getTypeElement();
String oldType = oldParameterTypes[index];
if (!oldType.equals(info.getTypeText())) {
PsiType newType = info.createType(header, changeInfo.getMethod().getManager());
if (newType != null) {
typeElement.replace(factory.createTypeElement(newType));
}
}
}
}
else {
String componentText = info.getTypeText() + " " + info.getName();
PsiRecordComponent[] dummyComponents = factory.createRecordHeaderFromText(componentText, header).getRecordComponents();
if (dummyComponents.length != 1) {
throw new IncorrectOperationException(componentText + " is not a valid component");
}
newComponents.add(dummyComponents[0]);
}
}
ChangeSignatureUtil.synchronizeList(header, newComponents, RecordHeader.INSTANCE, changeInfo.toRemoveParm());
}
private static int getNewParametersCount(JavaChangeInfo changeInfo, PsiMethod baseMethod, PsiParameterList list) {
final int delta = baseMethod != null ? baseMethod.getParameterList().getParametersCount() - list.getParametersCount() : 0;
return Math.max(changeInfo.getNewParameters().length - delta, 0);
}
private static void processMethodParams(JavaChangeInfo changeInfo,
PsiMethod baseMethod,
PsiElementFactory factory,
PsiSubstitutor substitutor,
PsiParameterList list,
PsiElement methodBody) {
PsiParameter[] parameters = list.getParameters();
final JavaParameterInfo[] parameterInfos = changeInfo.getNewParameters();
final int delta = baseMethod != null ? baseMethod.getParameterList().getParametersCount() - list.getParametersCount() : 0;
PsiParameter[] newParms = new PsiParameter[Math.max(parameterInfos.length - delta, 0)];
PsiParameter[] newParms = new PsiParameter[getNewParametersCount(changeInfo, baseMethod, list)];
final String[] oldParameterNames = changeInfo.getOldParameterNames();
final String[] oldParameterTypes = changeInfo.getOldParameterTypes();
for (int i = 0; i < newParms.length; i++) {
@@ -855,9 +937,7 @@ public class JavaChangeSignatureUsageProcessor implements ChangeSignatureUsagePr
}
}
resolveParameterVsFieldsConflicts(newParms, list, changeInfo.toRemoveParm(), methodBody);
return newParms.length;
}
private static PsiClassType[] getPrimaryChangedExceptionInfo(JavaChangeInfo changeInfo) throws IncorrectOperationException {
@@ -945,13 +1025,21 @@ public class JavaChangeSignatureUsageProcessor implements ChangeSignatureUsagePr
newParameters.add(parameters[i]);
}
}
RefactoringUtil.fixJavadocsForParams(method, newParameters, pair -> {
Condition<Pair<PsiParameter, String>> eqCondition = pair -> {
final PsiParameter parameter = pair.first;
final String oldParamName = pair.second;
final int oldIdx = ArrayUtil.find(oldParameterNames, oldParamName);
int newIndex = method.getParameterList().getParameterIndex(parameter);
return oldIdx >= 0 && newIndex >= 0 && changeInfo.getNewParameters()[newIndex].getOldIndex() == oldIdx;
}, paramName -> ArrayUtil.find(oldParameterNames, paramName) >= 0);
};
Condition<String> matchedToOldParam = paramName -> ArrayUtil.find(oldParameterNames, paramName) >= 0;
if (!(method instanceof SyntheticElement)) {
RefactoringUtil.fixJavadocsForParams(method, newParameters, eqCondition, matchedToOldParam);
}
PsiClass aClass = method.getContainingClass();
if (aClass != null && method.equals(JavaPsiRecordUtil.findCanonicalConstructor(aClass))) {
RefactoringUtil.fixJavadocsForParams(method, aClass.getDocComment(), newParameters, eqCondition, matchedToOldParam);
}
if (changeInfo.isReturnTypeChanged()) {
PsiDocComment docComment = method.getDocComment();
@@ -1016,6 +1104,15 @@ public class JavaChangeSignatureUsageProcessor implements ChangeSignatureUsagePr
}
}
private static class RecordHeader implements ChangeSignatureUtil.ChildrenGenerator<PsiRecordHeader, PsiRecordComponent> {
public static final RecordHeader INSTANCE = new RecordHeader();
@Override
public List<PsiRecordComponent> getChildren(PsiRecordHeader header) {
return Arrays.asList(header.getRecordComponents());
}
}
private static class ThrowsList implements ChangeSignatureUtil.ChildrenGenerator<PsiReferenceList, PsiJavaCodeReferenceElement> {
public static final ThrowsList INSTANCE = new ThrowsList();
@@ -25,6 +25,7 @@ import com.intellij.psi.search.searches.FunctionalExpressionSearch;
import com.intellij.psi.search.searches.MethodReferencesSearch;
import com.intellij.psi.search.searches.OverridingMethodsSearch;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.JavaPsiRecordUtil;
import com.intellij.psi.xml.XmlElement;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.rename.JavaUnresolvableLocalCollisionDetector;
@@ -37,6 +38,7 @@ import com.intellij.refactoring.util.usageInfo.NoConstructorClassUsageInfo;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewUtil;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.VariableAccessUtils;
import java.util.ArrayList;
import java.util.Arrays;
@@ -161,13 +163,18 @@ class JavaChangeSignatureUsageSearcher {
private void findParametersUsage(final PsiMethod method, ArrayList<? super UsageInfo> result, PsiMethod[] overriders) {
if (JavaLanguage.INSTANCE.equals(myChangeInfo.getLanguage())) {
PsiClass aClass = method.getContainingClass();
PsiRecordComponent[] components = null;
if (aClass != null && method.equals(JavaPsiRecordUtil.findCanonicalConstructor(aClass))) {
components = aClass.getRecordComponents();
}
PsiParameter[] parameters = method.getParameterList().getParameters();
for (ParameterInfo info : myChangeInfo.getNewParameters()) {
if (info.getOldIndex() >= 0) {
PsiParameter parameter = parameters[info.getOldIndex()];
if (!info.getName().equals(parameter.getName())) {
boolean nameChanged = !info.getName().equals(parameter.getName());
if (nameChanged) {
addParameterUsages(parameter, result, info);
for (PsiMethod overrider : overriders) {
PsiParameter parameter1 = overrider.getParameterList().getParameters()[info.getOldIndex()];
if (parameter1 != null && Comparing.strEqual(parameter.getName(), parameter1.getName())) {
@@ -175,6 +182,28 @@ class JavaChangeSignatureUsageSearcher {
}
}
}
if (components != null && components.length > info.getOldIndex()) {
PsiRecordComponent component = components[info.getOldIndex()];
if (nameChanged) {
PsiField field = JavaPsiRecordUtil.getFieldForComponent(component);
if (field != null) {
for (PsiReferenceExpression reference : VariableAccessUtils.getVariableReferences(field, aClass)) {
UsageInfo usageInfo = new ChangeSignatureParameterUsageInfo(reference, parameter.getName(), info.getName());
result.add(usageInfo);
}
}
}
PsiMethod explicitGetter = ContainerUtil
.find(aClass.findMethodsByName(parameter.getName(), false), m -> m.getParameterList().isEmpty());
if (explicitGetter != null) {
if (nameChanged) {
addParameterUsages(explicitGetter, result, info);
}
if (!(explicitGetter instanceof SyntheticElement) && (nameChanged || !parameter.getType().equalsToText(info.getTypeText()))) {
result.add(new RecordGetterDeclarationUsageInfo(explicitGetter, info.getName(), info.getTypeText()));
}
}
}
}
}
}
@@ -297,7 +326,7 @@ class JavaChangeSignatureUsageSearcher {
}
private static void addParameterUsages(PsiParameter parameter, ArrayList<? super UsageInfo> results, ParameterInfo info) {
private static void addParameterUsages(PsiNamedElement parameter, ArrayList<? super UsageInfo> results, ParameterInfo info) {
PsiManager manager = parameter.getManager();
GlobalSearchScope projectScope = GlobalSearchScope.projectScope(manager.getProject());
for (PsiReference psiReference : ReferencesSearch.search(parameter, projectScope, false)) {
@@ -5,6 +5,9 @@ import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiTypeElement;
import com.intellij.psi.util.AccessModifier;
import com.intellij.psi.util.JavaPsiRecordUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.VisibilityUtil;
import org.jetbrains.annotations.Nullable;
@@ -60,8 +63,7 @@ public class JavaMethodDescriptor implements MethodDescriptor<ParameterInfoImpl,
@Override
public boolean canChangeVisibility() {
PsiClass containingClass = myMethod.getContainingClass();
return containingClass != null && !containingClass.isInterface();
return AccessModifier.getAvailableModifiers(myMethod).size() > 1;
}
@Override
@@ -78,4 +80,8 @@ public class JavaMethodDescriptor implements MethodDescriptor<ParameterInfoImpl,
public boolean canChangeName() {
return !myMethod.isConstructor();
}
public List<AccessModifier> getAllowedModifiers() {
return AccessModifier.getAvailableModifiers(myMethod);
}
}
@@ -0,0 +1,16 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.refactoring.changeSignature;
import com.intellij.psi.PsiElement;
import com.intellij.usageView.UsageInfo;
public class RecordGetterDeclarationUsageInfo extends UsageInfo {
public final String myNewName;
public final String myNewType;
public RecordGetterDeclarationUsageInfo(PsiElement element, String newName, String newType) {
super(element);
myNewName = newName;
myNewType = newType;
}
}
@@ -16,22 +16,22 @@
package com.intellij.refactoring.ui;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.util.AccessModifier;
import com.intellij.util.VisibilityUtil;
import com.intellij.util.containers.ContainerUtil;
import java.util.List;
/**
* @author Konstantin Bulenkov
*/
public class JavaComboBoxVisibilityPanel extends ComboBoxVisibilityPanel<String> {
private static final String[] MODIFIERS = {PsiModifier.PRIVATE, PsiModifier.PACKAGE_LOCAL, PsiModifier.PROTECTED, PsiModifier.PUBLIC};
private static final String[] PRESENTABLE_NAMES = {
VisibilityUtil.toPresentableText(PsiModifier.PRIVATE),
VisibilityUtil.toPresentableText(PsiModifier.PACKAGE_LOCAL),
VisibilityUtil.toPresentableText(PsiModifier.PROTECTED),
VisibilityUtil.toPresentableText(PsiModifier.PUBLIC)
};
public JavaComboBoxVisibilityPanel(List<AccessModifier> modifiers) {
super(ContainerUtil.map2Array(modifiers, String.class, AccessModifier::toPsiModifier),
ContainerUtil.map2Array(modifiers, String.class, AccessModifier::toString));
}
public JavaComboBoxVisibilityPanel() {
super(MODIFIERS, PRESENTABLE_NAMES);
this(AccessModifier.ALL_MODIFIERS);
}
}
@@ -1137,11 +1137,18 @@ public class RefactoringUtil {
fixJavadocsForParams(method, newParameters, eqCondition, Conditions.alwaysTrue());
}
public static void fixJavadocsForParams(PsiMethod method,
Set<? extends PsiParameter> newParameters,
Condition<? super Pair<PsiParameter, String>> eqCondition,
Condition<? super String> matchedToOldParam) throws IncorrectOperationException {
final PsiDocComment docComment = method.getDocComment();
public static void fixJavadocsForParams(@NotNull PsiMethod method,
@NotNull Set<? extends PsiParameter> newParameters,
@NotNull Condition<? super Pair<PsiParameter, String>> eqCondition,
@NotNull Condition<? super String> matchedToOldParam) throws IncorrectOperationException {
fixJavadocsForParams(method, method.getDocComment(), newParameters, eqCondition, matchedToOldParam);
}
public static void fixJavadocsForParams(@NotNull PsiMethod method,
@Nullable PsiDocComment docComment,
@NotNull Set<? extends PsiParameter> newParameters,
@NotNull Condition<? super Pair<PsiParameter, String>> eqCondition,
@NotNull Condition<? super String> matchedToOldParam) throws IncorrectOperationException {
if (docComment == null) return;
final PsiParameter[] parameters = method.getParameterList().getParameters();
final PsiDocTag[] paramTags = docComment.findTagsByName("param");
@@ -0,0 +1,161 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.psi.util;
import com.intellij.psi.*;
import com.intellij.psi.impl.light.LightRecordCanonicalConstructor;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
/**
* Java access modifiers: public, protected, private, package-local
*/
public enum AccessModifier {
PUBLIC(PsiModifier.PUBLIC), PROTECTED(PsiModifier.PROTECTED), PACKAGE_LOCAL(PsiModifier.PACKAGE_LOCAL), PRIVATE(PsiModifier.PRIVATE);
public static final List<AccessModifier> ALL_MODIFIERS = ContainerUtil.immutableList(values());
private static final List<AccessModifier> PUBLIC_PACKAGE = ContainerUtil.immutableList(PUBLIC, PACKAGE_LOCAL);
private static final List<AccessModifier> PUBLIC_PRIVATE = ContainerUtil.immutableList(PUBLIC, PRIVATE);
@NotNull @PsiModifier.ModifierConstant
private final String myModifier;
AccessModifier(@PsiModifier.ModifierConstant @NotNull String modifier) {
myModifier = modifier;
}
/**
* @return a {@link PsiModifier} string constant which corresponds to this access modifier.
*/
@NotNull @PsiModifier.ModifierConstant
public String toPsiModifier() {
return myModifier;
}
/**
* Checks whether given modifier owner has this access modifier (probably implicit)
* @param owner element to check (e.g. class member)
* @return true if it has current modifier
*/
public boolean hasModifier(@NotNull PsiModifierListOwner owner) {
return owner.hasModifierProperty(toPsiModifier());
}
/**
* Returns an {@link AccessModifier} which corresponds to the given keyword;
* null if supplied keyword is null or don't correspond to access modifier
* @param keyword keyword to convert to access modifier
* @return a corresponding access modifier
*/
@Contract(value = "null -> null", pure = true)
@Nullable
public static AccessModifier fromKeyword(@Nullable PsiKeyword keyword) {
return keyword == null ? null : fromPsiModifier(keyword.getText());
}
/**
* Returns an {@link AccessModifier} which corresponds to the given String constant declared in
* {@link PsiModifier} class.
* @param modifier a modifier string
* @return an access modifier or null if supplied string doesn't correspond to any access modifier.
*/
@Contract(value = "null -> null", pure = true)
@Nullable
public static AccessModifier fromPsiModifier(@Nullable String modifier) {
if (modifier == null) return null;
switch (modifier) {
case PsiModifier.PUBLIC:
return PUBLIC;
case PsiModifier.PROTECTED:
return PROTECTED;
case PsiModifier.PACKAGE_LOCAL:
return PACKAGE_LOCAL;
case PsiModifier.PRIVATE:
return PRIVATE;
default:
return null;
}
}
public static AccessModifier fromModifierList(@NotNull PsiModifierList modifierList) {
if (modifierList.hasModifierProperty(PsiModifier.PRIVATE)) {
return PRIVATE;
}
if (modifierList.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) {
return PACKAGE_LOCAL;
}
if (modifierList.hasModifierProperty(PsiModifier.PROTECTED)) {
return PROTECTED;
}
return PUBLIC;
}
public boolean isWeaker(@NotNull AccessModifier other) {
return ordinal() < other.ordinal();
}
@Override
public String toString() {
return PsiBundle.visibilityPresentation(toPsiModifier());
}
@NotNull
public static List<AccessModifier> getAvailableModifiers(PsiMember member) {
if (member == null) return Collections.emptyList();
PsiClass containingClass = member.getContainingClass();
if (member instanceof PsiField) {
if (member instanceof PsiEnumConstant || containingClass == null || containingClass.isInterface()) return Collections.emptyList();
return ALL_MODIFIERS;
}
if (member instanceof PsiMethod) {
PsiMethod method = (PsiMethod)member;
if (containingClass == null || containingClass.isEnum() && method.isConstructor()) return Collections.emptyList();
if (JavaPsiRecordUtil.getRecordComponentForAccessor(method) != null ||
JavaPsiRecordUtil.isCompactConstructor(method) ||
JavaPsiRecordUtil.isExplicitCanonicalConstructor(method) ||
method instanceof LightRecordCanonicalConstructor) {
return Collections.singletonList(PUBLIC);
}
if (containingClass.isInterface()) {
if (PsiUtil.isLanguageLevel9OrHigher(member)) {
return PUBLIC_PRIVATE;
}
return Collections.singletonList(PUBLIC);
}
AccessModifier minAccess = getMinAccess(method);
if (minAccess != PRIVATE) {
return ContainerUtil.filter(ALL_MODIFIERS, mod -> mod.compareTo(minAccess) <= 0);
}
return ALL_MODIFIERS;
}
if (member instanceof PsiClass) {
if (PsiUtil.isLocalOrAnonymousClass((PsiClass)member)) return Collections.emptyList();
if (containingClass == null) return PUBLIC_PACKAGE;
return ALL_MODIFIERS;
}
return Collections.emptyList();
}
@NotNull
private static AccessModifier getMinAccess(PsiMethod method) {
if (method.isConstructor() || method.hasModifierProperty(PsiModifier.STATIC)) return PRIVATE;
HierarchicalMethodSignature signature = method.getHierarchicalMethodSignature();
AccessModifier lowest = PRIVATE;
for (HierarchicalMethodSignature superSignature : signature.getSuperSignatures()) {
PsiMethod superMethod = superSignature.getMethod();
AccessModifier current = fromModifierList(superMethod.getModifierList());
if (!current.isWeaker(lowest)) continue;
if (method.hasModifierProperty(PsiModifier.ABSTRACT) && !MethodSignatureUtil.isSuperMethod(superMethod, method)) continue;
if (!PsiUtil.isAccessible(method.getProject(), superMethod, method, null)) continue;
lowest = current;
if (lowest == PUBLIC) {
break;
}
}
return lowest;
}
}
@@ -0,0 +1,16 @@
/**
* Record javadoc
* @param x x
*/
record Rec(int x) {
public R<caret>ec(int x) {
this.x = x;
}
}
class Use {
public static void main(String[] args) {
Rec rec = new Rec(1);
System.out.println(rec.x());
}
}
@@ -0,0 +1,17 @@
/**
* Record javadoc
* @param x x
* @param y
*/
record Rec(int x, int y) {
public Rec(int x, int y) {
this.x = x;
}
}
class Use {
public static void main(String[] args) {
Rec rec = new Rec(1, );
System.out.println(rec.x());
}
}
@@ -0,0 +1,25 @@
/**
* @param x x
* @param y y
* @param z z
*/
record Rec(int x, int y, int z) {
public R<caret>ec(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
public int x() {return x;}
public int y() {return y;}
public int z() {return z;}
}
class Use {
public static void main(String[] args) {
Rec rec = new Rec(1, 2, 3);
System.out.println(rec.x());
System.out.println(rec.y());
System.out.println(rec.z());
}
}
@@ -0,0 +1,25 @@
/**
* @param y y
* @param z z
* @param x x
*/
record Rec(int y, int z, int x) {
public Rec(int y, int z, int x) {
this.y = y;
this.z = z;
this.x = x;
}
public int y() {return y;}
public int z() {return z;}
public int x() {return x;}
}
class Use {
public static void main(String[] args) {
Rec rec = new Rec(1, 2, 3);
System.out.println(rec.y());
System.out.println(rec.z());
System.out.println(rec.x());
}
}
@@ -0,0 +1,32 @@
/**
* Record javadoc
* @param x x
* @param y y
* @param z z
*/
record Rec(int x, int y, int z) {
/**
* Constructor javadoc
* @param x x
* @param y y
* @param z z
*/
public R<caret>ec(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
public int x() {return x;}
public int y() {return y;}
public int z() {return z;}
}
class Use {
public static void main(String[] args) {
Rec rec = new Rec(1, 2, 3);
System.out.println(rec.x());
System.out.println(rec.y());
System.out.println(rec.z());
}
}
@@ -0,0 +1,32 @@
/**
* Record javadoc
* @param y y
* @param z z
* @param x x
*/
record Rec(int y, int z, int x) {
/**
* Constructor javadoc
* @param y y
* @param z z
* @param x x
*/
public Rec(int y, int z, int x) {
this.x = x;
this.y = y;
this.z = z;
}
public int x() {return x;}
public int y() {return y;}
public int z() {return z;}
}
class Use {
public static void main(String[] args) {
Rec rec = new Rec(2, 3, 1);
System.out.println(rec.x());
System.out.println(rec.y());
System.out.println(rec.z());
}
}
@@ -0,0 +1,17 @@
/**
* @param x x
* @param y y
* @param z z
*/
record R<caret>ec(int x, int y, int z) {
public int y() {
return this.y;
}
}
class Use {
public static void main(String[] args) {
Rec rec = new Rec(1, 2, 3);
System.out.println(rec.y());
}
}
@@ -0,0 +1,16 @@
/**
* @param yyy y
*
*/
record Rec(long yyy) {
public long yyy() {
return this.yyy;
}
}
class Use {
public static void main(String[] args) {
Rec rec = new Rec(2);
System.out.println(rec.yyy());
}
}
@@ -5,6 +5,7 @@ import com.intellij.JavaTestUtil;
import com.intellij.codeInsight.TargetElementUtil;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.*;
import com.intellij.psi.util.JavaPsiRecordUtil;
import com.intellij.refactoring.changeSignature.ChangeSignatureProcessor;
import com.intellij.refactoring.changeSignature.JavaThrownExceptionInfo;
import com.intellij.refactoring.changeSignature.ParameterInfoImpl;
@@ -15,8 +16,6 @@ import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.refactoring.changeSignature.ParameterInfo.NEW_PARAMETER;
public abstract class ChangeSignatureBaseTest extends LightRefactoringTestCase {
protected PsiElementFactory myFactory;
@@ -105,6 +104,9 @@ public abstract class ChangeSignatureBaseTest extends LightRefactoringTestCase {
String basePath = getRelativePath() + getTestName(false);
configureByFile(basePath + ".java");
PsiElement targetElement = TargetElementUtil.findTargetElement(getEditor(), TargetElementUtil.ELEMENT_NAME_ACCEPTED);
if (targetElement instanceof PsiClass) {
targetElement = JavaPsiRecordUtil.findCanonicalConstructor((PsiClass)targetElement);
}
assertTrue("<caret> is not on method name", targetElement instanceof PsiMethod);
PsiMethod method = (PsiMethod)targetElement;
PsiType newType = newReturnType != null ? myFactory.createTypeFromText(newReturnType, method) : method.getReturnType();
@@ -11,13 +11,22 @@ import com.intellij.refactoring.changeSignature.JavaThrownExceptionInfo;
import com.intellij.refactoring.changeSignature.ParameterInfoImpl;
import com.intellij.refactoring.changeSignature.ThrownExceptionInfo;
import com.intellij.refactoring.util.CanonicalTypes;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
import static com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase.JAVA_14;
/**
* @author dsl
*/
public class ChangeSignatureTest extends ChangeSignatureBaseTest {
@Override
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
return JAVA_14;
}
private CommonCodeStyleSettings getJavaSettings() {
return getCurrentCodeStyleSettings().getCommonSettings(JavaLanguage.INSTANCE);
@@ -499,7 +508,7 @@ public class ChangeSignatureTest extends ChangeSignatureBaseTest {
//String[] ex = {"@TA(42) IllegalArgumentException", "java.lang.@TA(43) IllegalStateException"};
//doTest("java.util.@TA(0) List<@TA(1) C.@TA(1) Inner>", ps, ex, false);
String[] ps = {"@TA(2) int @TA(3) []", "@TA(4) List<@TA(5) Class<@TA(6) ?>>", "@TA(7) String @TA(8) ..."};
String[] ex = {};
String[] ex = ArrayUtil.EMPTY_STRING_ARRAY;
doTest("@TA(0) List<@TA(1) Inner>", ps, ex, false);
}
@@ -514,6 +523,43 @@ public class ChangeSignatureTest extends ChangeSignatureBaseTest {
};
doTest(null, null, null, genParams, new SimpleExceptionsGen(), false, false);
}
public void testRecordHeaderDeleteRename() {
doTest(null, null, null, method -> {
return new ParameterInfoImpl[]{
ParameterInfoImpl.create(1).withName("yyy").withType(PsiType.LONG)
};
}, false);
}
public void testRecordCanonicalConstructorRename() {
doTest(null, null, null, method -> {
return new ParameterInfoImpl[]{
ParameterInfoImpl.create(0).withName("y").withType(PsiType.INT),
ParameterInfoImpl.create(1).withName("z").withType(PsiType.INT),
ParameterInfoImpl.create(2).withName("x").withType(PsiType.INT)
};
}, false);
}
public void testRecordCanonicalConstructorReorder() {
doTest(null, null, null, method -> {
return new ParameterInfoImpl[]{
ParameterInfoImpl.create(1).withName("y").withType(PsiType.INT),
ParameterInfoImpl.create(2).withName("z").withType(PsiType.INT),
ParameterInfoImpl.create(0).withName("x").withType(PsiType.INT)
};
}, false);
}
public void testRecordCanonicalConstructorAddParameter() {
doTest(null, null, null, method -> {
return new ParameterInfoImpl[]{
ParameterInfoImpl.create(0).withName("x").withType(PsiType.INT),
ParameterInfoImpl.create(-1).withName("y").withType(PsiType.INT)
};
}, false);
}
/* workers */
}
@@ -1,99 +0,0 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.siyeh.ipp.modifiers;
import com.intellij.psi.PsiKeyword;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.PsiModifierList;
import com.intellij.psi.PsiModifierListOwner;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Java access modifiers: public, protected, private, package-local
*/
enum AccessModifier {
PUBLIC(PsiModifier.PUBLIC), PROTECTED(PsiModifier.PROTECTED), PACKAGE_LOCAL(PsiModifier.PACKAGE_LOCAL), PRIVATE(PsiModifier.PRIVATE);
@NotNull @PsiModifier.ModifierConstant
private final String myModifier;
AccessModifier(@PsiModifier.ModifierConstant @NotNull String modifier) {
myModifier = modifier;
}
/**
* @return a {@link PsiModifier} string constant which corresponds to this access modifier.
*/
@NotNull @PsiModifier.ModifierConstant
public String toPsiModifier() {
return myModifier;
}
/**
* Checks whether given modifier owner has this access modifier (probably implicit)
* @param owner element to check (e.g. class member)
* @return true if it has current modifier
*/
public boolean hasModifier(@NotNull PsiModifierListOwner owner) {
return owner.hasModifierProperty(toPsiModifier());
}
/**
* Returns an {@link AccessModifier} which corresponds to the given keyword;
* null if supplied keyword is null or don't correspond to access modifier
* @param keyword keyword to convert to access modifier
* @return a corresponding access modifier
*/
@Contract(value = "null -> null", pure = true)
@Nullable
public static AccessModifier fromKeyword(@Nullable PsiKeyword keyword) {
return keyword == null ? null : fromPsiModifier(keyword.getText());
}
/**
* Returns an {@link AccessModifier} which corresponds to the given String constant declared in
* {@link PsiModifier} class.
* @param modifier a modifier string
* @return an access modifier or null if supplied string doesn't correspond to any access modifier.
*/
@Contract(value = "null -> null", pure = true)
@Nullable
public static AccessModifier fromPsiModifier(@Nullable String modifier) {
if (modifier == null) return null;
switch (modifier) {
case PsiModifier.PUBLIC:
return PUBLIC;
case PsiModifier.PROTECTED:
return PROTECTED;
case PsiModifier.PACKAGE_LOCAL:
return PACKAGE_LOCAL;
case PsiModifier.PRIVATE:
return PRIVATE;
default:
return null;
}
}
public static AccessModifier fromModifierList(@NotNull PsiModifierList modifierList) {
if (modifierList.hasModifierProperty(PsiModifier.PRIVATE)) {
return PRIVATE;
}
if (modifierList.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) {
return PACKAGE_LOCAL;
}
if (modifierList.hasModifierProperty(PsiModifier.PROTECTED)) {
return PROTECTED;
}
return PUBLIC;
}
public boolean isWeaker(@NotNull AccessModifier other) {
return ordinal() < other.ordinal();
}
@Override
public String toString() {
return this == PACKAGE_LOCAL ? "package-private" : toPsiModifier();
}
}
@@ -38,10 +38,8 @@ import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.JavaPsiRecordUtil;
import com.intellij.psi.util.MethodSignatureUtil;
import com.intellij.psi.util.AccessModifier;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.changeSignature.ChangeSignatureProcessor;
@@ -63,14 +61,9 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ChangeModifierIntention extends BaseElementAtCaretIntentionAction {
private static final List<AccessModifier> ALL_MODIFIERS = ContainerUtil.immutableList(AccessModifier.values());
private static final List<AccessModifier> PUBLIC_PRIVATE = ContainerUtil.immutableList(AccessModifier.PUBLIC, AccessModifier.PRIVATE);
private static final List<AccessModifier> PUBLIC_PACKAGE =
ContainerUtil.immutableList(AccessModifier.PUBLIC, AccessModifier.PACKAGE_LOCAL);
private final boolean myErrorFix;
private AccessModifier myTarget;
@@ -92,7 +85,7 @@ public class ChangeModifierIntention extends BaseElementAtCaretIntentionAction {
if (!(member instanceof PsiNameIdentifierOwner)) return false;
PsiElement identifier = ((PsiNameIdentifierOwner)member).getNameIdentifier();
if (identifier == null || identifier.getTextRange().getEndOffset() <= element.getTextRange().getStartOffset()) return false;
List<AccessModifier> modifiers = new ArrayList<>(getAvailableModifiers(member));
List<AccessModifier> modifiers = new ArrayList<>(AccessModifier.getAvailableModifiers(member));
if (modifiers.isEmpty()) return false;
if (!myErrorFix && modifiers.stream().noneMatch(mod -> mod.hasModifier(member))) return false;
modifiers.removeIf(mod -> mod.hasModifier(member));
@@ -120,61 +113,6 @@ public class ChangeModifierIntention extends BaseElementAtCaretIntentionAction {
}
}
@NotNull
private static List<AccessModifier> getAvailableModifiers(PsiMember member) {
if (member == null) return Collections.emptyList();
PsiClass containingClass = member.getContainingClass();
if (member instanceof PsiField) {
if (member instanceof PsiEnumConstant || containingClass == null || containingClass.isInterface()) return Collections.emptyList();
return ALL_MODIFIERS;
}
if (member instanceof PsiMethod) {
PsiMethod method = (PsiMethod)member;
if (containingClass == null || containingClass.isEnum() && method.isConstructor()) return Collections.emptyList();
if (JavaPsiRecordUtil.getRecordComponentForAccessor(method) != null ||
JavaPsiRecordUtil.isCompactConstructor(method) ||
JavaPsiRecordUtil.isExplicitCanonicalConstructor(method)) {
return Collections.singletonList(AccessModifier.PUBLIC);
}
if (containingClass.isInterface()) {
if (PsiUtil.isLanguageLevel9OrHigher(member)) {
return PUBLIC_PRIVATE;
}
return Collections.singletonList(AccessModifier.PUBLIC);
}
AccessModifier minAccess = getMinAccess(method);
if (minAccess != AccessModifier.PRIVATE) {
return ContainerUtil.filter(ALL_MODIFIERS, mod -> mod.compareTo(minAccess) <= 0);
}
return ALL_MODIFIERS;
}
if (member instanceof PsiClass) {
if (PsiUtil.isLocalOrAnonymousClass((PsiClass)member)) return Collections.emptyList();
if (containingClass == null) return PUBLIC_PACKAGE;
return ALL_MODIFIERS;
}
return Collections.emptyList();
}
@NotNull
private static AccessModifier getMinAccess(PsiMethod method) {
if (method.isConstructor() || method.hasModifierProperty(PsiModifier.STATIC)) return AccessModifier.PRIVATE;
HierarchicalMethodSignature signature = method.getHierarchicalMethodSignature();
AccessModifier lowest = AccessModifier.PRIVATE;
for (HierarchicalMethodSignature superSignature : signature.getSuperSignatures()) {
PsiMethod superMethod = superSignature.getMethod();
AccessModifier current = AccessModifier.fromModifierList(superMethod.getModifierList());
if (!current.isWeaker(lowest)) continue;
if (method.hasModifierProperty(PsiModifier.ABSTRACT) && !MethodSignatureUtil.isSuperMethod(superMethod, method)) continue;
if (!PsiUtil.isAccessible(method.getProject(), superMethod, method, null)) continue;
lowest = current;
if (lowest == AccessModifier.PUBLIC) {
break;
}
}
return lowest;
}
@Nullable
@Override
public PsiElement getElementToMakeWritable(@NotNull PsiFile currentFile) {
@@ -192,7 +130,7 @@ public class ChangeModifierIntention extends BaseElementAtCaretIntentionAction {
if (member == null) return;
PsiFile file = member.getContainingFile();
if (file == null) return;
List<AccessModifier> modifiers = getAvailableModifiers(member);
List<AccessModifier> modifiers = AccessModifier.getAvailableModifiers(member);
if (modifiers.isEmpty()) return;
AccessModifier target = myTarget;
if (modifiers.contains(target)) {
@@ -356,7 +294,7 @@ public class ChangeModifierIntention extends BaseElementAtCaretIntentionAction {
@Nullable
private static PsiKeyword getAnchorKeyword(PsiModifierList modifierList) {
for (PsiElement child = modifierList.getFirstChild(); child != null; child = child.getNextSibling()) {
if (ALL_MODIFIERS.contains(AccessModifier.fromKeyword(ObjectUtils.tryCast(child, PsiKeyword.class)))) {
if (AccessModifier.ALL_MODIFIERS.contains(AccessModifier.fromKeyword(ObjectUtils.tryCast(child, PsiKeyword.class)))) {
return (PsiKeyword)child;
}
}