record (@NotNull PsiLoopStatement loopStatement, @NotNull PsiStatement loopBody, @NotNull PsiExpression condition, @NotNull PsiIfStatement statement, boolean conditionInTheBeginning, boolean conditionInThen, boolean isInfiniteLoop) { @Nullable private static Context from(@NotNull PsiConditionalLoopStatement loopStatement, boolean noConversionToDoWhile, boolean suggestConversionWhenIfIsASingleStmtInLoop) { boolean isEndlessLoop = ControlFlowUtils.isEndlessLoop(loopStatement); if (!isEndlessLoop) { if (loopStatement instanceof PsiForStatement forStatement) { if ((forStatement.getInitialization() != null && !(forStatement.getInitialization() instanceof PsiEmptyStatement)) || (forStatement.getUpdate() != null && !(forStatement.getUpdate() instanceof PsiEmptyStatement))) { return null; } } } PsiStatement body = loopStatement.getBody(); if (body == null) return null; PsiStatement[] statements = ControlFlowUtils.unwrapBlock(body); if (statements.length < 1) return null; if (statements.length == 1 && !suggestConversionWhenIfIsASingleStmtInLoop) 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; } PsiIfStatement first = tryCast(statements[0], PsiIfStatement.class); Ref<Boolean> isBreakInThen = new Ref<>(false); PsiExpression firstBreakCondition = extractBreakCondition(first, loopStatement, isBreakInThen); PsiExpression loopCondition = loopStatement.getCondition(); boolean isLoopConditionAtStart = !(loopStatement instanceof PsiDoWhileStatement); if (first != null && firstBreakCondition != null && (isEndlessLoop || (isLoopConditionAtStart && (BoolUtils.getLogicalOperandCount(loopCondition) + BoolUtils.getLogicalOperandCount(firstBreakCondition)) < 4))) { return new Context(loopStatement, body, firstBreakCondition, first, true, isBreakInThen.get(), isEndlessLoop); } if (noConversionToDoWhile) return null; PsiIfStatement last = tryCast(statements[statements.length - 1], PsiIfStatement.class); if (last != null && last.getElseBranch() != null) return null; PsiExpression lastBreakCondition = extractBreakCondition(last, loopStatement, isBreakInThen); if (lastBreakCondition == null || !isBreakInThen.get()) return null; if (!isEndlessLoop && (isLoopConditionAtStart || (3 < BoolUtils.getLogicalOperandCount(loopCondition) + BoolUtils.getLogicalOperandCount(lastBreakCondition)))) { return 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 = ContainerUtil.exists(VariableAccessUtils.collectUsedVariables(lastBreakCondition), variable -> PsiTreeUtil.isAncestor(loopStatement, variable, false)); if (variablesInLoop) return null; return new Context(loopStatement, body, lastBreakCondition, last, false, isBreakInThen.get(), isEndlessLoop); } @Contract("null, _, _ -> null") @Nullable private static PsiExpression extractBreakCondition(@Nullable PsiIfStatement ifStatement, @NotNull PsiLoopStatement loopStatement, Ref<? super @NotNull Boolean> isBreakInThen) { if (ifStatement == null) return null; if (ControlFlowUtils.statementBreaksLoop(ControlFlowUtils.stripBraces(ifStatement.getThenBranch()), loopStatement)) { if (hasVariableNameConflict(loopStatement, ifStatement, ifStatement.getElseBranch())) return null; isBreakInThen.set(true); return ifStatement.getCondition(); } if (ifStatement.getElseBranch() != null && ControlFlowUtils.statementBreaksLoop(ControlFlowUtils.stripBraces(ifStatement.getElseBranch()), loopStatement)) { if (hasVariableNameConflict(loopStatement, ifStatement, ifStatement.getThenBranch())) return null; isBreakInThen.set(false); return ifStatement.getCondition(); } return null; } private static boolean hasVariableNameConflict(@NotNull PsiLoopStatement loopStatement, @NotNull PsiIfStatement ifStatement, @Nullable PsiStatement branch) { if (branch == null) return false; Set<String> variablesCreatedInBranch = VariableAccessUtils.findDeclaredVariables(branch).stream() .map(PsiVariable::getName) .collect(Collectors.toSet()); Set<String> otherVariablesUsedInLoop = VariableAccessUtils.collectUsedVariables(loopStatement.getBody()).stream() .filter(variable -> !PsiTreeUtil.isAncestor(ifStatement, variable, false)) .map(PsiVariable::getName) .collect(Collectors.toSet()); return !Collections.disjoint(variablesCreatedInBranch, otherVariablesUsedInLoop); } private void simplify(@NotNull PsiConditionalLoopStatement loop) { CommentTracker ct = new CommentTracker(); String loopText; if (ControlFlowUtils.isEndlessLoop(loop)) { String conditionForWhile = this.conditionInThen ? BoolUtils.getNegatedExpressionText(this.condition, ct) : ct.text( this.condition); LoopTransformationFix.pullDownStatements(this.statement, this.conditionInThen ? this.statement.getElseBranch() : this.statement.getThenBranch()); ct.delete(this.statement); loopText = this.conditionInTheBeginning ? "while(" + conditionForWhile + ")" + ct.text(this.loopBody) : "do" + ct.text(this.loopBody) + "while(" + conditionForWhile + ");"; } else { String conditionForWhile = this.conditionInThen ? BoolUtils.getNegatedExpressionText(this.condition, ParenthesesUtils.AND_PRECEDENCE, ct) : ct.text( this.condition, ParenthesesUtils.AND_PRECEDENCE); ct.delete(this.statement); PsiExpression loopCondition = loop.getCondition(); assert loopCondition != null; loopText = this.conditionInTheBeginning ? "while(" + ct.text(loopCondition, ParenthesesUtils.AND_PRECEDENCE) + " && " + conditionForWhile + ")" + ct.text( this.loopBody) : "do" + ct.text(this.loopBody) + "while(" + conditionForWhile + " && " + ct.text(loopCondition, ParenthesesUtils.AND_PRECEDENCE) + ");"; } ct.replaceAndRestoreComments(this.loopStatement, loopText); } }