ConvertInterfaceToClassFix: fixes after review IDEA-216274

GitOrigin-RevId: cd0ac910a3e49f5329e34aee2751fd78c133e31f
This commit is contained in:
Andrey.Cherkasov
2021-03-18 10:49:53 +00:00
committed by intellij-monorepo-bot
parent 494abe4f2a
commit f75bc6d37a
16 changed files with 106 additions and 92 deletions
@@ -513,7 +513,7 @@ public abstract class QuickFixFactory {
public abstract @NotNull IntentionAction createReceiverParameterTypeFix(@NotNull PsiReceiverParameter receiverParameter,
@NotNull PsiType enclosingClassType);
public abstract @NotNull IntentionAction createConvertInterfaceContainingNotAllowedToClassFix(@NotNull PsiClass aClass);
public abstract @NotNull IntentionAction createConvertInterfaceToClassFix(@NotNull PsiClass aClass);
public abstract @NotNull IntentionAction createUnwrapArrayInitializerMemberValueAction(@NotNull PsiArrayInitializerMemberValue arrayValue);
}
@@ -758,7 +758,7 @@ public final class HighlightClassUtil {
String description = JavaErrorBundle.message("not.allowed.in.interface");
HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create();
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createDeleteFix(element));
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createConvertInterfaceContainingNotAllowedToClassFix(aClass));
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createConvertInterfaceToClassFix(aClass));
return info;
}
@@ -1244,6 +1244,7 @@ public final class HighlightMethodUtil {
boolean isExtension = method.hasModifierProperty(PsiModifier.DEFAULT);
boolean isStatic = method.hasModifierProperty(PsiModifier.STATIC);
boolean isPrivate = method.hasModifierProperty(PsiModifier.PRIVATE);
boolean isConstructor = method.isConstructor();
final List<IntentionAction> additionalFixes = new ArrayList<>();
String description = null;
@@ -1275,6 +1276,9 @@ public final class HighlightMethodUtil {
IntentionAction makeDefaultFix = QUICK_FIX_FACTORY.createModifierListFix(method, PsiModifier.DEFAULT, true, false);
additionalFixes.add(PriorityIntentionActionWrapper.highPriority(makeDefaultFix));
additionalFixes.add(QUICK_FIX_FACTORY.createModifierListFix(method, PsiModifier.STATIC, true, false));
if (isConstructor) {
additionalFixes.add(QUICK_FIX_FACTORY.createConvertInterfaceToClassFix(aClass));
}
}
}
}
@@ -1,5 +1,5 @@
// Copyright 2000-2021 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.siyeh.ipp.interfacetoclass;
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.intention.PriorityAction;
import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement;
@@ -7,14 +7,17 @@ import com.intellij.codeInspection.util.IntentionFamilyName;
import com.intellij.codeInspection.util.IntentionName;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.util.ObjectUtils;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.siyeh.IntentionPowerPackBundle;
import com.siyeh.ipp.interfacetoclass.ConvertInterfaceToClassIntention;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ConvertInterfaceContainingNotAllowedToClassFix extends LocalQuickFixAndIntentionActionOnPsiElement implements PriorityAction {
public class ConvertInterfaceToClassFix extends LocalQuickFixAndIntentionActionOnPsiElement implements PriorityAction {
public ConvertInterfaceContainingNotAllowedToClassFix(@Nullable PsiClass aClass) {
public ConvertInterfaceToClassFix(@Nullable PsiClass aClass) {
super(aClass);
}
@@ -24,9 +27,8 @@ public class ConvertInterfaceContainingNotAllowedToClassFix extends LocalQuickFi
@Nullable Editor editor,
@NotNull PsiElement startElement,
@NotNull PsiElement endElement) {
final PsiElement nameIdentifier = getNameIdentifier(startElement);
if (nameIdentifier == null) return false;
return new ConvertInterfaceToClassIntention().getElementPredicate().satisfiedBy(nameIdentifier);
if (!(startElement instanceof PsiClass)) return false;
return ConvertInterfaceToClassIntention.canConvertToClass((PsiClass)startElement);
}
@Override
@@ -35,9 +37,8 @@ public class ConvertInterfaceContainingNotAllowedToClassFix extends LocalQuickFi
@Nullable Editor editor,
@NotNull PsiElement startElement,
@NotNull PsiElement endElement) {
final PsiElement nameIdentifier = getNameIdentifier(startElement);
if (nameIdentifier == null) return;
new ConvertInterfaceToClassIntention().processIntention(nameIdentifier);
if (!(startElement instanceof PsiClass)) return;
ConvertInterfaceToClassIntention.convert((PsiClass)startElement);
}
@Override
@@ -45,19 +46,13 @@ public class ConvertInterfaceContainingNotAllowedToClassFix extends LocalQuickFi
return Priority.LOW;
}
private static PsiIdentifier getNameIdentifier(@NotNull PsiElement element) {
final PsiClass aClass = ObjectUtils.tryCast(element, PsiClass.class);
if (aClass == null) return null;
return aClass.getNameIdentifier();
}
@Override
public @IntentionName @NotNull String getText() {
return new ConvertInterfaceToClassIntention().getText();
return IntentionPowerPackBundle.message("convert.interface.to.class.intention.name");
}
@Override
public @IntentionFamilyName @NotNull String getFamilyName() {
return new ConvertInterfaceToClassIntention().getFamilyName();
return IntentionPowerPackBundle.message("convert.interface.to.class.intention.family.name");
}
}
@@ -46,7 +46,6 @@ import com.intellij.util.DocumentUtil;
import com.intellij.util.IncorrectOperationException;
import com.siyeh.ig.fixes.CreateDefaultBranchFix;
import com.siyeh.ig.fixes.CreateMissingSwitchBranchesFix;
import com.siyeh.ipp.interfacetoclass.ConvertInterfaceContainingNotAllowedToClassFix;
import com.siyeh.ipp.modifiers.ChangeModifierIntention;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
@@ -1002,8 +1001,8 @@ public final class QuickFixFactoryImpl extends QuickFixFactory {
}
@Override
public @NotNull IntentionAction createConvertInterfaceContainingNotAllowedToClassFix(@NotNull PsiClass aClass) {
return new ConvertInterfaceContainingNotAllowedToClassFix(aClass);
public @NotNull IntentionAction createConvertInterfaceToClassFix(@NotNull PsiClass aClass) {
return new ConvertInterfaceToClassFix(aClass);
}
@Override
@@ -0,0 +1,7 @@
// "Convert to 'class'" "true"
abstract class A {
public A() {
System.out.println(42);
}
}
@@ -0,0 +1,7 @@
// "Convert to 'class'" "true"
interface A {
A()<caret> {
System.out.println(42);
}
}
@@ -3,10 +3,10 @@ package com.intellij.java.codeInsight.daemon.quickFix;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
public class ConvertInterfaceContainingNotAllowedToClassFixTest extends LightQuickFixParameterizedTestCase {
public class ConvertInterfaceToClassFixTest extends LightQuickFixParameterizedTestCase {
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/convertInterfaceContainingNotAllowedToClass";
return "/codeInsight/daemonCodeAnalyzer/quickFix/convertInterfaceToClass";
}
}
@@ -15,6 +15,7 @@
*/
package com.siyeh.ipp.interfacetoclass;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
@@ -48,6 +49,67 @@ public class ConvertInterfaceToClassIntention extends Intention {
return false;
}
public static void convert(@NotNull PsiClass anInterface) {
final SearchScope searchScope = anInterface.getUseScope();
final Collection<PsiClass> inheritors = ClassInheritorsSearch.search(anInterface, searchScope, false).findAll();
final MultiMap<PsiElement, String> conflicts = new MultiMap<>();
inheritors.forEach(aClass -> {
final PsiReferenceList extendsList = aClass.getExtendsList();
if (extendsList == null) {
return;
}
final PsiJavaCodeReferenceElement[] referenceElements = extendsList.getReferenceElements();
if (referenceElements.length > 0) {
final PsiElement target = referenceElements[0].resolve();
if (target instanceof PsiClass && !CommonClassNames.JAVA_LANG_OBJECT.equals(((PsiClass)target).getQualifiedName())) {
conflicts.putValue(aClass, IntentionPowerPackBundle.message(
"0.already.extends.1.and.will.not.compile.after.converting.2.to.a.class",
RefactoringUIUtil.getDescription(aClass, true), RefactoringUIUtil.getDescription(target, true),
RefactoringUIUtil.getDescription(anInterface, false)));
}
}
});
final PsiFunctionalExpression functionalExpression = FunctionalExpressionSearch.search(anInterface, searchScope).findFirst();
if (functionalExpression != null) {
final String conflictMessage = ClassPresentationUtil.getFunctionalExpressionPresentation(functionalExpression, true) +
" will not compile after converting " +
RefactoringUIUtil.getDescription(anInterface, false) +
" to a class";
conflicts.putValue(functionalExpression, conflictMessage);
}
final boolean conflictsDialogOK;
if (conflicts.isEmpty()) {
conflictsDialogOK = true;
}
else {
final Application application = ApplicationManager.getApplication();
if (application.isUnitTestMode()) {
throw new BaseRefactoringProcessor.ConflictsInTestsException(conflicts.values());
}
final ConflictsDialog conflictsDialog =
new ConflictsDialog(anInterface.getProject(), conflicts, () -> convertInterfaceToClass(anInterface, inheritors));
conflictsDialogOK = conflictsDialog.showAndGet();
}
if (conflictsDialogOK) {
convertInterfaceToClass(anInterface, inheritors);
}
}
public static boolean canConvertToClass(@NotNull PsiClass aClass) {
if (!aClass.isInterface() || aClass.isAnnotationType()) {
return false;
}
final SearchScope useScope = aClass.getUseScope();
for (PsiClass inheritor :
ClassInheritorsSearch.search(aClass, useScope, true)) {
if (inheritor.isInterface()) {
return false;
}
}
return !AnnotationUtil.isAnnotated(aClass, CommonClassNames.JAVA_LANG_FUNCTIONAL_INTERFACE, 0);
}
public static void changeInterfaceToClass(PsiClass anInterface) {
final PsiIdentifier nameIdentifier = anInterface.getNameIdentifier();
assert nameIdentifier != null;
@@ -109,50 +171,7 @@ public class ConvertInterfaceToClassIntention extends Intention {
@Override
protected void processIntention(@NotNull PsiElement element) {
final PsiClass anInterface = (PsiClass)element.getParent();
final SearchScope searchScope = anInterface.getUseScope();
final Collection<PsiClass> inheritors = ClassInheritorsSearch.search(anInterface, searchScope, false).findAll();
final MultiMap<PsiElement, String> conflicts = new MultiMap<>();
inheritors.forEach(aClass -> {
final PsiReferenceList extendsList = aClass.getExtendsList();
if (extendsList == null) {
return;
}
final PsiJavaCodeReferenceElement[] referenceElements = extendsList.getReferenceElements();
if (referenceElements.length > 0) {
final PsiElement target = referenceElements[0].resolve();
if (target instanceof PsiClass && !CommonClassNames.JAVA_LANG_OBJECT.equals(((PsiClass)target).getQualifiedName())) {
conflicts.putValue(aClass, IntentionPowerPackBundle.message(
"0.already.extends.1.and.will.not.compile.after.converting.2.to.a.class",
RefactoringUIUtil.getDescription(aClass, true), RefactoringUIUtil.getDescription(target, true),
RefactoringUIUtil.getDescription(anInterface, false)));
}
}
});
final PsiFunctionalExpression functionalExpression = FunctionalExpressionSearch.search(anInterface, searchScope).findFirst();
if (functionalExpression != null) {
final String conflictMessage = ClassPresentationUtil.getFunctionalExpressionPresentation(functionalExpression, true) +
" will not compile after converting " +
RefactoringUIUtil.getDescription(anInterface, false) +
" to a class";
conflicts.putValue(functionalExpression, conflictMessage);
}
final boolean conflictsDialogOK;
if (conflicts.isEmpty()) {
conflictsDialogOK = true;
}
else {
final Application application = ApplicationManager.getApplication();
if (application.isUnitTestMode()) {
throw new BaseRefactoringProcessor.ConflictsInTestsException(conflicts.values());
}
final ConflictsDialog conflictsDialog =
new ConflictsDialog(anInterface.getProject(), conflicts, () -> convertInterfaceToClass(anInterface, inheritors));
conflictsDialogOK = conflictsDialog.showAndGet();
}
if (conflictsDialogOK) {
convertInterfaceToClass(anInterface, inheritors);
}
convert(anInterface);
}
private static void convertInterfaceToClass(PsiClass anInterface, Collection<PsiClass> inheritors) {
@@ -15,39 +15,22 @@
*/
package com.siyeh.ipp.interfacetoclass;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.psi.CommonClassNames;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.util.ObjectUtils;
import com.siyeh.ipp.base.PsiElementPredicate;
class ConvertInterfaceToClassPredicate implements PsiElementPredicate {
@Override
public boolean satisfiedBy(PsiElement element) {
final PsiElement parent = element.getParent();
if (!(parent instanceof PsiClass)) {
return false;
}
final PsiClass aClass = (PsiClass)parent;
if (!aClass.isInterface() || aClass.isAnnotationType()) {
return false;
}
final PsiClass aClass = ObjectUtils.tryCast(element.getParent(), PsiClass.class);
if (aClass == null) return false;
final PsiElement leftBrace = aClass.getLBrace();
final int offsetInParent = element.getStartOffsetInParent();
if (leftBrace == null ||
offsetInParent >= leftBrace.getStartOffsetInParent()) {
if (leftBrace == null || offsetInParent >= leftBrace.getStartOffsetInParent()) {
return false;
}
final SearchScope useScope = aClass.getUseScope();
for (PsiClass inheritor :
ClassInheritorsSearch.search(aClass, useScope, true)) {
if (inheritor.isInterface()) {
return false;
}
}
return !AnnotationUtil.isAnnotated(aClass, CommonClassNames.JAVA_LANG_FUNCTIONAL_INTERFACE, 0);
return ConvertInterfaceToClassIntention.canConvertToClass(aClass);
}
}