Java inspections: Created "Move return to computation" inspection, added a few tests (IDEA-121153)

This commit is contained in:
Pavel Dolgov
2016-09-06 19:05:05 +03:00
parent e0c424778e
commit a0afa83117
64 changed files with 1424 additions and 0 deletions
@@ -0,0 +1,467 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInspection.intermediaryVariable;
import com.intellij.codeInspection.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.controlFlow.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.util.RefactoringUtil;
import com.siyeh.ig.psiutils.ControlFlowUtils;
import com.siyeh.ig.psiutils.ExpressionUtils;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author Pavel.Dolgov
*/
public class ReturnSeparatedFromComputationInspection extends BaseJavaBatchLocalInspectionTool {
private static final Logger LOG = Logger.getInstance("#" + ReturnSeparatedFromComputationInspection.class.getName());
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
return new JavaElementVisitor() {
@Override
public void visitReturnStatement(PsiReturnStatement returnStatement) {
super.visitReturnStatement(returnStatement);
final ReturnContext context = createReturnContext(returnStatement);
if (context != null && isApplicable(context)) {
registerProblem(holder, returnStatement, context.returnedVariable);
}
}
};
}
private static ReturnContext createReturnContext(PsiReturnStatement returnStatement) {
final PsiElement returnParent = returnStatement.getParent();
if (returnParent instanceof PsiCodeBlock) {
final PsiCodeBlock returnScope = (PsiCodeBlock)returnParent;
final PsiStatement[] statements = returnScope.getStatements();
if (statements.length != 0 && statements[statements.length - 1] == returnStatement) {
PsiStatement refactoredStatement = getPrevNonEmptyStatement(returnStatement, new THashSet<>());
if (refactoredStatement != null) {
final PsiExpression returnValue = returnStatement.getReturnValue();
if (returnValue instanceof PsiReferenceExpression) {
final PsiElement resolved = ((PsiReferenceExpression)returnValue).resolve();
if (resolved instanceof PsiVariable) {
final PsiVariable returnedVariable = (PsiVariable)resolved;
final PsiCodeBlock variableScope = getVariableScopeBlock(returnedVariable);
if (variableScope != null) {
return new ReturnContext(returnStatement, returnScope, refactoredStatement, returnedVariable, variableScope);
}
}
}
}
}
}
return null;
}
@Nullable
private static PsiCodeBlock getVariableScopeBlock(@Nullable PsiVariable variable) {
if (variable instanceof PsiLocalVariable) {
final PsiElement variableScope = RefactoringUtil.getVariableScope((PsiLocalVariable)variable);
if (variableScope instanceof PsiCodeBlock) {
return ((PsiCodeBlock)variableScope);
}
}
else if (variable instanceof PsiParameter) {
final PsiParameter parameter = (PsiParameter)variable;
final PsiElement parameterScope = parameter.getDeclarationScope();
if (parameterScope instanceof PsiMethod) {
return ((PsiMethod)parameterScope).getBody();
}
else if (parameterScope instanceof PsiLambdaExpression) {
final PsiElement lambdaBody = ((PsiLambdaExpression)parameterScope).getBody();
if (lambdaBody instanceof PsiCodeBlock) {
return (PsiCodeBlock)lambdaBody;
}
}
}
return null;
}
private static boolean isApplicable(@NotNull ReturnContext context) {
final ControlFlow flow = createControlFlow(context);
return flow != null && isApplicable(flow, context);
}
@Nullable
private static ControlFlow createControlFlow(@NotNull ReturnContext context) {
try {
final ControlFlowPolicy policy = new LocalsControlFlowPolicy(context.variableScope);
return ControlFlowFactory.getInstance(context.variableScope.getProject()).getControlFlow(context.variableScope, policy);
}
catch (AnalysisCanceledException e) {
return null;
}
}
private static boolean isApplicable(@NotNull ControlFlow flow, @NotNull ReturnContext context) {
final int flowStart = flow.getStartOffset(context.returnScope);
final int flowEnd = flow.getEndOffset(context.returnScope);
if (flowStart < 0 || flowEnd < 0) return false;
final int returnStartOffset = flow.getStartOffset(context.returnStatement);
final int returnEndOffset = flow.getEndOffset(context.returnStatement);
if (returnStartOffset < 0 || returnEndOffset < 0) return false;
if (context.returnScope != context.variableScope &&
ControlFlowUtil.hasObservableThrowExitPoints(flow, flowStart, flowEnd,
new PsiElement[]{context.refactoredStatement}, context.variableScope)) {
return false;
}
Mover mover = new Mover(flow, context.refactoredStatement, context.returnedVariable);
mover.moveTo(context.refactoredStatement);
return !mover.isEmpty();
}
private static void doApply(PsiReturnStatement returnStatement) {
ReturnContext context = createReturnContext(returnStatement);
if (context != null) {
ControlFlow flow = createControlFlow(context);
if (flow != null) {
Mover mover = new Mover(flow, context.refactoredStatement, context.returnedVariable);
boolean removeReturn = mover.moveTo(context.refactoredStatement);
if (!mover.isEmpty()) {
applyChanges(mover, context, removeReturn);
}
}
}
}
private static void applyChanges(@NotNull Mover mover, @NotNull ReturnContext context, boolean removeReturn) {
mover.insertAfter.forEach(e -> e.getParent().addAfter(context.returnStatement, e));
mover.insertBefore.forEach(e -> e.getParent().addBefore(context.returnStatement, e));
mover.replaceInline.forEach(e -> {
if (e instanceof PsiBreakStatement) e.replace(context.returnStatement);
if (e instanceof PsiAssignmentExpression) inlineAssignment((PsiAssignmentExpression)e, context.returnStatement);
});
mover.removeCompletely.forEach(PsiElement::delete);
if (removeReturn) {
Set<PsiElement> skippedEmptyStatements = new THashSet<>();
getPrevNonEmptyStatement(context.returnStatement, skippedEmptyStatements);
skippedEmptyStatements.forEach(PsiElement::delete);
context.returnStatement.delete();
}
}
private static void inlineAssignment(PsiAssignmentExpression assignmentExpression, PsiReturnStatement returnStatement) {
PsiElement assignmentParent = assignmentExpression.getParent();
LOG.assertTrue(assignmentParent instanceof PsiExpressionStatement, "PsiExpressionStatement");
PsiReturnStatement returnStatementCopy = (PsiReturnStatement)returnStatement.copy();
PsiExpression rExpression = assignmentExpression.getRExpression();
PsiExpression returnValue = returnStatementCopy.getReturnValue();
if (rExpression != null && returnValue!=null) {
returnValue.replace(rExpression);
assignmentParent.replace(returnStatementCopy);
}
}
private static class Mover {
final ControlFlow flow;
final PsiStatement enclosingStatement;
final PsiVariable resultVariable;
final Set<PsiElement> insertBefore = new THashSet<>();
final Set<PsiElement> insertAfter = new THashSet<>();
final Set<PsiElement> replaceInline = new THashSet<>();
final Set<PsiElement> removeCompletely = new THashSet<>();
private Map<PsiStatement, Set<PsiBreakStatement>> breakStatements;
private Mover(@NotNull ControlFlow flow, @NotNull PsiStatement enclosingStatement, @NotNull PsiVariable resultVariable) {
this.flow = flow;
this.enclosingStatement = enclosingStatement;
this.resultVariable = resultVariable;
}
boolean isEmpty() {
return insertBefore.isEmpty() && insertAfter.isEmpty() && replaceInline.isEmpty();
}
/**
* Returns true if the targetStatement will always exit via return/throw/etc after the transformation,
* so if the next statement is a return or a break it can be removed safely.
*/
boolean moveTo(PsiStatement targetStatement) {
if (targetStatement instanceof PsiBlockStatement) {
return moveToBlock((PsiBlockStatement)targetStatement);
}
if (targetStatement instanceof PsiIfStatement) {
return moveToIf((PsiIfStatement)targetStatement);
}
if (targetStatement instanceof PsiForStatement) {
return moveToFor((PsiForStatement)targetStatement);
}
if (targetStatement instanceof PsiWhileStatement) {
return moveToWhile((PsiWhileStatement)targetStatement);
}
if (targetStatement instanceof PsiDoWhileStatement) {
return moveToDoWhile((PsiDoWhileStatement)targetStatement);
}
if (targetStatement instanceof PsiForeachStatement) {
return moveToForeach((PsiForeachStatement)targetStatement);
}
if (targetStatement instanceof PsiTryStatement) {
return moveToTry(((PsiTryStatement)targetStatement));
}
if (targetStatement instanceof PsiLabeledStatement) {
return moveToLabeled(((PsiLabeledStatement)targetStatement));
}
if (targetStatement instanceof PsiExpressionStatement) {
return inlineExpression(((PsiExpressionStatement)targetStatement));
}
return false;
}
private boolean moveToBlock(PsiBlockStatement targetStatement) {
return moveToBlock(targetStatement.getCodeBlock());
}
private boolean moveToBlock(@NotNull PsiCodeBlock codeBlock) {
PsiJavaToken rBrace = codeBlock.getRBrace();
if (rBrace != null) {
PsiStatement lastNonEmptyStatement = getPrevNonEmptyStatement(rBrace, removeCompletely);
if (lastNonEmptyStatement == null || !moveTo(lastNonEmptyStatement)) {
insertBefore.add(rBrace);
}
return true;
}
return false;
}
private boolean moveToIf(PsiIfStatement targetStatement) {
PsiStatement thenBranch = targetStatement.getThenBranch();
PsiStatement elseBranch = targetStatement.getElseBranch();
boolean thenPart = thenBranch != null && moveTo(thenBranch);
boolean elsePart = elseBranch != null && moveTo(elseBranch);
return thenPart && elsePart;
}
private boolean moveToFor(PsiForStatement targetStatement) {
moveToBreaks(targetStatement);
return isAlwaysTrue(targetStatement.getCondition(), true);
}
private boolean moveToDoWhile(PsiDoWhileStatement targetStatement) {
moveToBreaks(targetStatement);
return isAlwaysTrue(targetStatement.getCondition(), false);
}
private boolean moveToWhile(PsiWhileStatement targetStatement) {
moveToBreaks(targetStatement);
return isAlwaysTrue(targetStatement.getCondition(), false);
}
private boolean moveToForeach(PsiForeachStatement targetStatement) {
moveToBreaks(targetStatement);
return false;
}
private boolean moveToTry(PsiTryStatement targetStatement) {
PsiCodeBlock tryBlock = targetStatement.getTryBlock();
if (tryBlock == null) {
return false;
}
PsiCodeBlock finallyBlock = targetStatement.getFinallyBlock();
if (finallyBlock != null && ControlFlowUtils.codeBlockMayCompleteNormally(finallyBlock) && writesVariable(finallyBlock)) {
return false;
}
PsiCatchSection[] catchSections = targetStatement.getCatchSections();
for (PsiCatchSection catchSection : catchSections) {
PsiCodeBlock catchBlock = catchSection.getCatchBlock();
if (catchBlock != null && ControlFlowUtils.codeBlockMayCompleteNormally(catchBlock) && writesVariable(finallyBlock)) {
return false;
}
}
return moveToBlock(tryBlock);
}
private boolean moveToLabeled(PsiLabeledStatement targetStatement) {
PsiStatement statement = targetStatement.getStatement();
if (statement == null) {
return false;
}
moveToBreaks(statement);
return moveTo(statement);
}
private boolean inlineExpression(PsiExpressionStatement statement) {
PsiExpression expression = statement.getExpression();
if (expression instanceof PsiAssignmentExpression) {
PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression)expression;
PsiExpression lExpression = assignmentExpression.getLExpression();
if (lExpression instanceof PsiReferenceExpression) {
PsiReferenceExpression referenceExpression = (PsiReferenceExpression)lExpression;
if (!referenceExpression.isQualified() && referenceExpression.resolve() == resultVariable) {
if (assignmentExpression.getOperationTokenType() == JavaTokenType.EQ) {
replaceInline.add(assignmentExpression);
return true;
}
}
}
}
return false;
}
private void moveToBreaks(Set<PsiBreakStatement> breaks) {
for (PsiBreakStatement breakStatement : breaks) {
PsiStatement prevNonEmptyStatement = getPrevNonEmptyStatement(breakStatement, removeCompletely);
if (prevNonEmptyStatement == null || !moveTo(prevNonEmptyStatement)) {
replaceInline.add(breakStatement);
}
else {
removeCompletely.add(breakStatement);
}
}
}
private void moveToBreaks(PsiStatement targetStatement) {
Set<PsiBreakStatement> breaks = getBreaks(targetStatement);
moveToBreaks(breaks);
}
private boolean writesVariable(PsiElement element) {
int startOffset = flow.getStartOffset(element);
int endOffset = flow.getEndOffset(element);
if (startOffset < 0 || endOffset < 0) {
return true;
}
List<Instruction> instructions = flow.getInstructions();
for (int i = startOffset; i < endOffset; i++) {
Instruction instruction = instructions.get(i);
if (instruction instanceof WriteVariableInstruction && ((WriteVariableInstruction)instruction).variable == resultVariable) {
return true;
}
}
return false;
}
private static boolean isAlwaysTrue(@Nullable PsiExpression condition, boolean nullIsTrue) {
if(condition == null) return nullIsTrue;
return ExpressionUtils.computeConstantExpression(condition) == Boolean.TRUE;
}
private Set<PsiBreakStatement> getBreaks(PsiStatement targetStatement) {
if (breakStatements == null) {
breakStatements = new THashMap<>();
List<Instruction> instructions = flow.getInstructions();
for (int i = 0; i < instructions.size(); i++) {
PsiElement element = flow.getElement(i);
PsiStatement statement = getNearestEnclosingStatement(element);
if (statement instanceof PsiBreakStatement) {
PsiStatement exitedStatement = ((PsiBreakStatement)statement).findExitedStatement();
if (exitedStatement != null) {
breakStatements.computeIfAbsent(exitedStatement, unused -> new THashSet<>()).add((PsiBreakStatement)statement);
}
}
}
}
Set<PsiBreakStatement> breaks = breakStatements.get(targetStatement);
return breaks != null ? breaks : Collections.emptySet();
}
@Nullable
private static PsiStatement getNearestEnclosingStatement(PsiElement element) {
return element instanceof PsiStatement ? (PsiStatement)element : PsiTreeUtil.getParentOfType(element, PsiStatement.class);
}
}
private static PsiStatement getPrevNonEmptyStatement(PsiElement psiElement, Set<PsiElement> skippedEmptyStatements) {
PsiStatement prevStatement = PsiTreeUtil.getPrevSiblingOfType(psiElement, PsiStatement.class);
List<PsiStatement> skipped = new ArrayList<>();
while (prevStatement instanceof PsiEmptyStatement) {
skipped.add(prevStatement);
prevStatement = PsiTreeUtil.getPrevSiblingOfType(prevStatement, PsiStatement.class);
}
if (prevStatement != null) {
skippedEmptyStatements.addAll(skipped);
}
return prevStatement;
}
private static void registerProblem(@NotNull ProblemsHolder holder,
@NotNull PsiReturnStatement returnStatement,
@NotNull PsiVariable variable) {
String name = variable.getName();
holder.registerProblem(returnStatement, InspectionsBundle.message("inspection.return.separated.from.computation.descriptor", name),
new VariableFix(name, variable instanceof PsiParameter));
}
private static class VariableFix implements LocalQuickFix {
private String myName;
private boolean myIsParameter;
public VariableFix(String name, boolean isParameter) {
myName = name;
myIsParameter = isParameter;
}
@Nls
@NotNull
@Override
public String getName() {
return InspectionsBundle.message("inspection.return.separated.from.computation.quickfix", myName);
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return InspectionsBundle.message("inspection.return.separated.from.computation.family.quickfix");
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiElement element = descriptor.getPsiElement();
if (element instanceof PsiReturnStatement) {
doApply(((PsiReturnStatement)element));
}
}
}
private static class ReturnContext {
private final PsiReturnStatement returnStatement;
private final PsiCodeBlock returnScope;
private final PsiStatement refactoredStatement;
private final PsiVariable returnedVariable;
private final PsiCodeBlock variableScope;
private ReturnContext(@NotNull PsiReturnStatement returnStatement,
@NotNull PsiCodeBlock returnScope,
@NotNull PsiStatement refactoredStatement,
@NotNull PsiVariable returnedVariable,
@NotNull PsiCodeBlock variableScope) {
this.returnStatement = returnStatement;
this.returnScope = returnScope;
this.refactoredStatement = refactoredStatement;
this.returnedVariable = returnedVariable;
this.variableScope = variableScope;
}
}
}
@@ -0,0 +1,13 @@
class T {
int f(int[] a) {
int n = -1;
myLabel:
for (int i = 0; i < a.length; i++) {
if (a[0] == 0) {
n = i;
break myLabel;
}
}
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
}
}
@@ -0,0 +1,11 @@
class T {
int f(int[] a) {
int n = -1;
myLabel:
for (int i = 0; i < a.length; i++) {
n = i;
if (a[0] == 0) break myLabel;
}
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
}
}
@@ -0,0 +1,9 @@
class T {
int f(boolean b) {
int n = 0;
myLabel:
if (b) n = 1;
else break myLabel;
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
}
}
@@ -0,0 +1,9 @@
class T {
int f() {
int n;
{
n = 1;
}
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
}
}
@@ -0,0 +1,10 @@
class T {
int f() {
int n;
{
n = 1;
System.out.println();
}
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
}
}
@@ -0,0 +1,11 @@
class T {
int f(boolean a, boolean b) {
int n = -1;
if (a) {
if (b) {
n = 1;
}
}
return n;
}
}
@@ -0,0 +1,10 @@
class T {
int f(boolean a, boolean b) {
int n = -1;
if (a) {
if (b) n = 1;
else n = 2;
}
return n;
}
}
@@ -0,0 +1,10 @@
class T {
int f(boolean a, boolean b) {
int n = -1;
if (a) {
if (b) n = 1;
}
else n = 2;
return n;
}
}
@@ -0,0 +1,15 @@
import java.io.*;
class T {
private static String getString() throws IOException {
String s;
try (BufferedReader r = open()) {
s = r.readLine();
}
<warning descr="Return separated from computation of value of 's'">return s;</warning>
}
private static BufferedReader open() throws FileNotFoundException {
return null;
}
}
@@ -0,0 +1,25 @@
class T {
String f(String a) {
String r = "";
int i = 0;
do {
int j = a.indexOf(",", i);
String s = j > i ? a.substring(i, j) : a.substring(i);
if (s.startsWith("@")) {
r = s;
break;
}
i = j + 1;
}
while (i >= 0);
<warning descr="Return separated from computation of value of 'r'">return r;</warning>
}
boolean hasNext() {
return true;
}
String next() {
return null;
}
}
@@ -0,0 +1,12 @@
class T {
int f(int[] a, int b) {
int n = -1;
for (int i = 0; i < a.length; i++) {
if (a[i] == b) {
n = i;
break;
}
}
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
}
}
@@ -0,0 +1,12 @@
class T {
String f(String[] a) {
String r = "";
for (String s : a) {
if (s != null && s.contains("@")) {
r = s + ":" + s.length();
break;
}
}
<warning descr="Return separated from computation of value of 'r'">return r;</warning>
}
}
@@ -0,0 +1,7 @@
class T {
int f(boolean b) {
int n = 0;
if (b) n = 1;
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
}
}
@@ -0,0 +1,21 @@
class T {
String f() {
String r = "";
while (hasNext()) {
String s = next();
if (s != null) {
r = s;
break;
}
}
<warning descr="Return separated from computation of value of 'r'">return r;</warning>
}
boolean hasNext() {
return true;
}
String next() {
return null;
}
}
@@ -0,0 +1,19 @@
// "Move 'return' to computation of the value of 's'" "true"
import java.io.*;
class T {
private static String getString() throws IOException {
String s;
try (BufferedReader reader = open()) {
while (true) {
s = reader.readLine();
if (s == null || s.startsWith("$")) {
return s;
}
}
}
}
private static BufferedReader open() throws FileNotFoundException {
return null;
}
}
@@ -0,0 +1,21 @@
// "Move 'return' to computation of the value of 'r'" "true"
class T {
String f() {
String r = "";
do {
if (!hasNext()) return r;
String s = next();
if (s != null) {
return s;
}
} while (true);
}
boolean hasNext() {
return true;
}
String next() {
return null;
}
}
@@ -0,0 +1,11 @@
// "Move 'return' to computation of the value of 'n'" "true"
class T {
int f() {
int n = -1;
for(int i=0;; i++) {
if (i % 127 == 0 && i % 129 == 0) {
return i + 1;
}
}
}
}
@@ -0,0 +1,8 @@
// "Move 'return' to computation of the value of 'n'" "true"
class T {
int f(boolean b) {
int n = 0;
if (b) return 1;
else return 2;
}
}
@@ -0,0 +1,9 @@
// "Move 'return' to computation of the value of 'n'" "true"
class T {
int f(boolean b) {
int n = 0;
if (b) System.out.println("yes");
else return 2;
return n;
}
}
@@ -0,0 +1,9 @@
// "Move 'return' to computation of the value of 'n'" "true"
class T {
int f(boolean b) {
int n = 0;
if (b) return 1;
else System.out.println("no");
return n;
}
}
@@ -0,0 +1,12 @@
// "Move 'return' to computation of the value of 'n'" "true"
class T {
int f(boolean b) {
int n;
myLabel:
{
n = 1;
if (b) return n;
return 2;
}
}
}
@@ -0,0 +1,14 @@
// "Move 'return' to computation of the value of 'r'" "true"
class T {
long f() {
long r;
long s = System.currentTimeMillis();
long t = s;
while (true) {
t = System.currentTimeMillis();
if (t - s > 100) {
return t;
}
}
}
}
@@ -0,0 +1,8 @@
// "Move 'return' to computation of the value of 'n'" "false"
class T {
int f(int a) {
int n = a;
assert n != 0;
r<caret>eturn n;
}
}
@@ -0,0 +1,20 @@
// "Move 'return' to computation of the value of 's'" "true"
import java.io.*;
class T {
private static String getString() throws IOException {
String s;
try (BufferedReader reader = open()) {
while (true) {
s = reader.readLine();
if (s == null || s.startsWith("$")) {
break;
}
}
}
r<caret>eturn s;
}
private static BufferedReader open() throws FileNotFoundException {
return null;
}
}
@@ -0,0 +1,23 @@
// "Move 'return' to computation of the value of 'r'" "true"
class T {
String f() {
String r = "";
do {
if (!hasNext()) break;
String s = next();
if (s != null) {
r = s;
break;
}
} while (true);
<caret>return r;
}
boolean hasNext() {
return true;
}
String next() {
return null;
}
}
@@ -0,0 +1,13 @@
// "Move 'return' to computation of the value of 'n'" "true"
class T {
int f() {
int n = -1;
for(int i=0;; i++) {
if (i % 127 == 0 && i % 129 == 0) {
n = i + 1;
break;
}
}
r<caret>eturn n;
}
}
@@ -0,0 +1,9 @@
// "Move 'return' to computation of the value of 'n'" "false"
class T {
int f(boolean b) {
int n = 0;
if (b) System.out.println("yes");
else System.out.println("no");
ret<caret>urn n;
}
}
@@ -0,0 +1,9 @@
// "Move 'return' to computation of the value of 'n'" "true"
class T {
int f(boolean b) {
int n = 0;
if (b) n = 1;
else n = 2;
r<caret>eturn n;
}
}
@@ -0,0 +1,9 @@
// "Move 'return' to computation of the value of 'n'" "true"
class T {
int f(boolean b) {
int n = 0;
if (b) System.out.println("yes");
else n = 2;
r<caret>eturn n;
}
}
@@ -0,0 +1,9 @@
// "Move 'return' to computation of the value of 'n'" "true"
class T {
int f(boolean b) {
int n = 0;
if (b) n = 1;
else System.out.println("no");
ret<caret>urn n;
}
}
@@ -0,0 +1,13 @@
// "Move 'return' to computation of the value of 'n'" "true"
class T {
int f(boolean b) {
int n;
myLabel:
{
n = 1;
if (b) break myLabel;
n = 2;
}
ret<caret>urn n;
}
}
@@ -0,0 +1,16 @@
// "Move 'return' to computation of the value of 'r'" "true"
class T {
long f() {
long r;
long s = System.currentTimeMillis();
long t = s;
while (true) {
t = System.currentTimeMillis();
if (t - s > 100) {
r = t;
break;
}
}
retu<caret>rn r;
}
}
@@ -0,0 +1,7 @@
class T {
int f(int a) {
int n = a;
assert n != 0;
return n;
}
}
@@ -0,0 +1,19 @@
import java.io.*;
class T {
private static String getString() throws IOException {
String s;
try (BufferedReader reader = open()) {
while (true) {
s = reader.readLine();
if (s == null || s.startsWith("$")) {
break;
}
}
}
<warning descr="Return separated from computation of value of 's'">return s;</warning>
}
private static BufferedReader open() throws FileNotFoundException {
return null;
}
}
@@ -0,0 +1,22 @@
class T {
String f() {
String r = "";
do {
if (!hasNext()) break;
String s = next();
if (s != null) {
r = s;
break;
}
} while (true);
<warning descr="Return separated from computation of value of 'r'">return r;</warning>
}
boolean hasNext() {
return true;
}
String next() {
return null;
}
}
@@ -0,0 +1,12 @@
class T {
int f() {
int n = -1;
for(int i=0;; i++) {
if (i % 127 == 0 && i % 129 == 0) {
n = i + 1;
break;
}
}
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
}
}
@@ -0,0 +1,8 @@
class T {
int f(boolean b) {
int n = 0;
if (b) System.out.println("yes");
else System.out.println("no");
return n;
}
}
@@ -0,0 +1,8 @@
class T {
int f(boolean b) {
int n = 0;
if (b) n = 1;
else n = 2;
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
}
}
@@ -0,0 +1,8 @@
class T {
int f(boolean b) {
int n = 0;
if (b) System.out.println("yes");
else n = 2;
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
}
}
@@ -0,0 +1,8 @@
class T {
int f(boolean b) {
int n = 0;
if (b) n = 1;
else System.out.println("no");
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
}
}
@@ -0,0 +1,12 @@
class T {
int f(boolean b) {
int n;
myLabel:
{
n = 1;
if (b) break myLabel;
n = 2;
}
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
}
}
@@ -0,0 +1,13 @@
class T {
int f(int[] a) {
int n = -1;
myLabel:
for (int i = 0; i < a.length; i++) {
if (a[0] == 0) {
n = i;
break myLabel;
}
}
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
}
}
@@ -0,0 +1,11 @@
class T {
int f(int[] a) {
int n = -1;
myLabel:
for (int i = 0; i < a.length; i++) {
n = i;
if (a[0] == 0) break myLabel;
}
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
}
}
@@ -0,0 +1,9 @@
class T {
int f(boolean b) {
int n = 0;
myLabel:
if (b) n = 1;
else break myLabel;
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
}
}
@@ -0,0 +1,9 @@
class T {
int f() {
int n;
{
n = 1;
}
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
}
}
@@ -0,0 +1,10 @@
class T {
int f() {
int n;
{
n = 1;
System.out.println();
}
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
}
}
@@ -0,0 +1,11 @@
class T {
int f(boolean a, boolean b) {
int n = -1;
if (a) {
if (b) {
n = 1;
}
}
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
}
}
@@ -0,0 +1,10 @@
class T {
int f(boolean a, boolean b) {
int n = -1;
if (a) {
if (b) n = 1;
else n = 2;
}
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
}
}
@@ -0,0 +1,10 @@
class T {
int f(boolean a, boolean b) {
int n = -1;
if (a) {
if (b) n = 1;
}
else n = 2;
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
}
}
@@ -0,0 +1,15 @@
import java.io.*;
class T {
private static String getString() throws IOException {
String s;
try (BufferedReader r = open()) {
s = r.readLine();
}
<warning descr="Return separated from computation of value of 's'">return s;</warning>
}
private static BufferedReader open() throws FileNotFoundException {
return null;
}
}
@@ -0,0 +1,9 @@
class T {
int[] f(boolean b) {
int[] r = new int[]{-1};
if (b) {
r[0] = 1;
}
return r;
}
}
@@ -0,0 +1,25 @@
class T {
String f(String a) {
String r = "";
int i = 0;
do {
int j = a.indexOf(",", i);
String s = j > i ? a.substring(i, j) : a.substring(i);
if (s.startsWith("@")) {
r = s;
break;
}
i = j + 1;
}
while (i >= 0);
<warning descr="Return separated from computation of value of 'r'">return r;</warning>
}
boolean hasNext() {
return true;
}
String next() {
return null;
}
}
@@ -0,0 +1,12 @@
class T {
int f(int[] a, int b) {
int n = -1;
for (int i = 0; i < a.length; i++) {
if (a[i] == b) {
n = i;
break;
}
}
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
}
}
@@ -0,0 +1,12 @@
class T {
String f(String[] a) {
String r = "";
for (String s : a) {
if (s != null && s.contains("@")) {
r = s + ":" + s.length();
break;
}
}
<warning descr="Return separated from computation of value of 'r'">return r;</warning>
}
}
@@ -0,0 +1,7 @@
class T {
int f(boolean b) {
int n = 0;
if (b) n = 1;
<warning descr="Return separated from computation of value of 'n'">return n;</warning>
}
}
@@ -0,0 +1,21 @@
class T {
String f() {
String r = "";
while (hasNext()) {
String s = next();
if (s != null) {
r = s;
break;
}
}
<warning descr="Return separated from computation of value of 'r'">return r;</warning>
}
boolean hasNext() {
return true;
}
String next() {
return null;
}
}
@@ -0,0 +1,15 @@
class T {
long f() {
long r;
long s = System.currentTimeMillis();
long t = s;
while (true) {
t = System.currentTimeMillis();
if (t - s > 100) {
r = t;
break;
}
}
<warning descr="Return separated from computation of value of 'r'">return r;</warning>
}
}
@@ -0,0 +1,48 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInsight.daemon.quickFix;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.intermediaryVariable.ReturnSeparatedFromComputationInspection;
import org.jetbrains.annotations.NotNull;
/**
* @author Pavel.Dolgov
*/
public class ReturnSeparatedFromComputationFix2Test extends LightQuickFixTestCase {
@NotNull
@Override
protected LocalInspectionTool[] configureLocalInspectionTools() {
return new LocalInspectionTool[]{new ReturnSeparatedFromComputationInspection()};
}
public void testAssert() {
doTest();
}
public void testBreakFromLoopInTryWithResources() {
doTest();
}
private void doTest() {
doSingleTest(getTestName(false) +".java");
}
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/returnSeparatedFromComputation";
}
}
@@ -0,0 +1,40 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInsight.daemon.quickFix;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.intermediaryVariable.ReturnSeparatedFromComputationInspection;
import org.jetbrains.annotations.NotNull;
/**
* @author Pavel.Dolgov
*/
public class ReturnSeparatedFromComputationFixTest extends LightQuickFixParameterizedTestCase {
@NotNull
@Override
protected LocalInspectionTool[] configureLocalInspectionTools() {
return new LocalInspectionTool[]{new ReturnSeparatedFromComputationInspection()};
}
public void test() throws Exception {
doAllTests();
}
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/returnSeparatedFromComputation";
}
}
@@ -0,0 +1,136 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInspection;
import com.intellij.JavaTestUtil;
import com.intellij.codeInspection.intermediaryVariable.ReturnSeparatedFromComputationInspection;
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase;
/**
* @author Pavel.Dolgov
*/
public class ReturnSeparatedFromComputationTest extends LightCodeInsightFixtureTestCase {
@Override
protected String getBasePath() {
return JavaTestUtil.getRelativeJavaTestDataPath() + "/inspection/returnSeparatedFromComputation";
}
public void testReturnOutsideTryWithResources() {
doTest();
}
public void testBreakFromLoopInTryWithResources() {
doTest();
}
public void testSimpleIf() {
doTest();
}
public void testSimpleFor() {
doTest();
}
public void testIfElseWriteInBoth() {
doTest();
}
public void testIfElseWriteInIf() {
doTest();
}
public void testIfElseWriteInElse() {
doTest();
}
public void testIfElseNoWrite() {
doTest();
}
public void testNestedIf() {
doTest();
}
public void testNestedIfInnerElse() {
doTest();
}
public void testNestedIfOuterElse() {
doTest();
}
public void testNestedBlock() {
doTest();
}
public void testNestedBlockSideEffect() {
doTest();
}
public void testAssert() {
doTest();
}
public void testLabeledBlock() {
doTest();
}
public void testLabeledFor() {
doTest();
}
public void testLabeledFor2() {
doTest();
}
public void testLabeledIf() {
doTest();
}
public void testWhileTrue() {
doTest();
}
public void testSimpleWhile() {
doTest();
}
public void testForWithoutCondition() {
doTest();
}
public void testSimpleForeach() {
doTest();
}
public void testDoWhileTrue() {
doTest();
}
public void testSimpleDoWhile() {
doTest();
}
public void testSideEffectInIf() {
doTest();
}
private void doTest() {
myFixture.enableInspections(new ReturnSeparatedFromComputationInspection());
myFixture.testHighlighting(getTestName(false) + ".java");
}
}
@@ -136,6 +136,11 @@ inspection.local.can.be.final.option3=Report foreach parameters
inspection.can.be.local.parameter.problem.descriptor=Parameter <code>#ref</code> can have <code>final</code> modifier
inspection.can.be.local.variable.problem.descriptor=Variable <code>#ref</code> can have <code>final</code> modifier
inspection.return.separated.from.computation.name=Return separated from computation of result
inspection.return.separated.from.computation.descriptor=Return separated from computation of value of ''{0}''
inspection.return.separated.from.computation.quickfix=Move ''return'' to computation of the value of ''{0}''
inspection.return.separated.from.computation.family.quickfix=Move ''return'' to computation of the result
inspection.nullable.problems.display.name=@NotNull/@Nullable problems
#check box options
inspection.nullable.problems.method.overrides.notnull.option=<html>Report @NotNull &parameters overriding @Nullable and <br>@Nullable methods overriding @NotNull</html>
@@ -0,0 +1,14 @@
<html>
<body>
This inspection detects <code>return</code> statements which return a local variable, where the value of the variable is computed
somewhere else within the same code block with the <code>return</code> statement.
<p>The quick fix inlines the returned variable by moving the return statement to the location where the value of the variable is computed.
For example, the code below could be simplified:
<pre><code>int n = -1;
if (condition) n = compute();
return n;</code></pre>
After the quick fix it becomes the following:
<pre><code>if (condition) return compute();
return -1;</code></pre>
</body>
</html>
+4
View File
@@ -647,6 +647,10 @@
<localInspection groupPath="Java" language="JAVA" shortName="LocalCanBeFinal" bundle="messages.InspectionsBundle" key="inspection.local.can.be.final.display.name"
groupKey="group.names.code.style.issues" enabledByDefault="false" level="WARNING"
implementationClass="com.intellij.codeInspection.localCanBeFinal.LocalCanBeFinal"/>
<localInspection groupPath="Java" language="JAVA" shortName="ReturnSeparatedFromComputation" bundle="messages.InspectionsBundle"
key="inspection.return.separated.from.computation.name" groupKey="group.names.code.style.issues"
enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.intermediaryVariable.ReturnSeparatedFromComputationInspection"/>
<localInspection groupPath="Java" language="JAVA" shortName="JavaDoc" bundle="messages.InspectionsBundle" key="inspection.javadoc.display.name"
groupKey="group.names.javadoc.issues" enabledByDefault="true" level="WARNING"