Java: add error for class extending sealed class from another package (IDEA-358672)

GitOrigin-RevId: d7b0c68100ddc60f42bfedc1f3b88e54cbd3ce74
This commit is contained in:
Bas Leijdekkers
2024-10-29 01:52:20 +00:00
committed by intellij-monorepo-bot
parent 8b80330e6e
commit a016f884d3
8 changed files with 54 additions and 14 deletions
@@ -186,8 +186,9 @@ move.catch.up.family=Move 'catch' up
move.catch.up.text=Move catch for ''{0}'' before ''{1}''
move.class.to.separate.file.family=Move class to separate file
move.class.to.separate.file.text=Move class ''{0}'' to ''{0}.java''
move.class.to.package.family=Move Class to Package
move.class.to.package.family=Move to package
move.class.to.package.text=Move to package ''{0}''
move.class.0.to.package.text=Move {0} ''{1}'' to package ''{2}''
# change if (!a == b) ... => if (!(a == b)) ...
negation.broader.scope.family=Negation broader scope
@@ -27,6 +27,7 @@ import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.JavaFeature;
import com.intellij.pom.java.LanguageLevel;
@@ -1148,6 +1149,23 @@ public final class HighlightClassUtil {
return info;
}
if (!JavaPsiFacade.getInstance(aClass.getProject()).arePackagesTheSame(aClass, superClass) &&
JavaModuleGraphUtil.findDescriptorByElement(aClass) == null) {
String description = StringUtil.capitalize(JavaErrorBundle.message(
"class.not.allowed.to.extend.sealed.class.from.another.package",
JavaElementKind.fromElement(aClass).subject(), HighlightUtil.formatClass(aClass, false),
JavaElementKind.fromElement(superClass).object(), HighlightUtil.formatClass(superClass, true)));
HighlightInfo.Builder info =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(elementToHighlight).descriptionAndTooltip(description);
PsiFile parentFile = superClass.getContainingFile();
if (parentFile instanceof PsiClassOwner classOwner) {
String parentPackage = classOwner.getPackageName();
IntentionAction action = QuickFixFactory.getInstance().createMoveClassToPackageFix(aClass, parentPackage);
info.registerFix(action, null, null, null, null);
}
return info;
}
PsiClassType[] permittedTypes = superClass.getPermitsListTypes();
if (permittedTypes.length > 0) {
PsiManager manager = superClass.getManager();
@@ -1262,12 +1280,15 @@ public final class HighlightClassUtil {
}
else {
if (currentModule == null && !psiFacade.arePackagesTheSame(aClass, inheritorClass)) {
HighlightInfo.Builder info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
.range(permitted)
.descriptionAndTooltip(JavaErrorBundle.message("class.not.allowed.to.extend.sealed.class.from.another.package"));
String description = StringUtil.capitalize(
JavaErrorBundle.message("class.not.allowed.to.extend.sealed.class.from.another.package",
JavaElementKind.fromElement(inheritorClass).subject(), HighlightUtil.formatClass(inheritorClass, true),
JavaElementKind.fromElement(aClass).object(), HighlightUtil.formatClass(aClass, false)));
HighlightInfo.Builder info =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(permitted).descriptionAndTooltip(description);
PsiFile parentFile = aClass.getContainingFile();
if (parentFile instanceof PsiClassOwner) {
String parentPackage = ((PsiClassOwner)parentFile).getPackageName();
if (parentFile instanceof PsiClassOwner classOwner) {
String parentPackage = classOwner.getPackageName();
IntentionAction action = QuickFixFactory.getInstance().createMoveClassToPackageFix(inheritorClass, parentPackage);
info.registerFix(action, null, null, null, null);
}
@@ -967,7 +967,7 @@ public final class QuickFixFactoryImpl extends QuickFixFactory {
@Override
public @NotNull IntentionAction createMoveClassToPackageFix(@NotNull PsiClass classToMove, @NotNull String packageName) {
return new MoveToPackageFix(classToMove.getContainingFile(), packageName);
return new MoveToPackageFix(classToMove, packageName);
}
@Override
@@ -10,6 +10,7 @@ import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.psi.*;
import com.intellij.psi.util.JavaElementKind;
import com.intellij.refactoring.JavaRefactoringFactory;
import com.intellij.refactoring.PackageWrapper;
import com.intellij.refactoring.move.moveClassesOrPackages.SingleSourceRootMoveDestination;
@@ -22,14 +23,29 @@ import org.jetbrains.annotations.Nullable;
public class MoveToPackageFix extends LocalQuickFixAndIntentionActionOnPsiElement {
private static final Logger LOG = Logger.getInstance(MoveToPackageFix.class);
private final String myTargetPackage;
private final @Nullable SmartPsiElementPointer<@NotNull PsiClass> myClass;
public MoveToPackageFix(@NotNull PsiFile psiFile, @NotNull String targetPackage) {
super(psiFile);
myTargetPackage = targetPackage;
myClass = null;
}
public MoveToPackageFix(@NotNull PsiClass aClass, @NotNull String targetPackage) {
super(aClass.getContainingFile());
myTargetPackage = targetPackage;
myClass = SmartPointerManager.getInstance(aClass.getProject()).createSmartPsiElementPointer(aClass);
}
@Override
public @IntentionName @NotNull String getText() {
if (myClass != null) {
PsiClass aClass = myClass.getElement();
if (aClass != null) {
return QuickFixBundle.message("move.class.0.to.package.text",
JavaElementKind.fromElement(aClass).object(), aClass.getName(), myTargetPackage);
}
}
return QuickFixBundle.message("move.class.to.package.text", myTargetPackage);
}
@@ -581,7 +581,7 @@ permits.list.generics.are.not.allowed=Generics are not allowed in permits list
sealed.cannot.be.functional.interface=Sealed class can not be used as functional interface
local.classes.must.not.extend.sealed.classes=Local classes must not extend sealed classes
anonymous.classes.must.not.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.package={0} ''{1}'' from another package not allowed to extend sealed {2} ''{3}'' in unnamed module
class.not.allowed.to.extend.sealed.class.from.another.module=Class is not allowed to extend sealed class from another module
annotation.cannot.be.local=Local annotations are not allowed
create.class.action.this.not.valid.java.qualified.name=This is not a valid Java qualified name
@@ -20,7 +20,8 @@ sealed interface Indirect permits <error descr="Invalid permits clause: 'Indirec
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> {}
sealed class AnotherPackage permits <error descr="Class 'p1.P1' from another package not allowed to extend sealed class 'AnotherPackage' in unnamed module">p1.P1</error> {}
final class Mail extends <error descr="Class 'Mail' from another package not allowed to extend sealed class 'p1.Envelope' in unnamed module">p1.Envelope</error> {}
enum ImlicitlySealedWithPermitsClause <error descr="'permits' not allowed on enum">permits</error> FOO {
FOO {};
@@ -1,4 +1,4 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.psi.PsiClass;
@@ -9,20 +9,20 @@ import org.jetbrains.annotations.NotNull;
public class MoveToPackageTest extends LightJavaCodeInsightFixtureTestCase {
@Override
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
return LightJavaCodeInsightFixtureTestCase.JAVA_LATEST_WITH_LATEST_JDK;
return JAVA_LATEST_WITH_LATEST_JDK;
}
public void testSimple() {
PsiClass bClass = myFixture.addClass("package foo;\nimport bar.A;\npublic final class B extends A {}");
myFixture.configureByText("A.java", "package bar;\n import foo.B;\npublic sealed class A permits <caret>B {}");
invokeFix("Move to package 'bar'");
invokeFix("Move class 'B' to package 'bar'");
assertEquals("package bar;\n\npublic final class B extends A {}", bClass.getContainingFile().getText());
}
public void testNestedClass() {
PsiClass bClass = myFixture.addClass("package foo;\nimport bar.A;\npublic class B { public static final class C extends A {} }");
myFixture.configureByText("A.java", "package bar;\n import foo.B;\npublic sealed class A permits <caret>B.C {}");
invokeFix("Move to package 'bar'");
invokeFix("Move class 'C' to package 'bar'");
assertEquals("package bar;\n\npublic class B { public static final class C extends A {} }",
bClass.getContainingFile().getText());
}
@@ -29,7 +29,8 @@ public class LightSealedTypesHighlightingTest extends LightJavaCodeInsightFixtur
public void testPermitsList() {
myFixture.addClass("package p1; public class P1 extends p.AnotherPackage {}");
myFixture.addClass("package p; public class P extends A {}");
doTest();
myFixture.addClass("package p1; public sealed class Envelope permits p.Mail {}");
doTest();
}
public void testPermitsListInLibrarySources() {