[java-completion] IDEA-354763 IJ-CR-137494 Don't suggest protected/transient and so on the top file level

- skip `strictfp` according to language level
- more accurately determine position
- refactoring

GitOrigin-RevId: 2d660d7f4fd86bed089da62f4ca746c6f2348aec
This commit is contained in:
Mikhail Pyltsin
2024-06-25 15:47:02 +00:00
committed by intellij-monorepo-bot
parent fc7bb4d813
commit 114a8907cf
4 changed files with 78 additions and 93 deletions
@@ -15,75 +15,59 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public final class ModifierChooser {
private static final String[][] CLASS_MODIFIERS = {
{PsiKeyword.PUBLIC},
{PsiKeyword.FINAL, PsiKeyword.ABSTRACT}
/**
* ArrayOfModifiers contains
* @param modifiers which can't be use together.
* @param allowToUse when modifiers are applicable, if null they can be used always
*/
private record ArrayOfModifiers(@NotNull String @NotNull[] modifiers,
@Nullable Predicate<@NotNull PsiElement> allowToUse){
private ArrayOfModifiers(@NotNull String @NotNull[] modifiers) {
this(modifiers, (Predicate<PsiElement>)null);
}
private ArrayOfModifiers(@NotNull String @NotNull[] modifiers, @NotNull JavaFeature feature) {
this(modifiers, v->PsiUtil.isAvailable(feature, v));
}
}
private static final ArrayOfModifiers[] CLASS_MODIFIERS = {
new ArrayOfModifiers(new String[]{PsiKeyword.PUBLIC}),
new ArrayOfModifiers(new String[]{PsiKeyword.FINAL, PsiKeyword.ABSTRACT})
};
private static final String[] CLASS_MODIFIERS_WITH_SEALED = {
PsiKeyword.PUBLIC, PsiKeyword.FINAL, PsiKeyword.SEALED, PsiKeyword.NON_SEALED, PsiKeyword.ABSTRACT
};
private static final String[][] CLASS_MEMBER_MODIFIERS = {
{PsiKeyword.PUBLIC, PsiKeyword.PROTECTED, PsiKeyword.PRIVATE},
{PsiKeyword.STATIC},
{PsiKeyword.FINAL, PsiKeyword.ABSTRACT},
{PsiKeyword.NATIVE},
{PsiKeyword.SYNCHRONIZED},
{PsiKeyword.STRICTFP},
{PsiKeyword.VOLATILE},
{PsiKeyword.TRANSIENT}
private static final ArrayOfModifiers[] CLASS_MEMBER_MODIFIERS = {
new ArrayOfModifiers(new String[]{PsiKeyword.PUBLIC, PsiKeyword.PROTECTED, PsiKeyword.PRIVATE}),
new ArrayOfModifiers(new String[]{PsiKeyword.STATIC}),
new ArrayOfModifiers(new String[]{PsiKeyword.FINAL, PsiKeyword.ABSTRACT}),
new ArrayOfModifiers(new String[]{PsiKeyword.SEALED, PsiKeyword.NON_SEALED}, JavaFeature.SEALED_CLASSES),
new ArrayOfModifiers(new String[]{PsiKeyword.NATIVE}),
new ArrayOfModifiers(new String[]{PsiKeyword.SYNCHRONIZED}),
new ArrayOfModifiers(new String[]{PsiKeyword.STRICTFP}, el -> !PsiUtil.isAvailable(JavaFeature.ALWAYS_STRICTFP, el)),
new ArrayOfModifiers(new String[]{PsiKeyword.VOLATILE}),
new ArrayOfModifiers(new String[]{PsiKeyword.TRANSIENT})
};
private static final String[][] FILE_MEMBER_MODIFIERS = {
{PsiKeyword.PUBLIC},
{PsiKeyword.FINAL, PsiKeyword.ABSTRACT},
{PsiKeyword.STRICTFP},
private static final ArrayOfModifiers[] FILE_MEMBER_MODIFIERS = {
new ArrayOfModifiers(new String[]{PsiKeyword.PUBLIC}),
new ArrayOfModifiers(new String[]{PsiKeyword.FINAL, PsiKeyword.ABSTRACT}),
new ArrayOfModifiers(new String[]{PsiKeyword.STRICTFP}, el -> !PsiUtil.isAvailable(JavaFeature.ALWAYS_STRICTFP, el)),
new ArrayOfModifiers(new String[]{PsiKeyword.SEALED, PsiKeyword.NON_SEALED}, JavaFeature.SEALED_CLASSES)
};
private static final String[][] FILE_MEMBER_MODIFIERS_WITH_SEALED = {
{PsiKeyword.PUBLIC},
{PsiKeyword.FINAL, PsiKeyword.ABSTRACT},
{PsiKeyword.SEALED, PsiKeyword.NON_SEALED},
{PsiKeyword.STRICTFP},
};
private static final String[][] CLASS_MEMBER_MODIFIERS_WITH_SEALED = {
{PsiKeyword.PUBLIC, PsiKeyword.PROTECTED, PsiKeyword.PRIVATE},
{PsiKeyword.STATIC},
{PsiKeyword.FINAL, PsiKeyword.ABSTRACT},
{PsiKeyword.SEALED, PsiKeyword.NON_SEALED},
{PsiKeyword.NATIVE},
{PsiKeyword.SYNCHRONIZED},
{PsiKeyword.STRICTFP},
{PsiKeyword.VOLATILE},
{PsiKeyword.TRANSIENT}
};
private static final String[][] INTERFACE_MEMBER_MODIFIERS_WITH_SEALED = {
{PsiKeyword.PUBLIC, PsiKeyword.PROTECTED, PsiKeyword.PRIVATE},
{PsiKeyword.STATIC, PsiKeyword.DEFAULT},
{PsiKeyword.FINAL, PsiKeyword.ABSTRACT},
{PsiKeyword.SEALED, PsiKeyword.NON_SEALED}
};
private static final String[][] INTERFACE_MEMBER_MODIFIERS_WITH_PRIVATE = {
{PsiKeyword.PUBLIC, PsiKeyword.PROTECTED, PsiKeyword.PRIVATE},
{PsiKeyword.STATIC, PsiKeyword.DEFAULT},
{PsiKeyword.FINAL, PsiKeyword.ABSTRACT}
};
private static final String[][] INTERFACE_MEMBER_MODIFIERS_WITH_DEFAULT = {
{PsiKeyword.PUBLIC, PsiKeyword.PROTECTED},
{PsiKeyword.STATIC, PsiKeyword.DEFAULT},
{PsiKeyword.FINAL, PsiKeyword.ABSTRACT}
};
private static final String[][] INTERFACE_MEMBER_MODIFIERS = {
{PsiKeyword.PUBLIC, PsiKeyword.PROTECTED},
{PsiKeyword.FINAL, PsiKeyword.ABSTRACT}
private static final ArrayOfModifiers[] INTERFACE_MEMBER_MODIFIERS = {
new ArrayOfModifiers(new String[]{PsiKeyword.PUBLIC, PsiKeyword.PROTECTED},
el -> !PsiUtil.isAvailable(JavaFeature.PRIVATE_INTERFACE_METHODS, el)),
new ArrayOfModifiers(new String[]{PsiKeyword.PUBLIC, PsiKeyword.PROTECTED, PsiKeyword.PRIVATE}, JavaFeature.PRIVATE_INTERFACE_METHODS),
new ArrayOfModifiers(new String[]{PsiKeyword.FINAL, PsiKeyword.ABSTRACT}),
new ArrayOfModifiers(new String[]{PsiKeyword.STATIC, PsiKeyword.DEFAULT}, JavaFeature.STATIC_INTERFACE_CALLS),
new ArrayOfModifiers(new String[]{PsiKeyword.SEALED, PsiKeyword.NON_SEALED}, JavaFeature.SEALED_CLASSES)
};
static String[] getKeywords(@NotNull PsiElement position) {
@@ -94,19 +78,28 @@ public final class ModifierChooser {
PsiElement scope = position.getParent();
while (scope != null) {
if (scope instanceof PsiJavaFile) {
if (scope instanceof PsiJavaFile ||
scope instanceof PsiClass ||
scope.getParent() instanceof PsiImplicitClass) {
return addJavaFileMemberModifiers(list, position);
}
if (scope instanceof PsiClass) {
return addMemberModifiers(list, ((PsiClass)scope).isInterface(), scope);
if (scope.getParent() instanceof PsiClass psiClass) {
PsiIdentifier identifier = psiClass.getNameIdentifier();
if (identifier == null ||
identifier.getTextRange().getStartOffset() < scope.getTextRange().getStartOffset()) {
return addMemberModifiers(list, psiClass.isInterface(), psiClass);
}
}
scope = scope.getParent();
if (scope instanceof PsiDirectory) break;
}
return ArrayUtilRt.EMPTY_STRING_ARRAY;
}
/**
* @deprecated: it is not used.
*/
@Deprecated(forRemoval = true)
public static String[] addClassModifiers(PsiModifierList list, @NotNull PsiElement scope) {
if (PsiUtil.isAvailable(JavaFeature.SEALED_CLASSES, scope)) {
if (list == null) {
@@ -135,7 +128,7 @@ public final class ModifierChooser {
return ArrayUtilRt.toStringArray(ret);
}
}
return addKeywords(list, CLASS_MODIFIERS);
return addKeywords(list, CLASS_MODIFIERS, scope);
}
private static void addIfNotPresent(String modifier, PsiModifierList list, List<String> ret) {
@@ -149,42 +142,23 @@ public final class ModifierChooser {
(position.getContainingFile() instanceof PsiJavaFile javaFile && javaFile.getPackageStatement() == null)) {
return addMemberModifiers(list, false, position);
}
return addKeywords(list,
PsiUtil.isAvailable(JavaFeature.SEALED_CLASSES, position) ?
FILE_MEMBER_MODIFIERS_WITH_SEALED : FILE_MEMBER_MODIFIERS);
return addKeywords(list, FILE_MEMBER_MODIFIERS, position);
}
public static String[] addMemberModifiers(PsiModifierList list, final boolean inInterface, @NotNull PsiElement position) {
return addKeywords(list, inInterface ? getInterfaceMemberModifiers(position) : getClassMemberModifiers(position));
return addKeywords(list, inInterface ? INTERFACE_MEMBER_MODIFIERS : CLASS_MEMBER_MODIFIERS, position);
}
private static String[][] getInterfaceMemberModifiers(@NotNull PsiElement list) {
if (PsiUtil.isAvailable(JavaFeature.SEALED_CLASSES, list)) {
return INTERFACE_MEMBER_MODIFIERS_WITH_SEALED;
}
if (PsiUtil.isAvailable(JavaFeature.PRIVATE_INTERFACE_METHODS, list)) {
return INTERFACE_MEMBER_MODIFIERS_WITH_PRIVATE;
}
if (PsiUtil.isAvailable(JavaFeature.STATIC_INTERFACE_CALLS, list)) {
return INTERFACE_MEMBER_MODIFIERS_WITH_DEFAULT;
}
return INTERFACE_MEMBER_MODIFIERS;
}
private static String[][] getClassMemberModifiers(@NotNull PsiElement list) {
if (PsiUtil.isAvailable(JavaFeature.SEALED_CLASSES, list)) {
return CLASS_MEMBER_MODIFIERS_WITH_SEALED;
}
return CLASS_MEMBER_MODIFIERS;
}
private static String[] addKeywords(PsiModifierList list, String[][] keywordSets) {
private static String[] addKeywords(PsiModifierList list, ArrayOfModifiers[] keywordSets, @NotNull PsiElement position) {
final List<String> ret = new ArrayList<>();
for (int i = 0; i < keywordSets.length; i++) {
final String[] keywords = keywordSets[keywordSets.length - i - 1];
final ArrayOfModifiers keywords = keywordSets[keywordSets.length - i - 1];
if (keywords.allowToUse() != null && !keywords.allowToUse().test(position)) {
continue;
}
boolean containModifierFlag = false;
if (list != null) {
for (@PsiModifier.ModifierConstant String keyword : keywords) {
for (@PsiModifier.ModifierConstant String keyword : keywords.modifiers()) {
if (list.hasExplicitModifier(keyword)) {
containModifierFlag = true;
break;
@@ -192,14 +166,14 @@ public final class ModifierChooser {
}
}
if (!containModifierFlag) {
ContainerUtil.addAll(ret, keywords);
ContainerUtil.addAll(ret, keywords.modifiers());
}
}
return ArrayUtilRt.toStringArray(ret);
}
public static @Nullable PsiModifierList findModifierList(@NotNull PsiElement element) {
if(element.getParent() instanceof PsiModifierList) {
if (element.getParent() instanceof PsiModifierList) {
return (PsiModifierList)element.getParent();
}
@@ -0,0 +1 @@
public <caret> Cls
@@ -0,0 +1 @@
public <caret> Cls
@@ -36,12 +36,12 @@ public class KeywordCompletionTest extends LightCompletionTestCase {
setLanguageLevel(JavaFeature.IMPLICIT_CLASSES.getMinimumLevel());
doTest(16, "package", "public", "import", "final", "class", "interface", "abstract", "enum",
"transient", "static", "private", "protected", "volatile", "synchronized", "sealed", "non-sealed");
assertNotContainItems("default");
assertNotContainItems("default", "strictfp");
}
public void testFileScopeWithoutPackage2() {
setLanguageLevel(LanguageLevel.JDK_1_8);
doTest(8, "package", "public", "import", "final", "class", "interface", "abstract", "enum");
doTest(9, "package", "public", "import", "final", "class", "interface", "abstract", "enum", "strictfp");
assertNotContainItems("default", "transient", "static", "private", "protected", "volatile", "synchronized", "sealed", "non-sealed");
}
@@ -66,6 +66,15 @@ public class KeywordCompletionTest extends LightCompletionTestCase {
}
public void testClassScope3() { doTest(0, CLASS_SCOPE_KEYWORDS); }
public void testClassScope4() { doTest(11, CLASS_SCOPE_KEYWORDS_2); }
public void testClassScope5() {
setLanguageLevel(JavaFeature.ALWAYS_STRICTFP.getMinimumLevel());
doTestContains("class", "interface", "enum", "record", "sealed", "non-sealed");
assertNotContainItems("default", "transient", "static", "private", "protected", "volatile", "synchronized", "strictfp");
}
public void testClassScope5WithStrictfp() {
setLanguageLevel(LanguageLevel.JDK_1_8);
doTestContains("class", "interface", "enum", "strictfp");
}
public void testInnerClassScope() {
doTest(11, CLASS_SCOPE_KEYWORDS_2);
}