java: don't generate uncompilable code in "Create subclass" intention (IDEA-327254)

GitOrigin-RevId: ef1718bee6d9d9ed9886b1a02cd9c6be4aee68f7
This commit is contained in:
Bas Leijdekkers
2023-08-02 15:17:42 +02:00
committed by intellij-monorepo-bot
parent ce61367403
commit d5e0643962
11 changed files with 91 additions and 30 deletions

View File

@@ -1,8 +1,9 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.intention.impl;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.CodeInsightUtil;
import com.intellij.codeInsight.ExceptionUtil;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightNamesUtil;
import com.intellij.codeInsight.daemon.impl.quickfix.CreateClassKind;
@@ -20,7 +21,6 @@ import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.CaretModel;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory;
@@ -38,12 +38,14 @@ import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleSettings;
import com.intellij.psi.search.searches.DirectClassInheritorsSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ObjectUtils;
import com.intellij.util.Processor;
import com.siyeh.ig.psiutils.SealedUtils;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -67,8 +69,7 @@ public class CreateSubclassAction extends BaseIntentionAction {
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
final CaretModel caretModel = editor.getCaretModel();
final int position = caretModel.getOffset();
final int position = editor.getCaretModel().getOffset();
PsiElement element = file.findElementAt(position);
PsiClass psiClass = PsiTreeUtil.getParentOfType(element, PsiClass.class);
if (psiClass == null || psiClass.isAnnotationType() || psiClass.isEnum() || psiClass instanceof PsiAnonymousClass ||
@@ -135,6 +136,11 @@ public class CreateSubclassAction extends BaseIntentionAction {
createInnerClass(psiClass);
return;
}
if (PsiUtil.isLocalClass(psiClass)) {
if (!FileModificationService.getInstance().prepareFileForWrite(file)) return;
createLocalClass(psiClass);
return;
}
if (ScratchUtil.isScratch(PsiUtilCore.getVirtualFile(psiClass))) {
createSameFileClass(suggestTargetClassName(psiClass), psiClass);
return;
@@ -143,7 +149,12 @@ public class CreateSubclassAction extends BaseIntentionAction {
}
private static boolean shouldCreateInnerClass(PsiClass psiClass) {
return psiClass.hasModifierProperty(PsiModifier.PRIVATE) && psiClass.getContainingClass() != null;
if (psiClass.getContainingClass() == null) {
return false;
}
return psiClass.hasModifierProperty(PsiModifier.PRIVATE) ||
!psiClass.hasModifierProperty(PsiModifier.STATIC) ||
PsiUtil.isLocalClass(psiClass);
}
public static void createSameFileClass(String newClassName, PsiClass psiClass) {
@@ -172,7 +183,20 @@ public class CreateSubclassAction extends BaseIntentionAction {
});
}
public static void createInnerClass(final PsiClass aClass) {
private static void createLocalClass(PsiClass aClass) {
WriteCommandAction.writeCommandAction(aClass.getProject()).withName(getTitle(aClass)).withGroupId(getTitle(aClass)).run(() -> {
final PsiDeclarationStatement statement = (PsiDeclarationStatement)aClass.getParent();
final PsiTypeParameterList oldTypeParameterList = aClass.getTypeParameterList();
PsiModifierList modifierList = aClass.getModifierList();
assert modifierList != null;
PsiElementFactory factory = JavaPsiFacade.getElementFactory(aClass.getProject());
@NotNull PsiStatement newClassStatement = factory.createStatementFromText("class " + suggestTargetClassName(aClass) + " {}", aClass);
PsiClass newClass = (PsiClass)statement.getParent().addAfter(newClassStatement, statement).getFirstChild();
startTemplate(oldTypeParameterList, aClass.getProject(), aClass, newClass, true);
});
}
private static void createInnerClass(PsiClass aClass) {
WriteCommandAction.writeCommandAction(aClass.getProject()).withName(getTitle(aClass)).withGroupId(getTitle(aClass)).run(() -> {
final PsiClass containingClass = aClass.getContainingClass();
LOG.assertTrue(containingClass != null);
@@ -319,9 +343,11 @@ public class CreateSubclassAction extends BaseIntentionAction {
templateBuilder.replaceElement(param, param.getText());
}
}
final PsiTypeParameterList typeParameterList = targetClass.getTypeParameterList();
assert typeParameterList != null;
typeParameterList.replace(oldTypeParameterList);
}
replaceTypeParamsList(targetClass, oldTypeParameterList);
if (templateBuilder != null) {
templateBuilder.setEndVariableBefore(ref);
final Template template = templateBuilder.buildTemplate();
@@ -368,12 +394,6 @@ public class CreateSubclassAction extends BaseIntentionAction {
}
}
private static PsiElement replaceTypeParamsList(PsiClass psiClass, PsiTypeParameterList oldTypeParameterList) {
final PsiTypeParameterList typeParameterList = psiClass.getTypeParameterList();
assert typeParameterList != null;
return typeParameterList.replace(oldTypeParameterList);
}
protected static void chooseAndImplement(PsiClass psiClass, Project project, @NotNull PsiClass targetClass, Editor editor) {
chooseAndImplement(psiClass, project, targetClass, editor, true);
}
@@ -381,11 +401,18 @@ public class CreateSubclassAction extends BaseIntentionAction {
protected static void chooseAndImplement(PsiClass psiClass, Project project, @NotNull PsiClass targetClass, Editor editor, boolean showChooser) {
boolean hasNonTrivialConstructor = false;
final PsiMethod[] constructors = psiClass.getConstructors();
for (PsiMethod constructor : constructors) {
outer: for (PsiMethod constructor : constructors) {
if (!constructor.getParameterList().isEmpty()) {
hasNonTrivialConstructor = true;
break;
}
PsiClassType[] types = constructor.getThrowsList().getReferencedTypes();
for (PsiClassType type : types) {
if (!ExceptionUtil.isUncheckedException(type)) {
hasNonTrivialConstructor = true;
break outer;
}
}
}
if (hasNonTrivialConstructor) {
final int offset = editor.getCaretModel().getOffset();
@@ -406,7 +433,7 @@ public class CreateSubclassAction extends BaseIntentionAction {
return false;
}
private static @IntentionName String decapitalize(String s) {
private static @IntentionName String decapitalize(@Nls String s) {
return StringUtil.capitalize(StringUtil.wordsToBeginFromLowerCase(s));
}
}

View File

@@ -0,0 +1,3 @@
public class Test {
private interface <caret>Inner {}
}

View File

@@ -1,5 +1,5 @@
public class Test {
private abstract class Inner {
private abstract class Inner<caret> {
Inner(String s){}
abstract void bar();
}

View File

@@ -0,0 +1,6 @@
class X {
void x() {
class Local {}
class LocalImpl extends Local {}
}
}

View File

@@ -0,0 +1,5 @@
class X {
void x() {
class <caret>Local {}
}
}

View File

@@ -0,0 +1,11 @@
public class Test {
public class Inner {
Inner(String s) throws java.lang.IOException {}
}
public class InnerImpl extends Inner {
InnerImpl(String s) throws java.lang.IOException {
super(s);
}
}
}

View File

@@ -0,0 +1,5 @@
public class Test {
public <caret>class Inner {
Inner(String s) throws java.lang.IOException {}
}
}

View File

@@ -1,3 +0,0 @@
public class Test {
private interface Inner {}
}

View File

@@ -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-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.codeInsight.intention;
import com.intellij.codeInsight.intention.IntentionAction;
@@ -8,6 +8,7 @@ import com.intellij.ide.scratch.ScratchRootType;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ex.PathManagerEx;
import com.intellij.openapi.application.impl.NonBlockingReadActionImpl;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiDirectory;
@@ -27,11 +28,19 @@ public class CreateSubclassTest extends LightMultiFileTestCase {
}
public void testInnerClassImplement() {
doTestInner();
doTestIntention("Implement abstract class");
}
public void testInnerClass() {
doTestInner();
doTestIntention("Implement interface");
}
public void testPublicNonStaticInnerClass() {
doTestIntention("Create subclass");
}
public void testLocalClass() {
doTestIntention("Create subclass");
}
public void testSealed() {
@@ -59,14 +68,12 @@ public class CreateSubclassTest extends LightMultiFileTestCase {
}""");
}
private void doTestInner() {
doTest(() -> {
PsiClass superClass = myFixture.findClass("Test");
final PsiClass inner = superClass.findInnerClassByName("Inner", false);
assertNotNull(inner);
CreateSubclassAction.createInnerClass(inner);
UIUtil.dispatchAllInvocationEvents();
});
private void doTestIntention(String hint) {
myFixture.configureByFile(getTestName(false) + ".java");
IntentionAction action = myFixture.findSingleIntention(hint);
myFixture.launchAction(action);
NonBlockingReadActionImpl.waitForAsyncTaskCompletion();
myFixture.checkResultByFile(getTestName(false) + ".after.java");
}
private void doTestSameFileClass() {