diff --git a/java/java-psi-impl/src/com/intellij/codeInsight/folding/impl/JavaFoldingBuilderBase.java b/java/java-psi-impl/src/com/intellij/codeInsight/folding/impl/JavaFoldingBuilderBase.java index 9f5ecb109fd5..5e008afb4797 100644 --- a/java/java-psi-impl/src/com/intellij/codeInsight/folding/impl/JavaFoldingBuilderBase.java +++ b/java/java-psi-impl/src/com/intellij/codeInsight/folding/impl/JavaFoldingBuilderBase.java @@ -45,7 +45,6 @@ import com.intellij.psi.util.*; import com.intellij.util.Function; import com.intellij.util.ObjectUtils; import com.intellij.util.text.CharArrayUtil; -import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -701,7 +700,7 @@ public abstract class JavaFoldingBuilderBase extends CustomFoldingBuilder implem else if (element instanceof PsiComment) { return settings.isCollapseEndOfLineComments(); } - else if (isLiteralExpression(element) + else if (ParameterNameFoldingManager.isLiteralExpression(element) && element.getParent() instanceof PsiExpressionList && (element.getParent().getParent() instanceof PsiCallExpression || element.getParent().getParent() instanceof PsiAnonymousClass)) { @@ -760,55 +759,8 @@ public abstract class JavaFoldingBuilderBase extends CustomFoldingBuilder implem if (quick || !JavaCodeFoldingSettings.getInstance().isInlineParameterNamesForLiteralCallArguments()) { return; } - PsiExpressionList callArgumentsList = expression.getArgumentList(); - if (callArgumentsList == null) { - return; - } - - PsiExpression[] callArguments = callArgumentsList.getExpressions(); - if (callArguments.length > 1) { - PsiParameter[] parameters = null; - boolean isResolved = false; - - for (int i = 0; i < callArguments.length; i++) { - PsiExpression callArgument = callArguments[i]; - - if (callArgument.getType() != null && isLiteralExpression(callArgument)) { - if (!isResolved) { - PsiMethod method = expression.resolveMethod(); - isResolved = true; - if (method == null) { - return; - } - parameters = method.getParameterList().getParameters(); - if (parameters.length != callArguments.length) { - return; - } - } - - PsiParameter methodParam = parameters[i]; - if (TypeConversionUtil.isAssignable(methodParam.getType(), callArgument.getType())) { - TextRange range = callArgument.getTextRange(); - String placeholderText = methodParam.getName() + ": " + callArgument.getText(); - foldElements.add(new NamedFoldingDescriptor(callArgument, range.getStartOffset(), range.getEndOffset(), null, placeholderText)); - } - } - } - } - } - - @Contract("null -> false") - private static boolean isLiteralExpression(@Nullable PsiElement callArgument) { - if (callArgument instanceof PsiLiteralExpression) - return true; - - if (callArgument instanceof PsiPrefixExpression) { - PsiPrefixExpression expr = (PsiPrefixExpression)callArgument; - IElementType tokenType = expr.getOperationTokenType(); - return JavaTokenType.MINUS.equals(tokenType) && expr.getOperand() instanceof PsiLiteralExpression; - } - - return false; + ParameterNameFoldingManager manager = new ParameterNameFoldingManager(expression); + foldElements.addAll(manager.buildDescriptors()); } private boolean addClosureFolding(final PsiClass aClass, final Document document, final List foldElements, diff --git a/java/java-psi-impl/src/com/intellij/codeInsight/folding/impl/ParameterNameFoldingManager.java b/java/java-psi-impl/src/com/intellij/codeInsight/folding/impl/ParameterNameFoldingManager.java new file mode 100644 index 000000000000..f643e9ee1110 --- /dev/null +++ b/java/java-psi-impl/src/com/intellij/codeInsight/folding/impl/ParameterNameFoldingManager.java @@ -0,0 +1,154 @@ +/* + * Copyright 2000-2014 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.codeInsight.folding.impl; + +import com.intellij.lang.folding.FoldingDescriptor; +import com.intellij.lang.folding.NamedFoldingDescriptor; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.util.TypeConversionUtil; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class ParameterNameFoldingManager { + private static final int MIN_NAME_LENGTH_THRESHOLD = 3; + private static final int MIN_ARGS_TO_FOLD = 2; + + private static final String[] RANGE_START_WORDS = { + "begin", "start", "from", "first" + }; + private static final String[] RANGE_END_WORDS = { + "end", "to", "last" + }; + + private final PsiCallExpression myCallExpression; + + private PsiExpression[] myCallArguments; + private PsiParameter[] myParameters; + + public ParameterNameFoldingManager(@NotNull PsiCallExpression callExpression) { + myCallExpression = callExpression; + } + + public static boolean isLiteralExpression(@Nullable PsiElement callArgument) { + if (callArgument instanceof PsiLiteralExpression) + return true; + + if (callArgument instanceof PsiPrefixExpression) { + PsiPrefixExpression expr = (PsiPrefixExpression)callArgument; + IElementType tokenType = expr.getOperationTokenType(); + return JavaTokenType.MINUS.equals(tokenType) && expr.getOperand() instanceof PsiLiteralExpression; + } + + return false; + } + + @Nullable + public PsiExpression[] getArguments(@NotNull PsiCallExpression call) { + PsiExpressionList callArgumentsList = call.getArgumentList(); + return callArgumentsList != null ? callArgumentsList.getExpressions() : null; + } + + @NotNull + public List buildDescriptors() { + myCallArguments = getArguments(myCallExpression); + + if (myCallArguments != null && myCallArguments.length >= MIN_ARGS_TO_FOLD && hasLiteralExpression(myCallArguments)) { + PsiMethod method = myCallExpression.resolveMethod(); + + if (method != null) { + myParameters = method.getParameterList().getParameters(); + if (myParameters.length == myCallArguments.length) { + return buildDescriptorsForLiteralArguments(); + } + } + } + + return ContainerUtil.emptyList(); + } + + @NotNull + private List buildDescriptorsForLiteralArguments() { + List descriptors = ContainerUtil.newArrayList(); + + int i = 0; + while (i < myCallArguments.length) { + if (i + 1 < myCallArguments.length && isCommonlyNamedParameterPair(i, i + 1)) { + i += 2; + continue; + } + + if (shouldInlineParameterName(i)) { + descriptors.add(createFoldingDescriptor(myCallArguments[i], myParameters[i])); + } + i++; + } + + return descriptors; + } + + @NotNull + private static NamedFoldingDescriptor createFoldingDescriptor(@NotNull PsiExpression callArgument, @NotNull PsiParameter methodParam) { + TextRange range = callArgument.getTextRange(); + String placeholderText = methodParam.getName() + ": " + callArgument.getText(); + return new NamedFoldingDescriptor(callArgument, range.getStartOffset(), range.getEndOffset(), null, placeholderText); + } + + private boolean isCommonlyNamedParameterPair(int first, int second) { + assert first < myParameters.length && second < myParameters.length; + + String firstParamName = myParameters[first].getName(); + String secondParamName = myParameters[second].getName(); + if (firstParamName == null || secondParamName == null) return false; + + if (containsAnyWord(firstParamName, RANGE_START_WORDS) && containsAnyWord(secondParamName, RANGE_END_WORDS)) { + return true; + } + + return false; + } + + private static boolean containsAnyWord(@NotNull String str, @NotNull String[] words) { + for (String word : words) { + if (StringUtil.containsIgnoreCase(str, word)) return true; + } + return false; + } + + private boolean shouldInlineParameterName(int paramIndex) { + PsiExpression argument = myCallArguments[paramIndex]; + if (isLiteralExpression(argument) && argument.getType() != null) { + PsiParameter parameter = myParameters[paramIndex]; + String paramName = parameter.getName(); + if (paramName != null && paramName.length() >= MIN_NAME_LENGTH_THRESHOLD) { + return TypeConversionUtil.isAssignable(parameter.getType(), argument.getType()); + } + } + return false; + } + + private static boolean hasLiteralExpression(@NotNull PsiExpression[] arguments) { + for (PsiExpression argument : arguments) { + if (isLiteralExpression(argument)) return true; + } + return false; + } +} diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/folding/JavaFoldingTest.groovy b/java/java-tests/testSrc/com/intellij/codeInsight/folding/JavaFoldingTest.groovy index 2047f13b795d..7b877f7d0c22 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/folding/JavaFoldingTest.groovy +++ b/java/java-tests/testSrc/com/intellij/codeInsight/folding/JavaFoldingTest.groovy @@ -608,7 +608,7 @@ class Test { configure(testNow, shouldIgnoreRoots(), fourteen, pi, title, c, file); } - pubic void configure(boolean testNow, boolean shouldIgnoreRoots, int times, float pi, String title, char terminate, File file) { + pubic void configure(boolean testNow, boolean shouldIgnoreRoots, int times, float pii, String title, char terminate, File file) { System.out.println(); System.out.println(); } @@ -672,7 +672,7 @@ public class VarArgTest { assert regions[1].placeholderText == "test: 13" } - public void "test inline if argument length is one (EA-57555)"() { + public void "test do not inline if parameter length is one or two"() { def text = """ public class CharSymbol { @@ -681,7 +681,7 @@ public class CharSymbol { count(1, false); } - public void count(int test, boolean fast) { + public void count(int t, boolean fa) { int temp = test; boolean isFast = fast; } @@ -689,13 +689,47 @@ public class CharSymbol { """ configure text def regions = myFixture.editor.foldingModel.allFoldRegions.sort { it.startOffset } - assert regions.size() == 4 + assert regions.size() == 2 + } - checkRangeOffsetByPositionInText(regions[1], text, "1") - assert regions[1].placeholderText == "test: 1" + public void "test do not inline paired ranged names"() { + def text = """ +public class CharSymbol { - checkRangeOffsetByPositionInText(regions[2], text, "false") - assert regions[2].placeholderText == "fast: false" + public void main() { + String s = "AAA"; + int last = 3; + + substring1(1, last); + substring2(1, last); + substring3(1, last); + substring4(1, last); + } + + public void substring1(int beginIndex, int endIndex) { + int start = beginIndex; + int end = endIndex; + } + + public void substring2(int startIndex, int endIndex) { + int start = beginIndex; + int end = endIndex; + } + + public void substring3(int from, int to) { + int start = beginIndex; + int end = endIndex; + } + + public void substring4(int first, int last) { + int start = beginIndex; + int end = endIndex; + } +} +""" + configure text + def regions = myFixture.editor.foldingModel.allFoldRegions.sort { it.startOffset } + assert regions.size() == 5 } public void "test inline names if literal expression can be assigned to method parameter"() { @@ -764,7 +798,7 @@ public class Test { } abstract class Checker { - Checker(boolean applyToFirst, boolean applyToSecond) {} + Checker(boolean isActive, boolean requestFocus) {} abstract void test(); } } @@ -773,8 +807,8 @@ public class Test { def regions = myFixture.editor.foldingModel.allFoldRegions.sort { it.startOffset } assert regions.length == 6 - assert regions[1].placeholderText == "applyToFirst: true" - assert regions[2].placeholderText == "applyToSecond: false" + assert regions[1].placeholderText == "isActive: true" + assert regions[2].placeholderText == "requestFocus: false" checkRangeOffsetByPositionInText(regions[1], text, "true") checkRangeOffsetByPositionInText(regions[2], text, "false")