Java: allow to introduce a constant as a field of an inner, local or anonymous class (IDEA-341923)

for Introduce Constant refactoring. Also fixes IDEABKL-3269 when introducing a compile-time constant from an expression.

GitOrigin-RevId: 169e7ee8e14306a265948537b62746d9a26c0298
This commit is contained in:
Bas Leijdekkers
2023-12-28 17:40:25 +00:00
committed by intellij-monorepo-bot
parent cf79dfa709
commit 0dbc31a8b6
11 changed files with 108 additions and 41 deletions
@@ -295,14 +295,12 @@ public abstract class BaseExpressionToFieldHandler extends IntroduceHandlerBase
}
public @Nullable PsiClass getParentClass(@NotNull PsiExpression initializerExpression) {
PsiElement element = initializerExpression.getUserData(ElementToWorkOn.PARENT);
if (element == null) element = initializerExpression.getParent();
PsiElement parent = element;
while (parent != null) {
if (parent instanceof PsiClass && (!myIsConstant || LocalToFieldHandler.mayContainConstants((PsiClass)parent))) {
return (PsiClass)parent;
}
parent = PsiTreeUtil.getParentOfType(parent, PsiClass.class);
boolean compileTimeConstant = LocalToFieldHandler.isCompileTimeConstant(initializerExpression, initializerExpression.getType());
PsiElement parent = initializerExpression.getUserData(ElementToWorkOn.PARENT);
PsiClass aClass = PsiTreeUtil.getParentOfType((parent == null) ? initializerExpression : parent, PsiClass.class);
while (aClass != null) {
if (!myIsConstant || compileTimeConstant || LocalToFieldHandler.isStaticFieldAllowed(aClass)) return aClass;
aClass = PsiTreeUtil.getParentOfType(aClass, PsiClass.class);
}
return null;
}
@@ -16,7 +16,6 @@ import com.intellij.openapi.wm.WindowManager;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.HelpID;
import com.intellij.refactoring.JavaRefactoringSettings;
import com.intellij.refactoring.PreviewableRefactoringActionHandler;
@@ -208,7 +207,7 @@ public class IntroduceConstantHandler extends BaseExpressionToFieldHandler imple
}
@Override
public AbstractInplaceIntroducer getInplaceIntroducer() {
public InplaceIntroduceConstantPopup getInplaceIntroducer() {
return myInplaceIntroduceConstantPopup;
}
@@ -217,25 +216,6 @@ public class IntroduceConstantHandler extends BaseExpressionToFieldHandler imple
return new ExpressionOccurrenceManager(selectedExpr, parentClass, null);
}
@Override
public PsiClass getParentClass(@NotNull PsiExpression initializerExpression) {
final PsiType type = initializerExpression.getType();
if (PsiUtil.isConstantExpression(initializerExpression) &&
(type instanceof PsiPrimitiveType || type != null && type.equalsToText(CommonClassNames.JAVA_LANG_STRING))) {
return super.getParentClass(initializerExpression);
}
PsiElement parent = initializerExpression.getUserData(ElementToWorkOn.PARENT);
if (parent == null) parent = initializerExpression;
PsiClass aClass = PsiTreeUtil.getParentOfType(parent, PsiClass.class);
while (aClass != null) {
if (LocalToFieldHandler.mayContainConstants(aClass)) return aClass;
aClass = PsiTreeUtil.getParentOfType(aClass, PsiClass.class);
}
return null;
}
@Override
protected boolean accept(ElementToWorkOn elementToWorkOn) {
final PsiExpression expr = elementToWorkOn.getExpression();
@@ -4,6 +4,7 @@ package com.intellij.refactoring.introduceField;
import com.intellij.codeInsight.ChangeContextUtil;
import com.intellij.codeInsight.CodeInsightUtil;
import com.intellij.codeInsight.TestFrameworks;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightingFeature;
import com.intellij.codeInsight.daemon.impl.quickfix.AnonymousTargetClassPreselectionUtil;
import com.intellij.codeInsight.generation.GenerateMembersUtil;
import com.intellij.codeInsight.navigation.PsiTargetNavigator;
@@ -32,6 +33,7 @@ import com.intellij.util.IncorrectOperationException;
import com.intellij.util.VisibilityUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
@@ -55,12 +57,11 @@ public abstract class LocalToFieldHandler {
public boolean convertLocalToField(final PsiLocalVariable local, final Editor editor) {
boolean tempIsStatic = myIsConstant;
PsiElement parent = local.getParent();
PsiType localType = local.getType();
boolean runtimeConstant = (localType instanceof PsiPrimitiveType || localType.equalsToText(CommonClassNames.JAVA_LANG_STRING)) && PsiUtil.isConstantExpression(local.getInitializer());
boolean compileTimeConstant = isCompileTimeConstant(local.getInitializer(), local.getType());
final List<PsiClass> classes = new ArrayList<>();
PsiElement parent = local.getParent();
while (parent != null && parent.getContainingFile() != null) {
if (parent instanceof PsiClass && (runtimeConstant || !myIsConstant || mayContainConstants((PsiClass) parent))) {
if (parent instanceof PsiClass && (compileTimeConstant || !myIsConstant || isStaticFieldAllowed((PsiClass) parent))) {
classes.add((PsiClass)parent);
}
if (parent instanceof PsiFile && FileTypeUtils.isInServerPageFile(parent)) {
@@ -100,9 +101,31 @@ public abstract class LocalToFieldHandler {
return true;
}
public static boolean isCompileTimeConstant(@Nullable PsiExpression initializer, @Nullable PsiType type) {
return type != null && (type instanceof PsiPrimitiveType || type.equalsToText(CommonClassNames.JAVA_LANG_STRING)) &&
PsiUtil.isConstantExpression(initializer);
}
/**
* @deprecated Use {@link #isStaticFieldAllowed(PsiClass)} instead.
*/
@Deprecated
public static boolean mayContainConstants(@NotNull PsiClass aClass) {
return aClass.hasModifierProperty(PsiModifier.STATIC) ||
aClass.getParent() instanceof PsiJavaFile;
return isStaticFieldAllowed(aClass);
}
/**
* Checks if adding a static field is allowed in the specified class.
* Before Java 16 this was only allowed for inner, local and anonymous classes,
* if the static field was a compile-time constant.
* @param aClass the class to check
* @return true, if adding a non-compile-time constants static field to the specified class is allowed. False otherwise.
*/
public static boolean isStaticFieldAllowed(@NotNull PsiClass aClass) {
if (HighlightingFeature.INNER_STATICS.isAvailable(aClass)) {
return true;
}
return aClass.hasModifierProperty(PsiModifier.STATIC) || aClass.getParent() instanceof PsiJavaFile;
}
protected int getChosenClassIndex(List<PsiClass> classes) {
@@ -0,0 +1,7 @@
class Foo {
class Bar {
void foo() {
String str = <caret>"42";
}
}
}
@@ -0,0 +1,9 @@
class Foo {
class Bar {
public static final String xxx = "42";
void foo() {
String str = xxx;
}
}
}
@@ -1,9 +1,11 @@
import org.jetbrains.annotations.NonNls;
class Foo {
class Bar {
public static final String xxx = "42";
@NonNls
private static final String xxx = "42";
void foo() {
String str = xxx;
}
}
}
}
@@ -0,0 +1,10 @@
class X {
void x() {
new Object() {
void x() {
System.out.println(<selection>new <caret>Object()</selection>);
}
};
}
}
@@ -0,0 +1,10 @@
class X {
void x() {
new Object() {
void x() {
System.out.println(<selection>new <caret>Object()</selection>);
}
};
}
}
@@ -0,0 +1,12 @@
class X {
public static final Object xxx = new Object();
void x() {
new Object() {
void x() {
System.out.println(xxx);
}
};
}
}
@@ -0,0 +1,12 @@
class X {
void x() {
new Object() {
public static final Object xxx = new Object();
void x() {
System.out.println(xxx);
}
};
}
}
@@ -37,7 +37,7 @@ public class IntroduceConstantTest extends LightJavaCodeInsightTestCase {
public void testNonStaticContainerForCompileTimeConstant() {
configureByFile(BASE_PATH + getTestName(false) + ".java");
PsiLocalVariable local = PsiTreeUtil.getParentOfType(getFile().findElementAt(getEditor().getCaretModel().getOffset()), PsiLocalVariable.class);
new MockLocalToFieldHandler(getProject(), true, false){
new MockLocalToFieldHandler(getProject(), true, false) {
@Override
protected int getChosenClassIndex(List<PsiClass> classes) {
return 0;
@@ -47,6 +47,10 @@ public class IntroduceConstantTest extends LightJavaCodeInsightTestCase {
checkResultByFile(BASE_PATH + getTestName(false) + "_after.java");
}
public void testNonStaticContainerForCompileTimeConstant2() { doTest(); }
public void testStaticFieldInAnonymous() { doTest(); }
public void testStaticFieldInAnonymousJava8() { doTest(); }
private void doTest(boolean makeEnumConstant) {
configureByFile(BASE_PATH + getTestName(false) + ".java");
PsiLocalVariable local = PsiTreeUtil.getParentOfType(getFile().findElementAt(getEditor().getCaretModel().getOffset()), PsiLocalVariable.class);
@@ -60,7 +64,7 @@ public class IntroduceConstantTest extends LightJavaCodeInsightTestCase {
public void testUnresolvedReferenceInEnum() {
configureByFile(BASE_PATH + getTestName(false) + ".java");
new MockIntroduceConstantHandler(null).invoke(getProject(), getEditor(), getFile(), null);
new MockIntroduceConstantHandler(((PsiJavaFile)getFile()).getClasses()[0]).invoke(getProject(), getEditor(), getFile(), null);
checkResultByFile(BASE_PATH + getTestName(false) + "_after.java");
}
@@ -70,7 +74,7 @@ public class IntroduceConstantTest extends LightJavaCodeInsightTestCase {
public void testFromEnumConstantInitializer2() {
configureByFile(BASE_PATH + getTestName(false) + ".java");
new MockIntroduceConstantHandler(null).invoke(getProject(), getEditor(), getFile(), null);
new MockIntroduceConstantHandler(((PsiJavaFile)getFile()).getClasses()[0]).invoke(getProject(), getEditor(), getFile(), null);
checkResultByFile(BASE_PATH + getTestName(false) + "_after.java");
}