java sealed types: permits list highlighting (IDEA-243471)

GitOrigin-RevId: ca4b23403d6926d1c093eb0e2774020a98d83773
This commit is contained in:
Anna Kozlova
2020-06-16 17:13:12 +03:00
committed by intellij-monorepo-bot
parent a4146e3008
commit f1d7de98ed
5 changed files with 146 additions and 16 deletions
@@ -31,9 +31,12 @@ import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PackageScope;
import com.intellij.psi.search.searches.DirectClassInheritorsSearch;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.*;
import com.intellij.util.JavaPsiConstructorUtil;
import com.intellij.util.ObjectUtils;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -964,21 +967,6 @@ public class HighlightClassUtil {
return null;
}
public static HighlightInfo checkWellFormedSealedInheritor(PsiClass psiClass) {
if (!psiClass.hasModifierProperty(PsiModifier.SEALED) &&
!psiClass.hasModifierProperty(PsiModifier.NON_SEALED) &&
!psiClass.hasModifierProperty(PsiModifier.FINAL)) {
PsiIdentifier nameIdentifier = psiClass.getNameIdentifier();
if (nameIdentifier == null) return null;
if (Arrays.stream(psiClass.getSuperTypes()).map(superType -> superType.resolve())
.anyMatch(superClass -> superClass != null && superClass.hasModifierProperty(PsiModifier.SEALED))) {
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(nameIdentifier)
.descriptionAndTooltip(JavaErrorBundle.message("sealed.type.inheritor.expected.modifiers", PsiModifier.SEALED, PsiModifier.NON_SEALED, PsiModifier.FINAL)).create();
}
}
return null;
}
public static HighlightInfo checkIllegalInstanceMemberInRecord(PsiMember member) {
if (!member.hasModifierProperty(PsiModifier.STATIC)) {
PsiClass aClass = member.getContainingClass();
@@ -1016,6 +1004,12 @@ public class HighlightClassUtil {
static HighlightInfo checkExtendsSealedClass(PsiClass aClass, PsiClass superClass, PsiJavaCodeReferenceElement elementToHighlight) {
if (superClass.hasModifierProperty(PsiModifier.SEALED)) {
if (PsiUtil.isLocalClass(aClass)) {
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
.range(elementToHighlight)
.descriptionAndTooltip(JavaErrorBundle.message("local.classes.must.extend.sealed.classes")).create();
}
PsiClassType[] permittedTypes = superClass.getPermitsListTypes();
if (permittedTypes.length > 0) {
if (Arrays.stream(permittedTypes).map(permittedType -> permittedType.resolve()).anyMatch(permittedClass -> aClass.equals(permittedClass))) {
@@ -1037,4 +1031,105 @@ public class HighlightClassUtil {
}
return null;
}
public static HighlightInfo checkAnonymousSealedProhibited(PsiNewExpression newExpression) {
PsiAnonymousClass aClass = newExpression.getAnonymousClass();
if (aClass != null) {
PsiClass superClass = aClass.getBaseClassType().resolve();
if (superClass != null && superClass.hasModifierProperty(PsiModifier.SEALED)) {
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
.range(aClass.getBaseClassReference())
.descriptionAndTooltip(JavaErrorBundle.message("anonymous.classes.must.extend.sealed.classes")).create();
}
}
return null;
}
static void checkPermitsList(PsiReferenceList list,
HighlightInfoHolder holder) {
PsiElement parent = list.getParent();
if (parent instanceof PsiClass && list.equals(((PsiClass)parent).getPermitsList())) {
PsiClass aClass = (PsiClass)parent;
PsiIdentifier nameIdentifier = aClass.getNameIdentifier();
if (nameIdentifier == null) return;
if (!aClass.hasModifierProperty(PsiModifier.SEALED)) {
holder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
.range(list.getFirstChild())
.descriptionAndTooltip(JavaErrorBundle.message("invalid.permits.clause", aClass.getName()))
.create());
}
PsiJavaModule currentModule = JavaModuleGraphUtil.findDescriptorByElement(aClass);
JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(aClass.getProject());
for (PsiJavaCodeReferenceElement permitted : list.getReferenceElements()) {
@Nullable PsiElement resolve = permitted.resolve();
if (resolve instanceof PsiClass) {
PsiClass inheritorClass = (PsiClass)resolve;
if (Arrays.stream(inheritorClass.getSuperTypes()).noneMatch(type -> aClass.equals(type.resolve()))) {
holder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(permitted)
.descriptionAndTooltip(JavaErrorBundle.message("invalid.permits.clause.direct.implementation",
inheritorClass.getName(),
inheritorClass.isInterface() == aClass.isInterface() ? 1 : 2,
aClass.getName()))
.create());
}
else {
if (currentModule == null && !psiFacade.arePackagesTheSame(aClass, inheritorClass)) {
holder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
.range(permitted)
.descriptionAndTooltip(JavaErrorBundle.message("class.not.allowed.to.extend.sealed.class.from.another.package"))
.create());
}
else if (currentModule != null && currentModule != JavaModuleGraphUtil.findDescriptorByElement(inheritorClass)) {
holder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
.range(permitted)
.descriptionAndTooltip(JavaErrorBundle.message("class.not.allowed.to.extend.sealed.class.from.another.module"))
.create());
}
}
}
}
}
}
public static HighlightInfo checkSealedNonEnumeratedInheritors(PsiClass psiClass) {
if (psiClass.hasModifierProperty(PsiModifier.SEALED) && psiClass.getPermitsListTypes().length == 0) {
PsiIdentifier nameIdentifier = psiClass.getNameIdentifier();
if (nameIdentifier == null) return null;
PsiJavaModule currentModule = JavaModuleGraphUtil.findDescriptorByElement(psiClass);
GlobalSearchScope searchScope = null;
if (currentModule == null) {
PsiClassOwner classOwner = ObjectUtils.tryCast(psiClass.getContainingFile(), PsiClassOwner.class);
if (classOwner != null) {
String packageName = classOwner.getPackageName();
PsiPackage aPackage = JavaPsiFacade.getInstance(psiClass.getProject()).findPackage(packageName);
searchScope = PackageScope.packageScope(Objects.requireNonNull(aPackage), false);
}
}
else {
searchScope = currentModule.getResolveScope();
}
if (searchScope != null && !DirectClassInheritorsSearch.search(psiClass, searchScope).anyMatch(c -> !PsiUtil.isLocalOrAnonymousClass(c))) {
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
.range(nameIdentifier)
.descriptionAndTooltip(JavaErrorBundle.message("sealed.must.have.inheritors"))
.create();
}
}
return null;
}
public static HighlightInfo checkSealedSuper(PsiClass aClass) {
PsiIdentifier nameIdentifier = aClass.getNameIdentifier();
if (nameIdentifier != null &&
!aClass.hasModifierProperty(PsiModifier.SEALED) &&
!aClass.hasModifierProperty(PsiModifier.NON_SEALED) &&
!aClass.hasModifierProperty(PsiModifier.FINAL) &&
Arrays.stream(aClass.getSuperTypes()).map(type -> type.resolve()).anyMatch(superClass -> superClass != null && superClass.hasModifierProperty(PsiModifier.SEALED))) {
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(nameIdentifier)
.descriptionAndTooltip(JavaErrorBundle.message("sealed.type.inheritor.expected.modifiers", PsiModifier.SEALED, PsiModifier.NON_SEALED, PsiModifier.FINAL)).create();
}
return null;
}
}
@@ -473,7 +473,8 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkClassAndPackageConflict(aClass));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkPublicClassInRightFile(aClass));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkWellFormedRecord(aClass));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkWellFormedSealedInheritor(aClass));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkSealedNonEnumeratedInheritors(aClass));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkSealedSuper(aClass));
if (!myHolder.hasErrorResults()) myHolder.add(GenericsHighlightUtil.checkTypeParameterOverrideEquivalentMethods(aClass, myLanguageLevel));
}
@@ -1066,6 +1067,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
myHolder.add(HighlightUtil.checkUnhandledExceptions(expression, classReference != null ? classReference.getTextRange() : null));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkAnonymousInheritFinal(expression));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkAnonymousInheritProhibited(expression));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkAnonymousSealedProhibited(expression));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkQualifiedNew(expression, type, aClass));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkCreateInnerClassFromStaticContext(expression, type, aClass));
if (!myHolder.hasErrorResults()) myHolder.add(GenericsHighlightUtil.checkTypeParameterInstantiation(expression));
@@ -1649,6 +1651,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkExtendsAllowed(list));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkImplementsAllowed(list));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkClassExtendsOnlyOneClass(list));
if (!myHolder.hasErrorResults()) HighlightClassUtil.checkPermitsList(list, myHolder);
if (!myHolder.hasErrorResults()) myHolder.add(GenericsHighlightUtil.checkGenericCannotExtendException(list));
}
}
@@ -477,3 +477,10 @@ safevarargs.not.allowed.on.methods.with.fixed.arity=@SafeVarargs is not allowed
functional.interface.must.not.be.sealed.error.description=Functional interface can''t be declared as ''{0}''
sealed.type.inheritor.expected.modifiers={0}, {1} or {2} modifiers expected
not.allowed.in.sealed.hierarchy={0} is not allowed in the sealed hierarchy
invalid.permits.clause=Invalid permits clause: ''{0}'' must be sealed
invalid.permits.clause.direct.implementation=Invalid permits clause: ''{0}'' must directly {1, choice, 1#extend|2#implement} ''{2}''
sealed.must.have.inheritors=Sealed class must have subclasses
local.classes.must.extend.sealed.classes=Local classes must not extend sealed classes
anonymous.classes.must.extend.sealed.classes=Anonymous classes must not extend sealed classes
class.not.allowed.to.extend.sealed.class.from.another.package=Class is not allowed to extend sealed class from another package
class.not.allowed.to.extend.sealed.class.from.another.module=Class is not allowed to extend sealed class from another module
@@ -0,0 +1,20 @@
package p;
interface I0 <error descr="Invalid permits clause: 'I0' must be sealed">permits</error> I {}
sealed interface I extends I0 permits <error descr="Cannot resolve symbol 'Unresolved'">Unresolved</error>, <error descr="Duplicate class: 'p.I1'">I1</error>, <error descr="Duplicate class: 'p.I1'">I1</error>{}
non-sealed interface I1 extends I {}
sealed interface <error descr="Sealed class must have subclasses">A</error> {}
class Usage {
{
class Local implements <error descr="Local classes must not extend sealed classes">A</error> {}
A a = new <error descr="Anonymous classes must not extend sealed classes">A</error>() {};
}
}
sealed interface Indirect permits <error descr="Invalid permits clause: 'IndirectInheritor' must directly implement 'Indirect'">IndirectInheritor</error>, MiddleMan {}
non-sealed interface MiddleMan extends Indirect {}
final class IndirectInheritor implements MiddleMan {}
sealed class AnotherPackage permits <error descr="Class is not allowed to extend sealed class from another package">p1.P1</error> {}
@@ -21,6 +21,11 @@ public class LightSealedTypesHighlightingTest extends LightJavaCodeInsightFixtur
public void testSealedTypesBasics() { doTest(); }
public void testSealedFunctionalInterface() { doTest(); }
public void testSealedRestrictedTypes() { doTest(); }
public void testPermitsList() {
myFixture.addClass("package p1; public class P1 extends p.AnotherPackage {}");
myFixture.addClass("package p; public class P {}");
doTest();
}
private void doTest() {
myFixture.configureByFile(getTestName(false) + ".java");