From 5b3d31150563a09f0a8e40ef24dcd7d9d20f473c Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 19 Mar 2019 15:00:31 +0700 Subject: [PATCH] IDEA-209056 Action to transform method with multiple returns into the method with single exit point First implementation --- java/java-impl/src/META-INF/JavaPlugin.xml | 4 + .../ConvertToSingleReturnAction.java | 106 +++++++ .../impl/singlereturn/ExitContext.java | 152 ++++++++++ .../impl/singlereturn/FinishMarker.java | 278 +++++++++++++++++ .../ReturnReplacementContext.java | 285 ++++++++++++++++++ .../after.java.template | 7 + .../before.java.template | 5 + .../description.html | 6 + .../afterBooleanInnerLoop.java | 16 + .../afterBooleanLoop.java | 13 + .../afterBooleanLoopIf.java | 21 ++ .../afterBooleanNestedIf.java | 27 ++ .../afterBooleanSimple.java | 13 + .../afterChangedParameter.java | 21 ++ .../afterDeleteFinished.java | 35 +++ .../convertToSingleReturn/afterIntIfs.java | 19 ++ .../convertToSingleReturn/afterIntIfs2.java | 21 ++ .../convertToSingleReturn/afterIntRanges.java | 16 + .../afterIntRanges2.java | 16 + .../afterNestedIfUnknown.java | 13 + .../afterNestedIfUnknownLocalUsed.java | 19 ++ .../afterNestedIfUnknownPlusCall.java | 18 ++ .../afterNonBlockInLoop.java | 18 ++ .../convertToSingleReturn/afterNonNulls.java | 17 ++ .../convertToSingleReturn/afterNonNulls2.java | 16 + .../convertToSingleReturn/afterNulls.java | 15 + .../afterNullsInBlock.java | 15 + .../convertToSingleReturn/afterVoidLoop.java | 11 + .../afterVoidLoopNotFound.java | 16 + .../afterVoidSimple.java | 10 + .../beforeBooleanInnerLoop.java | 13 + .../beforeBooleanLoop.java | 9 + .../beforeBooleanLoopIf.java | 14 + .../beforeBooleanNestedIf.java | 15 + .../beforeBooleanSimple.java | 9 + .../beforeChangedParameter.java | 11 + .../beforeDeleteFinished.java | 29 ++ .../convertToSingleReturn/beforeIntIfs.java | 12 + .../convertToSingleReturn/beforeIntIfs2.java | 12 + .../beforeIntRanges.java | 11 + .../beforeIntRanges2.java | 11 + .../beforeNestedIfUnknown.java | 12 + .../beforeNestedIfUnknownLocalUsed.java | 13 + .../beforeNestedIfUnknownPlusCall.java | 12 + .../beforeNonBlockInLoop.java | 10 + .../convertToSingleReturn/beforeNonNulls.java | 12 + .../beforeNonNulls2.java | 12 + .../convertToSingleReturn/beforeNulls.java | 11 + .../beforeNullsInBlock.java | 15 + .../convertToSingleReturn/beforeVoidLoop.java | 11 + .../beforeVoidLoopNotFound.java | 12 + .../beforeVoidSimple.java | 9 + .../ConvertToSingleReturnActionTest.java | 11 + .../src/messages/CodeInsightBundle.properties | 4 +- 54 files changed, 1518 insertions(+), 1 deletion(-) create mode 100644 java/java-impl/src/com/intellij/codeInsight/intention/impl/singlereturn/ConvertToSingleReturnAction.java create mode 100644 java/java-impl/src/com/intellij/codeInsight/intention/impl/singlereturn/ExitContext.java create mode 100644 java/java-impl/src/com/intellij/codeInsight/intention/impl/singlereturn/FinishMarker.java create mode 100644 java/java-impl/src/com/intellij/codeInsight/intention/impl/singlereturn/ReturnReplacementContext.java create mode 100644 java/java-impl/src/intentionDescriptions/ConvertToSingleReturnAction/after.java.template create mode 100644 java/java-impl/src/intentionDescriptions/ConvertToSingleReturnAction/before.java.template create mode 100644 java/java-impl/src/intentionDescriptions/ConvertToSingleReturnAction/description.html create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterBooleanInnerLoop.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterBooleanLoop.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterBooleanLoopIf.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterBooleanNestedIf.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterBooleanSimple.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterChangedParameter.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterDeleteFinished.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterIntIfs.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterIntIfs2.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterIntRanges.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterIntRanges2.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNestedIfUnknown.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNestedIfUnknownLocalUsed.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNestedIfUnknownPlusCall.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNonBlockInLoop.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNonNulls.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNonNulls2.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNulls.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNullsInBlock.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterVoidLoop.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterVoidLoopNotFound.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterVoidSimple.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeBooleanInnerLoop.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeBooleanLoop.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeBooleanLoopIf.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeBooleanNestedIf.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeBooleanSimple.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeChangedParameter.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeDeleteFinished.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeIntIfs.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeIntIfs2.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeIntRanges.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeIntRanges2.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNestedIfUnknown.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNestedIfUnknownLocalUsed.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNestedIfUnknownPlusCall.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNonBlockInLoop.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNonNulls.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNonNulls2.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNulls.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNullsInBlock.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeVoidLoop.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeVoidLoopNotFound.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeVoidSimple.java create mode 100644 java/java-tests/testSrc/com/intellij/java/codeInsight/intention/ConvertToSingleReturnActionTest.java diff --git a/java/java-impl/src/META-INF/JavaPlugin.xml b/java/java-impl/src/META-INF/JavaPlugin.xml index 5ba3a37debd2..013edc69e962 100644 --- a/java/java-impl/src/META-INF/JavaPlugin.xml +++ b/java/java-impl/src/META-INF/JavaPlugin.xml @@ -1277,6 +1277,10 @@ com.intellij.codeInsight.intention.impl.SplitSwitchBranchWithSeveralCaseValuesAction Java/Control Flow + + com.intellij.codeInsight.intention.impl.singlereturn.ConvertToSingleReturnAction + Java/Control Flow + com.intellij.codeInsight.intention.impl.WrapWithUnmodifiableAction Java/Other diff --git a/java/java-impl/src/com/intellij/codeInsight/intention/impl/singlereturn/ConvertToSingleReturnAction.java b/java/java-impl/src/com/intellij/codeInsight/intention/impl/singlereturn/ConvertToSingleReturnAction.java new file mode 100644 index 000000000000..f17053f6ef0b --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/intention/impl/singlereturn/ConvertToSingleReturnAction.java @@ -0,0 +1,106 @@ +// Copyright 2000-2019 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.intention.impl.singlereturn; + +import com.intellij.codeInsight.CodeInsightBundle; +import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiTypesUtil; +import com.intellij.util.ArrayUtil; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static com.intellij.util.ObjectUtils.tryCast; + +public class ConvertToSingleReturnAction extends PsiElementBaseIntentionAction { + + @Override + public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException { + PsiCodeBlock block = findBlock(element); + if (block == null) return; + PsiType returnType = PsiTypesUtil.getMethodReturnType(block); + if (returnType == null) return; + PsiCodeBlock copy = (PsiCodeBlock)block.copy(); + + process(project, copy, returnType, FinishMarker.defineFinishMarker(block, returnType)); + CodeStyleManager.getInstance(project).reformat(block.replace(copy)); + } + + private static void process(@NotNull Project project, + PsiCodeBlock block, + PsiType returnType, + FinishMarker marker) { + ExitContext exitContext = new ExitContext(block, returnType, marker); + + while (true) { + ProgressManager.checkCanceled(); + PsiReturnStatement returnStatement = getNonTerminalReturn(block); + if (returnStatement == null) break; + ReturnReplacementContext.replaceSingleReturn(project, block, exitContext, returnStatement); + } + exitContext.declareVariables(); + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) { + PsiCodeBlock block = findBlock(element); + if (block == null) return false; + PsiType returnType = PsiTypesUtil.getMethodReturnType(block); + return returnType != null && getNonTerminalReturn(block) != null; + } + + @NotNull + @Override + public String getText() { + return getFamilyName(); + } + + @Nullable + private static PsiCodeBlock findBlock(PsiElement element) { + PsiParameterListOwner owner = PsiTreeUtil.getParentOfType(element, PsiParameterListOwner.class, false, PsiCodeBlock.class); + if (owner == null) return null; + return tryCast(owner.getBody(), PsiCodeBlock.class); + } + + private static PsiReturnStatement getNonTerminalReturn(@NotNull PsiCodeBlock block) { + PsiStatement lastStatement = ArrayUtil.getLastElement(block.getStatements()); + if (lastStatement == null) return null; + class Visitor extends JavaRecursiveElementWalkingVisitor { + private PsiReturnStatement myReturnStatement; + + @Override + public void visitReturnStatement(PsiReturnStatement statement) { + super.visitReturnStatement(statement); + if (lastStatement != statement) { + myReturnStatement = statement; + stopWalking(); + } + } + + @Override + public void visitExpression(PsiExpression expression) {} + + @Override + public void visitLambdaExpression(PsiLambdaExpression expression) {} + + @Override + public void visitClass(PsiClass aClass) {} + } + Visitor visitor = new Visitor(); + block.accept(visitor); + return visitor.myReturnStatement; + } + + @Nls(capitalization = Nls.Capitalization.Sentence) + @NotNull + @Override + public String getFamilyName() { + return CodeInsightBundle.message("intention.convert.to.single.return.name"); + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/intention/impl/singlereturn/ExitContext.java b/java/java-impl/src/com/intellij/codeInsight/intention/impl/singlereturn/ExitContext.java new file mode 100644 index 000000000000..9f39b6df1a23 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/intention/impl/singlereturn/ExitContext.java @@ -0,0 +1,152 @@ +// Copyright 2000-2019 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.intention.impl.singlereturn; + +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.VariableKind; +import com.intellij.psi.util.PsiTypesUtil; +import com.intellij.util.containers.ContainerUtil; +import com.siyeh.ig.psiutils.BoolUtils; +import com.siyeh.ig.psiutils.EquivalenceChecker; +import com.siyeh.ig.psiutils.VariableNameGenerator; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Objects; + +import static java.util.Objects.requireNonNull; + +/** + * Tracks method exit strategy and additional variables which could be necessary for single-return conversion + */ +class ExitContext { + private final @NotNull PsiType myReturnType; + private final @NotNull FinishMarker.FinishMarkerType myFinishMarkerType; + private String myFinishedVariable; + private final @NotNull PsiCodeBlock myBlock; + private final @NotNull String myReturnVariable; + private final @NotNull PsiElementFactory myFactory; + boolean myReturnVariableUsed = false; + PsiExpression myReturnVariableDefaultValue; + + ExitContext(@NotNull PsiCodeBlock block, @NotNull PsiType returnType, @NotNull FinishMarker marker) { + myBlock = block; + myFactory = JavaPsiFacade.getElementFactory(block.getProject()); + myReturnType = returnType; + myReturnVariable = + new VariableNameGenerator(block, VariableKind.LOCAL_VARIABLE).byName("result", "res").byType(returnType).generate(false); + myReturnVariableDefaultValue = marker.myDefaultValue; + if (myReturnVariableDefaultValue != null && myReturnVariableDefaultValue.isPhysical()) { + myReturnVariableDefaultValue = (PsiExpression)myReturnVariableDefaultValue.copy(); + } + myFinishMarkerType = marker.myType; + } + + String generateExitCondition() { + switch (myFinishMarkerType) { + case BOOLEAN_FALSE: + return "!" + myReturnVariable; + case BOOLEAN_TRUE: + return myReturnVariable; + case VALUE_EQUAL: + return myReturnVariable + "==" + myReturnVariableDefaultValue.getText(); + case VALUE_NON_EQUAL: + return myReturnVariable + "!=" + myReturnVariableDefaultValue.getText(); + default: + assert myFinishedVariable != null; + return myFinishedVariable; + } + } + + String getNonExitCondition() { + switch (myFinishMarkerType) { + case BOOLEAN_FALSE: + return myReturnVariable; + case BOOLEAN_TRUE: + return "!" + myReturnVariable; + case VALUE_EQUAL: + return myReturnVariable + "!=" + myReturnVariableDefaultValue.getText(); + case VALUE_NON_EQUAL: + return myReturnVariable + "==" + myReturnVariableDefaultValue.getText(); + default: + assert myFinishedVariable != null; + return "!" + myFinishedVariable; + } + } + + void registerReturnValue(PsiExpression value, List replacements) { + myReturnVariableUsed = true; + if (FinishMarker.canMoveToStart(value) && + (myReturnVariableDefaultValue == null || + EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(myReturnVariableDefaultValue, value))) { + myReturnVariableDefaultValue = (PsiExpression)value.copy(); + } + else { + replacements.add(myReturnVariable + "=" + value.getText() + ";"); + } + } + + void register(List replacements) { + if (myFinishMarkerType != FinishMarker.FinishMarkerType.SEPARATE_VAR) return; + if (myFinishedVariable == null) { + myFinishedVariable = + new VariableNameGenerator(myBlock, VariableKind.LOCAL_VARIABLE).byName("finished", "completed").generate(false); + } + String firstItem = ContainerUtil.getFirstItem(replacements); + String assignment = myFinishedVariable + "=true;"; + if (!assignment.equals(firstItem)) { + replacements.add(0, assignment); + } + } + + void declareVariables() { + if (myFinishedVariable != null) { + PsiJavaToken start = requireNonNull(myBlock.getLBrace()); + PsiExpression initializer = myFactory.createExpressionFromText("false", null); + PsiDeclarationStatement declaration = + myFactory.createVariableDeclarationStatement(myFinishedVariable, PsiType.BOOLEAN, initializer); + myBlock.addAfter(declaration, start); + } + if (myReturnVariableUsed) { + PsiJavaToken start = requireNonNull(myBlock.getLBrace()); + if (myReturnVariableDefaultValue == null && myFinishedVariable != null) { + myReturnVariableDefaultValue = myFactory.createExpressionFromText(PsiTypesUtil.getDefaultValueOfType(myReturnType), null); + } + PsiDeclarationStatement declaration = + myFactory.createVariableDeclarationStatement(myReturnVariable, myReturnType, myReturnVariableDefaultValue); + myBlock.addAfter(declaration, start); + PsiJavaToken end = requireNonNull(myBlock.getRBrace()); + myBlock.addBefore(myFactory.createStatementFromText("return " + myReturnVariable + ";", myBlock), end); + } + } + + public boolean isFinishCondition(PsiStatement statement) { + if (!(statement instanceof PsiIfStatement)) return false; + PsiExpression condition = ((PsiIfStatement)statement).getCondition(); + if (condition == null) return false; + if (BoolUtils.isNegation(condition)) { + condition = BoolUtils.getNegated(condition); + } + if (condition instanceof PsiBinaryExpression) { + condition = ((PsiBinaryExpression)condition).getLOperand(); + } + if (!(condition instanceof PsiReferenceExpression)) return false; + PsiReferenceExpression ref = (PsiReferenceExpression)condition; + return ref.getQualifierExpression() == null && + (Objects.equals(myFinishedVariable, ref.getReferenceName()) || + Objects.equals(myReturnVariable, ref.getReferenceName())); + } + + /** + * @param statement statement to check + * @return true if given statement is a return statement which returns registered default value, so it could be fully removed + */ + boolean isDefaultReturn(PsiStatement statement) { + if (myReturnVariableDefaultValue != null && statement instanceof PsiReturnStatement) { + PsiReturnStatement returnStatement = (PsiReturnStatement)statement; + return EquivalenceChecker.getCanonicalPsiEquivalence() + .expressionsAreEquivalent(myReturnVariableDefaultValue, returnStatement.getReturnValue()) && + !FinishMarker.mayNeedMarker(returnStatement, myBlock); + } + return false; + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/intention/impl/singlereturn/FinishMarker.java b/java/java-impl/src/com/intellij/codeInsight/intention/impl/singlereturn/FinishMarker.java new file mode 100644 index 000000000000..f60e261db089 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/intention/impl/singlereturn/FinishMarker.java @@ -0,0 +1,278 @@ +// Copyright 2000-2019 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.intention.impl.singlereturn; + +import com.intellij.codeInsight.Nullability; +import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil; +import com.intellij.codeInspection.dataFlow.CommonDataflow; +import com.intellij.codeInspection.dataFlow.NullabilityUtil; +import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; +import com.intellij.util.ArrayUtil; +import com.siyeh.ig.psiutils.ControlFlowUtils; +import com.siyeh.ig.psiutils.ExpressionUtils; +import one.util.streamex.StreamEx; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import static com.intellij.util.ObjectUtils.NULL; +import static com.intellij.util.ObjectUtils.tryCast; +import static java.util.Objects.requireNonNull; + +/** + * Represents a way to indicate whether method execution is already finished + */ +class FinishMarker { + /** + * Type of finish marker + */ + final @NotNull FinishMarkerType myType; + /** + * Sentinel value + */ + final @Nullable PsiExpression myDefaultValue; + + private FinishMarker(@NotNull FinishMarkerType type, @Nullable PsiExpression value) { + myType = type; + myDefaultValue = value; + } + + /** + * @param block method body (must be physical as CommonDataflow will be queried) + * @param returnType method return type + * @return a FinishMarker which is suitable for given method + */ + static FinishMarker defineFinishMarker(@NotNull PsiCodeBlock block, @NotNull PsiType returnType) { + List returns = findReturns(block); + boolean mayNeedMarker = mayNeedMarker(returns, block); + return defineFinishMarker(block, returns, returnType, mayNeedMarker, JavaPsiFacade.getElementFactory(block.getProject())); + } + + private static boolean mayNeedMarker(List returns, PsiCodeBlock block) { + for (PsiReturnStatement returnStatement : returns) { + if (mayNeedMarker(returnStatement, block)) return true; + } + return false; + } + + /** + * Checks whether we may need a marker value to indicate premature exit from given return statement. + * + * @param returnStatement return statement to check + * @param block method body (ancestor of return statement). + * @return false if it's possible to transform the code removing given return statement without introducing a marker; + * true if marker might be necessary. + */ + static boolean mayNeedMarker(PsiReturnStatement returnStatement, PsiCodeBlock block) { + PsiElement parent = returnStatement.getParent(); + if (parent instanceof PsiCodeBlock) { + PsiElement grandParent = parent.getParent(); + if (grandParent instanceof PsiStatement) { + parent = grandParent.getParent(); + } + else { + return parent != block; + } + } + if (!(parent instanceof PsiStatement)) return true; + PsiStatement currentContext = (PsiStatement)parent; + PsiStatement loopOrSwitch = PsiTreeUtil.getNonStrictParentOfType(currentContext, PsiLoopStatement.class, PsiSwitchStatement.class); + if (loopOrSwitch != null && PsiTreeUtil.isAncestor(block, loopOrSwitch, true)) { + currentContext = loopOrSwitch; + } + else { + while (true) { + PsiElement ifParent = currentContext.getParent(); + if (!(ifParent instanceof PsiCodeBlock)) break; + if (!(ifParent.getParent() instanceof PsiStatement)) { + return ifParent != block; + } + currentContext = (PsiStatement)ifParent.getParent(); + if (!(currentContext instanceof PsiBlockStatement) || + !(currentContext.getParent() instanceof PsiIfStatement) || + ControlFlowUtils.codeBlockMayCompleteNormally((PsiCodeBlock)ifParent)) { + break; + } + currentContext = (PsiStatement)currentContext.getParent(); + } + } + while (true) { + PsiElement contextParent = currentContext.getParent(); + if (contextParent instanceof PsiCodeBlock) { + PsiStatement[] contextStatements = ((PsiCodeBlock)contextParent).getStatements(); + int pos = ArrayUtil.indexOf(contextStatements, currentContext); + assert pos >= 0; + if (pos < contextStatements.length - 1) return true; + if (contextParent == block) return false; + if (!(contextParent.getParent() instanceof PsiStatement)) return true; + currentContext = (PsiStatement)contextParent.getParent(); + } + else if (contextParent instanceof PsiIfStatement || contextParent instanceof PsiLabeledStatement) { + currentContext = (PsiStatement)contextParent; + } + else { + return true; + } + } + } + + private static FinishMarker defineFinishMarker(PsiCodeBlock block, List returns, PsiType returnType, + boolean mayNeedMarker, PsiElementFactory factory) { + if (PsiType.VOID.equals(returnType)) { + return new FinishMarker(FinishMarkerType.SEPARATE_VAR, null); + } + PsiReturnStatement terminalReturn = tryCast(ArrayUtil.getLastElement(block.getStatements()), PsiReturnStatement.class); + List nonTerminalReturns = StreamEx.of(returns).without(terminalReturn) + .map(PsiReturnStatement::getReturnValue) + .map(PsiUtil::skipParenthesizedExprDown).toList(); + if (nonTerminalReturns.size() == 0) { + return new FinishMarker(FinishMarkerType.SEPARATE_VAR, null); + } + Set nonTerminalReturnValues = StreamEx.of(nonTerminalReturns) + .map(val -> val instanceof PsiLiteralExpression ? ((PsiLiteralExpression)val).getValue() : NULL) + .toSet(); + if (!mayNeedMarker) { + if (nonTerminalReturnValues.size() == 1 && nonTerminalReturnValues.iterator().next() != NULL) { + return new FinishMarker(FinishMarkerType.SEPARATE_VAR, nonTerminalReturns.iterator().next()); + } + return new FinishMarker(FinishMarkerType.SEPARATE_VAR, null); + } + if (PsiType.BOOLEAN.equals(returnType)) { + if (nonTerminalReturnValues.size() == 1) { + Object value = nonTerminalReturnValues.iterator().next(); + if (value instanceof Boolean) { + boolean boolReturn = (boolean)value; + FinishMarkerType markerType = boolReturn ? FinishMarkerType.BOOLEAN_TRUE : FinishMarkerType.BOOLEAN_FALSE; + return new FinishMarker(markerType, factory.createExpressionFromText(String.valueOf(!boolReturn), null)); + } + } + } + if (PsiType.INT.equals(returnType) || PsiType.LONG.equals(returnType)) { + return getMarkerForIntegral(nonTerminalReturns, terminalReturn, returnType, factory); + } + if (!(returnType instanceof PsiPrimitiveType)) { + if (StreamEx.of(nonTerminalReturns).map(ret -> NullabilityUtil.getExpressionNullability(ret, true)) + .allMatch(Nullability.NOT_NULL::equals)) { + return new FinishMarker(FinishMarkerType.VALUE_NON_EQUAL, factory.createExpressionFromText("null", null)); + } + } + if (terminalReturn != null) { + PsiExpression value = terminalReturn.getReturnValue(); + if (value != null && canMoveToStart(value)) { + return new FinishMarker(FinishMarkerType.SEPARATE_VAR, value); + } + } + return new FinishMarker(FinishMarkerType.SEPARATE_VAR, null); + } + + @NotNull + private static FinishMarker getMarkerForIntegral(List nonTerminalReturns, + PsiReturnStatement terminalReturn, + PsiType returnType, PsiElementFactory factory) { + boolean isLong = PsiType.LONG.equals(returnType); + LongRangeSet fullSet = requireNonNull(LongRangeSet.fromType(returnType)); + LongRangeSet set = nonTerminalReturns.stream() + .map(CommonDataflow::getExpressionRange) + .map(range -> range == null ? fullSet : range) + .reduce(LongRangeSet::unite) + .orElse(fullSet); + if (!set.isEmpty() && !set.contains(fullSet)) { + PsiExpression terminalReturnValue = terminalReturn == null ? null : terminalReturn.getReturnValue(); + LongRangeSet terminal = CommonDataflow.getExpressionRange(terminalReturnValue); + Long point; + if (terminal != null) { + point = terminal.getConstantValue(); + if (point != null && !set.contains(point)) { + PsiExpression defValue = canMoveToStart(terminalReturnValue) ? + (PsiExpression)terminalReturnValue.copy() : + factory.createExpressionFromText(point + (isLong ? "L" : ""), null); + return new FinishMarker(FinishMarkerType.VALUE_NON_EQUAL, defValue); + } + } + long[] candidates = {0, 1, -1, fullSet.min(), fullSet.max()}; + point = null; + for (long candidate : candidates) { + if (!set.contains(candidate)) { + point = candidate; + break; + } + } + if (point != null) { + String text = point == Integer.MIN_VALUE ? "java.lang.Integer.MIN_VALUE" : + point == Integer.MAX_VALUE ? "java.lang.Integer.MAX_VALUE" : + point == Long.MIN_VALUE ? "java.lang.Long.MIN_VALUE" : + point == Long.MAX_VALUE ? "java.lang.Long.MAX_VALUE" : + String.valueOf(point); + return new FinishMarker(FinishMarkerType.VALUE_NON_EQUAL, factory.createExpressionFromText(text, null)); + } + } + return new FinishMarker(FinishMarkerType.SEPARATE_VAR, null); + } + + static boolean canMoveToStart(PsiExpression value) { + if (!ExpressionUtils.isSafelyRecomputableExpression(value)) return false; + PsiReferenceExpression ref = tryCast(PsiUtil.skipParenthesizedExprDown(value), PsiReferenceExpression.class); + if (ref != null && !ref.isQualified()) { + PsiVariable target = tryCast(ref.resolve(), PsiVariable.class); + if (target instanceof PsiLocalVariable) return false; + if (target instanceof PsiParameter) { + PsiElement block = PsiUtil.getVariableCodeBlock(target, null); + return block != null && HighlightControlFlowUtil.isEffectivelyFinal(target, block, null); + } + } + return true; + } + + private static List findReturns(PsiCodeBlock block) { + List result = new ArrayList<>(); + block.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitReturnStatement(PsiReturnStatement statement) { + super.visitReturnStatement(statement); + result.add(statement); + } + + @Override + public void visitExpression(PsiExpression expression) {} + + @Override + public void visitLambdaExpression(PsiLambdaExpression expression) {} + + @Override + public void visitClass(PsiClass aClass) {} + }); + return result; + } + + /** + * Type of finish marker + */ + enum FinishMarkerType { + /** + * If result boolean variable is true, then the method execution is finished + */ + BOOLEAN_TRUE, + /** + * If result boolean variable is false, then the method execution is finished + */ + BOOLEAN_FALSE, + /** + * If result variable is equal to sentinel, then the method execution is finished + */ + VALUE_EQUAL, + /** + * If result variable is not equal to sentinel, then the method execution is finished + */ + VALUE_NON_EQUAL, + /** + * Separate boolean variable is used to indicate whether the method execution is finished. + * This value also used if it was detected that no finish marker is actually necessary + */ + SEPARATE_VAR + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/intention/impl/singlereturn/ReturnReplacementContext.java b/java/java-impl/src/com/intellij/codeInsight/intention/impl/singlereturn/ReturnReplacementContext.java new file mode 100644 index 000000000000..606ee7f6e7ad --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/intention/impl/singlereturn/ReturnReplacementContext.java @@ -0,0 +1,285 @@ +// Copyright 2000-2019 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.intention.impl.singlereturn; + +import com.intellij.codeInsight.BlockUtils; +import com.intellij.openapi.diagnostic.Attachment; +import com.intellij.openapi.diagnostic.RuntimeExceptionWithAttachments; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.ArrayUtil; +import com.intellij.util.containers.ContainerUtil; +import com.siyeh.ig.psiutils.BoolUtils; +import com.siyeh.ig.psiutils.CommentTracker; +import com.siyeh.ig.psiutils.ControlFlowUtils; +import com.siyeh.ig.psiutils.SideEffectChecker; +import one.util.streamex.StreamEx; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static com.intellij.util.ObjectUtils.tryCast; +import static java.util.Objects.requireNonNull; + +/** + * Performs replacement of single return statement as the part of {@link ConvertToSingleReturnAction}. + */ +class ReturnReplacementContext { + private final Project myProject; + private final PsiElementFactory myFactory; + private final PsiCodeBlock myBlock; + private final ExitContext myExitContext; + private PsiReturnStatement myReturnStatement; + private final List myReplacements = new ArrayList<>(); + + private ReturnReplacementContext(Project project, + PsiCodeBlock block, + ExitContext context, + PsiReturnStatement statement) { + myProject = project; + myFactory = JavaPsiFacade.getElementFactory(project); + myBlock = block; + myExitContext = context; + myReturnStatement = statement; + } + + private void process() { + PsiExpression value = myReturnStatement.getReturnValue(); + if (value != null) { + myExitContext.registerReturnValue(value, myReplacements); + } + PsiStatement currentContext = goUp(); + while (currentContext != null) { + currentContext = advance(currentContext); + } + replace(); + } + + @NotNull + private PsiStatement goUp() { + PsiElement parent = myReturnStatement.getParent(); + if (parent instanceof PsiCodeBlock) { + PsiElement grandParent = parent.getParent(); + if (!(grandParent instanceof PsiSwitchStatement)) { + PsiStatement[] statements = ((PsiCodeBlock)parent).getStatements(); + boolean afterReturn = false; + for (PsiStatement statement : statements) { + if (statement == myReturnStatement) { + afterReturn = true; + } + else if (afterReturn) { + // Unreachable statements after return (compilation error): remove them + new CommentTracker().deleteAndRestoreComments(statement); + } + } + } + if (grandParent instanceof PsiBlockStatement || grandParent instanceof PsiTryStatement || + grandParent instanceof PsiSwitchStatement) { + parent = grandParent.getParent(); + } + else if (parent != myBlock) { + throw new RuntimeExceptionWithAttachments("Unexpected structure: " + grandParent.getClass(), + new Attachment("body.txt", myBlock.getText()), + new Attachment("context.txt", grandParent.getText())); + } + } + if (!(parent instanceof PsiStatement)) { + throw new RuntimeExceptionWithAttachments("Unexpected structure: " + parent.getClass(), + new Attachment("body.txt", myBlock.getText()), + new Attachment("context.txt", parent.getText())); + } + PsiStatement currentContext = (PsiStatement)parent; + PsiStatement loopOrSwitch = PsiTreeUtil.getNonStrictParentOfType(currentContext, PsiLoopStatement.class, PsiSwitchStatement.class); + if (loopOrSwitch != null && PsiTreeUtil.isAncestor(myBlock, loopOrSwitch, true)) { + myReplacements.add("break;"); + return loopOrSwitch; + } + while (true) { + if (currentContext instanceof PsiIfStatement) { + PsiIfStatement ifStatement = (PsiIfStatement)currentContext; + boolean inThen = PsiTreeUtil.isAncestor(ifStatement.getThenBranch(), myReturnStatement, false); + PsiElement ifParent = currentContext.getParent(); + if (ifParent instanceof PsiCodeBlock) { + PsiCodeBlock resultBlock = swallowTail(currentContext, ifStatement, inThen, (PsiCodeBlock)ifParent); + if (resultBlock != null && + !ControlFlowUtils.codeBlockMayCompleteNormally(resultBlock) && + ifParent.getParent() instanceof PsiBlockStatement && + ifParent.getParent().getParent() instanceof PsiIfStatement) { + currentContext = (PsiStatement)ifParent.getParent().getParent(); + continue; + } + } + } + return currentContext; + } + } + + @Nullable + private PsiStatement advance(PsiStatement currentContext) { + PsiElement contextParent = currentContext.getParent(); + if (contextParent instanceof PsiLoopStatement) { + Object mark = new Object(); + PsiTreeUtil.mark(myReturnStatement, mark); + currentContext = BlockUtils.expandSingleStatementToBlockStatement(currentContext); + contextParent = currentContext.getParent(); + myReturnStatement = (PsiReturnStatement)requireNonNull(PsiTreeUtil.releaseMark(currentContext, mark)); + } + if (contextParent instanceof PsiCodeBlock) { + PsiElement[] tail = extractTail(currentContext, (PsiCodeBlock)contextParent); + PsiStatement loopOrSwitch = PsiTreeUtil.getParentOfType(currentContext, PsiLoopStatement.class, PsiSwitchStatement.class); + if (loopOrSwitch != null && PsiTreeUtil.isAncestor(myBlock, loopOrSwitch, true)) { + myExitContext.register(myReplacements); + String exitStatement = "if(" + myExitContext.generateExitCondition() + ") break;"; + contextParent.addAfter(myFactory.createStatementFromText(exitStatement, currentContext), currentContext); + currentContext = loopOrSwitch; + return currentContext; + } + List statements = StreamEx.of(tail).select(PsiStatement.class).toList(); + if (!statements.isEmpty()) { + PsiStatement statement = statements.get(0); + if (statements.size() == 1 && myExitContext.isDefaultReturn(statement)) { + new CommentTracker().deleteAndRestoreComments(statement); + } + else { + myExitContext.register(myReplacements); + if (!myExitContext.isFinishCondition(statement)) { + String conditionalBlock = "if(" + myExitContext.getNonExitCondition() + ") {}"; + PsiIfStatement ifStatement = (PsiIfStatement)myFactory.createStatementFromText(conditionalBlock, currentContext); + PsiCodeBlock ifBlock = requireNonNull(((PsiBlockStatement)requireNonNull(ifStatement.getThenBranch())).getCodeBlock()); + PsiJavaToken lBrace = requireNonNull(ifBlock.getLBrace()); + PsiElement tailStart = ArrayUtil.getFirstElement(tail); + PsiElement tailEnd = ArrayUtil.getLastElement(tail); + ifBlock.addRangeAfter(tailStart, tailEnd, lBrace); + contextParent.deleteChildRange(tailStart, tailEnd); + contextParent.addAfter(ifStatement, currentContext); + } + } + } + if (contextParent == myBlock) { + currentContext = null; + } + else if (contextParent.getParent() instanceof PsiStatement) { + currentContext = (PsiStatement)contextParent.getParent(); + } + else { + throw new RuntimeExceptionWithAttachments("Unexpected structure: " + contextParent.getParent().getClass(), + new Attachment("body.txt", myBlock.getText()), + new Attachment("context.txt", contextParent.getText())); + } + } + else if (contextParent instanceof PsiIfStatement || contextParent instanceof PsiLabeledStatement) { + currentContext = (PsiStatement)contextParent; + } + else { + throw new RuntimeExceptionWithAttachments("Unexpected structure: " + contextParent.getClass(), + new Attachment("body.txt", myBlock.getText()), + new Attachment("context.txt", contextParent.getText())); + } + return currentContext; + } + + @NotNull + private static PsiElement[] extractTail(PsiStatement current, PsiCodeBlock block) { + PsiElement[] children = block.getChildren(); + int pos = ArrayUtil.indexOf(children, current); + assert pos >= 0; + PsiElement rBrace = block.getRBrace(); + int endPos = rBrace == null ? children.length : ArrayUtil.lastIndexOf(children, rBrace); + assert endPos >= pos; + return Arrays.copyOfRange(children, pos + 1, endPos); + } + + private PsiCodeBlock swallowTail(PsiStatement currentContext, + PsiIfStatement ifStatement, + boolean inThen, PsiCodeBlock ifParent) { + PsiElement[] tail = extractTail(currentContext, ifParent); + if (Arrays.stream(tail).noneMatch(PsiStatement.class::isInstance)) return null; + PsiBlockStatement blockForTail = getBlockFromIf(ifStatement, inThen); + PsiCodeBlock codeBlock = blockForTail.getCodeBlock(); + PsiJavaToken brace = requireNonNull(codeBlock.getRBrace()); + for (PsiElement element : tail) { + if (element.isValid()) { + codeBlock.addBefore(element, brace); + element.delete(); + } + } + return codeBlock; + } + + @NotNull + private PsiBlockStatement getBlockFromIf(PsiIfStatement ifStatement, boolean inThen) { + if (inThen) { + PsiStatement elseBranch = ifStatement.getElseBranch(); + if (elseBranch == null) { + ifStatement.setElseBranch(BlockUtils.createBlockStatement(myProject)); + return (PsiBlockStatement)ifStatement.getElseBranch(); + } + if (!(elseBranch instanceof PsiBlockStatement)) { + return (PsiBlockStatement)BlockUtils.expandSingleStatementToBlockStatement(elseBranch).getParent().getParent(); + } + return (PsiBlockStatement)elseBranch; + } + else { + PsiStatement thenBranch = ifStatement.getThenBranch(); + if (thenBranch == null) { + ifStatement.setThenBranch(BlockUtils.createBlockStatement(myProject)); + return (PsiBlockStatement)ifStatement.getThenBranch(); + } + if (!(thenBranch instanceof PsiBlockStatement)) { + return (PsiBlockStatement)BlockUtils.expandSingleStatementToBlockStatement(thenBranch).getParent().getParent(); + } + return (PsiBlockStatement)thenBranch; + } + } + + private void replace() { + if (!(myReturnStatement.getParent().getParent() instanceof PsiBlockStatement)) { + myReturnStatement = BlockUtils.expandSingleStatementToBlockStatement(myReturnStatement); + } + PsiStatement[] newStatements = ContainerUtil.map2Array( + myReplacements, PsiStatement.class, text -> myFactory.createStatementFromText(text, null)); + if (newStatements.length > 0) { + BlockUtils.addBefore(myReturnStatement, newStatements); + } + PsiCodeBlock block = tryCast(myReturnStatement.getParent(), PsiCodeBlock.class); + new CommentTracker().deleteAndRestoreComments(myReturnStatement); + cleanUpEmptyBlocks(block); + } + + private static void cleanUpEmptyBlocks(PsiCodeBlock block) { + if (block == null || !block.isEmpty()) return; + PsiBlockStatement blockStatement = tryCast(block.getParent(), PsiBlockStatement.class); + if (blockStatement == null) return; + PsiIfStatement parent = tryCast(blockStatement.getParent(), PsiIfStatement.class); + if (parent == null) return; + PsiExpression condition = parent.getCondition(); + if (condition == null) return; + if (blockStatement == parent.getElseBranch()) { + new CommentTracker().deleteAndRestoreComments(blockStatement); + } + else if (blockStatement == parent.getThenBranch()) { + if (parent.getElseBranch() != null) { + new CommentTracker().replaceAndRestoreComments(blockStatement, parent.getElseBranch()); + parent.getElseBranch().delete(); + CommentTracker ct = new CommentTracker(); + String negatedCondition = BoolUtils.getNegatedExpressionText(condition, ct); + ct.replaceAndRestoreComments(condition, negatedCondition); + } + else if (!SideEffectChecker.mayHaveSideEffects(condition)) { + PsiCodeBlock parentBlock = tryCast(parent.getParent(), PsiCodeBlock.class); + new CommentTracker().deleteAndRestoreComments(parent); + cleanUpEmptyBlocks(parentBlock); + } + } + } + + static void replaceSingleReturn(@NotNull Project project, + PsiCodeBlock block, + ExitContext exitContext, + PsiReturnStatement returnStatement) { + new ReturnReplacementContext(project, block, exitContext, returnStatement).process(); + } +} diff --git a/java/java-impl/src/intentionDescriptions/ConvertToSingleReturnAction/after.java.template b/java/java-impl/src/intentionDescriptions/ConvertToSingleReturnAction/after.java.template new file mode 100644 index 000000000000..13ece7d1539e --- /dev/null +++ b/java/java-impl/src/intentionDescriptions/ConvertToSingleReturnAction/after.java.template @@ -0,0 +1,7 @@ +void test(String s) { + if(s != null) { + if(!s.isEmpty()) { + System.out.println(s); + } + } +} \ No newline at end of file diff --git a/java/java-impl/src/intentionDescriptions/ConvertToSingleReturnAction/before.java.template b/java/java-impl/src/intentionDescriptions/ConvertToSingleReturnAction/before.java.template new file mode 100644 index 000000000000..52c9b1f4cc96 --- /dev/null +++ b/java/java-impl/src/intentionDescriptions/ConvertToSingleReturnAction/before.java.template @@ -0,0 +1,5 @@ +void test(String s) { + if(s == null) return; + if(s.isEmpty()) return; + System.out.println(s); +} \ No newline at end of file diff --git a/java/java-impl/src/intentionDescriptions/ConvertToSingleReturnAction/description.html b/java/java-impl/src/intentionDescriptions/ConvertToSingleReturnAction/description.html new file mode 100644 index 000000000000..a832d7fe6f84 --- /dev/null +++ b/java/java-impl/src/intentionDescriptions/ConvertToSingleReturnAction/description.html @@ -0,0 +1,6 @@ + + +

This intention converts the method body to single 'return' form. +

+ + \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterBooleanInnerLoop.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterBooleanInnerLoop.java new file mode 100644 index 000000000000..2b86a683212f --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterBooleanInnerLoop.java @@ -0,0 +1,16 @@ +// "Transform body to single exit-point form" "true" +class Test { + boolean noEmptyStrings(String[][] list) { + boolean result = true; + for (String[] inner : list) { + for (String s : inner) { + if (s.isEmpty()) { + result = false; + break; + } + } + if (!result) break; + } + return result; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterBooleanLoop.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterBooleanLoop.java new file mode 100644 index 000000000000..97c8669ef234 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterBooleanLoop.java @@ -0,0 +1,13 @@ +// "Transform body to single exit-point form" "true" +class Test { + boolean hasEmptyString(List list) { + boolean result = false; + for (String s : list) { + if (s.isEmpty()) { + result = true; + break; + } + } + return result; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterBooleanLoopIf.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterBooleanLoopIf.java new file mode 100644 index 000000000000..3adbfacc0d6d --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterBooleanLoopIf.java @@ -0,0 +1,21 @@ +// "Transform body to single exit-point form" "true" +class Test { + boolean test(String[] arr) { + boolean result = false; + boolean finished = false; + if (arr != null) { + System.out.println("ok"); + for (String s : arr) { + if (s.isEmpty()) { + finished = true; + break; + } + System.out.println(s); + } + if (!finished) { + result = true; + } + } + return result; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterBooleanNestedIf.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterBooleanNestedIf.java new file mode 100644 index 000000000000..03409e0cf2df --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterBooleanNestedIf.java @@ -0,0 +1,27 @@ +// "Transform body to single exit-point form" "true" +class Test { + boolean test(String[] arr) { + boolean result = true; + if (arr == null) { + result = false; + } else { + String s = arr[0]; + if (s == null) { + result = false; + } else { + s = arr[1]; + if (s == null) { + result = false; + } else { + if (arr.length > 3) { + s = arr[2]; + if (s != null && s.isEmpty()) { + result = false; + } + } + } + } + } + return result; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterBooleanSimple.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterBooleanSimple.java new file mode 100644 index 000000000000..d243459280fc --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterBooleanSimple.java @@ -0,0 +1,13 @@ +// "Transform body to single exit-point form" "true" +class Test { + boolean test(String s) { + boolean result = false; + if (s != null) { + if (!s.isEmpty()) { + System.out.println(s); + result = true; + } + } + return result; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterChangedParameter.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterChangedParameter.java new file mode 100644 index 000000000000..fc236e8d7572 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterChangedParameter.java @@ -0,0 +1,21 @@ +// "Transform body to single exit-point form" "true" +class Test { + String test2(List list, String foo, String bar) { + String result = foo; + boolean finished = false; + for (String s : list) { + for (int i = 0; i < 10; i++) { + bar = s; + if (s.length() == i) { + finished = true; + break; + } + } + if (finished) break; + } + if (!finished) { + result = bar; + } + return result; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterDeleteFinished.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterDeleteFinished.java new file mode 100644 index 000000000000..ba61cb394f45 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterDeleteFinished.java @@ -0,0 +1,35 @@ +// "Transform body to single exit-point form" "true" +class Test { + native String get(String s); + + String test(String[] data) { + String result = null; + boolean finished = false; + if (data == null) { + result = get("foo"); + } else { + String s = data[0]; + int i = 0; + if (data.length > 2) { + if (data[2] != null) { + if (data[2].isEmpty()) { + finished = true; + } + } + if (!finished) { + while (true) { + if (!s.isEmpty()) { + if (s.length() > 2) { + result = s; + break; + } + } + System.out.println(s); + s = data[i++]; + } + } + } + } + return result; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterIntIfs.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterIntIfs.java new file mode 100644 index 000000000000..f457c1fd86ab --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterIntIfs.java @@ -0,0 +1,19 @@ +// "Transform body to single exit-point form" "true" +class Test { + int test(String s) { + int result = 2; + if (s == null) { + if (!(Math.random() > 0.5)) { + result = 4; + } + } else { + if (s.isEmpty()) { + result = 3; + } else { + System.out.println(s); + result = 1; + } + } + return result; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterIntIfs2.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterIntIfs2.java new file mode 100644 index 000000000000..470504b54505 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterIntIfs2.java @@ -0,0 +1,21 @@ +// "Transform body to single exit-point form" "true" +class Test { + int test(String s) { + int result = 1; + if (s == null) { + if (Math.random() > 0.5) { + result = 2; + } else { + System.out.println("going further"); + } + } + if (result == 1) { + if (s.isEmpty()) { + result = 3; + } else { + System.out.println(s); + } + } + return result; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterIntRanges.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterIntRanges.java new file mode 100644 index 000000000000..79bc776521a2 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterIntRanges.java @@ -0,0 +1,16 @@ +// "Transform body to single exit-point form" "true" +class Test { + int test(String[] strings) { + int result = 0; + for (String string : strings) { + if (!string.isEmpty()) { + result = string.length(); + break;// positive number + } + } + if (result == 0) { + result = strings.length; + } + return result; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterIntRanges2.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterIntRanges2.java new file mode 100644 index 000000000000..f03f6cd14ed2 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterIntRanges2.java @@ -0,0 +1,16 @@ +// "Transform body to single exit-point form" "true" +class Test { + int test(String[] strings) { + int result = -1; + for (String string : strings) { + if (!string.equal("foo")) { + result = string.length(); + break;// non-negative number + } + } + if (result == -1) { + result = strings.length; + } + return result; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNestedIfUnknown.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNestedIfUnknown.java new file mode 100644 index 000000000000..d32ba50b3358 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNestedIfUnknown.java @@ -0,0 +1,13 @@ +// "Transform body to single exit-point form" "true" +class Test { + String test(String[] strings) { + String result = ""; + if (strings.length > 2) { + String string = strings[0]; + if (string.equals(strings[1])) { + result = foo(string); + } + } + return result; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNestedIfUnknownLocalUsed.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNestedIfUnknownLocalUsed.java new file mode 100644 index 000000000000..f39ac4c64e47 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNestedIfUnknownLocalUsed.java @@ -0,0 +1,19 @@ +// "Transform body to single exit-point form" "true" +class Test { + String test(String[] strings) { + String res = null; + boolean finished = false; + if (strings.length > 2) { + String string = strings[0]; + if (string.equals(strings[1])) { + finished = true; + res = foo(string); + } + } + if (!finished) { + String result = bar(); + res = result; + } + return res; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNestedIfUnknownPlusCall.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNestedIfUnknownPlusCall.java new file mode 100644 index 000000000000..d2273a7f2b0a --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNestedIfUnknownPlusCall.java @@ -0,0 +1,18 @@ +// "Transform body to single exit-point form" "true" +class Test { + String test(String[] strings) { + String result = null; + boolean finished = false; + if (strings.length > 2) { + String string = strings[0]; + if (string.equals(strings[1])) { + finished = true; + result = foo(string); + } + } + if (!finished) { + result = bar(); + } + return result; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNonBlockInLoop.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNonBlockInLoop.java new file mode 100644 index 000000000000..c67b0c3b7a2d --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNonBlockInLoop.java @@ -0,0 +1,18 @@ +// "Transform body to single exit-point form" "true" +class Test { + String test2(List list, String foo, String bar) { + String result = bar; + boolean finished = false; + for (String s : list) { + for (int i = 0; i < 10; i++) { + if (s.length() == i) { + finished = true; + result = foo; + break; + } + } + if (finished) break; + } + return result; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNonNulls.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNonNulls.java new file mode 100644 index 000000000000..50710cfbf3ae --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNonNulls.java @@ -0,0 +1,17 @@ +// "Transform body to single exit-point form" "true" +class Test { + String process(String s, int x) { + String result = null; + if (x > 0) { + if (x == 2) { + result = s.trim(); + } else { + System.out.println(s.substring(0)); + } + } + if (result == null) { + result = s.substring(1); + } + return result; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNonNulls2.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNonNulls2.java new file mode 100644 index 000000000000..b0e7df496303 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNonNulls2.java @@ -0,0 +1,16 @@ +// "Transform body to single exit-point form" "true" +class Test { + String process(String s, int x) { + String result; + if (x > 0) { + if (x == 2) { + result = s.trim(); + } else { + result = s.substring(0); + } + } else { + result = s.substring(1); + } + return result; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNulls.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNulls.java new file mode 100644 index 000000000000..262589e26a94 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNulls.java @@ -0,0 +1,15 @@ +// "Transform body to single exit-point form" "true" +class Test { + String process(String s) { + String res = null; + if (s != null) { + s = s.trim(); + if (!s.isEmpty()) { + System.out.println(s); + String result = s + s; + res = result; + } + } + return res; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNullsInBlock.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNullsInBlock.java new file mode 100644 index 000000000000..262589e26a94 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterNullsInBlock.java @@ -0,0 +1,15 @@ +// "Transform body to single exit-point form" "true" +class Test { + String process(String s) { + String res = null; + if (s != null) { + s = s.trim(); + if (!s.isEmpty()) { + System.out.println(s); + String result = s + s; + res = result; + } + } + return res; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterVoidLoop.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterVoidLoop.java new file mode 100644 index 000000000000..357471c8a091 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterVoidLoop.java @@ -0,0 +1,11 @@ +// "Transform body to single exit-point form" "true" +class Test { + void test2(String[] arr) { + for (String s : arr) { + if (s.isEmpty()) { + System.out.println(s); + break; + } + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterVoidLoopNotFound.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterVoidLoopNotFound.java new file mode 100644 index 000000000000..c21092fb442a --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterVoidLoopNotFound.java @@ -0,0 +1,16 @@ +// "Transform body to single exit-point form" "true" +class Test { + void test2(String[] arr) { + boolean finished = false; + for (String s : arr) { + if (s.isEmpty()) { + System.out.println(s); + finished = true; + break; + } + } + if (!finished) { + System.out.println("Not found"); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterVoidSimple.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterVoidSimple.java new file mode 100644 index 000000000000..03c9722dc965 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/afterVoidSimple.java @@ -0,0 +1,10 @@ +// "Transform body to single exit-point form" "true" +class Test { + void test2(String s) { + if (s != null) { + if (!s.isEmpty()) { + System.out.println(s); + } + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeBooleanInnerLoop.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeBooleanInnerLoop.java new file mode 100644 index 000000000000..f39036c5d608 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeBooleanInnerLoop.java @@ -0,0 +1,13 @@ +// "Transform body to single exit-point form" "true" +class Test { + boolean noEmptyStrings(String[][] list) { + for (String[] inner : list) { + for (String s : inner) { + if (s.isEmpty()) { + return false; + } + } + } + return true; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeBooleanLoop.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeBooleanLoop.java new file mode 100644 index 000000000000..8a268f1c744f --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeBooleanLoop.java @@ -0,0 +1,9 @@ +// "Transform body to single exit-point form" "true" +class Test { + boolean hasEmptyString(List list) { + for (String s : list) { + if(s.isEmpty()) return true; + } + return false; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeBooleanLoopIf.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeBooleanLoopIf.java new file mode 100644 index 000000000000..d83344b9527d --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeBooleanLoopIf.java @@ -0,0 +1,14 @@ +// "Transform body to single exit-point form" "true" +class Test { + boolean test(String[] arr) { + if (arr != null) { + System.out.println("ok"); + for(String s : arr) { + if (s.isEmpty()) return false; + System.out.println(s); + } + return true; + } + return false; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeBooleanNestedIf.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeBooleanNestedIf.java new file mode 100644 index 000000000000..458ca42216d1 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeBooleanNestedIf.java @@ -0,0 +1,15 @@ +// "Transform body to single exit-point form" "true" +class Test { + boolean test(String[] arr) { + if (arr == null) return false; + String s = arr[0]; + if (s == null) return false; + s = arr[1]; + if (s == null) return false; + if (arr.length > 3) { + s = arr[2]; + if (s != null && s.isEmpty()) return false; + } + return true; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeBooleanSimple.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeBooleanSimple.java new file mode 100644 index 000000000000..d6a16df5a564 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeBooleanSimple.java @@ -0,0 +1,9 @@ +// "Transform body to single exit-point form" "true" +class Test { + boolean test(String s) { + if(s == null) return false; + if(s.isEmpty()) return false; + System.out.println(s); + return true; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeChangedParameter.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeChangedParameter.java new file mode 100644 index 000000000000..e17377e7ee21 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeChangedParameter.java @@ -0,0 +1,11 @@ +// "Transform body to single exit-point form" "true" +class Test { + String test2(List list, String foo, String bar) { + for(String s : list) + for(int i=0; i<10; i++) { + bar = s; + if(s.length() == i) return foo; + } + return bar; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeDeleteFinished.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeDeleteFinished.java new file mode 100644 index 000000000000..d497a4c1d19c --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeDeleteFinished.java @@ -0,0 +1,29 @@ +// "Transform body to single exit-point form" "true" +class Test { + native String get(String s); + + String test(String[] data) { + if (data == null) { + return get("foo"); + } + String s = data[0]; + int i=0; + if (data.length > 2) { + if (data[2] != null) { + if(data[2].isEmpty()) { + return null; + } + } + while (true) { + if (!s.isEmpty()) { + if (s.length() > 2) { + return s; + } + } + System.out.println(s); + s = data[i++]; + } + } + return null; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeIntIfs.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeIntIfs.java new file mode 100644 index 000000000000..dd140852bdb6 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeIntIfs.java @@ -0,0 +1,12 @@ +// "Transform body to single exit-point form" "true" +class Test { + int test(String s) { + if(s == null) { + if (Math.random() > 0.5) return 2; + return 4; + } + if(s.isEmpty()) return 3; + System.out.println(s); + return 1; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeIntIfs2.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeIntIfs2.java new file mode 100644 index 000000000000..2e885d16790f --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeIntIfs2.java @@ -0,0 +1,12 @@ +// "Transform body to single exit-point form" "true" +class Test { + int test(String s) { + if(s == null) { + if (Math.random() > 0.5) return 2; + System.out.println("going further"); + } + if(s.isEmpty()) return 3; + System.out.println(s); + return 1; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeIntRanges.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeIntRanges.java new file mode 100644 index 000000000000..a6d0394910a6 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeIntRanges.java @@ -0,0 +1,11 @@ +// "Transform body to single exit-point form" "true" +class Test { + int test(String[] strings) { + for (String string : strings) { + if (!string.isEmpty()) { + return string.length(); // positive number + } + } + return strings.length; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeIntRanges2.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeIntRanges2.java new file mode 100644 index 000000000000..e5efe69dbfed --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeIntRanges2.java @@ -0,0 +1,11 @@ +// "Transform body to single exit-point form" "true" +class Test { + int test(String[] strings) { + for (String string : strings) { + if (!string.equal("foo")) { + return string.length(); // non-negative number + } + } + return strings.length; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNestedIfUnknown.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNestedIfUnknown.java new file mode 100644 index 000000000000..3ccc018e352c --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNestedIfUnknown.java @@ -0,0 +1,12 @@ +// "Transform body to single exit-point form" "true" +class Test { + String test(String[] strings) { + if (strings.length > 2) { + String string = strings[0]; + if (string.equals(strings[1])) { + return foo(string); + } + } + return ""; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNestedIfUnknownLocalUsed.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNestedIfUnknownLocalUsed.java new file mode 100644 index 000000000000..cae95e042be9 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNestedIfUnknownLocalUsed.java @@ -0,0 +1,13 @@ +// "Transform body to single exit-point form" "true" +class Test { + String test(String[] strings) { + if (strings.length > 2) { + String string = strings[0]; + if (string.equals(strings[1])) { + return foo(string); + } + } + String result = bar(); + return result; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNestedIfUnknownPlusCall.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNestedIfUnknownPlusCall.java new file mode 100644 index 000000000000..48f8028bf3ef --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNestedIfUnknownPlusCall.java @@ -0,0 +1,12 @@ +// "Transform body to single exit-point form" "true" +class Test { + String test(String[] strings) { + if (strings.length > 2) { + String string = strings[0]; + if (string.equals(strings[1])) { + return foo(string); + } + } + return bar(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNonBlockInLoop.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNonBlockInLoop.java new file mode 100644 index 000000000000..9029b0f3d0ad --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNonBlockInLoop.java @@ -0,0 +1,10 @@ +// "Transform body to single exit-point form" "true" +class Test { + String test2(List list, String foo, String bar) { + for(String s : list) + for(int i=0; i<10; i++) { + if(s.length() == i) return foo; + } + return bar; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNonNulls.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNonNulls.java new file mode 100644 index 000000000000..f723fa4b9597 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNonNulls.java @@ -0,0 +1,12 @@ +// "Transform body to single exit-point form" "true" +class Test { + String process(String s, int x) { + if (x > 0) { + if (x == 2) { + return s.trim(); + } + System.out.println(s.substring(0)); + } + return s.substring(1); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNonNulls2.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNonNulls2.java new file mode 100644 index 000000000000..b1e38d60e52d --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNonNulls2.java @@ -0,0 +1,12 @@ +// "Transform body to single exit-point form" "true" +class Test { + String process(String s, int x) { + if (x > 0) { + if (x == 2) { + return s.trim(); + } + return s.substring(0); + } + return s.substring(1); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNulls.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNulls.java new file mode 100644 index 000000000000..900cdfaca5d6 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNulls.java @@ -0,0 +1,11 @@ +// "Transform body to single exit-point form" "true" +class Test { + String process(String s) { + if (s == null) return null; + s = s.trim(); + if (s.isEmpty()) return null; + System.out.println(s); + String result = s + s; + return result; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNullsInBlock.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNullsInBlock.java new file mode 100644 index 000000000000..9de8e7295808 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeNullsInBlock.java @@ -0,0 +1,15 @@ +// "Transform body to single exit-point form" "true" +class Test { + String process(String s) { + if (s == null) { + return null; + } + s = s.trim(); + if (s.isEmpty()) { + return null; + } + System.out.println(s); + String result = s + s; + return result; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeVoidLoop.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeVoidLoop.java new file mode 100644 index 000000000000..ec95d7ba8306 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeVoidLoop.java @@ -0,0 +1,11 @@ +// "Transform body to single exit-point form" "true" +class Test { + void test2(String[] arr) { + for(String s : arr) { + if (s.isEmpty()) { + System.out.println(s); + return; + } + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeVoidLoopNotFound.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeVoidLoopNotFound.java new file mode 100644 index 000000000000..7464874063eb --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeVoidLoopNotFound.java @@ -0,0 +1,12 @@ +// "Transform body to single exit-point form" "true" +class Test { + void test2(String[] arr) { + for(String s : arr) { + if (s.isEmpty()) { + System.out.println(s); + return; + } + } + System.out.println("Not found"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeVoidSimple.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeVoidSimple.java new file mode 100644 index 000000000000..5d1cb2a72362 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn/beforeVoidSimple.java @@ -0,0 +1,9 @@ +// "Transform body to single exit-point form" "true" +class Test { + void test2(String s) { + if(s == null) return; + if(s.isEmpty()) return; + System.out.println(s); + return; + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/intention/ConvertToSingleReturnActionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/intention/ConvertToSingleReturnActionTest.java new file mode 100644 index 000000000000..fd516898f705 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/intention/ConvertToSingleReturnActionTest.java @@ -0,0 +1,11 @@ +// Copyright 2000-2019 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.java.codeInsight.intention; + +import com.intellij.codeInsight.daemon.LightIntentionActionTestCase; + +public class ConvertToSingleReturnActionTest extends LightIntentionActionTestCase { + @Override + protected String getBasePath() { + return "/codeInsight/daemonCodeAnalyzer/quickFix/convertToSingleReturn"; + } +} diff --git a/platform/platform-resources-en/src/messages/CodeInsightBundle.properties b/platform/platform-resources-en/src/messages/CodeInsightBundle.properties index 99e933ee7d8c..5af60119a826 100644 --- a/platform/platform-resources-en/src/messages/CodeInsightBundle.properties +++ b/platform/platform-resources-en/src/messages/CodeInsightBundle.properties @@ -574,4 +574,6 @@ collapse.selection.overlapping.warning.text=Overlapping fold region(s) exist collapse.selection.overlapping.warning.ok=Remove collapse.selection.overlapping.warning.cancel=Cancel -change.uid.action.name=Randomly change 'serialVersionUID' initializer \ No newline at end of file +change.uid.action.name=Randomly change 'serialVersionUID' initializer + +intention.convert.to.single.return.name=Transform body to single exit-point form \ No newline at end of file