[duplicates] enable duplicates analysis in PyCharm/WebStorm/PhpStorm/RubyMine
[idea/community.git] / java / java-impl / src / com / intellij / codeInspection / ExplicitMinMaxCheckInspection.java
1 // 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.
2 package com.intellij.codeInspection;
3
4 import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel;
5 import com.intellij.openapi.project.Project;
6 import com.intellij.psi.*;
7 import com.intellij.psi.tree.IElementType;
8 import com.intellij.psi.util.PsiTreeUtil;
9 import com.intellij.psi.util.PsiUtil;
10 import com.intellij.psi.util.TypeConversionUtil;
11 import com.siyeh.ig.PsiReplacementUtil;
12 import com.siyeh.ig.psiutils.CommentTracker;
13 import com.siyeh.ig.psiutils.ControlFlowUtils;
14 import com.siyeh.ig.psiutils.EquivalenceChecker;
15 import com.siyeh.ig.psiutils.ParenthesesUtils;
16 import org.jetbrains.annotations.Contract;
17 import org.jetbrains.annotations.Nls;
18 import org.jetbrains.annotations.NotNull;
19 import org.jetbrains.annotations.Nullable;
20
21 import javax.swing.*;
22
23 import static com.intellij.util.ObjectUtils.tryCast;
24
25 public class ExplicitMinMaxCheckInspection extends AbstractBaseJavaLocalInspectionTool {
26
27   public boolean disableForNonIntegralTypes = false;
28
29   @Nullable
30   @Override
31   public JComponent createOptionsPanel() {
32     MultipleCheckboxOptionsPanel panel = new MultipleCheckboxOptionsPanel(this);
33     panel.addCheckbox(InspectionsBundle.message("inspection.explicit.min.max.check.disable.for.non.integral"),
34                       "disableForNonIntegralTypes");
35     return panel;
36   }
37
38   @NotNull
39   @Override
40   public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
41     return new JavaElementVisitor() {
42
43       @Override
44       public void visitIfStatement(PsiIfStatement statement) {
45         PsiBinaryExpression condition = getCondition(statement.getCondition());
46         if (condition == null) return;
47         PsiStatement thenStatement = ControlFlowUtils.stripBraces(statement.getThenBranch());
48         if (thenStatement == null) return;
49         PsiStatement elseStatement = ControlFlowUtils.stripBraces(statement.getElseBranch());
50         if (elseStatement == null) return;
51         if (thenStatement.getClass() != elseStatement.getClass()) return;
52
53         PsiExpression thenExpression = getBranchExpression(thenStatement);
54         if (thenExpression == null) return;
55         PsiExpression elseExpression = getBranchExpression(elseStatement);
56         if (elseExpression == null) return;
57         if (thenExpression instanceof PsiAssignmentExpression && elseExpression instanceof PsiAssignmentExpression) {
58           PsiAssignmentExpression thenAssignment = (PsiAssignmentExpression)thenExpression;
59           PsiAssignmentExpression elseAssignment = (PsiAssignmentExpression)elseExpression;
60           if (!thenAssignment.getOperationTokenType().equals(elseAssignment.getOperationTokenType())) return;
61           EquivalenceChecker equivalenceChecker = EquivalenceChecker.getCanonicalPsiEquivalence();
62           if (!equivalenceChecker.expressionsAreEquivalent(thenAssignment.getLExpression(), elseAssignment.getLExpression())) return;
63           visitConditional(statement.getFirstChild(), condition, thenAssignment.getRExpression(), elseAssignment.getRExpression());
64         }
65         else {
66           visitConditional(statement.getFirstChild(), condition, thenExpression, elseExpression);
67         }
68       }
69
70       @Override
71       public void visitConditionalExpression(PsiConditionalExpression expression) {
72         PsiBinaryExpression condition = getCondition(expression.getCondition());
73         if (condition == null) return;
74         visitConditional(expression, condition, expression.getThenExpression(), expression.getElseExpression());
75       }
76
77       private void visitConditional(@NotNull PsiElement element,
78                                     @NotNull PsiBinaryExpression condition,
79                                     @Nullable PsiExpression thenExpression,
80                                     @Nullable PsiExpression elseExpression) {
81         if (thenExpression == null || elseExpression == null) return;
82         PsiExpression left = condition.getLOperand();
83         PsiExpression right = condition.getROperand();
84         if (right == null) return;
85         if (!hasCompatibleType(left) || !hasCompatibleType(right) ||
86             PsiUtil.isIncrementDecrementOperation(left) || PsiUtil.isIncrementDecrementOperation(right)) {
87           return;
88         }
89         EquivalenceChecker equivalenceChecker = EquivalenceChecker.getCanonicalPsiEquivalence();
90         boolean useMathMin = equivalenceChecker.expressionsAreEquivalent(left, elseExpression);
91         if (!useMathMin && !equivalenceChecker.expressionsAreEquivalent(left, thenExpression)) return;
92         if (!equivalenceChecker.expressionsAreEquivalent(right, useMathMin ? thenExpression : elseExpression)) return;
93         useMathMin ^= JavaTokenType.LT.equals(condition.getOperationTokenType());
94         holder.registerProblem(element,
95                                InspectionsBundle.message("inspection.explicit.min.max.check.description", useMathMin ? "min" : "max"),
96                                new ReplaceWithMinMaxFix(useMathMin));
97       }
98
99       private boolean hasCompatibleType(@NotNull PsiExpression expression) {
100         PsiType type = expression.getType();
101         if (type == null) return false;
102         int rank = TypeConversionUtil.getTypeRank(type);
103         if (rank < TypeConversionUtil.INT_RANK || rank > TypeConversionUtil.DOUBLE_RANK) return false;
104         return !disableForNonIntegralTypes || rank <= TypeConversionUtil.LONG_RANK;
105       }
106     };
107   }
108
109   @Nullable
110   private static PsiBinaryExpression getCondition(@Nullable PsiExpression expression) {
111     PsiBinaryExpression condition = tryCast(ParenthesesUtils.stripParentheses(expression), PsiBinaryExpression.class);
112     if (condition == null) return null;
113     IElementType tokenType = condition.getOperationTokenType();
114     return JavaTokenType.LT.equals(tokenType) || JavaTokenType.GT.equals(tokenType) ? condition : null;
115   }
116
117   @Nullable
118   private static PsiExpression getBranchExpression(@NotNull PsiStatement statement) {
119     if (statement instanceof PsiReturnStatement) {
120       return ((PsiReturnStatement)statement).getReturnValue();
121     }
122     else if (statement instanceof PsiBreakStatement) {
123       return ((PsiBreakStatement)statement).getExpression();
124     }
125     else if (statement instanceof PsiExpressionStatement) {
126       return PsiTreeUtil.findChildOfType(((PsiExpressionStatement)statement).getExpression(), PsiAssignmentExpression.class, false);
127     }
128     return null;
129   }
130
131   private static class ReplaceWithMinMaxFix implements LocalQuickFix {
132
133     private final boolean myUseMathMin;
134
135     @Contract(pure = true)
136     private ReplaceWithMinMaxFix(boolean useMathMin) {
137       myUseMathMin = useMathMin;
138     }
139
140     @Nls(capitalization = Nls.Capitalization.Sentence)
141     @NotNull
142     @Override
143     public String getFamilyName() {
144       return CommonQuickFixBundle.message("fix.replace.with.x", "Math." + (myUseMathMin ? "min" : "max"));
145     }
146
147     @Override
148     public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
149       PsiElement element = descriptor.getPsiElement();
150       if (element instanceof PsiConditionalExpression) {
151         PsiBinaryExpression condition = getCondition(((PsiConditionalExpression)element).getCondition());
152         if (condition == null) return;
153         String replacement = createReplacement(condition);
154         if (replacement == null) return;
155         PsiReplacementUtil.replaceExpression((PsiExpression)element, replacement, new CommentTracker());
156         return;
157       }
158       PsiIfStatement ifStatement = PsiTreeUtil.getParentOfType(element, PsiIfStatement.class);
159       if (ifStatement == null) return;
160       PsiBinaryExpression condition = getCondition(ifStatement.getCondition());
161       if (condition == null) return;
162       String replacement = createReplacement(condition);
163       if (replacement == null) return;
164       PsiStatement thenBranch = ControlFlowUtils.stripBraces(ifStatement.getThenBranch());
165       if (thenBranch == null) return;
166       PsiExpression toReplace = getBranchExpression(thenBranch);
167       if (toReplace instanceof PsiAssignmentExpression) toReplace = ((PsiAssignmentExpression)toReplace).getRExpression();
168       if (toReplace == null) return;
169       PsiReplacementUtil.replaceExpression(toReplace, replacement, new CommentTracker());
170       CommentTracker tracker = new CommentTracker();
171       tracker.text(thenBranch);
172       PsiReplacementUtil.replaceStatement(ifStatement, thenBranch.getText(), tracker);
173     }
174
175     @Nullable
176     private String createReplacement(@NotNull PsiBinaryExpression condition) {
177       PsiExpression left = condition.getLOperand();
178       PsiExpression right = condition.getROperand();
179       if (right == null) return null;
180       return CommonClassNames.JAVA_LANG_MATH + (myUseMathMin ? ".min" : ".max") + "(" + left.getText() + "," + right.getText() + ")";
181     }
182   }
183 }