mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-02-05 08:06:56 +07:00
175 lines
7.3 KiB
Java
175 lines
7.3 KiB
Java
// Copyright 2000-2017 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.codeInspection;
|
|
|
|
|
|
import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel;
|
|
import com.intellij.java.JavaBundle;
|
|
import com.intellij.openapi.project.Project;
|
|
import com.intellij.psi.*;
|
|
import com.intellij.psi.util.PsiTreeUtil;
|
|
import com.siyeh.ig.psiutils.BoolUtils;
|
|
import com.siyeh.ig.psiutils.CommentTracker;
|
|
import com.siyeh.ig.psiutils.ControlFlowUtils;
|
|
import com.siyeh.ig.psiutils.VariableAccessUtils;
|
|
import one.util.streamex.StreamEx;
|
|
import org.jetbrains.annotations.Nls;
|
|
import org.jetbrains.annotations.NotNull;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
import javax.swing.*;
|
|
|
|
import static com.intellij.util.ObjectUtils.tryCast;
|
|
|
|
public class ConditionalBreakInInfiniteLoopInspection extends AbstractBaseJavaLocalInspectionTool {
|
|
public boolean noConversionToDoWhile = false;
|
|
|
|
@Nullable
|
|
@Override
|
|
public JComponent createOptionsPanel() {
|
|
MultipleCheckboxOptionsPanel panel = new MultipleCheckboxOptionsPanel(this);
|
|
panel.addCheckbox(JavaBundle.message("inspection.conditional.break.in.infinite.loop.no.conversion.with.do.while"), "noConversionToDoWhile");
|
|
return panel;
|
|
}
|
|
|
|
@NotNull
|
|
@Override
|
|
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
|
|
return new JavaElementVisitor() {
|
|
@Override
|
|
public void visitWhileStatement(PsiWhileStatement statement) {
|
|
visitLoop(statement);
|
|
}
|
|
|
|
@Override
|
|
public void visitDoWhileStatement(PsiDoWhileStatement statement) {
|
|
visitLoop(statement);
|
|
}
|
|
|
|
@Override
|
|
public void visitForStatement(PsiForStatement statement) {
|
|
visitLoop(statement);
|
|
}
|
|
|
|
private void visitLoop(@NotNull PsiConditionalLoopStatement loopStatement) {
|
|
PsiElement keyword = getKeyword(loopStatement);
|
|
if(keyword == null) return;
|
|
Context context = Context.from(loopStatement, noConversionToDoWhile);
|
|
if (context == null) return;
|
|
LocalQuickFix[] fixes;
|
|
if (context.myConditionInTheBeginning) {
|
|
fixes = new LocalQuickFix[]{new LoopTransformationFix()};
|
|
}
|
|
else {
|
|
SetInspectionOptionFix setInspectionOptionFix =
|
|
new SetInspectionOptionFix(ConditionalBreakInInfiniteLoopInspection.this,
|
|
"noConversionToDoWhile",
|
|
JavaBundle.message(
|
|
"inspection.conditional.break.in.infinite.loop.no.conversion.with.do.while"),
|
|
true);
|
|
fixes = new LocalQuickFix[]{new LoopTransformationFix(), setInspectionOptionFix};
|
|
}
|
|
holder.registerProblem(keyword, JavaBundle.message("inspection.conditional.break.in.infinite.loop.description"), fixes);
|
|
}
|
|
};
|
|
}
|
|
|
|
@Nullable
|
|
private static PsiElement getKeyword(@NotNull PsiLoopStatement loopStatement) {
|
|
if(loopStatement instanceof PsiWhileStatement || loopStatement instanceof PsiForStatement) {
|
|
return loopStatement.getFirstChild();
|
|
}
|
|
return ((PsiDoWhileStatement)loopStatement).getWhileKeyword();
|
|
}
|
|
|
|
private static class Context {
|
|
final @NotNull PsiLoopStatement myLoopStatement;
|
|
final @NotNull PsiStatement myLoopBody;
|
|
final @NotNull PsiExpression myCondition;
|
|
final @NotNull PsiStatement myConditionStatement;
|
|
final boolean myConditionInTheBeginning;
|
|
|
|
Context(@NotNull PsiLoopStatement loopStatement,
|
|
@NotNull PsiStatement loopBody,
|
|
@NotNull PsiExpression condition,
|
|
@NotNull PsiStatement statement,
|
|
boolean conditionInTheBeginning) {
|
|
myLoopStatement = loopStatement;
|
|
myLoopBody = loopBody;
|
|
myCondition = condition;
|
|
myConditionStatement = statement;
|
|
myConditionInTheBeginning = conditionInTheBeginning;
|
|
}
|
|
|
|
|
|
@Nullable
|
|
static Context from(@NotNull PsiConditionalLoopStatement loopStatement, boolean noConversionToDoWhile) {
|
|
if (!ControlFlowUtils.isEndlessLoop(loopStatement)) return null;
|
|
PsiStatement body = loopStatement.getBody();
|
|
if (body == null) return null;
|
|
PsiStatement[] statements = ControlFlowUtils.unwrapBlock(body);
|
|
if (statements.length < 2) return null;
|
|
if (StreamEx.ofTree((PsiElement)body, el -> StreamEx.of(el.getChildren()))
|
|
.select(PsiBreakStatement.class)
|
|
.filter(stmt -> ControlFlowUtils.statementBreaksLoop(stmt, loopStatement))
|
|
.count() != 1) {
|
|
return null;
|
|
}
|
|
PsiStatement first = statements[0];
|
|
PsiExpression firstBreakCondition = extractBreakCondition(first, loopStatement);
|
|
if (firstBreakCondition != null) {
|
|
return new Context(loopStatement, body, firstBreakCondition, first, true);
|
|
}
|
|
if (noConversionToDoWhile) return null;
|
|
PsiStatement last = statements[statements.length - 1];
|
|
PsiExpression lastBreakCondition = extractBreakCondition(last, loopStatement);
|
|
if (lastBreakCondition != null) {
|
|
if (StreamEx.of(statements)
|
|
.flatMap(statement -> StreamEx.ofTree((PsiElement)statement, el -> StreamEx.of(el.getChildren())))
|
|
.anyMatch(e -> e instanceof PsiContinueStatement &&
|
|
((PsiContinueStatement)e).findContinuedStatement() == loopStatement)) {
|
|
return null;
|
|
}
|
|
boolean variablesInLoop = VariableAccessUtils.collectUsedVariables(lastBreakCondition).stream()
|
|
.anyMatch(var -> PsiTreeUtil.isAncestor(loopStatement, var, false));
|
|
if (variablesInLoop) return null;
|
|
return new Context(loopStatement, body, lastBreakCondition, last, false);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Nullable
|
|
private static PsiExpression extractBreakCondition(@NotNull PsiStatement statement, @NotNull PsiLoopStatement loopStatement) {
|
|
PsiIfStatement ifStatement = tryCast(statement, PsiIfStatement.class);
|
|
if (ifStatement == null) return null;
|
|
if (ifStatement.getElseBranch() != null) return null;
|
|
PsiStatement thenBranch = ifStatement.getThenBranch();
|
|
if (!ControlFlowUtils.statementBreaksLoop(ControlFlowUtils.stripBraces(thenBranch), loopStatement)) return null;
|
|
return ifStatement.getCondition();
|
|
}
|
|
}
|
|
|
|
private static class LoopTransformationFix implements LocalQuickFix {
|
|
@Nls
|
|
@NotNull
|
|
@Override
|
|
public String getFamilyName() {
|
|
return JavaBundle.message("inspection.conditional.break.in.infinite.loop");
|
|
}
|
|
|
|
@Override
|
|
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
|
PsiConditionalLoopStatement loop = PsiTreeUtil.getParentOfType(descriptor.getStartElement(), PsiConditionalLoopStatement.class);
|
|
if (loop == null) return;
|
|
Context context = Context.from(loop, false);
|
|
if (context == null) return;
|
|
CommentTracker ct = new CommentTracker();
|
|
String negated = BoolUtils.getNegatedExpressionText(context.myCondition, ct);
|
|
ct.delete(context.myConditionStatement);
|
|
String loopText = context.myConditionInTheBeginning
|
|
? "while(" + negated + ")" + ct.text(context.myLoopBody)
|
|
: "do" + ct.text(context.myLoopBody) + "while(" + negated + ");";
|
|
ct.replaceAndRestoreComments(context.myLoopStatement, loopText);
|
|
}
|
|
}
|
|
}
|