mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
FillPermitsListAction: intention to add missing inheritors to permits list (IDEA-243901)
GitOrigin-RevId: a0fc70e03b6339890e8fe42ac651fe0ac910d02b
This commit is contained in:
committed by
intellij-monorepo-bot
parent
14f7907d29
commit
8eaa9d4c52
@@ -1880,6 +1880,10 @@
|
||||
<className>com.intellij.codeInsight.intention.impl.SealClassAction</className>
|
||||
<category>Java/Declaration</category>
|
||||
</intentionAction>
|
||||
<intentionAction>
|
||||
<className>com.intellij.codeInsight.intention.impl.FillPermitsListAction</className>
|
||||
<category>Java/Declaration</category>
|
||||
</intentionAction>
|
||||
<intentionAction>
|
||||
<className>com.intellij.codeInsight.intention.impl.UnwrapElseBranchAction</className>
|
||||
<category>Java/Control Flow</category>
|
||||
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.codeInsight.intention.impl;
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.HighlightingFeature;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil;
|
||||
import com.intellij.codeInsight.intention.BaseElementAtCaretIntentionAction;
|
||||
import com.intellij.codeInspection.util.IntentionFamilyName;
|
||||
import com.intellij.codeInspection.util.IntentionName;
|
||||
import com.intellij.java.JavaBundle;
|
||||
import com.intellij.lang.java.JavaLanguage;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.search.searches.ClassInheritorsSearch;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.refactoring.util.CommonRefactoringUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.Query;
|
||||
import com.intellij.util.SmartList;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.intellij.util.ObjectUtils.tryCast;
|
||||
|
||||
public class FillPermitsListAction extends BaseElementAtCaretIntentionAction {
|
||||
@Override
|
||||
public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) {
|
||||
if (!HighlightingFeature.SEALED_CLASSES.isAvailable(element)) return false;
|
||||
PsiIdentifier identifier = tryCast(element, PsiIdentifier.class);
|
||||
if (identifier == null) return false;
|
||||
PsiClass psiClass = tryCast(identifier.getParent(), PsiClass.class);
|
||||
if (psiClass == null || !(psiClass.getContainingFile() instanceof PsiJavaFile)) return false;
|
||||
PsiModifierList modifiers = psiClass.getModifierList();
|
||||
if (modifiers == null || !modifiers.hasExplicitModifier(PsiModifier.SEALED)) return false;
|
||||
PsiJavaCodeReferenceElement[] permittedRefs = getPermittedRefs(psiClass);
|
||||
Collection<PsiClass> sameFileInheritors = sameFileInheritors(psiClass).findAll();
|
||||
if (permittedRefs.length < sameFileInheritors.size()) return true;
|
||||
Set<PsiClass> permittedClasses = getPermittedClasses(permittedRefs);
|
||||
for (PsiClass inheritor : sameFileInheritors) {
|
||||
if (PsiUtil.isLocalOrAnonymousClass(inheritor)) return false;
|
||||
if (!permittedClasses.remove(inheritor)) return true;
|
||||
}
|
||||
for (PsiClass inheritor : outsideInheritors(psiClass)) {
|
||||
if (PsiUtil.isLocalOrAnonymousClass(inheritor)) return false;
|
||||
if (!permittedClasses.remove(inheritor)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException {
|
||||
PsiClass psiClass = PsiTreeUtil.getParentOfType(element, PsiClass.class);
|
||||
if (psiClass == null) return;
|
||||
PsiJavaFile psiJavaFile = tryCast(psiClass.getContainingFile(), PsiJavaFile.class);
|
||||
if (psiJavaFile == null) return;
|
||||
Set<PsiClass> permittedClasses = getPermittedClasses(getPermittedRefs(psiClass));
|
||||
Collection<String> missingInheritors = getMissingInheritors(project, editor, psiJavaFile, psiClass, permittedClasses);
|
||||
if (missingInheritors == null) return;
|
||||
fillPermitsList(psiClass, missingInheritors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @IntentionName @NotNull String getText() {
|
||||
return getFamilyName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull @IntentionFamilyName String getFamilyName() {
|
||||
return JavaBundle.message("intention.family.name.fill.permits.list");
|
||||
}
|
||||
|
||||
private static PsiJavaCodeReferenceElement @NotNull [] getPermittedRefs(@NotNull PsiClass psiClass) {
|
||||
PsiReferenceList permitsList = psiClass.getPermitsList();
|
||||
return permitsList == null ? PsiJavaCodeReferenceElement.EMPTY_ARRAY : permitsList.getReferenceElements();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Collection<String> getMissingInheritors(@NotNull Project project,
|
||||
Editor editor,
|
||||
@NotNull PsiJavaFile psiJavaFile,
|
||||
@NotNull PsiClass psiClass,
|
||||
@NotNull Set<PsiClass> permittedClasses) {
|
||||
Collection<String> missingInheritors = new SmartList<>();
|
||||
PsiJavaModule module = JavaModuleGraphUtil.findDescriptorByElement(psiClass);
|
||||
for (PsiClass inheritor : ClassInheritorsSearch.search(psiClass, false)) {
|
||||
String errorTitle = SealClassAction.checkInheritor(psiJavaFile, module, inheritor);
|
||||
if (errorTitle != null) {
|
||||
reportError(project, editor, JavaBundle.message(errorTitle));
|
||||
return null;
|
||||
}
|
||||
String qualifiedName = Objects.requireNonNull(inheritor.getQualifiedName());
|
||||
if (!permittedClasses.contains(inheritor)) missingInheritors.add(qualifiedName);
|
||||
}
|
||||
|
||||
if (missingInheritors.isEmpty()) {
|
||||
String message = JavaBundle.message("intention.error.fill.permits.list.no.missing.inheritors");
|
||||
reportError(project, editor, message);
|
||||
return null;
|
||||
}
|
||||
return missingInheritors;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Set<PsiClass> getPermittedClasses(PsiJavaCodeReferenceElement @NotNull [] permittedRefs) {
|
||||
return Arrays.stream(permittedRefs)
|
||||
.map(ref -> tryCast(ref.resolve(), PsiClass.class))
|
||||
.filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private static @NotNull Query<PsiClass> outsideInheritors(@NotNull PsiClass parent) {
|
||||
GlobalSearchScope scope = GlobalSearchScope.notScope(GlobalSearchScope.fileScope(parent.getContainingFile()));
|
||||
return ClassInheritorsSearch.search(parent, scope, false);
|
||||
}
|
||||
|
||||
private static @NotNull Query<PsiClass> sameFileInheritors(@NotNull PsiClass parent) {
|
||||
return ClassInheritorsSearch.search(parent, GlobalSearchScope.fileScope(parent.getContainingFile()), false);
|
||||
}
|
||||
|
||||
private static void fillPermitsList(@NotNull PsiClass parent, @NotNull Collection<String> missingInheritors) {
|
||||
PsiReferenceList permitsList = parent.getPermitsList();
|
||||
PsiFileFactory factory = PsiFileFactory.getInstance(parent.getProject());
|
||||
if (permitsList == null) {
|
||||
PsiReferenceList implementsList = Objects.requireNonNull(parent.getImplementsList());
|
||||
String permitsClause = StreamEx.of(missingInheritors).sorted().joining(",", "permits ", "");
|
||||
parent.addAfter(createPermitsClause(factory, permitsClause), implementsList);
|
||||
}
|
||||
else {
|
||||
Stream<String> curClasses = Arrays.stream(permitsList.getReferenceElements()).map(ref -> ref.getQualifiedName());
|
||||
String permitsClause = StreamEx.of(missingInheritors).append(curClasses).sorted().joining(",", "permits ", "");
|
||||
permitsList.replace(createPermitsClause(factory, permitsClause));
|
||||
}
|
||||
}
|
||||
|
||||
private static void reportError(@NotNull Project project, @NotNull Editor editor, @NotNull String message) {
|
||||
String title = JavaBundle.message("intention.fill.permits.list.hint.title");
|
||||
CommonRefactoringUtil.showErrorHint(project, editor, JavaBundle.message(message), title, null);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PsiReferenceList createPermitsClause(@NotNull PsiFileFactory factory, @NotNull String permitsClause) {
|
||||
PsiJavaFile javaFile = (PsiJavaFile)factory.createFileFromText(JavaLanguage.INSTANCE, "class __Dummy " + permitsClause + "{}");
|
||||
PsiClass newClass = javaFile.getClasses()[0];
|
||||
return Objects.requireNonNull(newClass.getPermitsList());
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import com.intellij.util.SequentialTask;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.PropertyKey;
|
||||
|
||||
import java.util.*;
|
||||
@@ -84,29 +85,12 @@ public class SealClassAction extends BaseElementAtCaretIntentionAction {
|
||||
List<PsiClass> inheritors = new ArrayList<>();
|
||||
Ref<String> message = new Ref<>();
|
||||
ClassInheritorsSearch.search(aClass, false).forEach(inheritor -> {
|
||||
if (PsiUtil.isLocalOrAnonymousClass(inheritor)) {
|
||||
message.set("intention.error.make.sealed.class.has.anonymous.or.local.inheritors");
|
||||
String errorTitle = checkInheritor(parentFile, module, inheritor);
|
||||
if (errorTitle != null) {
|
||||
message.set(errorTitle);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (module == null) {
|
||||
PsiJavaFile file = tryCast(inheritor.getContainingFile(), PsiJavaFile.class);
|
||||
if (file == null) {
|
||||
message.set("intention.error.make.sealed.class.inheritors.not.in.java.file");
|
||||
return false;
|
||||
}
|
||||
if (!parentFile.getPackageName().equals(file.getPackageName())) {
|
||||
message.set("intention.error.make.sealed.class.different.packages");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (JavaModuleGraphUtil.findDescriptorByElement(inheritor) != module) {
|
||||
message.set("intention.error.make.sealed.class.different.modules");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inheritors.add(inheritor);
|
||||
return true;
|
||||
});
|
||||
@@ -144,6 +128,26 @@ public class SealClassAction extends BaseElementAtCaretIntentionAction {
|
||||
});
|
||||
}
|
||||
|
||||
static @Nullable String checkInheritor(@NotNull PsiJavaFile parentFile, @Nullable PsiJavaModule module, @NotNull PsiClass inheritor) {
|
||||
if (PsiUtil.isLocalOrAnonymousClass(inheritor)) {
|
||||
return "intention.error.make.sealed.class.has.anonymous.or.local.inheritors";
|
||||
}
|
||||
|
||||
if (module == null) {
|
||||
PsiJavaFile file = tryCast(inheritor.getContainingFile(), PsiJavaFile.class);
|
||||
if (file == null) return "intention.error.make.sealed.class.inheritors.not.in.java.file";
|
||||
if (!parentFile.getPackageName().equals(file.getPackageName())) {
|
||||
return "intention.error.make.sealed.class.different.packages";
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (JavaModuleGraphUtil.findDescriptorByElement(inheritor) != module) {
|
||||
return "intention.error.make.sealed.class.different.modules";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void showError(@NotNull Project project, Editor editor, @PropertyKey(resourceBundle = JavaBundle.BUNDLE) String message) {
|
||||
CommonRefactoringUtil.showErrorHint(project, editor, JavaBundle.message(message), getErrorTitle(), null);
|
||||
}
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Add missing inheritors to permits list" "true"
|
||||
|
||||
sealed class A permits B, C {}
|
||||
|
||||
final class B extends A {}
|
||||
|
||||
final class C extends A {}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// "Add missing inheritors to permits list" "true"
|
||||
|
||||
sealed interface Parent permits Foo {
|
||||
|
||||
}
|
||||
|
||||
enum Foo implements Parent {
|
||||
A {}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// "Add missing inheritors to permits list" "true"
|
||||
|
||||
sealed class A permits B, C, D.E /*1*/ {}
|
||||
|
||||
sealed class B extends A permits D {}
|
||||
|
||||
final class C extends A {}
|
||||
|
||||
final class D extends B {
|
||||
non-sealed static class E extends A {}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// "Add missing inheritors to permits list" "true"
|
||||
|
||||
sealed class A permits C, D, F {}
|
||||
|
||||
final class D extends A {}
|
||||
|
||||
non-sealed class C extends A {}
|
||||
|
||||
sealed class F extends A {}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Add missing inheritors to permits list" "true"
|
||||
|
||||
sealed class A<caret> permits C {}
|
||||
|
||||
final class B extends A {}
|
||||
|
||||
final class C extends A {}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// "Add missing inheritors to permits list" "true"
|
||||
|
||||
sealed interface Pare<caret>nt {
|
||||
|
||||
}
|
||||
|
||||
enum Foo implements Parent {
|
||||
A {}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// "Add missing inheritors to permits list" "true"
|
||||
|
||||
sealed class A<caret> /*1*/ {}
|
||||
|
||||
sealed class B extends A permits D {}
|
||||
|
||||
final class C extends A {}
|
||||
|
||||
final class D extends B {
|
||||
non-sealed static class E extends A {}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
// "Add missing inheritors to permits list" "false"
|
||||
|
||||
sealed class A<caret> {}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// "Add missing inheritors to permits list" "true"
|
||||
|
||||
sealed class A<caret> {}
|
||||
|
||||
final class D extends A {}
|
||||
|
||||
non-sealed class C extends A {}
|
||||
|
||||
sealed class F extends A {}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.java.codeInsight.intention;
|
||||
|
||||
import com.intellij.codeInsight.daemon.LightIntentionActionTestCase;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class FillPermitsListActionTest extends LightIntentionActionTestCase {
|
||||
|
||||
@Override
|
||||
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
|
||||
return LightJavaCodeInsightFixtureTestCase.JAVA_15;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBasePath() {
|
||||
return "/codeInsight/daemonCodeAnalyzer/quickFix/fillPermitsList";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1270,4 +1270,7 @@ intention.error.make.sealed.class.different.packages=Module is unnamed and some
|
||||
intention.error.make.sealed.class.inheritors.not.in.java.file=Some of the inheritors are not in java files
|
||||
intention.error.make.sealed.class.different.modules=Some of the inheritors are in different modules
|
||||
intention.error.make.sealed.class.interface.has.no.inheritors=Interface has no inheritors
|
||||
intention.make.sealed.class.task.title.set.inheritors.modifiers=Setting inheritors modifiers
|
||||
intention.make.sealed.class.task.title.set.inheritors.modifiers=Setting inheritors modifiers
|
||||
intention.family.name.fill.permits.list=Add missing inheritors to permits list
|
||||
intention.error.fill.permits.list.no.missing.inheritors=Sealed class has no missing inheritors
|
||||
intention.fill.permits.list.hint.title=Fill Permits List
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
public sealed class X permits Y {
|
||||
}
|
||||
|
||||
final class Y extends X {}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
public sealed class <spot>X</spot> {
|
||||
}
|
||||
|
||||
final class Y extends X {}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body>
|
||||
Adds missing inheritors to permits list of a sealed class.
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user