[java] Support magic constants in enum constructor call (inspection+completion)

Fixes IDEA-274956 MagicConstants don't work in enum constructors
Also support non-public accessible constants in valuesFromClass

GitOrigin-RevId: 32daea7b636573cb599c21d634b26f61a91af69d
This commit is contained in:
Tagir Valeev
2021-09-22 08:47:12 +00:00
committed by intellij-monorepo-bot
parent ce48b06ff3
commit e0e69c4944
7 changed files with 119 additions and 26 deletions
@@ -58,7 +58,7 @@ public final class MagicCompletionContributor extends CompletionContributor impl
public static MagicConstantUtils.AllowedValues getAllowedValues(@NotNull PsiElement pos) {
MagicConstantUtils.AllowedValues allowedValues = null;
for (Pair<PsiModifierListOwner, PsiType> pair : getMembersWithAllowedValues(pos)) {
MagicConstantUtils.AllowedValues values = MagicConstantUtils.getAllowedValues(pair.first, pair.second);
MagicConstantUtils.AllowedValues values = MagicConstantUtils.getAllowedValues(pair.first, pair.second, pos);
if (values == null) continue;
if (allowedValues == null) {
allowedValues = values;
@@ -89,15 +89,10 @@ public final class MagicCompletionContributor extends CompletionContributor impl
Set<Pair<PsiModifierListOwner, PsiType>> result = new HashSet<>();
if (IN_METHOD_CALL_ARGUMENT.accepts(pos)) {
PsiCall call = PsiTreeUtil.getParentOfType(pos, PsiCall.class);
if (!(call instanceof PsiExpression)) return Collections.emptyList();
PsiType type = ((PsiExpression)call).getType();
if (call == null) return Collections.emptyList();
PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(call.getProject()).getResolveHelper();
JavaResolveResult[] methods = call instanceof PsiMethodCallExpression
? ((PsiMethodCallExpression)call).getMethodExpression().multiResolve(true)
: call instanceof PsiNewExpression && type instanceof PsiClassType
? resolveHelper.multiResolveConstructor((PsiClassType)type, call.getArgumentList(), call)
: JavaResolveResult.EMPTY_ARRAY;
JavaResolveResult[] methods = getMethodCandidates(call, resolveHelper);
for (JavaResolveResult resolveResult : methods) {
PsiElement element = resolveResult.getElement();
if (!(element instanceof PsiMethod)) return Collections.emptyList();
@@ -180,6 +175,26 @@ public final class MagicCompletionContributor extends CompletionContributor impl
return new ArrayList<>(result);
}
private static @NotNull JavaResolveResult @NotNull [] getMethodCandidates(PsiCall call, PsiResolveHelper resolveHelper) {
if (call instanceof PsiMethodCallExpression) {
return ((PsiMethodCallExpression)call).getMethodExpression().multiResolve(true);
}
if (call instanceof PsiNewExpression) {
PsiType type = ((PsiExpression)call).getType();
PsiExpressionList argumentList = call.getArgumentList();
if (type instanceof PsiClassType && argumentList != null) {
return resolveHelper.multiResolveConstructor((PsiClassType)type, argumentList, call);
}
}
if (call instanceof PsiEnumConstant) {
JavaResolveResult result = call.resolveMethodGenerics();
if (result != JavaResolveResult.EMPTY) {
return new JavaResolveResult[]{result};
}
}
return JavaResolveResult.EMPTY_ARRAY;
}
private static void addCompletionVariants(@NotNull final CompletionParameters parameters,
@NotNull final CompletionResultSet result,
PsiElement pos,
@@ -80,6 +80,11 @@ public final class MagicConstantInspection extends AbstractBaseJavaLocalInspecti
}
}
@Override
public void visitEnumConstant(PsiEnumConstant enumConstant) {
checkCall(enumConstant, holder);
}
@Override
public void visitCallExpression(PsiCallExpression callExpression) {
checkCall(callExpression, holder);
@@ -203,7 +208,7 @@ public final class MagicConstantInspection extends AbstractBaseJavaLocalInspecti
@NotNull PsiModifierListOwner owner,
@Nullable PsiType type,
@NotNull ProblemsHolder holder) {
AllowedValues allowed = MagicConstantUtils.getAllowedValues(owner, type);
AllowedValues allowed = MagicConstantUtils.getAllowedValues(owner, type, expression);
checkExpression(expression, holder, allowed);
}
@@ -218,7 +223,7 @@ public final class MagicConstantInspection extends AbstractBaseJavaLocalInspecti
}
}
private static void checkCall(@NotNull PsiCallExpression methodCall, @NotNull ProblemsHolder holder) {
private static void checkCall(@NotNull PsiCall methodCall, @NotNull ProblemsHolder holder) {
PsiExpressionList argumentList = methodCall.getArgumentList();
if (argumentList == null) return;
PsiMethod method = methodCall.resolveMethod();
@@ -233,7 +238,7 @@ public final class MagicConstantInspection extends AbstractBaseJavaLocalInspecti
type = ((PsiEllipsisType)type).getComponentType();
stopArg = arguments.length - 1;
}
AllowedValues values = MagicConstantUtils.getAllowedValues(parameter, type);
AllowedValues values = MagicConstantUtils.getAllowedValues(parameter, type, methodCall);
if (values == null) continue;
if (i >= arguments.length) break;
for (int j = i; j <= stopArg; j++) {
@@ -432,7 +437,7 @@ public final class MagicConstantInspection extends AbstractBaseJavaLocalInspecti
}
if (allowedForRef == null && resolved instanceof PsiModifierListOwner) {
allowedForRef = MagicConstantUtils.getAllowedValues((PsiModifierListOwner)resolved, getType((PsiModifierListOwner)resolved));
allowedForRef = MagicConstantUtils.getAllowedValues((PsiModifierListOwner)resolved, getType((PsiModifierListOwner)resolved), expression);
}
if (allowedForRef != null && allowedForRef.isSubsetOf(allowedValues, manager)) {
return true;
@@ -24,7 +24,8 @@ import java.util.*;
public final class MagicConstantUtils {
private static AllowedValues getAllowedValuesFromMagic(@NotNull PsiType type,
@NotNull PsiAnnotation magic,
@NotNull PsiManager manager) {
@NotNull PsiManager manager,
@Nullable PsiElement context) {
PsiAnnotationMemberValue[] allowedValues = PsiAnnotationMemberValue.EMPTY_ARRAY;
boolean values = false;
boolean flags = false;
@@ -62,12 +63,12 @@ public final class MagicConstantUtils {
return null; //other types not supported
}
PsiAnnotationMemberValue[] valuesFromClass = readFromClass("valuesFromClass", magic, type, manager);
PsiAnnotationMemberValue[] valuesFromClass = readFromClass("valuesFromClass", magic, type, manager, context);
if (valuesFromClass != null) {
allowedValues = ArrayUtil.mergeArrays(allowedValues, valuesFromClass, PsiAnnotationMemberValue.ARRAY_FACTORY);
values = true;
}
PsiAnnotationMemberValue[] flagsFromClass = readFromClass("flagsFromClass", magic, type, manager);
PsiAnnotationMemberValue[] flagsFromClass = readFromClass("flagsFromClass", magic, type, manager, context);
if (flagsFromClass != null) {
allowedValues = ArrayUtil.mergeArrays(allowedValues, flagsFromClass, PsiAnnotationMemberValue.ARRAY_FACTORY);
flags = true;
@@ -85,19 +86,29 @@ public final class MagicConstantUtils {
private static PsiAnnotationMemberValue[] readFromClass(@NonNls @NotNull String attributeName,
@NotNull PsiAnnotation magic,
@NotNull PsiType type,
@NotNull PsiManager manager) {
@NotNull PsiManager manager,
@Nullable PsiElement context) {
PsiAnnotationMemberValue fromClassAttr = magic.findAttributeValue(attributeName);
PsiType fromClassType = fromClassAttr instanceof PsiClassObjectAccessExpression ? ((PsiClassObjectAccessExpression)fromClassAttr).getOperand().getType() : null;
PsiType fromClassType = fromClassAttr instanceof PsiClassObjectAccessExpression
? ((PsiClassObjectAccessExpression)fromClassAttr).getOperand().getType()
: null;
PsiClass fromClass = fromClassType instanceof PsiClassType ? ((PsiClassType)fromClassType).resolve() : null;
if (fromClass == null) return null;
String fqn = fromClass.getQualifiedName();
if (fqn == null) return null;
List<PsiAnnotationMemberValue> constants = new ArrayList<>();
for (PsiField field : fromClass.getFields()) {
if (!field.hasModifierProperty(PsiModifier.PUBLIC) || !field.hasModifierProperty(PsiModifier.STATIC) || !field.hasModifierProperty(PsiModifier.FINAL)) continue;
if (!field.hasModifierProperty(PsiModifier.STATIC) || !field.hasModifierProperty(PsiModifier.FINAL)) continue;
if (!field.hasModifierProperty(PsiModifier.PUBLIC)) {
if (context == null ||
!JavaPsiFacade.getInstance(manager.getProject()).getResolveHelper().isAccessible(field, context, null)) {
continue;
}
}
PsiType fieldType = field.getType();
if (!Comparing.equal(fieldType, type)) continue;
PsiAssignmentExpression e = (PsiAssignmentExpression)JavaPsiFacade.getElementFactory(manager.getProject()).createExpressionFromText("x="+fqn + "." + field.getName(), field);
PsiAssignmentExpression e = (PsiAssignmentExpression)JavaPsiFacade.getElementFactory(manager.getProject())
.createExpressionFromText("x=" + fqn + "." + field.getName(), field);
PsiReferenceExpression refToField = (PsiReferenceExpression)e.getRExpression();
constants.add(refToField);
}
@@ -106,17 +117,37 @@ public final class MagicConstantUtils {
return constants.toArray(PsiAnnotationMemberValue.EMPTY_ARRAY);
}
/**
* @deprecated used {@link #getAllowedValues(PsiModifierListOwner, PsiType, PsiElement)}
*/
@Deprecated
@Nullable
public static AllowedValues getAllowedValues(@NotNull PsiModifierListOwner element, @Nullable PsiType type) {
return getAllowedValues(element, type, null);
}
/**
* @param element element with possible MagicConstant annotation
* @param type element type
* @param context context where annotation is applied (to check the accessibility of magic constant)
* @return possible allowed values to be used instead of constant literal; null if no MagicConstant annotation found
*/
@Nullable
static AllowedValues getAllowedValues(@NotNull PsiModifierListOwner element, @Nullable PsiType type, @Nullable Set<? super PsiClass> visited) {
public static AllowedValues getAllowedValues(@NotNull PsiModifierListOwner element,
@Nullable PsiType type,
@Nullable PsiElement context) {
return getAllowedValues(element, type, context, null);
}
@Nullable
static AllowedValues getAllowedValues(@NotNull PsiModifierListOwner element,
@Nullable PsiType type,
@Nullable PsiElement context,
@Nullable Set<? super PsiClass> visited) {
PsiManager manager = element.getManager();
for (PsiAnnotation annotation : getAllAnnotations(element)) {
if (type != null && MagicConstant.class.getName().equals(annotation.getQualifiedName())) {
AllowedValues values = getAllowedValuesFromMagic(type, annotation, manager);
AllowedValues values = getAllowedValuesFromMagic(type, annotation, manager, context);
if (values != null) return values;
}
@@ -129,7 +160,7 @@ public final class MagicConstantUtils {
if (!visited.add(aClass)) {
continue;
}
AllowedValues values = getAllowedValues(aClass, type, visited);
AllowedValues values = getAllowedValues(aClass, type, context, visited);
if (values != null) {
return values;
}
@@ -0,0 +1,16 @@
import org.intellij.lang.annotations.MagicConstant;
enum EnumConstructor {
FOO(<warning descr="Should be one of: MagicConstantIds.ONE">1</warning>),
;
private final int magicConstant;
EnumConstructor(@MagicConstant(valuesFromClass = MagicConstantIds.class) int magicConstant) {
this.magicConstant = magicConstant;
}
}
class MagicConstantIds {
static final int ONE = 1;
private static final int TWO = 2;
}
@@ -81,6 +81,31 @@ interface Foo {
myFixture.assertPreferredCompletionItems 0, 'BAR', 'FOO'
}
@NeedsIndex.ForStandardLibrary
void "test completion in enum constructor"() {
addMagicConstant()
myFixture.configureByText "a.java", """
import org.intellij.lang.annotations.MagicConstant;
enum MagicConstantTest {
FOO(<caret>),
;
private final int magicConstant;
MagicConstantTest(@MagicConstant(valuesFromClass = MagicConstantIds.class) int magicConstant) {
this.magicConstant = magicConstant;
}
}
class MagicConstantIds {
static final int ONE = 1;
static final int TWO = 2;
}
"""
myFixture.completeBasic()
myFixture.assertPreferredCompletionItems 0, 'ONE', 'TWO'
}
void "test magic constant in equality before another equality"() {
addMagicConstant()
@@ -46,6 +46,7 @@ public class MagicConstantInspectionTest extends LightJavaCodeInsightFixtureTest
public void testWithLibrary() { doTest(); }
public void testSpecialCases() { doTest(); }
public void testVarargMethodCall() { doTest(); }
public void testEnumConstructor() { doTest(); }
private void doTest() {
myFixture.configureByFile(getTestName(false) + ".java");
@@ -100,22 +100,22 @@ public class CreateMissingSwitchBranchesAction extends PsiElementBaseIntentionAc
}
if (expression instanceof PsiReferenceExpression) {
PsiModifierListOwner target = ObjectUtils.tryCast(((PsiReferenceExpression)expression).resolve(), PsiModifierListOwner.class);
List<Value> values = getValues(target, type);
List<Value> values = getValues(target, type, expression);
if (values != null) return values;
}
else if (expression instanceof PsiMethodCallExpression) {
PsiModifierListOwner target = ObjectUtils.tryCast(((PsiMethodCallExpression)expression).resolveMethod(), PsiModifierListOwner.class);
List<Value> values = getValues(target, type);
List<Value> values = getValues(target, type, expression);
if (values != null) return values;
}
return Collections.emptyList();
}
private static List<Value> getValues(PsiModifierListOwner target, PsiType type) {
private static List<Value> getValues(PsiModifierListOwner target, PsiType type, PsiElement context) {
if (target == null) {
return null;
}
MagicConstantUtils.AllowedValues values = MagicConstantUtils.getAllowedValues(target, type);
MagicConstantUtils.AllowedValues values = MagicConstantUtils.getAllowedValues(target, type, context);
if (values != null && !values.isFlagSet() && values.getValues().length <= MAX_NUMBER_OF_BRANCHES) {
List<Value> result = new ArrayList<>();
for (PsiAnnotationMemberValue value : values.getValues()) {