From f1d7de98edeeb1d163b48dccaeb5c1459de2a495 Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Tue, 16 Jun 2020 13:13:56 +0200 Subject: [PATCH] java sealed types: permits list highlighting (IDEA-243471) GitOrigin-RevId: ca4b23403d6926d1c093eb0e2774020a98d83773 --- .../impl/analysis/HighlightClassUtil.java | 125 +++++++++++++++--- .../impl/analysis/HighlightVisitorImpl.java | 5 +- .../src/messages/JavaErrorBundle.properties | 7 + .../PermitsList.java | 20 +++ .../LightSealedTypesHighlightingTest.java | 5 + 5 files changed, 146 insertions(+), 16 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingSealedTypes/PermitsList.java diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightClassUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightClassUtil.java index 312bbd0f4533..aecbf3457688 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightClassUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightClassUtil.java @@ -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; + } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java index 60a905e6a275..b4a1a2c0905c 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java @@ -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)); } } diff --git a/java/java-psi-impl/src/messages/JavaErrorBundle.properties b/java/java-psi-impl/src/messages/JavaErrorBundle.properties index ba34d1e2b5bd..7007b6869d10 100644 --- a/java/java-psi-impl/src/messages/JavaErrorBundle.properties +++ b/java/java-psi-impl/src/messages/JavaErrorBundle.properties @@ -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 diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingSealedTypes/PermitsList.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingSealedTypes/PermitsList.java new file mode 100644 index 000000000000..dabe372e9c71 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingSealedTypes/PermitsList.java @@ -0,0 +1,20 @@ +package p; +interface I0 permits I {} + +sealed interface I extends I0 permits Unresolved, I1, I1{} +non-sealed interface I1 extends I {} + +sealed interface A {} +class Usage { + { + class Local implements A {} + A a = new A() {}; + } +} + + +sealed interface Indirect permits IndirectInheritor, MiddleMan {} +non-sealed interface MiddleMan extends Indirect {} +final class IndirectInheritor implements MiddleMan {} + +sealed class AnotherPackage permits p1.P1 {} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightSealedTypesHighlightingTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightSealedTypesHighlightingTest.java index d2b3a14310e5..d88e350ced37 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightSealedTypesHighlightingTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightSealedTypesHighlightingTest.java @@ -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");