mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-21 22:11:40 +07:00
java: don't generate uncompilable code in "Create subclass" intention (IDEA-327254)
GitOrigin-RevId: ef1718bee6d9d9ed9886b1a02cd9c6be4aee68f7
This commit is contained in:
committed by
intellij-monorepo-bot
parent
ce61367403
commit
d5e0643962
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
public class Test {
|
||||
private interface <caret>Inner {}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
public class Test {
|
||||
private abstract class Inner {
|
||||
private abstract class Inner<caret> {
|
||||
Inner(String s){}
|
||||
abstract void bar();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
class X {
|
||||
void x() {
|
||||
class Local {}
|
||||
class LocalImpl extends Local {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
class X {
|
||||
void x() {
|
||||
class <caret>Local {}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
public class Test {
|
||||
public <caret>class Inner {
|
||||
Inner(String s) throws java.lang.IOException {}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
public class Test {
|
||||
private interface Inner {}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user