PsiElementVisitor (@NotNull ProblemsHolder holder, boolean isOnTheFly) { return new JavaElementVisitor() { @Override public void visitMethodCallExpression(@NotNull PsiMethodCallExpression call) { if (!LIST_REMOVE.test(call)) return; PsiExpression listExpression = call.getMethodExpression().getQualifierExpression(); if (listExpression == null) return; PsiElement parent = call.getParent(); if (!(parent instanceof PsiExpressionStatement)) return; PsiLoopStatement loop = PsiTreeUtil.getParentOfType(parent, PsiLoopStatement.class, true, PsiMember.class); if (loop == null) return; if (ControlFlowUtils.stripBraces(loop.getBody()) != parent) return; PsiExpression arg = call.getArgumentList().getExpressions()[0]; ProblemHighlightType type; if (isRemoveInCountingLoop(loop, arg)) { type = ProblemHighlightType.GENERIC_ERROR_OR_WARNING; } else if (isRemoveInWhileLoop(loop, listExpression, arg)) { // while-loop scenario is not so bad (always the last element is removed which is usually fast), // the replacement is often longer and harder to read // and such loop is often used to remove at most one element which is faster than using subList().clear() if (!isOnTheFly) return; type = ProblemHighlightType.INFORMATION; } else { return; } holder.registerProblem(loop.getFirstChild(), InspectionGadgetsBundle.message("inspection.list.remove.in.loop.message"), type, new ListRemoveInLoopFix()); } /** * Looks for pattern like * <pre>{@code * while (list.size() > smth) { * list.remove(list.size() - 1); * }}</pre> */ private static boolean isRemoveInWhileLoop(PsiLoopStatement loop, PsiExpression listExpression, PsiExpression arg) { if (!(loop instanceof PsiWhileStatement)) return false; PsiBinaryExpression condition = tryCast(PsiUtil.skipParenthesizedExprDown(((PsiWhileStatement)loop).getCondition()), PsiBinaryExpression.class); if (condition == null) return false; RelationType relationType = DfaPsiUtil.getRelationByToken(condition.getOperationTokenType()); if (relationType == null) return false; PsiExpression sizeExpression; switch (relationType) { case GE, GT -> sizeExpression = condition.getLOperand(); case LE, LT -> sizeExpression = condition.getROperand(); default -> { return false; } } PsiMethodCallExpression sizeCall = tryCast(PsiUtil.skipParenthesizedExprDown(sizeExpression), PsiMethodCallExpression.class); if (!LIST_SIZE.test(sizeCall)) return false; PsiExpression sizeQualifier = sizeCall.getMethodExpression().getQualifierExpression(); if (sizeQualifier == null || !PsiEquivalenceUtil.areElementsEquivalent(sizeQualifier, listExpression)) return false; PsiBinaryExpression diff = tryCast(PsiUtil.skipParenthesizedExprDown(arg), PsiBinaryExpression.class); if (diff == null || !diff.getOperationTokenType().equals(JavaTokenType.MINUS)) return false; if (!ExpressionUtils.isLiteral(diff.getROperand(), 1)) return false; return PsiEquivalenceUtil.areElementsEquivalent(sizeCall, diff.getLOperand()); } /** * Looks for pattern like * <pre>{@code * for (int i=from; i<to; i++) { * list.remove(from); * }}</pre> or * <pre>{@code * for (int i=to-1; i>=from; i--) { * list.remove(i); * }}</pre> */ private static boolean isRemoveInCountingLoop(PsiLoopStatement forLoop, PsiExpression arg) { if (!(forLoop instanceof PsiForStatement)) return false; CountingLoop loop = CountingLoop.from((PsiForStatement)forLoop); if (loop == null) return false; if (loop.isDescending()) { return ExpressionUtils.isReferenceTo(arg, loop.getCounter()); } return PsiEquivalenceUtil.areElementsEquivalent(arg, loop.getInitializer()); } }; }