From 5b8862129553c9128308aee3766caa1239425b68 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Wed, 2 Sep 2020 16:52:07 +0700 Subject: [PATCH] [java-intentions] IDEA-249810 Automatically fix closing parentheses in nested calls GitOrigin-RevId: 9447905d0f2fda68b7f61b9310088b2f109d6b0e --- .../impl/analysis/HighlightMethodUtil.java | 3 +- .../impl/quickfix/MoveParenthesisFix.java | 139 ++++++++++++++++++ .../src/messages/QuickFixBundle.properties | 3 +- .../moveParenthesis/afterShiftLeft.java | 14 ++ .../moveParenthesis/afterShiftLeft1.java | 14 ++ .../moveParenthesis/afterShiftLeft2.java | 14 ++ .../afterShiftLeftOverload.java | 18 +++ .../moveParenthesis/afterShiftRight.java | 14 ++ .../moveParenthesis/beforeShiftLeft.java | 14 ++ .../moveParenthesis/beforeShiftLeft1.java | 14 ++ .../moveParenthesis/beforeShiftLeft2.java | 14 ++ .../beforeShiftLeftAmbiguous.java | 22 +++ .../beforeShiftLeftOverload.java | 18 +++ .../moveParenthesis/beforeShiftRight.java | 14 ++ .../beforeShiftRightNoOuterArg.java | 14 ++ .../quickFix/MoveParenthesisFixTest.java | 26 ++++ 16 files changed, 353 insertions(+), 2 deletions(-) create mode 100644 java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/MoveParenthesisFix.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/afterShiftLeft.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/afterShiftLeft1.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/afterShiftLeft2.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/afterShiftLeftOverload.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/afterShiftRight.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftLeft.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftLeft1.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftLeft2.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftLeftAmbiguous.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftLeftOverload.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftRight.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftRightNoOuterArg.java create mode 100644 java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/MoveParenthesisFixTest.java diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java index 52120ba92f8c..edd560626d4c 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java @@ -933,7 +933,8 @@ public final class HighlightMethodUtil { MethodReturnFixFactory.INSTANCE.registerCastActions(methodCandidates, methodCall, highlightInfo, fixRange); WrapWithAdapterMethodCallFix.registerCastActions(methodCandidates, methodCall, highlightInfo, fixRange); registerMethodAccessLevelIntentions(methodCandidates, methodCall, list, highlightInfo); - if (!PermuteArgumentsFix.registerFix(highlightInfo, methodCall, methodCandidates, fixRange)) { + if (!PermuteArgumentsFix.registerFix(highlightInfo, methodCall, methodCandidates, fixRange) && + !MoveParenthesisFix.registerFix(highlightInfo, methodCall, methodCandidates)) { registerChangeMethodSignatureFromUsageIntentions(methodCandidates, list, highlightInfo, fixRange); } RemoveRedundantArgumentsFix.registerIntentions(methodCandidates, list, highlightInfo, fixRange); diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/MoveParenthesisFix.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/MoveParenthesisFix.java new file mode 100644 index 000000000000..9cc6d7550916 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/MoveParenthesisFix.java @@ -0,0 +1,139 @@ +// 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.daemon.impl.quickfix; + +import com.intellij.codeInsight.daemon.QuickFixBundle; +import com.intellij.codeInsight.daemon.impl.HighlightInfo; +import com.intellij.codeInsight.intention.FileModifier; +import com.intellij.codeInsight.intention.HighPriorityAction; +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.codeInsight.intention.impl.BaseIntentionAction; +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.psi.infos.CandidateInfo; +import com.intellij.psi.infos.MethodCandidateInfo; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.ArrayUtil; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.ObjectUtils; +import com.siyeh.ig.psiutils.CommentTracker; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +public final class MoveParenthesisFix implements IntentionAction, HighPriorityAction { + private final PsiCallExpression myCall; + private final int myPos; + private final int myShiftSize; + + public MoveParenthesisFix(PsiCallExpression call, int pos, int shiftSize) { + myCall = call; + myPos = pos; + myShiftSize = shiftSize; + } + + @Override + public @IntentionName @NotNull String getText() { + return QuickFixBundle.message("intention.move.parenthesis.name"); + } + + @Override + public @NotNull @IntentionFamilyName String getFamilyName() { + return getText(); + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + return !project.isDisposed() && myCall.isValid() && BaseIntentionAction.canModify(myCall); + } + + @Override + public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + PsiCallExpression copy = copyWithShift(myCall, myPos, myShiftSize); + if (copy != null) { + new CommentTracker().replaceAndRestoreComments(myCall, copy); + } + } + + @Override + public boolean startInWriteAction() { + return true; + } + + @Override + public @NotNull FileModifier getFileModifierForPreview(@NotNull PsiFile target) { + return new MoveParenthesisFix(PsiTreeUtil.findSameElementInCopy(myCall, target), myPos, myShiftSize); + } + + private static PsiCallExpression copyWithShift(PsiCallExpression parentCall, int pos, int shift) { + PsiCallExpression parentCopy = (PsiCallExpression)parentCall.copy(); + PsiExpressionList parentArgsCopy = Objects.requireNonNull(parentCopy.getArgumentList()); + PsiCallExpression childCopy = (PsiCallExpression)parentArgsCopy.getExpressions()[pos]; + PsiExpressionList childArgsCopy = Objects.requireNonNull(childCopy.getArgumentList()); + if (shift > 0) { + if (shift >= childArgsCopy.getExpressionCount()) { + return null; + } + for(int i=0; i shift; i--) { + PsiExpression nextArg = parentArgsCopy.getExpressions()[pos + 1]; + childArgsCopy.add(nextArg); + nextArg.delete(); + } + } + return parentCopy; + } + + public static boolean registerFix(HighlightInfo info, PsiCallExpression callExpression, final CandidateInfo[] candidates) { + PsiExpressionList parent = ObjectUtils.tryCast(callExpression.getParent(), PsiExpressionList.class); + if (parent == null) return false; + PsiCallExpression parentCall = ObjectUtils.tryCast(parent.getParent(), PsiCallExpression.class); + if (parentCall == null) return false; + PsiExpressionList argList = callExpression.getArgumentList(); + if (argList == null) return false; + PsiExpression[] args = argList.getExpressions(); + if (args.length == 0) return false; + PsiExpression[] parentArgs = parent.getExpressions(); + int pos = ArrayUtil.indexOf(parentArgs, callExpression); + if (pos == -1) return false; + IntOpenHashSet shifts = new IntOpenHashSet(); + for (CandidateInfo candidate : candidates) { + PsiMethod candidateMethod = ObjectUtils.tryCast(candidate.getElement(), PsiMethod.class); + if (candidateMethod == null || candidateMethod.isVarArgs()) return false; + int count = candidateMethod.getParameterList().getParametersCount(); + if (count == 0 || count == args.length) return false; + shifts.add(args.length - count); + } + if (shifts.isEmpty()) return false; + MoveParenthesisFix fix = null; + for (int shift : shifts) { + PsiCallExpression copy = copyWithShift(parentCall, pos, shift); + if (copy == null) continue; + JavaResolveResult parentResolve = copy.resolveMethodGenerics(); + if (!(parentResolve instanceof MethodCandidateInfo)) return false; + if (!parentResolve.isValidResult()) continue; + var childCopy = (PsiCallExpression)Objects.requireNonNull(copy.getArgumentList()).getExpressions()[pos]; + JavaResolveResult childResolve = childCopy.resolveMethodGenerics(); + if (!(childResolve instanceof MethodCandidateInfo)) return false; + if (!childResolve.isValidResult()) continue; + if (fix != null) return false; + fix = new MoveParenthesisFix(parentCall, pos, shift); + } + if (fix == null) return false; + QuickFixAction.registerQuickFixAction(info, null, fix); + return true; + } +} diff --git a/java/java-analysis-impl/src/messages/QuickFixBundle.properties b/java/java-analysis-impl/src/messages/QuickFixBundle.properties index 7202d67fa9cc..b7a18df2ef17 100644 --- a/java/java-analysis-impl/src/messages/QuickFixBundle.properties +++ b/java/java-analysis-impl/src/messages/QuickFixBundle.properties @@ -386,4 +386,5 @@ generate.overloaded.method.with.default.parameter.values=Generate overloaded met remove.unreachable.branches=Remove unreachable branches set.inspection.option.fix=Set inspection option -simplify.boolean.expression.extracting.side.effects=\ extracting side effects \ No newline at end of file +simplify.boolean.expression.extracting.side.effects=\ extracting side effects +intention.move.parenthesis.name=Fix closing parenthesis placement diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/afterShiftLeft.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/afterShiftLeft.java new file mode 100644 index 000000000000..503c69c265de --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/afterShiftLeft.java @@ -0,0 +1,14 @@ +// "Fix closing parenthesis placement" "true" +public class Example { + String foo(String s, boolean b) { + return s; + } + + String bar(String s) { + return s; + } + + void test() { + foo(bar("hello"), true); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/afterShiftLeft1.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/afterShiftLeft1.java new file mode 100644 index 000000000000..a1cd68a01c5c --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/afterShiftLeft1.java @@ -0,0 +1,14 @@ +// "Fix closing parenthesis placement" "true" +public class Example { + String foo(String s, boolean b, boolean c) { + return s; + } + + String bar(String s) { + return s; + } + + void test() { + foo(bar("hello"), true, true); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/afterShiftLeft2.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/afterShiftLeft2.java new file mode 100644 index 000000000000..a1cd68a01c5c --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/afterShiftLeft2.java @@ -0,0 +1,14 @@ +// "Fix closing parenthesis placement" "true" +public class Example { + String foo(String s, boolean b, boolean c) { + return s; + } + + String bar(String s) { + return s; + } + + void test() { + foo(bar("hello"), true, true); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/afterShiftLeftOverload.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/afterShiftLeftOverload.java new file mode 100644 index 000000000000..c74e58605220 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/afterShiftLeftOverload.java @@ -0,0 +1,18 @@ +// "Fix closing parenthesis placement" "true" +public class Example { + String foo(String s, boolean b, boolean c) { + return s; + } + + String bar(String s) { + return s; + } + + String bar(String s, boolean b) { + return s; + } + + void test() { + foo(bar("hello"), true, true); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/afterShiftRight.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/afterShiftRight.java new file mode 100644 index 000000000000..06aca5d1a558 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/afterShiftRight.java @@ -0,0 +1,14 @@ +// "Fix closing parenthesis placement" "true" +public class Example { + String foo(String s, boolean b) { + return s; + } + + String bar(String s) { + return s; + } + + void test() { + bar(foo("hello", true)); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftLeft.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftLeft.java new file mode 100644 index 000000000000..3be06e77e00b --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftLeft.java @@ -0,0 +1,14 @@ +// "Fix closing parenthesis placement" "true" +public class Example { + String foo(String s, boolean b) { + return s; + } + + String bar(String s) { + return s; + } + + void test() { + foo(bar("hello", true)); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftLeft1.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftLeft1.java new file mode 100644 index 000000000000..2328e6998f42 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftLeft1.java @@ -0,0 +1,14 @@ +// "Fix closing parenthesis placement" "true" +public class Example { + String foo(String s, boolean b, boolean c) { + return s; + } + + String bar(String s) { + return s; + } + + void test() { + foo(bar("hello", true), true); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftLeft2.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftLeft2.java new file mode 100644 index 000000000000..bfda5379e597 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftLeft2.java @@ -0,0 +1,14 @@ +// "Fix closing parenthesis placement" "true" +public class Example { + String foo(String s, boolean b, boolean c) { + return s; + } + + String bar(String s) { + return s; + } + + void test() { + foo(bar("hello", true, true)); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftLeftAmbiguous.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftLeftAmbiguous.java new file mode 100644 index 000000000000..aad0e7ecc1a9 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftLeftAmbiguous.java @@ -0,0 +1,22 @@ +// "Fix closing parenthesis placement" "false" +public class Example { + String foo(String s, boolean b, boolean c) { + return s; + } + + String foo(String s, boolean b) { + return s; + } + + String bar(String s) { + return s; + } + + String bar(String s, boolean b) { + return s; + } + + void test() { + foo(bar("hello", true, true)); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftLeftOverload.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftLeftOverload.java new file mode 100644 index 000000000000..bc5de3d88418 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftLeftOverload.java @@ -0,0 +1,18 @@ +// "Fix closing parenthesis placement" "true" +public class Example { + String foo(String s, boolean b, boolean c) { + return s; + } + + String bar(String s) { + return s; + } + + String bar(String s, boolean b) { + return s; + } + + void test() { + foo(bar("hello", true, true)); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftRight.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftRight.java new file mode 100644 index 000000000000..044a4ca61cb5 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftRight.java @@ -0,0 +1,14 @@ +// "Fix closing parenthesis placement" "true" +public class Example { + String foo(String s, boolean b) { + return s; + } + + String bar(String s) { + return s; + } + + void test() { + bar(foo("hello"), true); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftRightNoOuterArg.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftRightNoOuterArg.java new file mode 100644 index 000000000000..b4e70a34bcf1 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis/beforeShiftRightNoOuterArg.java @@ -0,0 +1,14 @@ +// "Fix closing parenthesis placement" "false" +public class Example { + String foo(String s, boolean b) { + return s; + } + + String bar(String s) { + return s; + } + + void test() { + bar(foo("hello")); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/MoveParenthesisFixTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/MoveParenthesisFixTest.java new file mode 100644 index 000000000000..d533bda901c7 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/MoveParenthesisFixTest.java @@ -0,0 +1,26 @@ +/* + * Copyright 2000-2017 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. + */ +package com.intellij.java.codeInsight.daemon.quickFix; + +import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase; + +public class MoveParenthesisFixTest extends LightQuickFixParameterizedTestCase { + + @Override + protected String getBasePath() { + return "/codeInsight/daemonCodeAnalyzer/quickFix/moveParenthesis"; + } +}