[java-inspections] IDEA-298475 Redundant compareTo() call for java.time classes

GitOrigin-RevId: c7effac4c48448e5e9db6360f012cc13846df41d
This commit is contained in:
pyltsin-m
2022-08-09 16:31:36 +02:00
committed by intellij-monorepo-bot
parent c3bdb26560
commit 794497240b
7 changed files with 536 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
<!-- Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<html>
<body>
Reports <code>java.time</code> comparisons with <code>compareTo()</code> calls that can be replaced with
<code>isAfter()</code>, <code>isBefore()</code> or <code>isEqual()</code> calls.
<p>Example:</p>
<pre><code>
LocalDate date1 = LocalDate.now();
LocalDate date2 = LocalDate.now();
boolean t = date1.compareTo(date2) > 0;
</code></pre>
<p>After the quick-fix is applied:</p>
<pre><code>
LocalDate date1 = LocalDate.now();
LocalDate date2 = LocalDate.now();
boolean t = date1.isAfter(date2);
</code></pre>
<!-- tooltip end -->
<p><small>New in 2022.3</small></p>
</body>
</html>

View File

@@ -0,0 +1,124 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
import java.time.*;
import java.time.chrono.*;
public class Main {
public static void main(String[] args) {
LocalTime localTime = LocalTime.now();
LocalTime localTime2 = LocalTime.now();
boolean b = localTime.isAfter(localTime2);
System.out.println(b);
b = !localTime.isBefore(localTime2);
System.out.println(b);
b = localTime.isBefore(localTime2);
System.out.println(b);
b = !localTime.isAfter(localTime2);
System.out.println(b);
b = localTime.equals(localTime2);
System.out.println(b);
b = !localTime.equals(localTime2);
System.out.println(b);
b = localTime.isAfter(localTime2);
System.out.println(b);
b = !localTime.isBefore(localTime2);
System.out.println(b);
b = localTime.isBefore(localTime2);
System.out.println(b);
b = !localTime.isAfter(localTime2);
System.out.println(b);
b = localTime.equals(localTime2);
System.out.println(b);
b = !localTime.equals(localTime2);
System.out.println(b);
b = (!(localTime).equals((localTime2)));
System.out.println(b);
OffsetTime offsetTime = OffsetTime.now();
OffsetTime offsetTime2 = OffsetTime.now();
b = offsetTime.isAfter(offsetTime2);
System.out.println(b);
b = offsetTime.isAfter(offsetTime2);
System.out.println(b);
b = !offsetTime.isBefore(offsetTime2);
System.out.println(b);
b = offsetTime.isBefore(offsetTime2);
System.out.println(b);
b = !offsetTime.isAfter(offsetTime2);
System.out.println(b);
b = offsetTime.isEqual(offsetTime2);
System.out.println(b);
b = !offsetTime.isEqual(offsetTime2);
System.out.println(b);
OffsetDateTime offsetDateTime = OffsetDateTime.now();
OffsetDateTime offsetDateTime2 = OffsetDateTime.now();
b = offsetDateTime.isAfter(offsetDateTime2);
System.out.println(b);
b = !offsetDateTime.isBefore(offsetDateTime2);
System.out.println(b);
b = offsetDateTime.isBefore(offsetDateTime2);
System.out.println(b);
b = !offsetDateTime.isAfter(offsetDateTime2);
System.out.println(b);
b = offsetDateTime.isEqual(offsetDateTime2);
System.out.println(b);
b = !offsetDateTime.isEqual(offsetDateTime2);
System.out.println(b);
LocalDate localDate = LocalDate.now();
LocalDate localDate2 = LocalDate.now();
b = localDate.isAfter(localDate2);
System.out.println(b);
b = !localDate.isBefore(localDate2);
System.out.println(b);
b = localDate.isBefore(localDate2);
System.out.println(b);
b = !localDate.isAfter(localDate2);
System.out.println(b);
b = localDate.isEqual(localDate2);
System.out.println(b);
b = !localDate.isEqual(localDate2);
System.out.println(b);
b = localDate.compareTo(new ChronoLocalDate() {}) > 0;
LocalDateTime localDateTime = LocalDateTime.now();
LocalDateTime localDateTime2 = LocalDateTime.now();
b = localDateTime.isAfter(localDateTime2);
System.out.println(b);
b = !localDateTime.isBefore(localDateTime2);
System.out.println(b);
b = localDateTime.isBefore(localDateTime2);
System.out.println(b);
b = !localDateTime.isAfter(localDateTime2);
System.out.println(b);
b = localDateTime.isEqual(localDateTime2);
System.out.println(b);
b = !localDateTime.isEqual(localDateTime2);
System.out.println(b);
/*test1*/
/*test2*/
/*test3*/
/*test4*/
/*test5*/
b = /*test0*/localDateTime.isAfter(localDateTime2);
System.out.println(b);
try {
b = localDateTime.compareTo(new ChronoLocalDateTime<>() {}) > 0;
}
catch (Exception e) {
}
/*test1*/
/*test2*/
/*test3*/
/*test4*/
/*test5*/
b = /*test0*/localDateTime.isAfter(localDateTime2);
}
}

View File

@@ -0,0 +1,114 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
import java.time.*;
import java.time.chrono.*;
public class Main {
public static void main(String[] args) {
LocalTime localTime = LocalTime.now();
LocalTime localTime2 = LocalTime.now();
boolean b = localTime.com<caret>pareTo(localTime2) > 0;
System.out.println(b);
b = localTime.compareTo(localTime2) >= 0;
System.out.println(b);
b = localTime.compareTo(localTime2) < 0;
System.out.println(b);
b = localTime.compareTo(localTime2) <= 0;
System.out.println(b);
b = localTime.compareTo(localTime2) == 0;
System.out.println(b);
b = localTime.compareTo(localTime2) != 0;
System.out.println(b);
b = 0 < localTime.compareTo(localTime2);
System.out.println(b);
b = 0 <= localTime.compareTo(localTime2);
System.out.println(b);
b = 0 > localTime.compareTo(localTime2);
System.out.println(b);
b = 0 >= localTime.compareTo(localTime2);
System.out.println(b);
b = 0 == localTime.compareTo(localTime2);
System.out.println(b);
b = 0 != localTime.compareTo(localTime2);
System.out.println(b);
b = ((localTime).compareTo((localTime2)) != 0);
System.out.println(b);
OffsetTime offsetTime = OffsetTime.now();
OffsetTime offsetTime2 = OffsetTime.now();
b = offsetTime.compareTo(offsetTime2) > 0;
System.out.println(b);
b = (((offsetTime.compareTo(offsetTime2)))) > 0;
System.out.println(b);
b = offsetTime.compareTo(offsetTime2) >= 0;
System.out.println(b);
b = offsetTime.compareTo(offsetTime2) < 0;
System.out.println(b);
b = offsetTime.compareTo(offsetTime2) <= 0;
System.out.println(b);
b = offsetTime.compareTo(offsetTime2) == 0;
System.out.println(b);
b = offsetTime.compareTo(offsetTime2) != 0;
System.out.println(b);
OffsetDateTime offsetDateTime = OffsetDateTime.now();
OffsetDateTime offsetDateTime2 = OffsetDateTime.now();
b = offsetDateTime.compareTo(offsetDateTime2) > 0;
System.out.println(b);
b = offsetDateTime.compareTo(offsetDateTime2) >= 0;
System.out.println(b);
b = offsetDateTime.compareTo(offsetDateTime2) < 0;
System.out.println(b);
b = offsetDateTime.compareTo(offsetDateTime2) <= 0;
System.out.println(b);
b = offsetDateTime.compareTo(offsetDateTime2) == 0;
System.out.println(b);
b = offsetDateTime.compareTo(offsetDateTime2) != 0;
System.out.println(b);
LocalDate localDate = LocalDate.now();
LocalDate localDate2 = LocalDate.now();
b = localDate.compareTo(localDate2) > 0;
System.out.println(b);
b = localDate.compareTo(localDate2) >= 0;
System.out.println(b);
b = localDate.compareTo(localDate2) < 0;
System.out.println(b);
b = localDate.compareTo(localDate2) <= 0;
System.out.println(b);
b = localDate.compareTo(localDate2) == 0;
System.out.println(b);
b = localDate.compareTo(localDate2) != 0;
System.out.println(b);
b = localDate.compareTo(new ChronoLocalDate() {}) > 0;
LocalDateTime localDateTime = LocalDateTime.now();
LocalDateTime localDateTime2 = LocalDateTime.now();
b = localDateTime.compareTo(localDateTime2) > 0;
System.out.println(b);
b = localDateTime.compareTo(localDateTime2) >= 0;
System.out.println(b);
b = localDateTime.compareTo(localDateTime2) < 0;
System.out.println(b);
b = localDateTime.compareTo(localDateTime2) <= 0;
System.out.println(b);
b = localDateTime.compareTo(localDateTime2) == 0;
System.out.println(b);
b = localDateTime.compareTo(localDateTime2) != 0;
System.out.println(b);
b = /*test0*/localDateTime/*test1*/./*test2*/compareTo/*test3*/(/*test4*/localDateTime2/*test5*/) > 0;
System.out.println(b);
try {
b = localDateTime.compareTo(new ChronoLocalDateTime<>() {}) > 0;
}
catch (Exception e) {
}
b = /*test0*/localDateTime/*test1*/./*test2*/compareTo/*test3*/(/*test4*/localDateTime2/*test5*/) > 0;
}
}

View File

@@ -0,0 +1,122 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.codeInsight.daemon.quickFix;
import com.intellij.JavaTestUtil;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.RedundantCompareToJavaTimeInspection;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
public class RedundantCompareToJavaTimeInspectionTest extends LightJavaCodeInsightFixtureTestCase {
public void testCompareTo() {
myFixture.addClass("package java.time.chrono;\n" +
"interface ChronoLocalDateTime<T>{}\n" +
"interface ChronoLocalDate{}\n");
//ignore because we mock these classes
//noinspection MethodOverloadsMethodOfSuperclass
myFixture.addClass("package java.time;\n" +
"import java.time.chrono.*;" +
"class LocalDate implements ChronoLocalDate, Comparable<ChronoLocalDate>{\n" +
" public static LocalDate now() {\n" +
" return new LocalDate();\n" +
" }\n" +
" @Override\n" +
" public int compareTo(ChronoLocalDate o) {\n" +
" return 0;\n" +
" }\n" +
" public boolean isAfter(LocalDate localDate) {\n" +
" return true;\n" +
" }\n" +
" public boolean isBefore(LocalDate localDate) {\n" +
" return true;\n" +
" }\n" +
" public boolean isEqual(LocalDate localDate) {\n" +
" return true;\n" +
" }\n" +
"}\n" +
"class LocalDateTime implements ChronoLocalDateTime<LocalDateTime>, Comparable<ChronoLocalDateTime>{\n" +
" public static LocalDateTime now() {\n" +
" return new LocalDateTime();\n" +
" }\n" +
" @Override\n" +
" public int compareTo(ChronoLocalDateTime<?> o) {\n" +
" return 0;\n" +
" }\n" +
" public boolean isAfter(LocalDateTime localDateTime) {\n" +
" return true;\n" +
" }\n" +
" public boolean isBefore(LocalDateTime localDateTime) {\n" +
" return true;\n" +
" }\n" +
" public boolean isEqual(LocalDateTime localDateTime) {\n" +
" return true;\n" +
" }\n" +
"}\n" +
"class LocalTime implements Comparable<LocalTime>{\n" +
" public static LocalTime now() {\n" +
" return new LocalTime();\n" +
" }\n" +
" @Override\n" +
" public int compareTo(LocalTime o) {\n" +
" return 0;\n" +
" }\n" +
" public boolean isAfter(LocalTime localTime) {\n" +
" return true;\n" +
" }\n" +
" public boolean isBefore(LocalTime localTime) {\n" +
" return true;\n" +
" }\n" +
"}\n" +
"class OffsetTime implements Comparable<OffsetTime> {\n" +
" public static OffsetTime now() {\n" +
" return new OffsetTime();\n" +
" }\n" +
"\n" +
" @Override\n" +
" public int compareTo(OffsetTime o) {\n" +
" return 0;\n" +
" }\n" +
" public boolean isAfter(OffsetTime offsetTime) {\n" +
" return true;\n" +
" }\n" +
" public boolean isBefore(OffsetTime offsetTime) {\n" +
" return true;\n" +
" }\n" +
" public boolean isEqual(OffsetTime offsetTime) {\n" +
" return true;\n" +
" }\n" +
"}\n" +
"class OffsetDateTime implements Comparable<OffsetDateTime>{\n" +
" public static OffsetDateTime now() {\n" +
" return new OffsetDateTime();\n" +
" }\n" +
" @Override\n" +
" public int compareTo(OffsetDateTime o) {\n" +
" return 0;\n" +
" }\n" +
" public boolean isAfter(OffsetDateTime offsetDateTime) {\n" +
" return true;\n" +
" }\n" +
" public boolean isBefore(OffsetDateTime offsetDateTime) {\n" +
" return true;\n" +
" }\n" +
" public boolean isEqual(OffsetDateTime offsetDateTime) {\n" +
" return true;\n" +
" }\n" +
"}");
final LocalInspectionTool inspection = new RedundantCompareToJavaTimeInspection();
myFixture.enableInspections(inspection);
myFixture.configureByFile("beforeCompareTo.java");
myFixture.launchAction(myFixture.findSingleIntention("Fix all 'Expression with 'java.time' 'compareTo()' call can be simplified' problems in file"));
myFixture.checkResultByFile("afterCompareTo.java");
}
@Override
protected String getBasePath() {
return JavaTestUtil.getRelativeJavaTestDataPath() +
"/codeInsight/daemonCodeAnalyzer/quickFix/redundantCompareToJavaTime";
}
}

View File

@@ -1652,6 +1652,9 @@ if.may.be.factorized.problem.descriptor=<code>#ref</code> can be factorized #loc
if.may.be.factorized.quickfix=Replace with factorized expression
redundant.string.format.call.display.name=Redundant call to 'String.format()'
redundant.call.problem.descriptor=Redundant call to <code>#ref()</code> #loc
inspection.simplifiable.compare.java.time.display.name=Expression with 'java.time' 'compareTo()' call can be simplified
inspection.simplifiable.compare.java.time.family.name=Simplify expression with 'java.time' 'compareTo()' call
inspection.simplifiable.compare.java.time.problem.descriptor=Expression with 'java.time' <code>#ref()</code> call can be simplified
redundant.string.format.call.quickfix=Remove redundant call to 'String.format()'
redundant.string.formatted.call.quickfix=Remove redundant call to 'String.formatted()'
junit4.test.method.in.class.extending.junit3.testcase.display.name=JUnit 4 test method in class extending JUnit 3 TestCase

View File

@@ -2690,6 +2690,11 @@
groupKey="group.names.verbose.or.redundant.code.constructs" enabledByDefault="true" level="WARNING"
editorAttributes="NOT_USED_ELEMENT_ATTRIBUTES"
implementationClass="com.siyeh.ig.controlflow.UnnecessaryReturnInspection" cleanupTool="true"/>
<localInspection groupPath="Java" language="JAVA" shortName="RedundantCompareToJavaTime"
groupBundle="messages.InspectionsBundle" groupKey="group.names.verbose.or.redundant.code.constructs"
enabledByDefault="true" level="WARNING" key="inspection.simplifiable.compare.java.time.display.name"
bundle="messages.InspectionGadgetsBundle" implementationClass="com.intellij.codeInspection.RedundantCompareToJavaTimeInspection"
cleanupTool="true"/>
<!--group.names.visibility.issues-->
<localInspection groupPath="Java" language="JAVA" shortName="AmbiguousMethodCall" bundle="messages.InspectionGadgetsBundle" key="ambiguous.method.call.display.name"

View File

@@ -0,0 +1,147 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection;
import com.intellij.codeInspection.dataFlow.DfaPsiUtil;
import com.intellij.codeInspection.dataFlow.value.RelationType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ObjectUtils;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.ExpressionUtils;
import org.jetbrains.annotations.NotNull;
public class RedundantCompareToJavaTimeInspection extends AbstractBaseJavaLocalInspectionTool implements CleanupLocalInspectionTool {
private static final String JAVA_TIME_LOCAL_DATE = "java.time.LocalDate";
private static final String JAVA_TIME_LOCAL_DATE_TIME = "java.time.LocalDateTime";
private static final String JAVA_TIME_LOCAL_TIME = "java.time.LocalTime";
private static final String JAVA_TIME_OFFSET_DATE_TIME = "java.time.OffsetDateTime";
private static final String JAVA_TIME_OFFSET_TIME = "java.time.OffsetTime";
private static final CallMatcher COMPARE_TO_METHODS = CallMatcher.anyOf(
CallMatcher.instanceCall(JAVA_TIME_LOCAL_DATE, "compareTo").parameterTypes("java.time.chrono.ChronoLocalDate"),
CallMatcher.instanceCall(JAVA_TIME_LOCAL_TIME, "compareTo").parameterTypes(JAVA_TIME_LOCAL_TIME),
CallMatcher.instanceCall(JAVA_TIME_LOCAL_DATE_TIME, "compareTo")
.parameterTypes("java.time.chrono.ChronoLocalDateTime"),
CallMatcher.instanceCall(JAVA_TIME_OFFSET_TIME, "compareTo").parameterTypes(JAVA_TIME_OFFSET_TIME),
CallMatcher.instanceCall(JAVA_TIME_OFFSET_DATE_TIME, "compareTo")
.parameterTypes(JAVA_TIME_OFFSET_DATE_TIME)
);
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
return new JavaElementVisitor() {
@Override
public void visitMethodCallExpression(@NotNull PsiMethodCallExpression call) {
if (!COMPARE_TO_METHODS.test(call)) return;
PsiElement nameElement = call.getMethodExpression().getReferenceNameElement();
if (nameElement == null) return;
final PsiExpression qualifierExpression = call.getMethodExpression().getQualifierExpression();
if (qualifierExpression == null) {
return;
}
final PsiType argumentType = call.getArgumentList().getExpressionTypes()[0];
if (!argumentType.equals(qualifierExpression.getType())) {
return;
}
PsiBinaryExpression binOp = ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprUp(call.getParent()), PsiBinaryExpression.class);
if (binOp == null) return;
RelationType relationType = DfaPsiUtil.getRelationByToken(binOp.getOperationTokenType());
if (relationType == RelationType.IS || relationType == RelationType.IS_NOT) {
return;
}
if (relationType == null) return;
if (ExpressionUtils.isZero(binOp.getLOperand())) {
relationType = relationType.getFlipped();
if (relationType == null) return;
}
else if (!ExpressionUtils.isZero(binOp.getROperand())) {
return;
}
holder.registerProblem(nameElement,
InspectionGadgetsBundle.message("inspection.simplifiable.compare.java.time.problem.descriptor"),
new InlineCompareToTimeCallFix(relationType, argumentType.getCanonicalText()));
}
};
}
private static class InlineCompareToTimeCallFix implements LocalQuickFix {
private @NotNull final RelationType myRelationType;
private @NotNull final String myArgumentType;
InlineCompareToTimeCallFix(@NotNull RelationType relationType, @NotNull @NlsSafe String argumentType) {
myRelationType = relationType;
myArgumentType = argumentType;
}
@NotNull
@Override
public String getName() {
String method = getMethodName();
return CommonQuickFixBundle.message("fix.replace.with.x.call", method);
}
@NotNull
@Override
public String getFamilyName() {
return InspectionGadgetsBundle.message("inspection.simplifiable.compare.java.time.family.name");
}
@NotNull
private String getMethodName() {
String method;
switch (myRelationType) {
case EQ:
case NE:
if (myArgumentType.equals(JAVA_TIME_LOCAL_TIME)) {
method = "equals";
}
else {
method = "isEqual";
}
break;
case GT:
case LE:
method = "isAfter";
break;
case LT:
case GE:
method = "isBefore";
break;
default:
throw new UnsupportedOperationException(myRelationType.toString());
}
return method;
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(descriptor.getStartElement(), PsiMethodCallExpression.class);
if (call == null) return;
PsiExpression first = call.getMethodExpression().getQualifierExpression();
if (first == null) {
return;
}
PsiExpression second = call.getArgumentList().getExpressions()[0];
CommentTracker ct = new CommentTracker();
String text = ct.text(first) +
"." + getMethodName() + "(" +
ct.text(second) + ")";
if (myRelationType == RelationType.NE || myRelationType == RelationType.LE || myRelationType == RelationType.GE) {
text = "!" + text;
}
PsiBinaryExpression parent = PsiTreeUtil.getParentOfType(call, PsiBinaryExpression.class);
if (parent == null) return;
ct.replaceAndRestoreComments(parent, text);
}
}
}