mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java-highlighting] modifier checks -> ModifierChecker
Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only) GitOrigin-RevId: 43e782e0950b1a0e2a05de63186ea1d97a786865
This commit is contained in:
committed by
intellij-monorepo-bot
parent
2111b45002
commit
ac754128ca
@@ -228,3 +228,9 @@ array.type.expected=Array type expected; found: ''{0}''
|
||||
pattern.type.pattern.expected=Type pattern expected
|
||||
|
||||
expression.expected=Expression expected
|
||||
|
||||
modifier.not.allowed=Modifier ''{0}'' not allowed here
|
||||
modifier.not.allowed.local.class=Modifier ''{0}'' not allowed on local classes
|
||||
modifier.not.allowed.non.sealed=Modifier 'non-sealed' is not allowed on classes that do not have a sealed superclass
|
||||
modifier.repeated=Repeated modifier ''{0}''
|
||||
modifier.incompatible=Illegal combination of modifiers ''{0}'' and ''{1}''
|
||||
|
||||
+6
@@ -45,6 +45,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
|
||||
private final @NotNull GenericsChecker myGenericsChecker = new GenericsChecker(this);
|
||||
final @NotNull MethodChecker myMethodChecker = new MethodChecker(this);
|
||||
private final @NotNull ReceiverChecker myReceiverChecker = new ReceiverChecker(this);
|
||||
private final @NotNull ModifierChecker myModifierChecker = new ModifierChecker(this);
|
||||
final @NotNull ExpressionChecker myExpressionChecker = new ExpressionChecker(this);
|
||||
private final @NotNull StatementChecker myStatementChecker = new StatementChecker(this);
|
||||
private final @NotNull LiteralChecker myLiteralChecker = new LiteralChecker(this);
|
||||
@@ -364,6 +365,11 @@ final class JavaErrorVisitor extends JavaElementVisitor {
|
||||
super.visitKeyword(keyword);
|
||||
if (!hasErrorResults()) myClassChecker.checkStaticDeclarationInInnerClass(keyword);
|
||||
if (!hasErrorResults()) myExpressionChecker.checkIllegalVoidType(keyword);
|
||||
PsiElement parent = keyword.getParent();
|
||||
if (parent instanceof PsiModifierList psiModifierList) {
|
||||
if (!hasErrorResults()) myModifierChecker.checkNotAllowedModifier(keyword, psiModifierList);
|
||||
if (!hasErrorResults()) myModifierChecker.checkIllegalModifierCombination(keyword, psiModifierList);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.java.codeserver.highlighting;
|
||||
|
||||
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds;
|
||||
import com.intellij.pom.java.JavaFeature;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.FileTypeUtils;
|
||||
import com.intellij.psi.util.JavaPsiModifierUtil;
|
||||
import com.intellij.psi.util.JavaPsiRecordUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
|
||||
final class ModifierChecker {
|
||||
private static final Set<String> ourConstructorNotAllowedModifiers =
|
||||
Set.of(PsiModifier.ABSTRACT, PsiModifier.STATIC, PsiModifier.NATIVE, PsiModifier.FINAL, PsiModifier.STRICTFP, PsiModifier.SYNCHRONIZED);
|
||||
|
||||
private final @NotNull JavaErrorVisitor myVisitor;
|
||||
|
||||
ModifierChecker(@NotNull JavaErrorVisitor visitor) { myVisitor = visitor; }
|
||||
|
||||
void checkNotAllowedModifier(@NotNull PsiKeyword keyword, @NotNull PsiModifierList modifierList) {
|
||||
PsiElement modifierOwner = modifierList.getParent();
|
||||
if (modifierOwner == null) return;
|
||||
@SuppressWarnings("MagicConstant")
|
||||
@PsiModifier.ModifierConstant String modifier = keyword.getText();
|
||||
|
||||
PsiElement modifierOwnerParent =
|
||||
modifierOwner instanceof PsiMember psiMember ? psiMember.getContainingClass() : modifierOwner.getParent();
|
||||
if (modifierOwnerParent == null) modifierOwnerParent = modifierOwner.getParent();
|
||||
boolean isAllowed = true;
|
||||
String message = null;
|
||||
if (modifierOwner instanceof PsiClass aClass) {
|
||||
boolean privateOrProtected = PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier);
|
||||
if (aClass.isInterface()) {
|
||||
if (PsiModifier.STATIC.equals(modifier) || privateOrProtected || PsiModifier.PACKAGE_LOCAL.equals(modifier)) {
|
||||
isAllowed = modifierOwnerParent instanceof PsiClass;
|
||||
}
|
||||
if (PsiModifier.PUBLIC.equals(modifier)) {
|
||||
isAllowed = !(modifierOwnerParent instanceof PsiDeclarationStatement);
|
||||
}
|
||||
if (PsiModifier.SEALED.equals(modifier)) {
|
||||
isAllowed = !aClass.isAnnotationType();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (PsiModifier.PUBLIC.equals(modifier)) {
|
||||
isAllowed = modifierOwnerParent instanceof PsiImportHolder ||
|
||||
// PsiJavaFile or JavaDummyHolder
|
||||
modifierOwnerParent instanceof PsiClass psiClass &&
|
||||
(modifierOwnerParent instanceof PsiSyntheticClass ||
|
||||
PsiUtil.isAvailable(JavaFeature.INNER_STATICS, modifierOwnerParent) ||
|
||||
psiClass.getQualifiedName() != null ||
|
||||
!modifierOwnerParent.isPhysical());
|
||||
}
|
||||
else {
|
||||
if (PsiModifier.STATIC.equals(modifier) || privateOrProtected || PsiModifier.PACKAGE_LOCAL.equals(modifier)) {
|
||||
isAllowed = modifierOwnerParent instanceof PsiClass psiClass &&
|
||||
(PsiModifier.STATIC.equals(modifier) ||
|
||||
PsiUtil.isAvailable(JavaFeature.INNER_STATICS, modifierOwnerParent) ||
|
||||
psiClass.getQualifiedName() != null) ||
|
||||
FileTypeUtils.isInServerPageFile(modifierOwnerParent) ||
|
||||
// non-physical dummy holder might not have FQN
|
||||
!modifierOwnerParent.isPhysical();
|
||||
}
|
||||
}
|
||||
|
||||
if (aClass.isEnum()) {
|
||||
isAllowed &=
|
||||
!PsiModifier.FINAL.equals(modifier) && !PsiModifier.ABSTRACT.equals(modifier) && !PsiModifier.SEALED.equals(modifier)
|
||||
&& !PsiModifier.VALUE.equals(modifier);
|
||||
}
|
||||
else if (aClass.isRecord()) {
|
||||
isAllowed &= !PsiModifier.ABSTRACT.equals(modifier);
|
||||
}
|
||||
|
||||
if (aClass.getContainingClass() instanceof PsiAnonymousClass &&
|
||||
privateOrProtected && !PsiUtil.getLanguageLevel(modifierOwnerParent).isAtLeast(LanguageLevel.JDK_16)) {
|
||||
isAllowed = false;
|
||||
}
|
||||
}
|
||||
if ((PsiModifier.NON_SEALED.equals(modifier) || PsiModifier.SEALED.equals(modifier)) &&
|
||||
modifierOwnerParent instanceof PsiDeclarationStatement) {
|
||||
// JLS 14.3
|
||||
myVisitor.report(JavaErrorKinds.MODIFIER_NOT_ALLOWED_LOCAL_CLASS.create(keyword, modifier));
|
||||
return;
|
||||
}
|
||||
else if (PsiModifier.NON_SEALED.equals(modifier) && !aClass.hasModifierProperty(PsiModifier.SEALED)) {
|
||||
isAllowed = Arrays.stream(aClass.getSuperTypes())
|
||||
.map(PsiClassType::resolve)
|
||||
.anyMatch(superClass -> superClass != null && superClass.hasModifierProperty(PsiModifier.SEALED));
|
||||
if (!isAllowed) {
|
||||
myVisitor.report(JavaErrorKinds.MODIFIER_NOT_ALLOWED_NON_SEALED.create(keyword, modifier));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (modifierOwner instanceof PsiMethod method) {
|
||||
isAllowed = !(method.isConstructor() && ourConstructorNotAllowedModifiers.contains(modifier));
|
||||
PsiClass containingClass = method.getContainingClass();
|
||||
if ((method.hasModifierProperty(PsiModifier.PUBLIC) || method.hasModifierProperty(PsiModifier.PROTECTED)) && method.isConstructor() &&
|
||||
containingClass != null && containingClass.isEnum()) {
|
||||
isAllowed = false;
|
||||
}
|
||||
|
||||
boolean isInterface = modifierOwnerParent instanceof PsiClass psiClass && psiClass.isInterface();
|
||||
if (PsiModifier.PRIVATE.equals(modifier) && modifierOwnerParent instanceof PsiClass psiClass) {
|
||||
isAllowed &= !isInterface || PsiUtil.isAvailable(JavaFeature.PRIVATE_INTERFACE_METHODS, modifierOwner) && !psiClass.isAnnotationType();
|
||||
}
|
||||
else if (PsiModifier.STRICTFP.equals(modifier)) {
|
||||
isAllowed &= !isInterface || PsiUtil.isAvailable(JavaFeature.EXTENSION_METHODS, modifierOwner);
|
||||
}
|
||||
else if (PsiModifier.PROTECTED.equals(modifier) ||
|
||||
PsiModifier.TRANSIENT.equals(modifier) ||
|
||||
PsiModifier.FINAL.equals(modifier)) {
|
||||
isAllowed &= !isInterface;
|
||||
}
|
||||
else if (PsiModifier.SYNCHRONIZED.equals(modifier)) {
|
||||
isAllowed &= !isInterface && (containingClass == null || !containingClass.isValueClass());
|
||||
}
|
||||
|
||||
if (containingClass != null && (containingClass.isInterface() || containingClass.isRecord())) {
|
||||
isAllowed &= !PsiModifier.NATIVE.equals(modifier);
|
||||
}
|
||||
|
||||
if (containingClass != null && containingClass.isAnnotationType()) {
|
||||
isAllowed &= !PsiModifier.STATIC.equals(modifier);
|
||||
isAllowed &= !PsiModifier.DEFAULT.equals(modifier);
|
||||
}
|
||||
|
||||
if (JavaPsiRecordUtil.getRecordComponentForAccessor(method) != null) {
|
||||
isAllowed &= !PsiModifier.STATIC.equals(modifier);
|
||||
}
|
||||
}
|
||||
else if (modifierOwner instanceof PsiField) {
|
||||
if (PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier) || PsiModifier.TRANSIENT.equals(modifier) ||
|
||||
PsiModifier.STRICTFP.equals(modifier)) {
|
||||
isAllowed = modifierOwnerParent instanceof PsiClass psiClass && !psiClass.isInterface();
|
||||
}
|
||||
}
|
||||
else if (modifierOwner instanceof PsiClassInitializer) {
|
||||
isAllowed = PsiModifier.STATIC.equals(modifier);
|
||||
}
|
||||
else if (modifierOwner instanceof PsiLocalVariable || modifierOwner instanceof PsiParameter) {
|
||||
isAllowed = PsiModifier.FINAL.equals(modifier);
|
||||
}
|
||||
else if (modifierOwner instanceof PsiReceiverParameter || modifierOwner instanceof PsiRecordComponent) {
|
||||
isAllowed = false;
|
||||
}
|
||||
|
||||
if (isAllowed && !JavaPsiModifierUtil.isAllowed(modifier, modifierList)) {
|
||||
isAllowed = false;
|
||||
}
|
||||
if (!isAllowed) {
|
||||
myVisitor.report(JavaErrorKinds.MODIFIER_NOT_ALLOWED.create(keyword, modifier));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void checkIllegalModifierCombination(@NotNull PsiKeyword keyword, @NotNull PsiModifierList modifierList) {
|
||||
@SuppressWarnings("MagicConstant") @PsiModifier.ModifierConstant String modifier = keyword.getText();
|
||||
String incompatible = JavaPsiModifierUtil.getIncompatibleModifier(modifier, modifierList);
|
||||
if (incompatible != null) {
|
||||
if (incompatible.equals(modifier)) {
|
||||
for (PsiElement child = modifierList.getFirstChild(); child != null; child = child.getNextSibling()) {
|
||||
if (modifier.equals(child.getText())) {
|
||||
if (child == keyword) return;
|
||||
else break;
|
||||
}
|
||||
}
|
||||
myVisitor.report(JavaErrorKinds.MODIFIER_REPEATED.create(keyword, modifier));
|
||||
}
|
||||
else {
|
||||
myVisitor.report(JavaErrorKinds.MODIFIER_INCOMPATIBLE.create(keyword, incompatible));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+15
@@ -614,6 +614,21 @@ public final class JavaErrorKinds {
|
||||
public static final Simple<PsiLiteralValue> LITERAL_TEXT_BLOCK_NO_NEW_LINE =
|
||||
error(PsiLiteralValue.class, "literal.text.block.no.new.line").withRange(e -> TextRange.create(0, 3));
|
||||
|
||||
public static final Parameterized<PsiKeyword, String> MODIFIER_NOT_ALLOWED =
|
||||
parameterized(PsiKeyword.class, String.class, "modifier.not.allowed")
|
||||
.withRawDescription((keyword, text) -> message("modifier.not.allowed", text));
|
||||
public static final Parameterized<PsiKeyword, String> MODIFIER_REPEATED =
|
||||
parameterized(PsiKeyword.class, String.class, "modifier.repeated")
|
||||
.withRawDescription((keyword, text) -> message("modifier.repeated", text));
|
||||
public static final Parameterized<PsiKeyword, String> MODIFIER_INCOMPATIBLE =
|
||||
parameterized(PsiKeyword.class, String.class, "modifier.incompatible")
|
||||
.withRawDescription((keyword, text) -> message("modifier.incompatible", keyword.getText(), text));
|
||||
public static final Parameterized<PsiKeyword, String> MODIFIER_NOT_ALLOWED_LOCAL_CLASS =
|
||||
parameterized(PsiKeyword.class, String.class, "modifier.not.allowed.local.class")
|
||||
.withRawDescription((keyword, text) -> message("modifier.not.allowed.local.class", text));
|
||||
public static final Parameterized<PsiKeyword, String> MODIFIER_NOT_ALLOWED_NON_SEALED =
|
||||
parameterized(PsiKeyword.class, String.class, "modifier.not.allowed.non.sealed");
|
||||
|
||||
public static final Simple<PsiMethodCallExpression> CALL_SUPER_ENUM_CONSTRUCTOR = error("call.super.enum.constructor");
|
||||
public static final Parameterized<PsiExpression, PsiClass> CALL_SUPER_QUALIFIER_NOT_INNER_CLASS =
|
||||
parameterized(PsiExpression.class, PsiClass.class, "call.super.qualifier.not.inner.class")
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ public final class HighlightClassUtil {
|
||||
if (result == null || aClass == null) return;
|
||||
if (aClass.getContainingClass() != null) {
|
||||
PsiModifierList modifierList = aClass.getModifierList();
|
||||
if (modifierList != null && HighlightUtil.getIncompatibleModifier(PsiModifier.STATIC, modifierList) == null) {
|
||||
if (modifierList != null && JavaPsiModifierUtil.getIncompatibleModifier(PsiModifier.STATIC, modifierList) == null) {
|
||||
IntentionAction action = QuickFixFactory.getInstance().createModifierListFix(aClass, PsiModifier.STATIC, true, false);
|
||||
result.registerFix(action, null, null, null, null);
|
||||
}
|
||||
|
||||
-305
@@ -80,61 +80,6 @@ import static com.intellij.util.ObjectUtils.tryCast;
|
||||
public final class HighlightUtil {
|
||||
private static final Logger LOG = Logger.getInstance(HighlightUtil.class);
|
||||
|
||||
private static final Map<String, Set<String>> ourInterfaceIncompatibleModifiers = Map.of(
|
||||
PsiModifier.ABSTRACT, Set.of(),
|
||||
PsiModifier.PACKAGE_LOCAL, Set.of(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED),
|
||||
PsiModifier.PRIVATE, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED),
|
||||
PsiModifier.PUBLIC, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED),
|
||||
PsiModifier.PROTECTED, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE),
|
||||
PsiModifier.STRICTFP, Set.of(),
|
||||
PsiModifier.STATIC, Set.of(),
|
||||
PsiModifier.SEALED, Set.of(PsiModifier.NON_SEALED),
|
||||
PsiModifier.NON_SEALED, Set.of(PsiModifier.SEALED));
|
||||
private static final Map<String, Set<String>> ourMethodIncompatibleModifiers = Map.ofEntries(
|
||||
Map.entry(PsiModifier.ABSTRACT, Set.of(
|
||||
PsiModifier.NATIVE, PsiModifier.STATIC, PsiModifier.FINAL, PsiModifier.PRIVATE, PsiModifier.STRICTFP, PsiModifier.SYNCHRONIZED,
|
||||
PsiModifier.DEFAULT)),
|
||||
Map.entry(PsiModifier.NATIVE, Set.of(PsiModifier.ABSTRACT, PsiModifier.STRICTFP)),
|
||||
Map.entry(PsiModifier.PACKAGE_LOCAL, Set.of(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED)),
|
||||
Map.entry(PsiModifier.PRIVATE, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED)),
|
||||
Map.entry(PsiModifier.PUBLIC, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED)),
|
||||
Map.entry(PsiModifier.PROTECTED, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE)),
|
||||
Map.entry(PsiModifier.STATIC, Set.of(PsiModifier.ABSTRACT, PsiModifier.DEFAULT)),
|
||||
Map.entry(PsiModifier.DEFAULT, Set.of(PsiModifier.ABSTRACT, PsiModifier.STATIC, PsiModifier.PRIVATE)),
|
||||
Map.entry(PsiModifier.SYNCHRONIZED, Set.of(PsiModifier.ABSTRACT)),
|
||||
Map.entry(PsiModifier.STRICTFP, Set.of(PsiModifier.ABSTRACT)),
|
||||
Map.entry(PsiModifier.FINAL, Set.of(PsiModifier.ABSTRACT)));
|
||||
private static final Map<String, Set<String>> ourFieldIncompatibleModifiers = Map.of(
|
||||
PsiModifier.FINAL, Set.of(PsiModifier.VOLATILE),
|
||||
PsiModifier.PACKAGE_LOCAL, Set.of(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED),
|
||||
PsiModifier.PRIVATE, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED),
|
||||
PsiModifier.PUBLIC, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED),
|
||||
PsiModifier.PROTECTED, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE),
|
||||
PsiModifier.STATIC, Set.of(),
|
||||
PsiModifier.TRANSIENT, Set.of(),
|
||||
PsiModifier.VOLATILE, Set.of(PsiModifier.FINAL));
|
||||
private static final Map<String, Set<String>> ourClassIncompatibleModifiers = Map.ofEntries(
|
||||
Map.entry(PsiModifier.ABSTRACT, Set.of(PsiModifier.FINAL)),
|
||||
Map.entry(PsiModifier.FINAL, Set.of(PsiModifier.ABSTRACT, PsiModifier.SEALED, PsiModifier.NON_SEALED)),
|
||||
Map.entry(PsiModifier.PACKAGE_LOCAL, Set.of(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED)),
|
||||
Map.entry(PsiModifier.PRIVATE, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED)),
|
||||
Map.entry(PsiModifier.PUBLIC, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED)),
|
||||
Map.entry(PsiModifier.PROTECTED, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE)),
|
||||
Map.entry(PsiModifier.STRICTFP, Set.of()),
|
||||
Map.entry(PsiModifier.STATIC, Set.of()),
|
||||
Map.entry(PsiModifier.SEALED, Set.of(PsiModifier.FINAL, PsiModifier.NON_SEALED)),
|
||||
Map.entry(PsiModifier.NON_SEALED, Set.of(PsiModifier.FINAL, PsiModifier.SEALED)),
|
||||
Map.entry(PsiModifier.VALUE, Set.of())
|
||||
);
|
||||
private static final Map<String, Set<String>> ourClassInitializerIncompatibleModifiers = Map.of(PsiModifier.STATIC, Set.of());
|
||||
private static final Map<String, Set<String>> ourModuleIncompatibleModifiers = Map.of(PsiModifier.OPEN, Set.of());
|
||||
private static final Map<String, Set<String>> ourRequiresIncompatibleModifiers = Map.of(
|
||||
PsiModifier.STATIC, Set.of(),
|
||||
PsiModifier.TRANSITIVE, Set.of());
|
||||
|
||||
private static final Set<String> ourConstructorNotAllowedModifiers =
|
||||
Set.of(PsiModifier.ABSTRACT, PsiModifier.STATIC, PsiModifier.NATIVE, PsiModifier.FINAL, PsiModifier.STRICTFP, PsiModifier.SYNCHRONIZED);
|
||||
|
||||
private static final String SERIAL_PERSISTENT_FIELDS_FIELD_NAME = "serialPersistentFields";
|
||||
private static final @NlsSafe String ANONYMOUS = "anonymous ";
|
||||
|
||||
@@ -144,50 +89,6 @@ public final class HighlightUtil {
|
||||
return QuickFixFactory.getInstance();
|
||||
}
|
||||
|
||||
private static String getIncompatibleModifier(@NotNull String modifier,
|
||||
@NotNull PsiModifierList modifierList,
|
||||
@NotNull Map<String, Set<String>> incompatibleModifiersHash) {
|
||||
// modifier is always incompatible with itself
|
||||
int modifierCount = 0;
|
||||
for (PsiElement otherModifier = modifierList.getFirstChild(); otherModifier != null; otherModifier = otherModifier.getNextSibling()) {
|
||||
if (modifier.equals(otherModifier.getText())) modifierCount++;
|
||||
}
|
||||
if (modifierCount > 1) return modifier;
|
||||
|
||||
Set<String> incompatibles = incompatibleModifiersHash.get(modifier);
|
||||
if (incompatibles == null) return null;
|
||||
PsiElement parent = modifierList.getParent();
|
||||
boolean level8OrHigher = PsiUtil.isLanguageLevel8OrHigher(modifierList);
|
||||
boolean level9OrHigher = PsiUtil.isLanguageLevel9OrHigher(modifierList);
|
||||
for (@PsiModifier.ModifierConstant String incompatible : incompatibles) {
|
||||
if (level8OrHigher) {
|
||||
if (modifier.equals(PsiModifier.STATIC) && incompatible.equals(PsiModifier.ABSTRACT)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (parent instanceof PsiMethod psiMethod) {
|
||||
if (level9OrHigher && modifier.equals(PsiModifier.PRIVATE) && incompatible.equals(PsiModifier.PUBLIC)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (modifier.equals(PsiModifier.STATIC) && incompatible.equals(PsiModifier.FINAL)) {
|
||||
PsiClass containingClass = psiMethod.getContainingClass();
|
||||
if (containingClass == null || !containingClass.isInterface()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (modifierList.hasModifierProperty(incompatible)) {
|
||||
return incompatible;
|
||||
}
|
||||
if (PsiModifier.ABSTRACT.equals(incompatible) && modifierList.hasExplicitModifier(incompatible)) {
|
||||
return incompatible;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
static void checkInstanceOfApplicable(@NotNull PsiInstanceOfExpression expression, @NotNull Consumer<? super HighlightInfo.Builder> errorSink) {
|
||||
PsiExpression operand = expression.getOperand();
|
||||
@@ -1064,212 +965,6 @@ public final class HighlightUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
static HighlightInfo.Builder checkIllegalModifierCombination(@NotNull PsiKeyword keyword, @NotNull PsiModifierList modifierList) {
|
||||
@PsiModifier.ModifierConstant String modifier = keyword.getText();
|
||||
String incompatible = getIncompatibleModifier(modifier, modifierList);
|
||||
if (incompatible != null) {
|
||||
String message;
|
||||
if (incompatible.equals(modifier)) {
|
||||
for (PsiElement child = modifierList.getFirstChild(); child != null; child = child.getNextSibling()) {
|
||||
if (modifier.equals(child.getText())) {
|
||||
if (child == keyword) return null;
|
||||
else break;
|
||||
}
|
||||
}
|
||||
message = JavaErrorBundle.message("repeated.modifier", incompatible);
|
||||
}
|
||||
else {
|
||||
message = JavaErrorBundle.message("incompatible.modifiers", modifier, incompatible);
|
||||
}
|
||||
HighlightInfo.Builder highlightInfo =
|
||||
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(keyword).descriptionAndTooltip(message);
|
||||
IntentionAction action = getFixFactory().createModifierListFix(modifierList, modifier, false, false);
|
||||
highlightInfo.registerFix(action, null, null, null, null);
|
||||
return highlightInfo;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the supplied modifier list contains incompatible modifiers (e.g. "public private").
|
||||
*
|
||||
* @param modifierList a {@link PsiModifierList} to check
|
||||
* @return true if the supplied modifier list contains compatible modifiers
|
||||
*/
|
||||
public static boolean isLegalModifierCombination(@NotNull PsiModifierList modifierList) {
|
||||
for (PsiElement child = modifierList.getFirstChild(); child != null; child = child.getNextSibling()) {
|
||||
if (child instanceof PsiKeyword && getIncompatibleModifier(child.getText(), modifierList) != null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Map<String, Set<String>> getIncompatibleModifierMap(@NotNull PsiElement modifierListOwner) {
|
||||
if (PsiUtilCore.hasErrorElementChild(modifierListOwner)) return null;
|
||||
if (modifierListOwner instanceof PsiClass psiClass) {
|
||||
return psiClass.isInterface() ? ourInterfaceIncompatibleModifiers : ourClassIncompatibleModifiers;
|
||||
}
|
||||
if (modifierListOwner instanceof PsiMethod) return ourMethodIncompatibleModifiers;
|
||||
if (modifierListOwner instanceof PsiVariable) return ourFieldIncompatibleModifiers;
|
||||
if (modifierListOwner instanceof PsiClassInitializer) return ourClassInitializerIncompatibleModifiers;
|
||||
if (modifierListOwner instanceof PsiJavaModule) return ourModuleIncompatibleModifiers;
|
||||
if (modifierListOwner instanceof PsiRequiresStatement) return ourRequiresIncompatibleModifiers;
|
||||
return null;
|
||||
}
|
||||
|
||||
static @Nullable String getIncompatibleModifier(@NotNull String modifier, @NotNull PsiModifierList modifierList) {
|
||||
PsiElement parent = modifierList.getParent();
|
||||
if (parent == null) return null;
|
||||
Map<String, Set<String>> incompatibleModifierMap = getIncompatibleModifierMap(parent);
|
||||
return incompatibleModifierMap == null ? null : getIncompatibleModifier(modifier, modifierList, incompatibleModifierMap);
|
||||
}
|
||||
|
||||
static HighlightInfo.Builder checkNotAllowedModifier(@NotNull PsiKeyword keyword, @NotNull PsiModifierList modifierList) {
|
||||
PsiElement modifierOwner = modifierList.getParent();
|
||||
if (modifierOwner == null) return null;
|
||||
Map<String, Set<String>> incompatibleModifierMap = getIncompatibleModifierMap(modifierOwner);
|
||||
if (incompatibleModifierMap == null) return null;
|
||||
|
||||
@PsiModifier.ModifierConstant String modifier = keyword.getText();
|
||||
Set<String> incompatibles = incompatibleModifierMap.get(modifier);
|
||||
PsiElement modifierOwnerParent =
|
||||
modifierOwner instanceof PsiMember psiMember ? psiMember.getContainingClass() : modifierOwner.getParent();
|
||||
if (modifierOwnerParent == null) modifierOwnerParent = modifierOwner.getParent();
|
||||
boolean isAllowed = true;
|
||||
String message = null;
|
||||
IntentionAction fix = null;
|
||||
if (modifierOwner instanceof PsiClass aClass) {
|
||||
boolean privateOrProtected = PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier);
|
||||
if (aClass.isInterface()) {
|
||||
if (PsiModifier.STATIC.equals(modifier) || privateOrProtected || PsiModifier.PACKAGE_LOCAL.equals(modifier)) {
|
||||
isAllowed = modifierOwnerParent instanceof PsiClass;
|
||||
}
|
||||
if (PsiModifier.PUBLIC.equals(modifier)) {
|
||||
isAllowed = !(modifierOwnerParent instanceof PsiDeclarationStatement);
|
||||
}
|
||||
if (PsiModifier.SEALED.equals(modifier)) {
|
||||
isAllowed = !aClass.isAnnotationType();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (PsiModifier.PUBLIC.equals(modifier)) {
|
||||
isAllowed = modifierOwnerParent instanceof PsiImportHolder ||
|
||||
// PsiJavaFile or JavaDummyHolder
|
||||
modifierOwnerParent instanceof PsiClass psiClass &&
|
||||
(modifierOwnerParent instanceof PsiSyntheticClass ||
|
||||
PsiUtil.isAvailable(JavaFeature.INNER_STATICS, modifierOwnerParent) ||
|
||||
psiClass.getQualifiedName() != null ||
|
||||
!modifierOwnerParent.isPhysical());
|
||||
}
|
||||
else {
|
||||
if (PsiModifier.STATIC.equals(modifier) || privateOrProtected || PsiModifier.PACKAGE_LOCAL.equals(modifier)) {
|
||||
isAllowed = modifierOwnerParent instanceof PsiClass psiClass &&
|
||||
(PsiModifier.STATIC.equals(modifier) ||
|
||||
PsiUtil.isAvailable(JavaFeature.INNER_STATICS, modifierOwnerParent) ||
|
||||
psiClass.getQualifiedName() != null) ||
|
||||
FileTypeUtils.isInServerPageFile(modifierOwnerParent) ||
|
||||
// non-physical dummy holder might not have FQN
|
||||
!modifierOwnerParent.isPhysical();
|
||||
}
|
||||
if (privateOrProtected && !isAllowed) {
|
||||
fix = getFixFactory().createChangeModifierFix();
|
||||
}
|
||||
}
|
||||
|
||||
if (aClass.isEnum()) {
|
||||
isAllowed &=
|
||||
!PsiModifier.FINAL.equals(modifier) && !PsiModifier.ABSTRACT.equals(modifier) && !PsiModifier.SEALED.equals(modifier)
|
||||
&& !PsiModifier.VALUE.equals(modifier);
|
||||
}
|
||||
else if (aClass.isRecord()) {
|
||||
isAllowed &= !PsiModifier.ABSTRACT.equals(modifier);
|
||||
}
|
||||
|
||||
if (aClass.getContainingClass() instanceof PsiAnonymousClass &&
|
||||
privateOrProtected && !PsiUtil.getLanguageLevel(modifierOwnerParent).isAtLeast(LanguageLevel.JDK_16)) {
|
||||
isAllowed = false;
|
||||
}
|
||||
}
|
||||
if ((PsiModifier.NON_SEALED.equals(modifier) || PsiModifier.SEALED.equals(modifier)) &&
|
||||
modifierOwnerParent instanceof PsiDeclarationStatement) {
|
||||
isAllowed = false; // JLS 14.3
|
||||
message = JavaErrorBundle.message("modifier.not.allowed.on.local.classes", modifier);
|
||||
}
|
||||
else if (PsiModifier.NON_SEALED.equals(modifier) && !aClass.hasModifierProperty(PsiModifier.SEALED)) {
|
||||
isAllowed = Arrays.stream(aClass.getSuperTypes())
|
||||
.map(PsiClassType::resolve)
|
||||
.anyMatch(superClass -> superClass != null && superClass.hasModifierProperty(PsiModifier.SEALED));
|
||||
message = JavaErrorBundle.message("modifier.not.allowed.on.classes.without.sealed.super");
|
||||
}
|
||||
}
|
||||
else if (modifierOwner instanceof PsiMethod method) {
|
||||
isAllowed = !(method.isConstructor() && ourConstructorNotAllowedModifiers.contains(modifier));
|
||||
PsiClass containingClass = method.getContainingClass();
|
||||
if ((method.hasModifierProperty(PsiModifier.PUBLIC) || method.hasModifierProperty(PsiModifier.PROTECTED)) && method.isConstructor() &&
|
||||
containingClass != null && containingClass.isEnum()) {
|
||||
isAllowed = false;
|
||||
}
|
||||
|
||||
boolean isInterface = modifierOwnerParent instanceof PsiClass psiClass && psiClass.isInterface();
|
||||
if (PsiModifier.PRIVATE.equals(modifier) && modifierOwnerParent instanceof PsiClass psiClass) {
|
||||
isAllowed &= !isInterface || PsiUtil.isAvailable(JavaFeature.PRIVATE_INTERFACE_METHODS, modifierOwner) && !psiClass.isAnnotationType();
|
||||
}
|
||||
else if (PsiModifier.STRICTFP.equals(modifier)) {
|
||||
isAllowed &= !isInterface || PsiUtil.isAvailable(JavaFeature.EXTENSION_METHODS, modifierOwner);
|
||||
}
|
||||
else if (PsiModifier.PROTECTED.equals(modifier) ||
|
||||
PsiModifier.TRANSIENT.equals(modifier) ||
|
||||
PsiModifier.FINAL.equals(modifier)) {
|
||||
isAllowed &= !isInterface;
|
||||
}
|
||||
else if (PsiModifier.SYNCHRONIZED.equals(modifier)) {
|
||||
isAllowed &= !isInterface && (containingClass == null || !containingClass.isValueClass());
|
||||
}
|
||||
|
||||
if (containingClass != null && (containingClass.isInterface() || containingClass.isRecord())) {
|
||||
isAllowed &= !PsiModifier.NATIVE.equals(modifier);
|
||||
}
|
||||
|
||||
if (containingClass != null && containingClass.isAnnotationType()) {
|
||||
isAllowed &= !PsiModifier.STATIC.equals(modifier);
|
||||
isAllowed &= !PsiModifier.DEFAULT.equals(modifier);
|
||||
}
|
||||
|
||||
if (JavaPsiRecordUtil.getRecordComponentForAccessor(method) != null) {
|
||||
isAllowed &= !PsiModifier.STATIC.equals(modifier);
|
||||
}
|
||||
}
|
||||
else if (modifierOwner instanceof PsiField) {
|
||||
if (PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier) || PsiModifier.TRANSIENT.equals(modifier) ||
|
||||
PsiModifier.STRICTFP.equals(modifier)) {
|
||||
isAllowed = modifierOwnerParent instanceof PsiClass psiClass && !psiClass.isInterface();
|
||||
}
|
||||
}
|
||||
else if (modifierOwner instanceof PsiClassInitializer) {
|
||||
isAllowed = PsiModifier.STATIC.equals(modifier);
|
||||
}
|
||||
else if (modifierOwner instanceof PsiLocalVariable || modifierOwner instanceof PsiParameter) {
|
||||
isAllowed = PsiModifier.FINAL.equals(modifier);
|
||||
}
|
||||
else if (modifierOwner instanceof PsiReceiverParameter || modifierOwner instanceof PsiRecordComponent) {
|
||||
isAllowed = false;
|
||||
}
|
||||
|
||||
isAllowed &= incompatibles != null;
|
||||
if (!isAllowed) {
|
||||
if (message == null) message = JavaErrorBundle.message("modifier.not.allowed", modifier);
|
||||
HighlightInfo.Builder highlightInfo =
|
||||
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(keyword).descriptionAndTooltip(message);
|
||||
IntentionAction action = fix != null ? fix : getFixFactory().createModifierListFix(modifierList, modifier, false, false);
|
||||
highlightInfo.registerFix(action, null, null, null, null);
|
||||
return highlightInfo;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static @NotNull UnhandledExceptions collectUnhandledExceptions(@NotNull PsiTryStatement statement) {
|
||||
UnhandledExceptions thrownTypes = UnhandledExceptions.EMPTY;
|
||||
|
||||
|
||||
-2
@@ -760,8 +760,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
PsiElement parent = keyword.getParent();
|
||||
String text = keyword.getText();
|
||||
if (parent instanceof PsiModifierList psiModifierList) {
|
||||
if (!hasErrorResults()) add(HighlightUtil.checkNotAllowedModifier(keyword, psiModifierList));
|
||||
if (!hasErrorResults()) add(HighlightUtil.checkIllegalModifierCombination(keyword, psiModifierList));
|
||||
PsiElement pParent = psiModifierList.getParent();
|
||||
if (PsiModifier.ABSTRACT.equals(text) && pParent instanceof PsiMethod psiMethod) {
|
||||
if (!hasErrorResults()) {
|
||||
|
||||
+24
-1
@@ -25,6 +25,7 @@ import com.intellij.pom.java.JavaFeature;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.VariableKind;
|
||||
import com.intellij.psi.impl.light.LightRecordMethod;
|
||||
import com.intellij.psi.util.JavaPsiModifierUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
@@ -81,6 +82,7 @@ final class JavaErrorFixProvider {
|
||||
fix(kind, genericRemover);
|
||||
}
|
||||
|
||||
createModifierFixes();
|
||||
createClassFixes();
|
||||
createConstructorFixes();
|
||||
createMethodFixes();
|
||||
@@ -97,6 +99,27 @@ final class JavaErrorFixProvider {
|
||||
return ApplicationManager.getApplication().getService(JavaErrorFixProvider.class);
|
||||
}
|
||||
|
||||
private void createModifierFixes() {
|
||||
fix(MODIFIER_NOT_ALLOWED, error -> {
|
||||
@SuppressWarnings("MagicConstant") @PsiModifier.ModifierConstant String modifier = error.context();
|
||||
PsiModifierList list = (PsiModifierList)error.psi().getParent();
|
||||
if (list.getParent() instanceof PsiClass aClass && !aClass.isInterface()
|
||||
&& (PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier))) {
|
||||
return myFactory.createChangeModifierFix();
|
||||
}
|
||||
return removeModifierFix((PsiModifierListOwner)list.getParent(), modifier);
|
||||
});
|
||||
JavaFixProvider<PsiKeyword, Object> removeModifier = error -> {
|
||||
@SuppressWarnings("MagicConstant") @PsiModifier.ModifierConstant String modifier = error.psi().getText();
|
||||
PsiModifierList list = (PsiModifierList)error.psi().getParent();
|
||||
return removeModifierFix((PsiModifierListOwner)list.getParent(), modifier);
|
||||
};
|
||||
fix(MODIFIER_NOT_ALLOWED_LOCAL_CLASS, removeModifier);
|
||||
fix(MODIFIER_REPEATED, removeModifier);
|
||||
fix(MODIFIER_INCOMPATIBLE, removeModifier);
|
||||
fix(MODIFIER_NOT_ALLOWED_NON_SEALED, removeModifier);
|
||||
}
|
||||
|
||||
private void createMethodFixes() {
|
||||
JavaFixProvider<PsiMethod, Void> addBody = error -> myFactory.createAddMethodBodyFix(error.psi());
|
||||
fix(METHOD_DEFAULT_SHOULD_HAVE_BODY, addBody);
|
||||
@@ -563,7 +586,7 @@ final class JavaErrorFixProvider {
|
||||
|
||||
private @Nullable IntentionAction maybeAddModifierFix(@NotNull PsiModifierListOwner owner, @PsiModifier.ModifierConstant String modifier) {
|
||||
PsiModifierList modifierList = owner.getModifierList();
|
||||
if (modifierList != null && HighlightUtil.getIncompatibleModifier(modifier, modifierList) != null) return null;
|
||||
if (modifierList != null && JavaPsiModifierUtil.getIncompatibleModifier(modifier, modifierList) != null) return null;
|
||||
return addModifierFix(owner, modifier);
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -16,12 +16,12 @@
|
||||
package com.siyeh.ig.classlayout;
|
||||
|
||||
import com.intellij.codeInsight.AnnotationUtil;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.ProblemHighlightType;
|
||||
import com.intellij.psi.CommonClassNames;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiModifier;
|
||||
import com.intellij.psi.util.JavaPsiModifierUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
@@ -62,7 +62,7 @@ public final class FinalPrivateMethodInspection extends BaseInspection {
|
||||
if (!PsiUtil.isLanguageLevel9OrHigher(method) && AnnotationUtil.isAnnotated(method, CommonClassNames.JAVA_LANG_SAFE_VARARGS, 0)) {
|
||||
return;
|
||||
}
|
||||
if (!HighlightUtil.isLegalModifierCombination(method.getModifierList())) {
|
||||
if (!JavaPsiModifierUtil.isLegalModifierCombination(method.getModifierList())) {
|
||||
return;
|
||||
}
|
||||
registerModifierError(PsiModifier.FINAL, method, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, PsiModifier.FINAL);
|
||||
|
||||
+2
-2
@@ -15,10 +15,10 @@
|
||||
*/
|
||||
package com.siyeh.ig.classlayout;
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiModifier;
|
||||
import com.intellij.psi.util.JavaPsiModifierUtil;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
@@ -58,7 +58,7 @@ public final class FinalStaticMethodInspection extends BaseInspection {
|
||||
|| !method.hasModifierProperty(PsiModifier.STATIC)) {
|
||||
return;
|
||||
}
|
||||
if (!HighlightUtil.isLegalModifierCombination(method.getModifierList())) {
|
||||
if (!JavaPsiModifierUtil.isLegalModifierCombination(method.getModifierList())) {
|
||||
return;
|
||||
}
|
||||
registerModifierError(PsiModifier.FINAL, method, PsiModifier.FINAL);
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.psi.util;
|
||||
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Utilities related to Java PSI modifiers
|
||||
*
|
||||
* @see PsiModifier
|
||||
* @see PsiModifierList
|
||||
*/
|
||||
public final class JavaPsiModifierUtil {
|
||||
|
||||
private static final Map<String, Set<String>> ourInterfaceIncompatibleModifiers = ContainerUtil.<String, Set<String>>immutableMapBuilder()
|
||||
.put(PsiModifier.ABSTRACT, ContainerUtil.immutableSet())
|
||||
.put(PsiModifier.PACKAGE_LOCAL, ContainerUtil.immutableSet(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED))
|
||||
.put(PsiModifier.PRIVATE, ContainerUtil.immutableSet(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED))
|
||||
.put(PsiModifier.PUBLIC, ContainerUtil.immutableSet(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED))
|
||||
.put(PsiModifier.PROTECTED, ContainerUtil.immutableSet(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE))
|
||||
.put(PsiModifier.STRICTFP, ContainerUtil.immutableSet())
|
||||
.put(PsiModifier.STATIC, ContainerUtil.immutableSet())
|
||||
.put(PsiModifier.SEALED, ContainerUtil.immutableSet(PsiModifier.NON_SEALED))
|
||||
.put(PsiModifier.NON_SEALED, ContainerUtil.immutableSet(PsiModifier.SEALED))
|
||||
.build();
|
||||
private static final Map<String, Set<String>> ourMethodIncompatibleModifiers = ContainerUtil.<String, Set<String>>immutableMapBuilder()
|
||||
.put(PsiModifier.ABSTRACT, ContainerUtil.immutableSet(
|
||||
PsiModifier.NATIVE, PsiModifier.STATIC, PsiModifier.FINAL, PsiModifier.PRIVATE, PsiModifier.STRICTFP, PsiModifier.SYNCHRONIZED,
|
||||
PsiModifier.DEFAULT))
|
||||
.put(PsiModifier.NATIVE, ContainerUtil.immutableSet(PsiModifier.ABSTRACT, PsiModifier.STRICTFP))
|
||||
.put(PsiModifier.PACKAGE_LOCAL, ContainerUtil.immutableSet(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED))
|
||||
.put(PsiModifier.PRIVATE, ContainerUtil.immutableSet(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED))
|
||||
.put(PsiModifier.PUBLIC, ContainerUtil.immutableSet(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED))
|
||||
.put(PsiModifier.PROTECTED, ContainerUtil.immutableSet(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE))
|
||||
.put(PsiModifier.STATIC, ContainerUtil.immutableSet(PsiModifier.ABSTRACT, PsiModifier.DEFAULT))
|
||||
.put(PsiModifier.DEFAULT, ContainerUtil.immutableSet(PsiModifier.ABSTRACT, PsiModifier.STATIC, PsiModifier.PRIVATE))
|
||||
.put(PsiModifier.SYNCHRONIZED, ContainerUtil.immutableSet(PsiModifier.ABSTRACT))
|
||||
.put(PsiModifier.STRICTFP, ContainerUtil.immutableSet(PsiModifier.ABSTRACT))
|
||||
.put(PsiModifier.FINAL, ContainerUtil.immutableSet(PsiModifier.ABSTRACT))
|
||||
.build();
|
||||
|
||||
private static final Map<String, Set<String>> ourFieldIncompatibleModifiers = ContainerUtil.<String, Set<String>>immutableMapBuilder()
|
||||
.put(PsiModifier.FINAL, ContainerUtil.immutableSet(PsiModifier.VOLATILE))
|
||||
.put(PsiModifier.PACKAGE_LOCAL, ContainerUtil.immutableSet(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED))
|
||||
.put(PsiModifier.PRIVATE, ContainerUtil.immutableSet(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED))
|
||||
.put(PsiModifier.PUBLIC, ContainerUtil.immutableSet(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED))
|
||||
.put(PsiModifier.PROTECTED, ContainerUtil.immutableSet(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE))
|
||||
.put(PsiModifier.STATIC, ContainerUtil.immutableSet())
|
||||
.put(PsiModifier.TRANSIENT, ContainerUtil.immutableSet())
|
||||
.put(PsiModifier.VOLATILE, ContainerUtil.immutableSet(PsiModifier.FINAL))
|
||||
.build();
|
||||
private static final Map<String, Set<String>> ourClassIncompatibleModifiers = ContainerUtil.<String, Set<String>>immutableMapBuilder()
|
||||
.put(PsiModifier.ABSTRACT, ContainerUtil.immutableSet(PsiModifier.FINAL))
|
||||
.put(PsiModifier.FINAL, ContainerUtil.immutableSet(PsiModifier.ABSTRACT, PsiModifier.SEALED, PsiModifier.NON_SEALED))
|
||||
.put(PsiModifier.PACKAGE_LOCAL, ContainerUtil.immutableSet(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED))
|
||||
.put(PsiModifier.PRIVATE, ContainerUtil.immutableSet(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED))
|
||||
.put(PsiModifier.PUBLIC, ContainerUtil.immutableSet(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED))
|
||||
.put(PsiModifier.PROTECTED, ContainerUtil.immutableSet(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE))
|
||||
.put(PsiModifier.STRICTFP, ContainerUtil.immutableSet())
|
||||
.put(PsiModifier.STATIC, ContainerUtil.immutableSet())
|
||||
.put(PsiModifier.SEALED, ContainerUtil.immutableSet(PsiModifier.FINAL, PsiModifier.NON_SEALED))
|
||||
.put(PsiModifier.NON_SEALED, ContainerUtil.immutableSet(PsiModifier.FINAL, PsiModifier.SEALED))
|
||||
.put(PsiModifier.VALUE, ContainerUtil.immutableSet())
|
||||
.build();
|
||||
private static final Map<String, Set<String>> ourClassInitializerIncompatibleModifiers =
|
||||
ContainerUtil.<String, Set<String>>immutableMapBuilder()
|
||||
.put(PsiModifier.STATIC, ContainerUtil.immutableSet())
|
||||
.build();
|
||||
private static final Map<String, Set<String>> ourModuleIncompatibleModifiers = ContainerUtil.<String, Set<String>>immutableMapBuilder()
|
||||
.put(PsiModifier.OPEN, ContainerUtil.immutableSet())
|
||||
.build();
|
||||
private static final Map<String, Set<String>> ourRequiresIncompatibleModifiers = ContainerUtil.<String, Set<String>>immutableMapBuilder()
|
||||
.put(PsiModifier.STATIC, ContainerUtil.immutableSet())
|
||||
.put(PsiModifier.TRANSITIVE, ContainerUtil.immutableSet())
|
||||
.build();
|
||||
|
||||
private static String getIncompatibleModifier(@NotNull String modifier,
|
||||
@NotNull PsiModifierList modifierList,
|
||||
@NotNull Map<String, Set<String>> incompatibleModifiersHash) {
|
||||
// modifier is always incompatible with itself
|
||||
int modifierCount = 0;
|
||||
for (PsiElement otherModifier = modifierList.getFirstChild(); otherModifier != null; otherModifier = otherModifier.getNextSibling()) {
|
||||
if (modifier.equals(otherModifier.getText())) modifierCount++;
|
||||
}
|
||||
if (modifierCount > 1) return modifier;
|
||||
|
||||
Set<String> incompatibles = incompatibleModifiersHash.get(modifier);
|
||||
if (incompatibles == null) return null;
|
||||
PsiElement parent = modifierList.getParent();
|
||||
boolean level8OrHigher = PsiUtil.isLanguageLevel8OrHigher(modifierList);
|
||||
boolean level9OrHigher = PsiUtil.isLanguageLevel9OrHigher(modifierList);
|
||||
for (@PsiModifier.ModifierConstant String incompatible : incompatibles) {
|
||||
if (level8OrHigher) {
|
||||
if (modifier.equals(PsiModifier.STATIC) && incompatible.equals(PsiModifier.ABSTRACT)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (parent instanceof PsiMethod) {
|
||||
if (level9OrHigher && modifier.equals(PsiModifier.PRIVATE) && incompatible.equals(PsiModifier.PUBLIC)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (modifier.equals(PsiModifier.STATIC) && incompatible.equals(PsiModifier.FINAL)) {
|
||||
PsiClass containingClass = ((PsiMethod)parent).getContainingClass();
|
||||
if (containingClass == null || !containingClass.isInterface()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (modifierList.hasModifierProperty(incompatible)) {
|
||||
return incompatible;
|
||||
}
|
||||
if (PsiModifier.ABSTRACT.equals(incompatible) && modifierList.hasExplicitModifier(incompatible)) {
|
||||
return incompatible;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the supplied modifier list contains incompatible modifiers (e.g. "public private").
|
||||
*
|
||||
* @param modifierList a {@link PsiModifierList} to check
|
||||
* @return true if the supplied modifier list contains compatible modifiers
|
||||
*/
|
||||
public static boolean isLegalModifierCombination(@NotNull PsiModifierList modifierList) {
|
||||
for (PsiElement child = modifierList.getFirstChild(); child != null; child = child.getNextSibling()) {
|
||||
if (child instanceof PsiKeyword && getIncompatibleModifier(child.getText(), modifierList) != null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Map<String, Set<String>> getIncompatibleModifierMap(@NotNull PsiElement modifierListOwner) {
|
||||
if (PsiUtilCore.hasErrorElementChild(modifierListOwner)) return null;
|
||||
if (modifierListOwner instanceof PsiClass) {
|
||||
return ((PsiClass)modifierListOwner).isInterface() ? ourInterfaceIncompatibleModifiers : ourClassIncompatibleModifiers;
|
||||
}
|
||||
if (modifierListOwner instanceof PsiMethod) return ourMethodIncompatibleModifiers;
|
||||
if (modifierListOwner instanceof PsiVariable) return ourFieldIncompatibleModifiers;
|
||||
if (modifierListOwner instanceof PsiClassInitializer) return ourClassInitializerIncompatibleModifiers;
|
||||
if (modifierListOwner instanceof PsiJavaModule) return ourModuleIncompatibleModifiers;
|
||||
if (modifierListOwner instanceof PsiRequiresStatement) return ourRequiresIncompatibleModifiers;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param modifier modifier to check
|
||||
* @param modifierList modifier list to check
|
||||
* @return true if the modifier can be used in a given modifier list
|
||||
*/
|
||||
public static boolean isAllowed(@NotNull String modifier, @NotNull PsiModifierList modifierList) {
|
||||
PsiElement parent = modifierList.getParent();
|
||||
if (parent == null) return false;
|
||||
Map<String, Set<String>> incompatibleModifierMap = getIncompatibleModifierMap(parent);
|
||||
return incompatibleModifierMap == null || incompatibleModifierMap.containsKey(modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param modifier modifier to check (it does not yet belong to the modifier list supplied)
|
||||
* @param modifierList modifier list to check
|
||||
* @return a modifier that exists inside modifierList, which is incompatible with the supplied modifier
|
||||
*/
|
||||
public static @Nullable String getIncompatibleModifier(@NotNull String modifier, @NotNull PsiModifierList modifierList) {
|
||||
PsiElement parent = modifierList.getParent();
|
||||
if (parent == null) return null;
|
||||
Map<String, Set<String>> incompatibleModifierMap = getIncompatibleModifierMap(parent);
|
||||
return incompatibleModifierMap == null ? null : getIncompatibleModifier(modifier, modifierList, incompatibleModifierMap);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -20,7 +20,7 @@ sealed interface Foos {
|
||||
|
||||
sealed interface ID0 {}
|
||||
non-sealed interface ID1 extends ID0 {}
|
||||
<error descr="Modifier 'non-sealed' not allowed on classes that do not have a sealed superclass">non-sealed</error> interface ID extends ID1 {}
|
||||
<error descr="Modifier 'non-sealed' is not allowed on classes that do not have a sealed superclass">non-sealed</error> interface ID extends ID1 {}
|
||||
|
||||
enum ImplicitlySealedEnum {
|
||||
A {}
|
||||
|
||||
@@ -13,7 +13,7 @@ class Six extends Four {} // it's valid to extend a value class with an identity
|
||||
<error descr="Modifier 'value' not allowed here">value</error> enum Eight {}
|
||||
value record Nine(int no) {}
|
||||
<error descr="Illegal combination of modifiers 'sealed' and 'final'">sealed</error> value class Ten {}
|
||||
<error descr="Modifier 'non-sealed' not allowed on classes that do not have a sealed superclass">non-sealed</error> value class Eleven {}
|
||||
<error descr="Modifier 'non-sealed' is not allowed on classes that do not have a sealed superclass">non-sealed</error> value class Eleven {}
|
||||
abstract sealed value class Twelve {}
|
||||
value class Thirteen extends Twelve {
|
||||
void x() {
|
||||
|
||||
Reference in New Issue
Block a user