[java] Improve parentheses fixer in complete statement action

1. Do not add parentheses left to caret
2. Use LongRangeSet to add parentheses at valid overload positions
3. Support vararg methods
4. Filter by outer method call arguments range
5. Do not try to fix anything in already valid inner calls

Fixes IDEA-264821 Complete current statement: wrong choice for missing bracket

GitOrigin-RevId: d637703725e9e67ddb83e67a1e05d95d7fc4c1fe
This commit is contained in:
Tagir Valeev
2021-03-23 18:28:23 +07:00
committed by intellij-monorepo-bot
parent e8a35c538f
commit 33584b6815
16 changed files with 231 additions and 31 deletions

View File

@@ -15,18 +15,19 @@
*/
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.jsp.jspJava.JspMethodCall;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import org.jetbrains.annotations.NotNull;
public class MethodCallFixer implements Fixer {
@Override
@@ -41,40 +42,40 @@ public class MethodCallFixer implements Fixer {
int caret = editor.getCaretModel().getOffset();
if (argList != null && !hasRParenth(argList)) {
PsiCallExpression innermostCall = PsiTreeUtil.findElementOfClassAtOffset(psiElement.getContainingFile(), caret - 1, PsiCallExpression.class, false);
while (innermostCall != null && innermostCall != psiElement && hasRParenth(innermostCall.getArgumentList())
&& innermostCall.resolveMethodGenerics().isValidResult()) {
innermostCall = PsiTreeUtil.getParentOfType(innermostCall, PsiCallExpression.class);
}
if (innermostCall == null) return;
argList = innermostCall.getArgumentList();
if (argList == null) return;
int endOffset = -1;
PsiElement child = argList.getFirstChild();
while (child != null) {
if (child instanceof PsiErrorElement) {
final PsiErrorElement errorElement = (PsiErrorElement)child;
if (errorElement.getErrorDescription().contains("')'")) {
endOffset = errorElement.getTextRange().getStartOffset();
break;
}
}
child = child.getNextSibling();
}
int endOffset = getMissingParenthesisOffset(argList);
TextRange argListRange = argList.getTextRange();
if (endOffset == -1) {
endOffset = argList.getTextRange().getEndOffset();
endOffset = argListRange.getEndOffset();
}
PsiExpression[] args = argList.getExpressions();
if (args.length > 0 &&
startLine(editor, argList) != startLine(editor, args[0]) &&
caret < args[0].getTextRange().getStartOffset()) {
endOffset = argList.getTextRange().getStartOffset() + 1;
endOffset = argListRange.getStartOffset() + 1;
}
if (!DumbService.isDumb(argList.getProject())) {
int caretArg = ContainerUtil.indexOf(Arrays.asList(args), arg -> arg.getTextRange().containsOffset(caret));
Integer argCount = getMinimalParameterCount(innermostCall);
if (argCount != null && argCount > 0 && argCount < args.length) {
endOffset = Math.min(endOffset, args[Math.max(argCount - 1, caretArg)].getTextRange().getEndOffset());
if (args.length > 0 && !DumbService.isDumb(argList.getProject())) {
int caretArg = getCaretArgIndex(caret, argListRange, args);
LongRangeSet innerCounts = getPossibleParameterCounts(innermostCall).intersect(LongRangeSet.range(caretArg, args.length));
if (!innerCounts.isEmpty()) {
innerCounts = tryFilterByOuterCall(innermostCall, args, innerCounts);
int minArg = (int)innerCounts.min();
if (minArg > 0) {
endOffset = Math.min(endOffset, args[minArg - 1].getTextRange().getEndOffset());
}
}
}
@@ -83,21 +84,71 @@ public class MethodCallFixer implements Fixer {
}
}
private static int getCaretArgIndex(int caret, TextRange argListRange, PsiExpression[] args) {
int caretArg = 0;
while (caretArg < args.length) {
if (args[caretArg].getStartOffsetInParent() + argListRange.getStartOffset() > caret) {
break;
}
caretArg++;
}
return caretArg;
}
private static int getMissingParenthesisOffset(PsiExpressionList argList) {
PsiElement child = argList.getFirstChild();
while (child != null) {
if (child instanceof PsiErrorElement) {
final PsiErrorElement errorElement = (PsiErrorElement)child;
if (errorElement.getErrorDescription().contains("')'")) {
return errorElement.getTextRange().getStartOffset();
}
}
child = child.getNextSibling();
}
return -1;
}
private static boolean hasRParenth(PsiExpressionList args) {
if (args == null) return false;
PsiElement parenth = args.getLastChild();
return parenth != null && ")".equals(parenth.getText());
}
@Nullable
private static Integer getMinimalParameterCount(PsiCallExpression call) {
int paramCount = Integer.MAX_VALUE;
for (CandidateInfo candidate : PsiResolveHelper.SERVICE.getInstance(call.getProject()).getReferencedMethodCandidates(call, false)) {
PsiElement element = candidate.getElement();
if (element instanceof PsiMethod && !((PsiMethod)element).isVarArgs()) {
paramCount = Math.min(paramCount, ((PsiMethod)element).getParameterList().getParametersCount());
private static @NotNull LongRangeSet tryFilterByOuterCall(@NotNull PsiCallExpression innermostCall,
@NotNull PsiExpression @NotNull [] args,
@NotNull LongRangeSet innerCounts) {
PsiExpressionList outerArgList = ObjectUtils.tryCast(innermostCall.getParent(), PsiExpressionList.class);
if (outerArgList != null) {
PsiExpression[] outerArgs = outerArgList.getExpressions();
if (innermostCall == ArrayUtil.getLastElement(outerArgs)) {
PsiCallExpression outerCall = ObjectUtils.tryCast(outerArgList.getParent(), PsiCallExpression.class);
if (outerCall != null) {
LongRangeSet outerCounts = getPossibleParameterCounts(outerCall);
if (!outerCounts.isEmpty()) {
LongRangeSet allowedByOuter =
LongRangeSet.point(args.length).minus(outerCounts.minus(LongRangeSet.point(outerArgs.length), false), false);
LongRangeSet innerCountsFiltered = innerCounts.intersect(allowedByOuter);
if (!innerCountsFiltered.isEmpty()) {
return innerCountsFiltered;
}
}
}
}
}
return paramCount == Integer.MAX_VALUE ? null : paramCount;
return innerCounts;
}
private static @NotNull LongRangeSet getPossibleParameterCounts(@NotNull PsiCallExpression call) {
LongRangeSet counts = LongRangeSet.empty();
for (CandidateInfo candidate : PsiResolveHelper.SERVICE.getInstance(call.getProject()).getReferencedMethodCandidates(call, false)) {
PsiMethod element = ObjectUtils.tryCast(candidate.getElement(), PsiMethod.class);
if (element != null) {
int count = element.getParameterList().getParametersCount();
counts = counts.unite(element.isVarArgs() ? LongRangeSet.range(count - 1, Integer.MAX_VALUE) : LongRangeSet.point(count));
}
}
return counts;
}
private static int startLine(Editor editor, PsiElement psiElement) {

View File

@@ -0,0 +1,11 @@
public class Foo {
public Foo(String a) {
}
public Foo(String a, String b) {
}
public static void main(String[] args) {
System.out.println(new Foo("a", "b")<caret>;
}
}

View File

@@ -0,0 +1,11 @@
public class Foo {
public Foo(String a) {
}
public Foo(String a, String b) {
}
public static void main(String[] args) {
System.out.println(new Foo(<caret>"a", "b";
}
}

View File

@@ -0,0 +1,11 @@
public class Foo {
public Foo(String a) {
}
public Foo(String a, String b) {
}
public static void main(String[] args) {
System.out.println(new Foo("a", "b"));
}
}

View File

@@ -0,0 +1,11 @@
public class Foo {
public Foo(String a) {
}
public Foo(String a, String b, String c) {
}
public static void main(String[] args) {
Arrays.asList(new Foo("a", <caret>"b", "c", new Foo("d")
}
}

View File

@@ -0,0 +1,11 @@
public class Foo {
public Foo(String a) {
}
public Foo(String a, String b, String c) {
}
public static void main(String[] args) {
Arrays.asList(new Foo("a", <caret>"b", "c", new Foo("d"))
}
}

View File

@@ -0,0 +1,11 @@
public class Foo {
public Foo(String a) {
}
public Foo(String a, String b, String c) {
}
public static void main(String[] args) {
Arrays.asList(new Foo("a", "b", "c"), new Foo("d"));
}
}

View File

@@ -0,0 +1,11 @@
public class Foo {
public Foo(String a) {
}
public Foo(String a, String b, String c) {
}
public static void main(String[] args) {
Arrays.asList(new Foo(<caret>"a", new Foo("b"), new Foo("c"), new Foo("d")
}
}

View File

@@ -0,0 +1,11 @@
public class Foo {
public Foo(String a) {
}
public Foo(String a, String b, String c) {
}
public static void main(String[] args) {
Arrays.asList(new Foo("a"), new Foo("b"), new Foo("c"), new Foo("d"));
}
}

View File

@@ -0,0 +1,11 @@
public class Foo {
public Foo(String a) {
}
public Foo(String a, String b, String c) {
}
public static void main(String[] args) {
Arrays.asList(new Foo("a", "b", "c"), new Foo("d"));
}
}

View File

@@ -0,0 +1,11 @@
public class Foo {
public Foo(String a) {
}
public Foo(String a, String b) {
}
public static void main(String[] args) {
System.out.println(new Foo("a", "b"));
}
}

View File

@@ -0,0 +1,8 @@
import java.util.*;
public class VarargMethod {
void test() {
Collection<String> list = new ArrayList<>();
list.add(String.format("m"<caret>, p1, p2
}
}

View File

@@ -0,0 +1,8 @@
import java.util.*;
public class VarargMethod {
void test() {
Collection<String> list = new ArrayList<>();
list.add(String.format("m", p1, p2));
}
}

View File

@@ -0,0 +1,8 @@
import java.util.*;
public class VarargMethod {
void test(int p1, int p2) {
List<String> list = new ArrayList<>();
list.add(String.format("m"<caret>, p1, p2)
}
}

View File

@@ -0,0 +1,8 @@
import java.util.*;
public class VarargMethod {
void test(int p1, int p2) {
List<String> list = new ArrayList<>();
list.add(String.format("m", p1, p2));
}
}

View File

@@ -182,7 +182,14 @@ public class CompleteStatementTest extends EditorActionTestCase {
public void testRecordWithComponent() { doTest(); }
public void testRecordWithComponentNoBody() { doTest(); }
public void testVarargMethod() { doTest(); }
public void testVarargMethod2() { doTest(); }
public void testVarargMethod3() { doTest(); }
public void testSemicolonAfterSwitchExpression() { doTest(); }
public void testOverloadedMethod() { doTest(); }
public void testOverloadedMethodNoCaretHint() { doTest(); }
public void testOverloadedMethodOneOrThree() { doTest(); }
public void testOverloadedMethodOneOrThree2() { doTest(); }
public void testOverloadedMethodOneOrThree3() { doTest(); }
private void doTestBracesNextLineStyle() {
myJavaSettings.BRACE_STYLE = CommonCodeStyleSettings.NEXT_LINE;