[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:
Tagir Valeev
2025-01-21 09:12:08 +00:00
committed by intellij-monorepo-bot
parent 2111b45002
commit ac754128ca
13 changed files with 417 additions and 315 deletions
@@ -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}''
@@ -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
@@ -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));
}
}
}
}
@@ -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")
@@ -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);
}
@@ -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;
@@ -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()) {
@@ -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);
}
@@ -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);
@@ -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);
}
}
@@ -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() {