mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 21:11:28 +07:00
[java-intentions] AddMethodQualifierFix: ModCommand
GitOrigin-RevId: efe77eed876481149c7b58356ffd6dcada649a16
This commit is contained in:
committed by
intellij-monorepo-bot
parent
f5ed03ef33
commit
5d5dfb997a
@@ -1,80 +1,32 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the 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.daemon.impl.quickfix;
|
||||
|
||||
import com.intellij.codeInsight.daemon.QuickFixBundle;
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.command.WriteCommandAction;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.popup.JBPopup;
|
||||
import com.intellij.openapi.ui.popup.JBPopupFactory;
|
||||
import com.intellij.openapi.ui.popup.PopupStep;
|
||||
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
|
||||
import com.intellij.codeInsight.intention.preview.IntentionPreviewUtils;
|
||||
import com.intellij.codeInspection.ModCommands;
|
||||
import com.intellij.modcommand.ModChooseAction;
|
||||
import com.intellij.modcommand.ModCommand;
|
||||
import com.intellij.modcommand.ModCommandAction;
|
||||
import com.intellij.modcommand.PsiBasedModCommandAction;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Objects.requireNonNullElse;
|
||||
|
||||
/**
|
||||
* @author Dmitry Batkovich
|
||||
*/
|
||||
public class AddMethodQualifierFix implements IntentionAction {
|
||||
public class AddMethodQualifierFix extends PsiBasedModCommandAction<PsiMethodCallExpression> {
|
||||
private enum SearchMode { MAX_2_CANDIDATES, FULL_SEARCH }
|
||||
|
||||
private final SmartPsiElementPointer<PsiMethodCallExpression> myMethodCall;
|
||||
private volatile List<PsiVariable> myCandidates;
|
||||
|
||||
public AddMethodQualifierFix(@NotNull PsiMethodCallExpression methodCallExpression) {
|
||||
myMethodCall = SmartPointerManager.getInstance(methodCallExpression.getProject()).createSmartPsiElementPointer(methodCallExpression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull IntentionPreviewInfo generatePreview(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
|
||||
PsiMethodCallExpression element = myMethodCall.getElement();
|
||||
if (element == null || myCandidates.isEmpty()) {
|
||||
return IntentionPreviewInfo.EMPTY;
|
||||
}
|
||||
PsiMethodCallExpression copyExpression = PsiTreeUtil.findSameElementInCopy(element, file);
|
||||
replaceWithQualifier(myCandidates.get(0), copyExpression);
|
||||
return IntentionPreviewInfo.DIFF;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getText() {
|
||||
if (myCandidates == null || myCandidates.isEmpty()) {
|
||||
if (ApplicationManager.getApplication().isHeadlessEnvironment()) {
|
||||
return "";
|
||||
}
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (myCandidates.size() == 1) {
|
||||
return QuickFixBundle.message("add.method.qualifier.fix.text", myCandidates.get(0).getName());
|
||||
}
|
||||
return getFamilyName();
|
||||
super(methodCallExpression);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -84,23 +36,18 @@ public class AddMethodQualifierFix implements IntentionAction {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
|
||||
PsiMethodCallExpression element = myMethodCall.getElement();
|
||||
if (element == null || !element.isValid() || element.getMethodExpression().getQualifierExpression() != null) {
|
||||
return false;
|
||||
protected @Nullable Presentation getPresentation(@NotNull ActionContext context, @NotNull PsiMethodCallExpression element) {
|
||||
if (element.getMethodExpression().getQualifierExpression() != null) return null;
|
||||
List<PsiVariable> candidates = findCandidates(element, SearchMode.MAX_2_CANDIDATES);
|
||||
if (candidates.isEmpty()) return null;
|
||||
if (candidates.size() == 1) {
|
||||
return Presentation.of(QuickFixBundle.message("add.method.qualifier.fix.text", candidates.get(0).getName()));
|
||||
}
|
||||
if (myCandidates == null) {
|
||||
myCandidates = findCandidates(SearchMode.MAX_2_CANDIDATES);
|
||||
}
|
||||
return !myCandidates.isEmpty();
|
||||
return Presentation.of(getFamilyName());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<PsiVariable> findCandidates(@NotNull SearchMode mode) {
|
||||
PsiMethodCallExpression methodCallElement = myMethodCall.getElement();
|
||||
if (methodCallElement == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
private List<PsiVariable> findCandidates(@NotNull PsiMethodCallExpression methodCallElement, @NotNull SearchMode mode) {
|
||||
String methodName = methodCallElement.getMethodExpression().getReferenceName();
|
||||
if (methodName == null) {
|
||||
return Collections.emptyList();
|
||||
@@ -129,70 +76,25 @@ public class AddMethodQualifierFix implements IntentionAction {
|
||||
return List.copyOf(candidates);
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public List<PsiVariable> getCandidates() {
|
||||
return findCandidates(SearchMode.FULL_SEARCH);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PsiElement getElementToMakeWritable(@NotNull PsiFile currentFile) {
|
||||
return myMethodCall.getElement();
|
||||
protected @NotNull ModCommand perform(@NotNull ActionContext context, @NotNull PsiMethodCallExpression element) {
|
||||
List<PsiVariable> candidates = findCandidates(element, IntentionPreviewUtils.isIntentionPreviewActive() ?
|
||||
SearchMode.MAX_2_CANDIDATES : SearchMode.FULL_SEARCH);
|
||||
SmartPsiElementPointer<PsiMethodCallExpression> pointer = SmartPointerManager.createPointer(element);
|
||||
List<ModCommandAction> qualifyActions = ContainerUtil.map(candidates, candidate -> createAction(candidate, pointer));
|
||||
return new ModChooseAction(QuickFixBundle.message("add.qualifier"), qualifyActions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
|
||||
List<PsiVariable> candidates = findCandidates(SearchMode.FULL_SEARCH);
|
||||
if (candidates.size() == 1 || ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
qualify(candidates.get(0), editor);
|
||||
}
|
||||
else {
|
||||
chooseAndQualify(project, editor, candidates);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean startInWriteAction() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void chooseAndQualify(Project project, Editor editor, List<? extends PsiVariable> candidates) {
|
||||
BaseListPopupStep<PsiVariable> step =
|
||||
new BaseListPopupStep<>(QuickFixBundle.message("add.qualifier"), candidates) {
|
||||
@Override
|
||||
public PopupStep<?> onChosen(PsiVariable selectedValue, boolean finalChoice) {
|
||||
if (selectedValue != null && finalChoice) {
|
||||
qualify(selectedValue, editor);
|
||||
}
|
||||
return FINAL_CHOICE;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getTextFor(PsiVariable value) {
|
||||
String name = value.getName();
|
||||
return name == null ? "" : name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIconFor(PsiVariable aValue) {
|
||||
return aValue.getIcon(0);
|
||||
}
|
||||
};
|
||||
|
||||
JBPopup popup = JBPopupFactory.getInstance().createListPopup(project, step, (baseRenderer) -> baseRenderer);
|
||||
popup.showInBestPositionFor(editor);
|
||||
}
|
||||
|
||||
private void qualify(@NotNull PsiVariable qualifier, @NotNull Editor editor) {
|
||||
WriteCommandAction.runWriteCommandAction(qualifier.getProject(), () -> {
|
||||
PsiMethodCallExpression element = myMethodCall.getElement();
|
||||
if (element == null) {
|
||||
return;
|
||||
}
|
||||
PsiElement replacedExpression = replaceWithQualifier(qualifier, element);
|
||||
editor.getCaretModel().moveToOffset(replacedExpression.getTextOffset() + replacedExpression.getTextLength());
|
||||
});
|
||||
@NotNull
|
||||
private static ModCommandAction createAction(PsiVariable candidate, SmartPsiElementPointer<PsiMethodCallExpression> pointer) {
|
||||
return ModCommands.psiUpdateStep(
|
||||
candidate, requireNonNullElse(candidate.getName(), ""), (var, updater) -> {
|
||||
PsiMethodCallExpression call = updater.getWritable(pointer.getElement());
|
||||
if (call == null) return;
|
||||
PsiElement replacedExpression = replaceWithQualifier(var, call);
|
||||
updater.moveTo(replacedExpression.getTextOffset() + replacedExpression.getTextLength());
|
||||
}, var -> requireNonNullElse(var.getNameIdentifier(), var).getTextRange())
|
||||
.withPresentation(p -> p.withIcon(candidate.getIcon(0)));
|
||||
}
|
||||
|
||||
private static PsiElement replaceWithQualifier(@NotNull PsiVariable qualifier, @NotNull PsiMethodCallExpression oldExpression) {
|
||||
|
||||
@@ -836,7 +836,7 @@ public final class QuickFixFactoryImpl extends QuickFixFactory {
|
||||
@NotNull
|
||||
@Override
|
||||
public IntentionAction addMethodQualifierFix(@NotNull PsiMethodCallExpression methodCall) {
|
||||
return new AddMethodQualifierFix(methodCall);
|
||||
return new AddMethodQualifierFix(methodCall).asIntention();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
public class A {
|
||||
|
||||
Project p;
|
||||
MyElement fieldElement;
|
||||
|
||||
static MyElement staticElement;
|
||||
|
||||
public A() {
|
||||
|
||||
MyElement localElement1 = getElement();
|
||||
|
||||
localElement1.getProject()<caret>;
|
||||
|
||||
MyElement localElement2 = getElement();
|
||||
|
||||
}
|
||||
|
||||
interface Project {
|
||||
|
||||
}
|
||||
|
||||
interface MyElement {
|
||||
Project getProject();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import java.lang.Object;
|
||||
|
||||
public class A {
|
||||
|
||||
Project p;
|
||||
MyElement fieldElement;
|
||||
|
||||
static MyElement staticElement;
|
||||
|
||||
public void m(MyElement paramElement) {
|
||||
|
||||
Object o = new Object() {
|
||||
|
||||
private final MyElement nestedField;
|
||||
|
||||
public void targetMethod(MyElement nestedParamElement) {
|
||||
|
||||
final MyElement localElement1 = getElement();
|
||||
|
||||
localElement1.getProject()<caret>;
|
||||
|
||||
MyElement localElement2 = getElement();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Project {
|
||||
|
||||
}
|
||||
|
||||
interface MyElement {
|
||||
Project getProject();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
public class A {
|
||||
|
||||
Project p;
|
||||
MyElement fieldElement;
|
||||
|
||||
static MyElement staticElement;
|
||||
|
||||
public void targetMethod(MyElement paramElement) {
|
||||
|
||||
MyElement localElement1 = getElement();
|
||||
|
||||
localElement1.getProject();
|
||||
|
||||
MyElement localElement2 = getElement();
|
||||
|
||||
}
|
||||
|
||||
interface Project {
|
||||
|
||||
}
|
||||
|
||||
interface MyElement {
|
||||
Project getProject();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
class X {
|
||||
public class ObjectValue {
|
||||
public ObjectValue get(String name) { return null; }
|
||||
}
|
||||
|
||||
public class JsonObjectValue extends ObjectValue {
|
||||
public int sizeInBytes() { return 0; }
|
||||
}
|
||||
|
||||
{
|
||||
JsonObjectValue obj = null;
|
||||
obj.get("href").siz<caret>eInBytes();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
public class A {
|
||||
|
||||
Project p;
|
||||
MyElement fieldElement;
|
||||
|
||||
static MyElement staticElement;
|
||||
|
||||
static {
|
||||
|
||||
MyElement localElement1 = getElement();
|
||||
|
||||
localElement1.getProject()<caret>;
|
||||
|
||||
MyElement localElement2 = getElement();
|
||||
|
||||
}
|
||||
|
||||
interface Project {
|
||||
|
||||
}
|
||||
|
||||
interface MyElement {
|
||||
Project getProject();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
public class A {
|
||||
|
||||
Project p;
|
||||
MyElement fieldElement;
|
||||
|
||||
static MyElement staticElement;
|
||||
|
||||
public static void targetMethod(MyElement paramElement) {
|
||||
|
||||
MyElement localElement1 = getElement();
|
||||
|
||||
localElement1.getProject()<caret>;
|
||||
|
||||
MyElement localElement2 = getElement();
|
||||
|
||||
}
|
||||
|
||||
interface Project {
|
||||
|
||||
}
|
||||
|
||||
interface MyElement {
|
||||
Project getProject();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,19 +1,15 @@
|
||||
// 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.daemon.impl.quickfix;
|
||||
|
||||
import com.intellij.JavaTestUtil;
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInsight.intention.IntentionActionDelegate;
|
||||
import com.intellij.psi.PsiNamedElement;
|
||||
import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase;
|
||||
import com.intellij.util.Function;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.ui.ChooserInterceptor;
|
||||
import com.intellij.ui.UiInterceptors;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* @author Dmitry Batkovich
|
||||
@@ -26,23 +22,23 @@ public class AddMethodQualifierTest extends JavaCodeInsightFixtureTestCase {
|
||||
}
|
||||
|
||||
public void testNonStaticMethod() {
|
||||
doTest("fieldElement", "staticElement", "localElement1", "paramElement");
|
||||
doTest("fieldElement", "localElement1", "paramElement", "staticElement");
|
||||
}
|
||||
|
||||
public void testStaticMethod() {
|
||||
doTest("staticElement", "localElement1", "paramElement");
|
||||
doTest("localElement1", "paramElement", "staticElement");
|
||||
}
|
||||
|
||||
public void testNestedMethod() {
|
||||
doTest("fieldElement", "localElement1", "nestedField", "nestedParamElement", "staticElement", "paramElement");
|
||||
doTest("fieldElement", "localElement1", "nestedField", "nestedParamElement", "paramElement", "staticElement");
|
||||
}
|
||||
|
||||
public void testConstructor() {
|
||||
doTest("fieldElement", "staticElement", "localElement1");
|
||||
doTest("fieldElement", "localElement1", "staticElement");
|
||||
}
|
||||
|
||||
public void testStaticInitializer() {
|
||||
doTest("staticElement", "localElement1");
|
||||
doTest("localElement1", "staticElement");
|
||||
}
|
||||
|
||||
public void testFix() {
|
||||
@@ -55,29 +51,15 @@ public class AddMethodQualifierTest extends JavaCodeInsightFixtureTestCase {
|
||||
}
|
||||
|
||||
private void doTestFix() {
|
||||
myFixture.configureByFile(getTestName(false) + "Before.java");
|
||||
final AddMethodQualifierFix quickFix = getQuickFix();
|
||||
assertNotNull(quickFix);
|
||||
myFixture.checkPreviewAndLaunchAction(quickFix);
|
||||
myFixture.checkResultByFile(getTestName(false) + "After.java");
|
||||
myFixture.configureByFile(getTestName(false) + ".java");
|
||||
IntentionAction action = myFixture.findSingleIntention("Add ");
|
||||
myFixture.checkPreviewAndLaunchAction(action);
|
||||
myFixture.checkResultByFile(getTestName(false) + "_after.java");
|
||||
}
|
||||
|
||||
private void doTest(final String... candidatesNames) {
|
||||
myFixture.configureByFile(getTestName(false) + ".java");
|
||||
final AddMethodQualifierFix addMethodQualifierFix = getQuickFix();
|
||||
if (candidatesNames.length == 0) {
|
||||
assertNull(addMethodQualifierFix);
|
||||
return;
|
||||
}
|
||||
assertNotNull(addMethodQualifierFix);
|
||||
final Set<String> actualCandidatesNames =
|
||||
new TreeSet<>(ContainerUtil.map(addMethodQualifierFix.getCandidates(), (Function<PsiNamedElement, String>)psiNamedElement -> {
|
||||
final String name = psiNamedElement.getName();
|
||||
assertNotNull(name);
|
||||
return name;
|
||||
}));
|
||||
final Set<String> expectedCandidatesNames = new TreeSet<>(Arrays.asList(candidatesNames));
|
||||
assertEquals(expectedCandidatesNames, actualCandidatesNames);
|
||||
UiInterceptors.register(new ChooserInterceptor(List.of(candidatesNames), "localElement1"));
|
||||
doTestFix();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
Reference in New Issue
Block a user