IJ-CR-145218 [java-inspections] IDEA-201932 Provide inspection to highlight redundant creation operations in java date time api

- combine into one inspection
- fix messages

GitOrigin-RevId: d4e064948f8c730c4d68c58e6c9b1277c6b66b4d
This commit is contained in:
Mikhail Pyltsin
2024-09-27 17:12:29 +00:00
committed by intellij-monorepo-bot
parent a7dfa7615e
commit f474d8047b
26 changed files with 869 additions and 934 deletions
@@ -1673,15 +1673,17 @@ 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.explicit.chrono.field.display.name=Calls of 'java.time' methods with explicit 'ChronoField' or 'ChronoUnit' arguments can be simplified
inspection.explicit.chrono.field.problem.descriptor=Calls with explicit 'ChronoField' or 'ChronoUnit' arguments call can be simplified
inspection.explicit.chrono.field.family.name=Simplify calls with explicit 'ChronoField' or 'ChronoUnit' arguments
inspection.redundant.creation.java.time.display.name=Redundant creation of 'java.time' objects
inspection.redundant.creation.java.time.error.message=Redundant creation of ''{0}'' object
inspection.redundant.creation.java.time.family.name=Simplify creation of 'java.time' object
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
inspection.redundant.java.time.operation.display.name=Redundant operation on 'java.time' object
inspection.redundant.java.time.operation.explicit.chrono.field.family.name=Simplify calls with explicit 'ChronoField' or 'ChronoUnit' arguments
inspection.redundant.java.time.operation.explicit.chrono.field.problem.descriptor=Calls with explicit 'ChronoField' or 'ChronoUnit' arguments call can be simplified
inspection.redundant.java.time.operation.creation.java.time.redundant.call.message=Redundant ''{0}'' call
inspection.redundant.java.time.operation.creation.java.time.error.message=Redundant creation of ''{0}'' object
inspection.redundant.java.time.operation.creation.java.time.error.remove.fix.message=Remove ''{0}'' call
inspection.redundant.java.time.operation.creation.java.time.error.replace.fix.message=Replace with ''{0}'' call
inspection.redundant.java.time.operation.creation.java.time.family.name=Simplify creation of 'java.time' object
inspection.redundant.java.time.operation.creation.java.time.remove.family.name=Remove unnecessary call
inspection.redundant.java.time.operation.compare.java.time.family.name=Simplify expression with 'java.time' 'compareTo()' call
inspection.redundant.java.time.operation.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()'
equals.called.on.enum.constant.display.name='equals()' called on enum value
@@ -2676,21 +2676,12 @@
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"/>
<localInspection groupPath="Java" language="JAVA" shortName="RedundantExplicitChronoField"
groupBundle="messages.InspectionsBundle" groupKey="group.names.verbose.or.redundant.code.constructs"
enabledByDefault="true" level="WARNING" key="inspection.explicit.chrono.field.display.name"
bundle="messages.InspectionGadgetsBundle" implementationClass="com.siyeh.ig.redundancy.RedundantExplicitChronoFieldInspection"
cleanupTool="true"/>
<localInspection groupPath="Java" language="JAVA"
groupBundle="messages.InspectionsBundle" groupKey="group.names.verbose.or.redundant.code.constructs"
enabledByDefault="true" level="WEAK WARNING" key="inspection.redundant.creation.java.time.display.name"
bundle="messages.InspectionGadgetsBundle" implementationClass="com.siyeh.ig.redundancy.RedundantCreationJavaTimeInspection"
enabledByDefault="true" level="WARNING" key="inspection.redundant.java.time.operation.display.name"
bundle="messages.InspectionGadgetsBundle" implementationClass="com.siyeh.ig.redundancy.RedundantJavaTimeOperationsInspection"
cleanupTool="true"/>
<inspectionElementsMerger implementation="com.siyeh.ig.redundancy.RedundantJavaTimeOperationMerger"/>
<!--group.names.visibility.issues-->
<localInspection groupPath="Java" language="JAVA" shortName="AmbiguousMethodCall" bundle="messages.InspectionGadgetsBundle" key="ambiguous.method.call.display.name"
@@ -1,130 +0,0 @@
// Copyright 2000-2024 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.modcommand.ModPsiUpdater;
import com.intellij.modcommand.PsiUpdateModCommandQuickFix;
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 final 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)
);
@Override
public @NotNull 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;
}
PsiType[] types = call.getArgumentList().getExpressionTypes();
if (types.length != 1) {
return;
}
final PsiType argumentType = types[0];
if (argumentType == null || !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 extends PsiUpdateModCommandQuickFix {
private final @NotNull RelationType myRelationType;
private final @NotNull String myArgumentType;
InlineCompareToTimeCallFix(@NotNull RelationType relationType, @NotNull @NlsSafe String argumentType) {
myRelationType = relationType;
myArgumentType = argumentType;
}
@Override
public @NotNull String getName() {
String method = getMethodName();
return CommonQuickFixBundle.message("fix.replace.with.x.call", method);
}
@Override
public @NotNull String getFamilyName() {
return InspectionGadgetsBundle.message("inspection.simplifiable.compare.java.time.family.name");
}
private @NotNull String getMethodName() {
return switch (myRelationType) {
case EQ, NE -> myArgumentType.equals(JAVA_TIME_LOCAL_TIME) ? "equals" : "isEqual";
case GT, LE -> "isAfter";
case LT, GE -> "isBefore";
default -> throw new UnsupportedOperationException(myRelationType.toString());
};
}
@Override
protected void applyFix(@NotNull Project project, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) {
PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(element, 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);
}
}
}
@@ -1,272 +0,0 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.siyeh.ig.redundancy;
import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool;
import com.intellij.codeInspection.CleanupLocalInspectionTool;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.modcommand.ModCommandQuickFix;
import com.intellij.modcommand.ModPsiUpdater;
import com.intellij.modcommand.PsiUpdateModCommandQuickFix;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.CommentTracker;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.psi.CommonClassNames.*;
public final class RedundantCreationJavaTimeInspection extends AbstractBaseJavaLocalInspectionTool implements CleanupLocalInspectionTool {
private static final CallMatcher FROM_MATCHER = CallMatcher.anyOf(
CallMatcher.staticCall(JAVA_TIME_LOCAL_TIME, "from").parameterCount(1),
CallMatcher.staticCall(JAVA_TIME_LOCAL_DATE, "from").parameterCount(1),
CallMatcher.staticCall(JAVA_TIME_LOCAL_DATE_TIME, "from")
.parameterCount(1),
CallMatcher.staticCall(JAVA_TIME_OFFSET_DATE_TIME, "from")
.parameterCount(1),
CallMatcher.staticCall(JAVA_TIME_OFFSET_TIME, "from").parameterCount(1),
CallMatcher.staticCall(JAVA_TIME_ZONED_DATE_TIME, "from")
.parameterCount(1));
private static final CallMatcher LOCAL_DATE_OF_MATCHER =
CallMatcher.anyOf(CallMatcher.staticCall(JAVA_TIME_LOCAL_DATE, "of").parameterTypes("int", "int", "int"),
CallMatcher.staticCall(JAVA_TIME_LOCAL_DATE, "of").parameterTypes("int", "java.time.Month", "int"));
private static final CallMatcher GET_YEAR_MATCHER =
CallMatcher.anyOf(
CallMatcher.instanceCall(JAVA_TIME_LOCAL_DATE, "getYear").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_LOCAL_DATE_TIME, "getYear").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_OFFSET_DATE_TIME, "getYear").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_ZONED_DATE_TIME, "getYear").parameterCount(0));
private static final CallMatcher GET_MONTH_MATCHER =
CallMatcher.anyOf(
CallMatcher.instanceCall(JAVA_TIME_LOCAL_DATE, "getMonth").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_LOCAL_DATE_TIME, "getMonth").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_OFFSET_DATE_TIME, "getMonth").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_ZONED_DATE_TIME, "getMonth").parameterCount(0));
private static final CallMatcher GET_MONTH_VALUE_MATCHER =
CallMatcher.anyOf(
CallMatcher.instanceCall(JAVA_TIME_LOCAL_DATE, "getMonthValue").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_LOCAL_DATE_TIME, "getMonthValue").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_OFFSET_DATE_TIME, "getMonthValue").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_ZONED_DATE_TIME, "getMonthValue").parameterCount(0));
private static final CallMatcher GET_DAY_OF_MONTH_MATCHER =
CallMatcher.anyOf(
CallMatcher.instanceCall(JAVA_TIME_LOCAL_DATE, "getDayOfMonth").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_LOCAL_DATE_TIME, "getDayOfMonth").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_OFFSET_DATE_TIME, "getDayOfMonth").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_ZONED_DATE_TIME, "getDayOfMonth").parameterCount(0));
private static final CallMatcher LOCAL_TIME_OF_MATCHER =
CallMatcher.staticCall(JAVA_TIME_LOCAL_TIME, "of").parameterTypes("int", "int", "int", "int");
private static final CallMatcher GET_HOUR_MATCHER =
CallMatcher.anyOf(CallMatcher.instanceCall(JAVA_TIME_LOCAL_TIME, "getHour").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_LOCAL_DATE_TIME, "getHour").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_OFFSET_DATE_TIME, "getHour").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_ZONED_DATE_TIME, "getHour").parameterCount(0));
private static final CallMatcher GET_MINUTE_MATCHER =
CallMatcher.anyOf(CallMatcher.instanceCall(JAVA_TIME_LOCAL_TIME, "getMinute").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_LOCAL_DATE_TIME, "getMinute").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_OFFSET_DATE_TIME, "getMinute").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_ZONED_DATE_TIME, "getMinute").parameterCount(0));
private static final CallMatcher GET_SECOND_MATCHER =
CallMatcher.anyOf(CallMatcher.instanceCall(JAVA_TIME_LOCAL_TIME, "getSecond").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_LOCAL_DATE_TIME, "getSecond").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_OFFSET_DATE_TIME, "getSecond").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_ZONED_DATE_TIME, "getSecond").parameterCount(0));
private static final CallMatcher GET_NANO_MATCHER =
CallMatcher.anyOf(CallMatcher.instanceCall(JAVA_TIME_LOCAL_TIME, "getNano").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_LOCAL_DATE_TIME, "getNano").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_OFFSET_DATE_TIME, "getNano").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_ZONED_DATE_TIME, "getNano").parameterCount(0));
private static final String TO_LOCAL_TIME = "toLocalTime";
private static final String TO_LOCAL_DATE = "toLocalDate";
@Override
public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
return new JavaElementVisitor() {
@SuppressWarnings("UnnecessaryReturnStatement")
@Override
public void visitMethodCallExpression(@NotNull PsiMethodCallExpression call) {
if (fixRedundantFrom(call)) return;
if (fixRedundantOfLocalDate(call)) return;
if (fixRedundantOfLocalTime(call)) return;
}
private boolean fixRedundantOfLocalTime(@NotNull PsiMethodCallExpression call) {
if (!LOCAL_TIME_OF_MATCHER.test(call)) return false;
PsiExpression[] arguments = call.getArgumentList().getExpressions();
if (arguments.length != 4) return false;
if (!(arguments[0] instanceof PsiMethodCallExpression firstArgumentCall)) return false;
if (!GET_HOUR_MATCHER.test(firstArgumentCall)) return false;
if (!(arguments[1] instanceof PsiMethodCallExpression secondArgumentCall)) return false;
if (!GET_MINUTE_MATCHER.test(secondArgumentCall)) return false;
if (!(arguments[2] instanceof PsiMethodCallExpression thirdArgumentCall)) return false;
if (!GET_SECOND_MATCHER.test(thirdArgumentCall)) return false;
if (!(arguments[3] instanceof PsiMethodCallExpression fourthArgumentCall)) return false;
if (!GET_NANO_MATCHER.test(fourthArgumentCall)) return false;
PsiExpression expression1 = firstArgumentCall.getMethodExpression().getQualifierExpression();
if (!(PsiUtil.skipParenthesizedExprDown(expression1) instanceof PsiReferenceExpression referenceExpression1 &&
referenceExpression1.resolve() instanceof PsiVariable variable1)) {
return false;
}
if (!(areSameVariableReferences(holder.getProject(),
expression1,
secondArgumentCall.getMethodExpression().getQualifierExpression(),
thirdArgumentCall.getMethodExpression().getQualifierExpression(),
fourthArgumentCall.getMethodExpression().getQualifierExpression()))) {
return false;
}
PsiClass variableClass = PsiUtil.resolveClassInClassTypeOnly(variable1.getType());
if (variableClass == null) return false;
PsiElement referenceNameElement = call.getMethodExpression().getReferenceNameElement();
if (referenceNameElement == null) return false;
PsiIdentifier identifier = variable1.getNameIdentifier();
if (identifier == null) return false;
if(JAVA_TIME_LOCAL_TIME.equals(variableClass.getQualifiedName())) {
holder.registerProblem(referenceNameElement,
InspectionGadgetsBundle.message("inspection.redundant.creation.java.time.error.message", "LocalTime"),
RedundantCreationFix.create(identifier.getText()));
return true;
}
PsiMethod[] localTimes = variableClass.findMethodsByName(TO_LOCAL_TIME, false);
if (localTimes.length != 1) return false;
String newText = identifier.getText() + "." + TO_LOCAL_TIME + "()";
holder.registerProblem(referenceNameElement,
InspectionGadgetsBundle.message("inspection.redundant.creation.java.time.error.message", "LocalTime"),
RedundantCreationFix.create(newText));
return true;
}
private boolean fixRedundantOfLocalDate(@NotNull PsiMethodCallExpression call) {
if (!LOCAL_DATE_OF_MATCHER.test(call)) return false;
PsiExpression[] arguments = call.getArgumentList().getExpressions();
if (arguments.length != 3) return false;
if (!(arguments[0] instanceof PsiMethodCallExpression firstArgumentCall)) return false;
if (!GET_YEAR_MATCHER.test(firstArgumentCall)) return false;
if (!(arguments[1] instanceof PsiMethodCallExpression secondArgumentCall)) return false;
if (!GET_MONTH_VALUE_MATCHER.test(secondArgumentCall) && !GET_MONTH_MATCHER.test(secondArgumentCall)) return false;
if (!(arguments[2] instanceof PsiMethodCallExpression thirdArgumentCall)) return false;
if (!GET_DAY_OF_MONTH_MATCHER.test(thirdArgumentCall)) return false;
PsiExpression firstArgument = PsiUtil.skipParenthesizedExprDown(firstArgumentCall.getMethodExpression().getQualifierExpression());
if (!(firstArgument instanceof PsiReferenceExpression referenceExpression &&
referenceExpression.resolve() instanceof PsiVariable firstVariable)) return false;
if (!areSameVariableReferences(holder.getProject(),
firstArgument,
secondArgumentCall.getMethodExpression().getQualifierExpression(),
thirdArgumentCall.getMethodExpression().getQualifierExpression())) {
return false;
}
PsiClass variableClass = PsiUtil.resolveClassInClassTypeOnly(firstArgument.getType());
if (variableClass == null) return false;
PsiElement referenceNameElement = call.getMethodExpression().getReferenceNameElement();
if (referenceNameElement == null) return false;
PsiIdentifier identifier = firstVariable.getNameIdentifier();
if (identifier == null) return false;
if (JAVA_TIME_LOCAL_DATE.equals(variableClass.getQualifiedName())) {
holder.registerProblem(referenceNameElement,
InspectionGadgetsBundle.message("inspection.redundant.creation.java.time.error.message", "LocalDate"),
RedundantCreationFix.create(identifier.getText()));
return true;
}
PsiMethod[] localDates = variableClass.findMethodsByName(TO_LOCAL_DATE, false);
if (localDates.length != 1) return false;
String newText = identifier.getText() + "." + TO_LOCAL_DATE + "()";
holder.registerProblem(referenceNameElement,
InspectionGadgetsBundle.message("inspection.redundant.creation.java.time.error.message", "LocalDate"),
RedundantCreationFix.create(newText));
return true;
}
private static boolean areSameVariableReferences(@NotNull Project project, PsiExpression... expressions) {
if (expressions.length == 0) return false;
PsiManager psiManager = PsiManager.getInstance(project);
PsiVariable firstVariable = resolveVariable(expressions[0]);
if (firstVariable == null) return false;
for (PsiExpression expression : expressions) {
PsiVariable variable = resolveVariable(expression);
if (variable == null || !psiManager.areElementsEquivalent(firstVariable, variable)) {
return false;
}
}
return true;
}
@Nullable
private static PsiVariable resolveVariable(@Nullable PsiExpression expression) {
if (expression == null) return null;
PsiExpression unwrappedExpression = PsiUtil.skipParenthesizedExprDown(expression);
if (!(unwrappedExpression instanceof PsiReferenceExpression referenceExpression)) return null;
PsiElement resolvedElement = referenceExpression.resolve();
return (resolvedElement instanceof PsiVariable variable) ? variable : null;
}
private boolean fixRedundantFrom(@NotNull PsiMethodCallExpression call) {
if (!FROM_MATCHER.test(call)) return false;
PsiExpression[] arguments = call.getArgumentList().getExpressions();
if (arguments.length != 1) return false;
PsiExpression expression = arguments[0];
PsiClass classOfArgument = PsiUtil.resolveClassInClassTypeOnly(expression.getType());
PsiMethod method = call.resolveMethod();
if (method == null) return false;
PsiClass classOfMethod = method.getContainingClass();
if (classOfMethod == null) return false;
PsiManager manager = classOfMethod.getManager();
if (!manager.areElementsEquivalent(classOfArgument, classOfMethod)) return false;
String newText = expression.getText();
if (newText == null) return false;
PsiElement identifier = call.getMethodExpression().getReferenceNameElement();
if (identifier == null) return false;
String className = classOfMethod.getName();
if (className == null) return false;
holder.registerProblem(identifier,
InspectionGadgetsBundle.message("inspection.redundant.creation.java.time.error.message", className),
RedundantCreationFix.create(newText));
return true;
}
};
}
private static class RedundantCreationFix extends PsiUpdateModCommandQuickFix {
@NotNull private final String myNewText;
private RedundantCreationFix(@NotNull String text) { myNewText = text; }
@Override
protected void applyFix(@NotNull Project project, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) {
PsiMethodCallExpression callExpression = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression.class, false);
if (callExpression == null) return;
new CommentTracker().replaceAndRestoreComments(callExpression, myNewText);
}
@Override
public @NotNull String getFamilyName() {
return InspectionGadgetsBundle.message("inspection.redundant.creation.java.time.family.name");
}
private static @NotNull ModCommandQuickFix create(@NotNull String newText) {
return new RedundantCreationFix(newText);
}
}
}
@@ -1,216 +0,0 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.siyeh.ig.redundancy;
import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool;
import com.intellij.codeInspection.CleanupLocalInspectionTool;
import com.intellij.codeInspection.CommonQuickFixBundle;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.util.ChronoUtil;
import com.intellij.modcommand.ModPsiUpdater;
import com.intellij.modcommand.PsiUpdateModCommandQuickFix;
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.TypeConversionUtil;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.CommentTracker;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.StringJoiner;
public final class RedundantExplicitChronoFieldInspection extends AbstractBaseJavaLocalInspectionTool
implements CleanupLocalInspectionTool {
private static final CallMatcher CAN_BE_SIMPLIFIED_MATCHERS = CallMatcher.anyOf(
ChronoUtil.CHRONO_GET_MATCHERS,
ChronoUtil.CHRONO_WITH_MATCHERS,
ChronoUtil.CHRONO_PLUS_MINUS_MATCHERS
);
@Override
public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
return new JavaElementVisitor() {
@Override
public void visitMethodCallExpression(@NotNull PsiMethodCallExpression call) {
String methodName = call.getMethodExpression().getReferenceName();
if (!"get".equals(methodName) && !"with".equals(methodName) &&
!"plus".equals(methodName) && !"minus".equals(methodName)) {
return;
}
if (!CAN_BE_SIMPLIFIED_MATCHERS.matches(call)) return;
int fieldArgumentIndex = 1;
if ("get".equals(methodName) || "with".equals(methodName)) {
fieldArgumentIndex = 0;
}
PsiExpression[] expressions = call.getArgumentList().getExpressions();
if (expressions.length < fieldArgumentIndex + 1) {
return;
}
@Nullable PsiExpression fieldExpression = expressions[fieldArgumentIndex];
String chronoEnumName = getNameOfChronoEnum(fieldExpression, methodName);
if (chronoEnumName == null) return;
String newMethodName = getNewMethodName(chronoEnumName, call);
if (newMethodName == null) return;
PsiElement identifier = getIdentifier(call.getMethodExpression());
if (identifier == null) return;
holder.registerProblem(identifier,
InspectionGadgetsBundle.message("inspection.explicit.chrono.field.problem.descriptor"),
new InlineChronoEnumCallFix(newMethodName, fieldArgumentIndex));
}
private static @Nullable String getNewMethodName(@NotNull String chronoEnumName, @NotNull PsiMethodCallExpression call) {
PsiMethod method = call.resolveMethod();
if (method == null) {
return null;
}
if (!isAvailableCall(method, chronoEnumName)) {
return null;
}
//'with(ChronoField, long)' can be converted only to 'with...(int)'
if ("with".equals(method.getName())) {
PsiType[] types = call.getArgumentList().getExpressionTypes();
if (types.length != 2 || types[1] == null || TypeConversionUtil.getTypeRank(types[1]) > TypeConversionUtil.INT_RANK) {
return null;
}
}
String methodName = method.getName();
return findEquivalentMethod(chronoEnumName, methodName);
}
private static boolean isAvailableCall(@NotNull PsiMethod method, @NotNull String chronoEnumName) {
return switch (method.getName()) {
case "get" -> ChronoUtil.isAnyGetSupported(method, ChronoUtil.getChronoField(chronoEnumName));
case "with" -> ChronoUtil.isWithSupported(method, ChronoUtil.getChronoField(chronoEnumName));
case "plus", "minus" -> ChronoUtil.isPlusMinusSupported(method, ChronoUtil.getChronoUnit(chronoEnumName));
default -> false;
};
}
private static @Nullable String findEquivalentMethod(@NotNull String chronoEnumName, @NotNull String methodName) {
return switch (methodName) {
case "plus" -> switch (chronoEnumName) {
case "NANOS" -> "plusNanos";
case "SECONDS" -> "plusSeconds";
case "MINUTES" -> "plusMinutes";
case "HOURS" -> "plusHours";
case "DAYS" -> "plusDays";
case "WEEKS" -> "plusWeeks";
case "MONTHS" -> "plusMonths";
case "YEARS" -> "plusYears";
default -> null;
};
case "minus" -> switch (chronoEnumName) {
case "NANOS" -> "minusNanos";
case "SECONDS" -> "minusSeconds";
case "MINUTES" -> "minusMinutes";
case "HOURS" -> "minusHours";
case "DAYS" -> "minusDays";
case "WEEKS" -> "minusWeeks";
case "MONTHS" -> "minusMonths";
case "YEARS" -> "minusYears";
default -> null;
};
case "get" -> switch (chronoEnumName) {
case "NANO_OF_SECOND" -> "getNano";
case "SECOND_OF_MINUTE" -> "getSecond";
case "MINUTE_OF_HOUR" -> "getMinute";
case "HOUR_OF_DAY" -> "getHour";
case "DAY_OF_MONTH" -> "getDayOfMonth";
case "DAY_OF_YEAR" -> "getDayOfYear";
case "MONTH_OF_YEAR" -> "getMonth";
case "YEAR" -> "getYear";
default -> null;
};
case "with" -> switch (chronoEnumName) {
case "NANO_OF_SECOND" -> "withNano";
case "SECOND_OF_MINUTE" -> "withSecond";
case "MINUTE_OF_HOUR" -> "withMinute";
case "HOUR_OF_DAY" -> "withHour";
case "DAY_OF_MONTH" -> "withDayOfMonth";
case "DAY_OF_YEAR" -> "withDayOfYear";
case "MONTH_OF_YEAR" -> "withMonth";
case "YEAR" -> "withYear";
default -> null;
};
default -> null;
};
}
private static @Nullable PsiElement getIdentifier(@Nullable PsiReferenceExpression expression) {
if (expression == null) return null;
PsiIdentifier[] identifiers = PsiTreeUtil.getChildrenOfType(expression, PsiIdentifier.class);
if (identifiers == null || identifiers.length != 1) {
return null;
}
return identifiers[0];
}
private static @Nullable String getNameOfChronoEnum(@Nullable PsiExpression expression, @Nullable String methodName) {
if (expression == null || methodName == null) return null;
if (!(expression instanceof PsiReferenceExpression referenceExpression)) {
return null;
}
PsiElement resolvedElement = referenceExpression.resolve();
if (!(resolvedElement instanceof PsiEnumConstant enumConstant)) {
return null;
}
PsiClass containingClass = enumConstant.getContainingClass();
if (containingClass == null || !containingClass.isEnum()) {
return null;
}
String classQualifiedName = containingClass.getQualifiedName();
if (!(ChronoUtil.CHRONO_FIELD.equals(classQualifiedName) && (methodName.equals("get") || methodName.equals("with"))) &&
!(ChronoUtil.CHRONO_UNIT.equals(classQualifiedName)) && (methodName.equals("plus") || methodName.equals("minus"))) {
return null;
}
return enumConstant.getName();
}
};
}
private static class InlineChronoEnumCallFix extends PsiUpdateModCommandQuickFix {
private final @NotNull String myNewMethodName;
private final int myDeletedArgumentIndex;
InlineChronoEnumCallFix(@NotNull @NlsSafe String newMethodName, int deletedArgumentIndex) {
myNewMethodName = newMethodName;
myDeletedArgumentIndex = deletedArgumentIndex;
}
@Override
public @NotNull String getName() {
return CommonQuickFixBundle.message("fix.replace.with.x.call", myNewMethodName + "()");
}
@Override
public @NotNull String getFamilyName() {
return InspectionGadgetsBundle.message("inspection.explicit.chrono.field.family.name");
}
@Override
protected void applyFix(@NotNull Project project, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) {
PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression.class);
if (call == null) return;
PsiExpression qualifierExpression = call.getMethodExpression().getQualifierExpression();
if (qualifierExpression == null) {
return;
}
CommentTracker ct = new CommentTracker();
String text = ct.text(qualifierExpression) + "." + myNewMethodName;
PsiExpression[] expressions = call.getArgumentList().getExpressions();
StringJoiner joiner = new StringJoiner(",", "(", ")");
for (int i = 0; i < expressions.length; i++) {
if (i == myDeletedArgumentIndex) {
continue;
}
joiner.add(ct.text(expressions[i]));
}
text += joiner.toString();
ct.replaceAndRestoreComments(call, text);
}
}
}
@@ -0,0 +1,27 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.siyeh.ig.redundancy;
import com.intellij.codeInspection.ex.InspectionElementsMergerBase;
import org.jetbrains.annotations.NotNull;
public final class RedundantJavaTimeOperationMerger extends InspectionElementsMergerBase {
@Override
public @NotNull String getMergedToolName() {
return "RedundantJavaTimeOperations";
}
@Override
public String @NotNull [] getSourceToolNames() {
return new String[] {
"RedundantCreationJavaTime", "RedundantCompareToJavaTime", "RedundantExplicitChronoField"
};
}
@Override
public String @NotNull [] getSuppressIds() {
return new String[] {
"RedundantCreationJavaTime", "RedundantCompareToJavaTime", "RedundantExplicitChronoField"
};
}
}
@@ -0,0 +1,605 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.siyeh.ig.redundancy;
import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool;
import com.intellij.codeInspection.CleanupLocalInspectionTool;
import com.intellij.codeInspection.CommonQuickFixBundle;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.dataFlow.DfaPsiUtil;
import com.intellij.codeInspection.dataFlow.value.RelationType;
import com.intellij.codeInspection.util.ChronoUtil;
import com.intellij.codeInspection.util.IntentionName;
import com.intellij.modcommand.ModCommandQuickFix;
import com.intellij.modcommand.ModPsiUpdater;
import com.intellij.modcommand.PsiUpdateModCommandQuickFix;
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.psi.util.TypeConversionUtil;
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;
import org.jetbrains.annotations.Nullable;
import java.util.StringJoiner;
import static com.intellij.psi.CommonClassNames.*;
public class RedundantJavaTimeOperationsInspection extends AbstractBaseJavaLocalInspectionTool implements CleanupLocalInspectionTool {
private static final CallMatcher FROM_MATCHER = CallMatcher.anyOf(
CallMatcher.staticCall(JAVA_TIME_LOCAL_TIME, "from").parameterCount(1),
CallMatcher.staticCall(JAVA_TIME_LOCAL_DATE, "from").parameterCount(1),
CallMatcher.staticCall(JAVA_TIME_LOCAL_DATE_TIME, "from")
.parameterCount(1),
CallMatcher.staticCall(JAVA_TIME_OFFSET_DATE_TIME, "from")
.parameterCount(1),
CallMatcher.staticCall(JAVA_TIME_OFFSET_TIME, "from").parameterCount(1),
CallMatcher.staticCall(JAVA_TIME_ZONED_DATE_TIME, "from")
.parameterCount(1));
private static final CallMatcher LOCAL_DATE_OF_MATCHER =
CallMatcher.anyOf(CallMatcher.staticCall(JAVA_TIME_LOCAL_DATE, "of").parameterTypes("int", "int", "int"),
CallMatcher.staticCall(JAVA_TIME_LOCAL_DATE, "of").parameterTypes("int", "java.time.Month", "int"));
private static final CallMatcher GET_YEAR_MATCHER =
CallMatcher.anyOf(
CallMatcher.instanceCall(JAVA_TIME_LOCAL_DATE, "getYear").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_LOCAL_DATE_TIME, "getYear").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_OFFSET_DATE_TIME, "getYear").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_ZONED_DATE_TIME, "getYear").parameterCount(0));
private static final CallMatcher GET_MONTH_MATCHER =
CallMatcher.anyOf(
CallMatcher.instanceCall(JAVA_TIME_LOCAL_DATE, "getMonth").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_LOCAL_DATE_TIME, "getMonth").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_OFFSET_DATE_TIME, "getMonth").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_ZONED_DATE_TIME, "getMonth").parameterCount(0));
private static final CallMatcher GET_MONTH_VALUE_MATCHER =
CallMatcher.anyOf(
CallMatcher.instanceCall(JAVA_TIME_LOCAL_DATE, "getMonthValue").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_LOCAL_DATE_TIME, "getMonthValue").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_OFFSET_DATE_TIME, "getMonthValue").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_ZONED_DATE_TIME, "getMonthValue").parameterCount(0));
private static final CallMatcher GET_DAY_OF_MONTH_MATCHER =
CallMatcher.anyOf(
CallMatcher.instanceCall(JAVA_TIME_LOCAL_DATE, "getDayOfMonth").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_LOCAL_DATE_TIME, "getDayOfMonth").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_OFFSET_DATE_TIME, "getDayOfMonth").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_ZONED_DATE_TIME, "getDayOfMonth").parameterCount(0));
private static final CallMatcher LOCAL_TIME_OF_MATCHER =
CallMatcher.staticCall(JAVA_TIME_LOCAL_TIME, "of").parameterTypes("int", "int", "int", "int");
private static final CallMatcher GET_HOUR_MATCHER =
CallMatcher.anyOf(CallMatcher.instanceCall(JAVA_TIME_LOCAL_TIME, "getHour").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_LOCAL_DATE_TIME, "getHour").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_OFFSET_DATE_TIME, "getHour").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_ZONED_DATE_TIME, "getHour").parameterCount(0));
private static final CallMatcher GET_MINUTE_MATCHER =
CallMatcher.anyOf(CallMatcher.instanceCall(JAVA_TIME_LOCAL_TIME, "getMinute").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_LOCAL_DATE_TIME, "getMinute").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_OFFSET_DATE_TIME, "getMinute").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_ZONED_DATE_TIME, "getMinute").parameterCount(0));
private static final CallMatcher GET_SECOND_MATCHER =
CallMatcher.anyOf(CallMatcher.instanceCall(JAVA_TIME_LOCAL_TIME, "getSecond").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_LOCAL_DATE_TIME, "getSecond").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_OFFSET_DATE_TIME, "getSecond").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_ZONED_DATE_TIME, "getSecond").parameterCount(0));
private static final CallMatcher GET_NANO_MATCHER =
CallMatcher.anyOf(CallMatcher.instanceCall(JAVA_TIME_LOCAL_TIME, "getNano").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_LOCAL_DATE_TIME, "getNano").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_OFFSET_DATE_TIME, "getNano").parameterCount(0),
CallMatcher.instanceCall(JAVA_TIME_ZONED_DATE_TIME, "getNano").parameterCount(0));
private static final String TO_LOCAL_TIME = "toLocalTime";
private static final String TO_LOCAL_DATE = "toLocalDate";
private static final CallMatcher CAN_BE_SIMPLIFIED_MATCHERS = CallMatcher.anyOf(
ChronoUtil.CHRONO_GET_MATCHERS,
ChronoUtil.CHRONO_WITH_MATCHERS,
ChronoUtil.CHRONO_PLUS_MINUS_MATCHERS
);
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)
);
@Override
public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
return new JavaElementVisitor() {
@SuppressWarnings("UnnecessaryReturnStatement")
@Override
public void visitMethodCallExpression(@NotNull PsiMethodCallExpression call) {
if (fixRedundantFrom(call)) return;
if (fixRedundantOfLocalDate(call)) return;
if (fixRedundantOfLocalTime(call)) return;
if (fixRedundantExplicitChronoField(call)) return;
if (fixRedundantComparison(call)) return;
}
private boolean fixRedundantComparison(@NotNull PsiMethodCallExpression call) {
if (!COMPARE_TO_METHODS.test(call)) return false;
PsiElement nameElement = call.getMethodExpression().getReferenceNameElement();
if (nameElement == null) return false;
final PsiExpression qualifierExpression = call.getMethodExpression().getQualifierExpression();
if (qualifierExpression == null) {
return false;
}
PsiType[] types = call.getArgumentList().getExpressionTypes();
if (types.length != 1) {
return false;
}
final PsiType argumentType = types[0];
if (argumentType == null || !argumentType.equals(qualifierExpression.getType())) {
return false;
}
PsiBinaryExpression binOp = ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprUp(call.getParent()), PsiBinaryExpression.class);
if (binOp == null) return false;
RelationType relationType = DfaPsiUtil.getRelationByToken(binOp.getOperationTokenType());
if (relationType == RelationType.IS || relationType == RelationType.IS_NOT) return false;
if (relationType == null) return false;
if (ExpressionUtils.isZero(binOp.getLOperand())) {
relationType = relationType.getFlipped();
if (relationType == null) return false;
}
else if (!ExpressionUtils.isZero(binOp.getROperand())) {
return false;
}
holder.registerProblem(nameElement,
InspectionGadgetsBundle.message(
"inspection.redundant.java.time.operation.compare.java.time.problem.descriptor"),
new InlineCompareToTimeCallFix(relationType, argumentType.getCanonicalText()));
return true;
}
private boolean fixRedundantExplicitChronoField(@NotNull PsiMethodCallExpression call) {
String methodName = call.getMethodExpression().getReferenceName();
if (!"get".equals(methodName) && !"with".equals(methodName) &&
!"plus".equals(methodName) && !"minus".equals(methodName)) {
return false;
}
if (!CAN_BE_SIMPLIFIED_MATCHERS.matches(call)) return false;
int fieldArgumentIndex = 1;
if ("get".equals(methodName) || "with".equals(methodName)) {
fieldArgumentIndex = 0;
}
PsiExpression[] expressions = call.getArgumentList().getExpressions();
if (expressions.length < fieldArgumentIndex + 1) {
return false;
}
@Nullable PsiExpression fieldExpression = expressions[fieldArgumentIndex];
String chronoEnumName = getNameOfChronoEnum(fieldExpression, methodName);
if (chronoEnumName == null) return false;
String newMethodName = getNewMethodName(chronoEnumName, call);
if (newMethodName == null) return false;
PsiElement identifier = getIdentifier(call.getMethodExpression());
if (identifier == null) return false;
holder.registerProblem(identifier,
InspectionGadgetsBundle.message("inspection.redundant.java.time.operation.explicit.chrono.field.problem.descriptor"),
new InlineChronoEnumCallFix(newMethodName, fieldArgumentIndex));
return true;
}
private static @Nullable String getNewMethodName(@NotNull String chronoEnumName, @NotNull PsiMethodCallExpression call) {
PsiMethod method = call.resolveMethod();
if (method == null) {
return null;
}
if (!isAvailableCall(method, chronoEnumName)) {
return null;
}
//'with(ChronoField, long)' can be converted only to 'with...(int)'
if ("with".equals(method.getName())) {
PsiType[] types = call.getArgumentList().getExpressionTypes();
if (types.length != 2 || types[1] == null || TypeConversionUtil.getTypeRank(types[1]) > TypeConversionUtil.INT_RANK) {
return null;
}
}
String methodName = method.getName();
return findEquivalentMethod(chronoEnumName, methodName);
}
private static boolean isAvailableCall(@NotNull PsiMethod method, @NotNull String chronoEnumName) {
return switch (method.getName()) {
case "get" -> ChronoUtil.isAnyGetSupported(method, ChronoUtil.getChronoField(chronoEnumName));
case "with" -> ChronoUtil.isWithSupported(method, ChronoUtil.getChronoField(chronoEnumName));
case "plus", "minus" -> ChronoUtil.isPlusMinusSupported(method, ChronoUtil.getChronoUnit(chronoEnumName));
default -> false;
};
}
private static @Nullable String findEquivalentMethod(@NotNull String chronoEnumName, @NotNull String methodName) {
return switch (methodName) {
case "plus" -> switch (chronoEnumName) {
case "NANOS" -> "plusNanos";
case "SECONDS" -> "plusSeconds";
case "MINUTES" -> "plusMinutes";
case "HOURS" -> "plusHours";
case "DAYS" -> "plusDays";
case "WEEKS" -> "plusWeeks";
case "MONTHS" -> "plusMonths";
case "YEARS" -> "plusYears";
default -> null;
};
case "minus" -> switch (chronoEnumName) {
case "NANOS" -> "minusNanos";
case "SECONDS" -> "minusSeconds";
case "MINUTES" -> "minusMinutes";
case "HOURS" -> "minusHours";
case "DAYS" -> "minusDays";
case "WEEKS" -> "minusWeeks";
case "MONTHS" -> "minusMonths";
case "YEARS" -> "minusYears";
default -> null;
};
case "get" -> switch (chronoEnumName) {
case "NANO_OF_SECOND" -> "getNano";
case "SECOND_OF_MINUTE" -> "getSecond";
case "MINUTE_OF_HOUR" -> "getMinute";
case "HOUR_OF_DAY" -> "getHour";
case "DAY_OF_MONTH" -> "getDayOfMonth";
case "DAY_OF_YEAR" -> "getDayOfYear";
case "MONTH_OF_YEAR" -> "getMonth";
case "YEAR" -> "getYear";
default -> null;
};
case "with" -> switch (chronoEnumName) {
case "NANO_OF_SECOND" -> "withNano";
case "SECOND_OF_MINUTE" -> "withSecond";
case "MINUTE_OF_HOUR" -> "withMinute";
case "HOUR_OF_DAY" -> "withHour";
case "DAY_OF_MONTH" -> "withDayOfMonth";
case "DAY_OF_YEAR" -> "withDayOfYear";
case "MONTH_OF_YEAR" -> "withMonth";
case "YEAR" -> "withYear";
default -> null;
};
default -> null;
};
}
private static @Nullable PsiElement getIdentifier(@Nullable PsiReferenceExpression expression) {
if (expression == null) return null;
PsiIdentifier[] identifiers = PsiTreeUtil.getChildrenOfType(expression, PsiIdentifier.class);
if (identifiers == null || identifiers.length != 1) {
return null;
}
return identifiers[0];
}
private static @Nullable String getNameOfChronoEnum(@Nullable PsiExpression expression, @Nullable String methodName) {
if (expression == null || methodName == null) return null;
if (!(expression instanceof PsiReferenceExpression referenceExpression)) {
return null;
}
PsiElement resolvedElement = referenceExpression.resolve();
if (!(resolvedElement instanceof PsiEnumConstant enumConstant)) {
return null;
}
PsiClass containingClass = enumConstant.getContainingClass();
if (containingClass == null || !containingClass.isEnum()) {
return null;
}
String classQualifiedName = containingClass.getQualifiedName();
if (!(ChronoUtil.CHRONO_FIELD.equals(classQualifiedName) && (methodName.equals("get") || methodName.equals("with"))) &&
!(ChronoUtil.CHRONO_UNIT.equals(classQualifiedName)) && (methodName.equals("plus") || methodName.equals("minus"))) {
return null;
}
return enumConstant.getName();
}
private boolean fixRedundantOfLocalTime(@NotNull PsiMethodCallExpression call) {
if (!LOCAL_TIME_OF_MATCHER.test(call)) return false;
PsiExpression[] arguments = call.getArgumentList().getExpressions();
if (arguments.length != 4) return false;
if (!(arguments[0] instanceof PsiMethodCallExpression firstArgumentCall)) return false;
if (!GET_HOUR_MATCHER.test(firstArgumentCall)) return false;
if (!(arguments[1] instanceof PsiMethodCallExpression secondArgumentCall)) return false;
if (!GET_MINUTE_MATCHER.test(secondArgumentCall)) return false;
if (!(arguments[2] instanceof PsiMethodCallExpression thirdArgumentCall)) return false;
if (!GET_SECOND_MATCHER.test(thirdArgumentCall)) return false;
if (!(arguments[3] instanceof PsiMethodCallExpression fourthArgumentCall)) return false;
if (!GET_NANO_MATCHER.test(fourthArgumentCall)) return false;
PsiExpression expression1 = firstArgumentCall.getMethodExpression().getQualifierExpression();
if (!(PsiUtil.skipParenthesizedExprDown(expression1) instanceof PsiReferenceExpression referenceExpression1 &&
referenceExpression1.resolve() instanceof PsiVariable variable1)) {
return false;
}
if (!(areSameVariableReferences(holder.getProject(),
expression1,
secondArgumentCall.getMethodExpression().getQualifierExpression(),
thirdArgumentCall.getMethodExpression().getQualifierExpression(),
fourthArgumentCall.getMethodExpression().getQualifierExpression()))) {
return false;
}
PsiClass variableClass = PsiUtil.resolveClassInClassTypeOnly(variable1.getType());
if (variableClass == null) return false;
PsiElement referenceNameElement = call.getMethodExpression().getReferenceNameElement();
if (referenceNameElement == null) return false;
PsiIdentifier identifier = variable1.getNameIdentifier();
if (identifier == null) return false;
if (JAVA_TIME_LOCAL_TIME.equals(variableClass.getQualifiedName())) {
holder.registerProblem(referenceNameElement,
InspectionGadgetsBundle.message("inspection.redundant.java.time.operation.creation.java.time.error.message", "LocalTime"),
RedundantCreationFix.create(identifier.getText(), RedundantCreationFix.FixType.REMOVE,
InspectionGadgetsBundle.message(
"inspection.redundant.java.time.operation.creation.java.time.error.remove.fix.message",
"LocalTime.of()")));
return true;
}
PsiMethod[] localTimes = variableClass.findMethodsByName(TO_LOCAL_TIME, false);
if (localTimes.length != 1) return false;
String newText = identifier.getText() + "." + TO_LOCAL_TIME + "()";
holder.registerProblem(referenceNameElement,
InspectionGadgetsBundle.message("inspection.redundant.java.time.operation.creation.java.time.error.message", "LocalTime"),
RedundantCreationFix.create(newText, RedundantCreationFix.FixType.SIMPLIFY,
InspectionGadgetsBundle.message(
"inspection.redundant.java.time.operation.creation.java.time.error.replace.fix.message",
TO_LOCAL_TIME + "()")));
return true;
}
private boolean fixRedundantOfLocalDate(@NotNull PsiMethodCallExpression call) {
if (!LOCAL_DATE_OF_MATCHER.test(call)) return false;
PsiExpression[] arguments = call.getArgumentList().getExpressions();
if (arguments.length != 3) return false;
if (!(arguments[0] instanceof PsiMethodCallExpression firstArgumentCall)) return false;
if (!GET_YEAR_MATCHER.test(firstArgumentCall)) return false;
if (!(arguments[1] instanceof PsiMethodCallExpression secondArgumentCall)) return false;
if (!GET_MONTH_VALUE_MATCHER.test(secondArgumentCall) && !GET_MONTH_MATCHER.test(secondArgumentCall)) return false;
if (!(arguments[2] instanceof PsiMethodCallExpression thirdArgumentCall)) return false;
if (!GET_DAY_OF_MONTH_MATCHER.test(thirdArgumentCall)) return false;
PsiExpression firstArgument = PsiUtil.skipParenthesizedExprDown(firstArgumentCall.getMethodExpression().getQualifierExpression());
if (!(firstArgument instanceof PsiReferenceExpression referenceExpression &&
referenceExpression.resolve() instanceof PsiVariable firstVariable)) {
return false;
}
if (!areSameVariableReferences(holder.getProject(),
firstArgument,
secondArgumentCall.getMethodExpression().getQualifierExpression(),
thirdArgumentCall.getMethodExpression().getQualifierExpression())) {
return false;
}
PsiClass variableClass = PsiUtil.resolveClassInClassTypeOnly(firstArgument.getType());
if (variableClass == null) return false;
PsiElement referenceNameElement = call.getMethodExpression().getReferenceNameElement();
if (referenceNameElement == null) return false;
PsiIdentifier identifier = firstVariable.getNameIdentifier();
if (identifier == null) return false;
if (JAVA_TIME_LOCAL_DATE.equals(variableClass.getQualifiedName())) {
holder.registerProblem(referenceNameElement,
InspectionGadgetsBundle.message("inspection.redundant.java.time.operation.creation.java.time.error.message", "LocalDate"),
RedundantCreationFix.create(identifier.getText(),
RedundantCreationFix.FixType.REMOVE,
InspectionGadgetsBundle.message(
"inspection.redundant.java.time.operation.creation.java.time.error.remove.fix.message",
"LocalDate.of()")));
return true;
}
PsiMethod[] localDates = variableClass.findMethodsByName(TO_LOCAL_DATE, false);
if (localDates.length != 1) return false;
String newText = identifier.getText() + "." + TO_LOCAL_DATE + "()";
holder.registerProblem(referenceNameElement,
InspectionGadgetsBundle.message("inspection.redundant.java.time.operation.creation.java.time.error.message", "LocalDate"),
RedundantCreationFix.create(newText, RedundantCreationFix.FixType.SIMPLIFY,
InspectionGadgetsBundle.message(
"inspection.redundant.java.time.operation.creation.java.time.error.replace.fix.message",
TO_LOCAL_DATE + "()")));
return true;
}
private static boolean areSameVariableReferences(@NotNull Project project, PsiExpression... expressions) {
if (expressions.length == 0) return false;
PsiManager psiManager = PsiManager.getInstance(project);
PsiVariable firstVariable = resolveVariable(expressions[0]);
if (firstVariable == null) return false;
for (PsiExpression expression : expressions) {
PsiVariable variable = resolveVariable(expression);
if (variable == null || !psiManager.areElementsEquivalent(firstVariable, variable)) {
return false;
}
}
return true;
}
@Nullable
private static PsiVariable resolveVariable(@Nullable PsiExpression expression) {
if (expression == null) return null;
PsiExpression unwrappedExpression = PsiUtil.skipParenthesizedExprDown(expression);
if (!(unwrappedExpression instanceof PsiReferenceExpression referenceExpression)) return null;
PsiElement resolvedElement = referenceExpression.resolve();
return (resolvedElement instanceof PsiVariable variable) ? variable : null;
}
private boolean fixRedundantFrom(@NotNull PsiMethodCallExpression call) {
if (!FROM_MATCHER.test(call)) return false;
PsiExpression[] arguments = call.getArgumentList().getExpressions();
if (arguments.length != 1) return false;
PsiExpression expression = arguments[0];
PsiClass classOfArgument = PsiUtil.resolveClassInClassTypeOnly(expression.getType());
PsiMethod method = call.resolveMethod();
if (method == null) return false;
PsiClass classOfMethod = method.getContainingClass();
if (classOfMethod == null) return false;
PsiManager manager = classOfMethod.getManager();
if (!manager.areElementsEquivalent(classOfArgument, classOfMethod)) return false;
String newText = expression.getText();
if (newText == null) return false;
PsiElement identifier = call.getMethodExpression().getReferenceNameElement();
if (identifier == null) return false;
String className = classOfMethod.getName();
if (className == null) return false;
holder.registerProblem(identifier,
InspectionGadgetsBundle.message("inspection.redundant.java.time.operation.creation.java.time.redundant.call.message",
className + ".from()"),
RedundantCreationFix.create(newText, RedundantCreationFix.FixType.REMOVE,
InspectionGadgetsBundle.message(
"inspection.redundant.java.time.operation.creation.java.time.error.remove.fix.message",
className + ".from()")));
return true;
}
};
}
private static class InlineChronoEnumCallFix extends PsiUpdateModCommandQuickFix {
private final @NotNull String myNewMethodName;
private final int myDeletedArgumentIndex;
InlineChronoEnumCallFix(@NotNull @NlsSafe String newMethodName, int deletedArgumentIndex) {
myNewMethodName = newMethodName;
myDeletedArgumentIndex = deletedArgumentIndex;
}
@Override
public @NotNull String getName() {
return CommonQuickFixBundle.message("fix.replace.with.x.call", myNewMethodName + "()");
}
@Override
public @NotNull String getFamilyName() {
return InspectionGadgetsBundle.message("inspection.redundant.java.time.operation.explicit.chrono.field.family.name");
}
@Override
protected void applyFix(@NotNull Project project, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) {
PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression.class);
if (call == null) return;
PsiExpression qualifierExpression = call.getMethodExpression().getQualifierExpression();
if (qualifierExpression == null) {
return;
}
CommentTracker ct = new CommentTracker();
String text = ct.text(qualifierExpression) + "." + myNewMethodName;
PsiExpression[] expressions = call.getArgumentList().getExpressions();
StringJoiner joiner = new StringJoiner(",", "(", ")");
for (int i = 0; i < expressions.length; i++) {
if (i == myDeletedArgumentIndex) {
continue;
}
joiner.add(ct.text(expressions[i]));
}
text += joiner.toString();
ct.replaceAndRestoreComments(call, text);
}
}
private static class RedundantCreationFix extends PsiUpdateModCommandQuickFix {
private enum FixType {
REMOVE, SIMPLIFY
}
@NotNull private final String myNewText;
@NotNull private final RedundantCreationFix.FixType myFixType;
@IntentionName @Nullable private final String myMessageError;
private RedundantCreationFix(@NotNull String text, @NotNull RedundantCreationFix.FixType type,
@IntentionName @Nullable String messageError) {
myNewText = text;
myFixType = type;
myMessageError = messageError;
}
@Override
protected void applyFix(@NotNull Project project, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) {
PsiMethodCallExpression callExpression = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression.class, false);
if (callExpression == null) return;
new CommentTracker().replaceAndRestoreComments(callExpression, myNewText);
}
@Override
public @NotNull String getName() {
if (myMessageError != null) return myMessageError;
return super.getName();
}
@Override
public @NotNull String getFamilyName() {
return switch (myFixType) {
case SIMPLIFY -> InspectionGadgetsBundle.message("inspection.redundant.java.time.operation.creation.java.time.family.name");
case REMOVE -> InspectionGadgetsBundle.message("inspection.redundant.java.time.operation.creation.java.time.remove.family.name");
};
}
private static @NotNull ModCommandQuickFix create(@NotNull String newText, @NotNull RedundantCreationFix.FixType type,
@IntentionName @Nullable String messageError) {
return new RedundantCreationFix(newText, type, messageError);
}
}
private static class InlineCompareToTimeCallFix extends PsiUpdateModCommandQuickFix {
private final @NotNull RelationType myRelationType;
private final @NotNull String myArgumentType;
InlineCompareToTimeCallFix(@NotNull RelationType relationType, @NotNull @NlsSafe String argumentType) {
myRelationType = relationType;
myArgumentType = argumentType;
}
@Override
public @NotNull String getName() {
String method = getMethodName();
return CommonQuickFixBundle.message("fix.replace.with.x.call", method);
}
@Override
public @NotNull String getFamilyName() {
return InspectionGadgetsBundle.message("inspection.redundant.java.time.operation.compare.java.time.family.name");
}
private @NotNull String getMethodName() {
return switch (myRelationType) {
case EQ, NE -> myArgumentType.equals(JAVA_TIME_LOCAL_TIME) ? "equals" : "isEqual";
case GT, LE -> "isAfter";
case LT, GE -> "isBefore";
default -> throw new UnsupportedOperationException(myRelationType.toString());
};
}
@Override
protected void applyFix(@NotNull Project project, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) {
PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(element, 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);
}
}
}
@@ -1,21 +0,0 @@
<!-- 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>
@@ -1,24 +0,0 @@
<!-- Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<html>
<body>
Reports redundant creation of date/time objects using <code>java.time</code> classes
when simpler method calls can be used or creation can be avoided.
<p>The main <code>java.date</code> classes are marked as <code>@jdk.internal.ValueBased</code>.
Such creations should be avoided as these classes are designed to be used in a value-based manner.</p>
<p>Example:</p>
<pre><code>
LocalDateTime now = LocalDateTime.now();
return LocalDateTime.from(now);
</code></pre>
<p>After the quick-fix is applied:</p>
<pre><code>
LocalDateTime now = LocalDateTime.now();
return now;
</code></pre>
<!-- tooltip end -->
<p><small>New in 2024.3</small></p>
</body>
</html>
@@ -1,20 +0,0 @@
<!-- Copyright 2000-2023 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> method calls with <code>java.time.temporal.ChronoField</code> and
<code>java.time.temporal.ChronoUnit</code> as arguments when these calls can be replaced with
calls of more specific methods.
<p>Example:</p>
<pre><code>
LocalTime localTime = LocalTime.now();
int minute = localTime.get(ChronoField.MINUTE_OF_HOUR);
</code></pre>
<p>After the quick-fix is applied:</p>
<pre><code>
LocalTime localTime = LocalTime.now();
int minute = localTime.getMinute();
</code></pre>
<!-- tooltip end -->
<p><small>New in 2023.2</small></p>
</body>
</html>
@@ -0,0 +1,51 @@
<!-- Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<html>
<body>
Reports redundant operation on 'java.time' object redundant:
<ul>
<li>creation of date/time objects from the JDK <code>java.time</code> package
when simpler method calls can be used or creation can be avoided.</li>
<li><code>java.time</code> method calls with <code>java.time.temporal.ChronoField</code> and
<code>java.time.temporal.ChronoUnit</code> as arguments when these calls can be replaced with
calls of more specific methods.</li>
<li><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.</li>
</ul>
<p>Examples:</p>
- Before:
<pre><code>
LocalDateTime now = LocalDateTime.now();
return LocalDateTime.from(now);
</code></pre>
<p>After the quick-fix is applied:</p>
<pre><code>
LocalDateTime now = LocalDateTime.now();
return now;
</code></pre>
- Before:
<pre><code>
LocalTime localTime = LocalTime.now();
int minute = localTime.get(ChronoField.MINUTE_OF_HOUR);
</code></pre>
<p>After the quick-fix is applied:</p>
<pre><code>
LocalTime localTime = LocalTime.now();
int minute = localTime.getMinute();
</code></pre>
- Before:
<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 2024.3</small></p>
</body>
</html>
@@ -1,118 +0,0 @@
// 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;
}
public static boolean unresolvedType(LocalTime localTime) {
return localTime.compareTo(unresolved) > 0;
}
}
@@ -1,9 +0,0 @@
import java.time.*;
class Main {
LocalTime convert(LocalTime source)
{
return LocalTime.from<caret>(source);
}
}
@@ -1,5 +1,6 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.temporal.ChronoField;
class Main {
@@ -1,6 +1,6 @@
import java.time.*;
public class DateRedundant {
class DateRedundant {
class First {
LocalDateTime convert(LocalDateTime source)
{
@@ -5,6 +5,6 @@ import java.time.temporal.ChronoField;
class Main {
public static int test(OffsetDateTime offsetDateTime) {
return offsetDateTime.get<caret>(ChronoField.NANO_OF_SECOND);
return offsetDateTime.<warning descr="Calls with explicit 'ChronoField' or 'ChronoUnit' arguments call can be simplified">get<caret></warning>(ChronoField.NANO_OF_SECOND);
}
}
@@ -0,0 +1,118 @@
// 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.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo<caret></warning>(localTime2) > 0;
System.out.println(b);
b = localTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(localTime2) >= 0;
System.out.println(b);
b = localTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(localTime2) < 0;
System.out.println(b);
b = localTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(localTime2) <= 0;
System.out.println(b);
b = localTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(localTime2) == 0;
System.out.println(b);
b = localTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(localTime2) != 0;
System.out.println(b);
b = 0 < localTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(localTime2);
System.out.println(b);
b = 0 <= localTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(localTime2);
System.out.println(b);
b = 0 > localTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(localTime2);
System.out.println(b);
b = 0 >= localTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(localTime2);
System.out.println(b);
b = 0 == localTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(localTime2);
System.out.println(b);
b = 0 != localTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(localTime2);
System.out.println(b);
b = ((localTime).<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>((localTime2)) != 0);
System.out.println(b);
OffsetTime offsetTime = OffsetTime.now();
OffsetTime offsetTime2 = OffsetTime.now();
b = offsetTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(offsetTime2) > 0;
System.out.println(b);
b = (((offsetTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(offsetTime2)))) > 0;
System.out.println(b);
b = offsetTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(offsetTime2) >= 0;
System.out.println(b);
b = offsetTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(offsetTime2) < 0;
System.out.println(b);
b = offsetTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(offsetTime2) <= 0;
System.out.println(b);
b = offsetTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(offsetTime2) == 0;
System.out.println(b);
b = offsetTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(offsetTime2) != 0;
System.out.println(b);
OffsetDateTime offsetDateTime = OffsetDateTime.now();
OffsetDateTime offsetDateTime2 = OffsetDateTime.now();
b = offsetDateTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(offsetDateTime2) > 0;
System.out.println(b);
b = offsetDateTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(offsetDateTime2) >= 0;
System.out.println(b);
b = offsetDateTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(offsetDateTime2) < 0;
System.out.println(b);
b = offsetDateTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(offsetDateTime2) <= 0;
System.out.println(b);
b = offsetDateTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(offsetDateTime2) == 0;
System.out.println(b);
b = offsetDateTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(offsetDateTime2) != 0;
System.out.println(b);
LocalDate localDate = LocalDate.now();
LocalDate localDate2 = LocalDate.now();
b = localDate.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(localDate2) > 0;
System.out.println(b);
b = localDate.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(localDate2) >= 0;
System.out.println(b);
b = localDate.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(localDate2) < 0;
System.out.println(b);
b = localDate.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(localDate2) <= 0;
System.out.println(b);
b = localDate.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(localDate2) == 0;
System.out.println(b);
b = localDate.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(localDate2) != 0;
System.out.println(b);
b = localDate.compareTo(new <error descr="Class 'Anonymous class derived from ChronoLocalDate' must implement abstract method 'getChronology()' in 'ChronoLocalDate'">ChronoLocalDate</error>() {}) > 0;
LocalDateTime localDateTime = LocalDateTime.now();
LocalDateTime localDateTime2 = LocalDateTime.now();
b = localDateTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(localDateTime2) > 0;
System.out.println(b);
b = localDateTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(localDateTime2) >= 0;
System.out.println(b);
b = localDateTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(localDateTime2) < 0;
System.out.println(b);
b = localDateTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(localDateTime2) <= 0;
System.out.println(b);
b = localDateTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(localDateTime2) == 0;
System.out.println(b);
b = localDateTime.<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>(localDateTime2) != 0;
System.out.println(b);
b = /*test0*/localDateTime/*test1*/./*test2*/<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>/*test3*/(/*test4*/localDateTime2/*test5*/) > 0;
System.out.println(b);
try {
b = localDateTime.compareTo(new <error descr="Class 'Anonymous class derived from ChronoLocalDateTime' must implement abstract method 'toLocalDate()' in 'ChronoLocalDateTime'">ChronoLocalDateTime<></error>() {}) > 0;
}
catch (Exception e) {
}
b = /*test0*/localDateTime/*test1*/./*test2*/<warning descr="Expression with 'java.time' 'compareTo()' call can be simplified">compareTo</warning>/*test3*/(/*test4*/localDateTime2/*test5*/) > 0;
}
public static boolean unresolvedType(LocalTime localTime) {
return localTime.compareTo(<error descr="Cannot resolve symbol 'unresolved'">unresolved</error>) > 0;
}
}
@@ -0,0 +1,9 @@
import java.time.*;
class Main {
LocalTime convert(LocalTime source)
{
return LocalTime.<warning descr="Redundant 'LocalTime.from()' call">from<caret></warning>(source);
}
}
@@ -1,19 +1,20 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.temporal.ChronoField;
class Main {
public static int test(OffsetDateTime offsetDateTime) {
return offsetDateTime.get<caret>(ChronoField.NANO_OF_SECOND);
return offsetDateTime.<warning descr="Calls with explicit 'ChronoField' or 'ChronoUnit' arguments call can be simplified">get<caret></warning>(ChronoField.NANO_OF_SECOND);
}
public static int test2(OffsetDateTime offsetDateTime) {
return offsetDateTime.get(ChronoField.INSTANT_SECONDS);
}
public static int test3(OffsetDateTime offsetDateTime) {
return offsetDateTime.get(ChronoField.YEAR);
return offsetDateTime.<warning descr="Calls with explicit 'ChronoField' or 'ChronoUnit' arguments call can be simplified">get</warning>(ChronoField.YEAR);
}
public static void test4(OffsetTime time) {
time.get(1, ChronoUnit.YEARS);
time.get(1, <error descr="Cannot resolve symbol 'ChronoUnit'">ChronoUnit</error>.YEARS);
}
}
@@ -1,35 +1,35 @@
import java.time.*;
public class DateRedundant {
class DateRedundant {
class First {
LocalDateTime convert(LocalDateTime source)
{
return LocalDateTime.from<caret>(source);
return LocalDateTime.<warning descr="Redundant 'LocalDateTime.from()' call">from<caret></warning>(source);
}
LocalTime convert(LocalTime source)
{
return LocalTime.from(source);
return LocalTime.<warning descr="Redundant 'LocalTime.from()' call">from</warning>(source);
}
LocalDate convert(LocalDate source)
{
return LocalDate.from(source);
return LocalDate.<warning descr="Redundant 'LocalDate.from()' call">from</warning>(source);
}
OffsetDateTime convert(OffsetDateTime source)
{
return OffsetDateTime.from(source);
return OffsetDateTime.<warning descr="Redundant 'OffsetDateTime.from()' call">from</warning>(source);
}
OffsetTime convert(OffsetTime source)
{
return OffsetTime.from(source);
return OffsetTime.<warning descr="Redundant 'OffsetTime.from()' call">from</warning>(source);
}
ZonedDateTime convert(ZonedDateTime source)
{
return ZonedDateTime.from(source);
return ZonedDateTime.<warning descr="Redundant 'ZonedDateTime.from()' call">from</warning>(source);
}
}
@@ -37,7 +37,7 @@ public class DateRedundant {
static class Third {
private LocalDate getLocalDate(LocalDate fullTime)
{
return LocalDate.of(fullTime.getYear(),
return LocalDate.<warning descr="Redundant creation of 'LocalDate' object">of</warning>(fullTime.getYear(),
fullTime.getMonth(),
fullTime.getDayOfMonth()
);
@@ -45,7 +45,7 @@ public class DateRedundant {
private LocalDate getLocalDate(ZonedDateTime fullTime)
{
return LocalDate.of(fullTime.getYear(),
return LocalDate.<warning descr="Redundant creation of 'LocalDate' object">of</warning>(fullTime.getYear(),
fullTime.getMonth(),
fullTime.getDayOfMonth()
);
@@ -53,7 +53,7 @@ public class DateRedundant {
private LocalTime getLocalTime(ZonedDateTime fullTime)
{
return LocalTime.of(fullTime.getHour(),
return LocalTime.<warning descr="Redundant creation of 'LocalTime' object">of</warning>(fullTime.getHour(),
fullTime.getMinute(),
fullTime.getSecond(),
fullTime.getNano());
@@ -61,7 +61,7 @@ public class DateRedundant {
private LocalDate getLocalDate(LocalDateTime fullTime)
{
return LocalDate.of(fullTime.getYear(),
return LocalDate.<warning descr="Redundant creation of 'LocalDate' object">of</warning>(fullTime.getYear(),
fullTime.getMonth(),
fullTime.getDayOfMonth()
);
@@ -69,7 +69,7 @@ public class DateRedundant {
private LocalDate getLocalDate2(LocalDateTime fullTime)
{
return LocalDate.of(fullTime.getYear(),
return LocalDate.<warning descr="Redundant creation of 'LocalDate' object">of</warning>(fullTime.getYear(),
fullTime.getMonthValue(),
fullTime.getDayOfMonth()
);
@@ -77,7 +77,7 @@ public class DateRedundant {
private LocalTime getLocalTime(LocalDateTime fullTime)
{
return LocalTime.of(fullTime.getHour(),
return LocalTime.<warning descr="Redundant creation of 'LocalTime' object">of</warning>(fullTime.getHour(),
fullTime.getMinute(),
fullTime.getSecond(),
fullTime.getNano());
@@ -85,7 +85,7 @@ public class DateRedundant {
private LocalDate getLocalDate(OffsetDateTime fullTime)
{
return LocalDate.of(fullTime.getYear(),
return LocalDate.<warning descr="Redundant creation of 'LocalDate' object">of</warning>(fullTime.getYear(),
fullTime.getMonthValue(),
fullTime.getDayOfMonth()
);
@@ -94,7 +94,7 @@ public class DateRedundant {
private LocalTime getLocalTime(OffsetDateTime fullTime)
{
return LocalTime.
of(fullTime.getHour(),
<warning descr="Redundant creation of 'LocalTime' object">of</warning>(fullTime.getHour(),
fullTime.getMinute(),
fullTime.getSecond(),
fullTime.getNano());
@@ -102,7 +102,7 @@ public class DateRedundant {
private LocalTime getLocalTime(LocalTime fullTime)
{
return LocalTime.of(fullTime.getHour(),
return LocalTime.<warning descr="Redundant creation of 'LocalTime' object">of</warning>(fullTime.getHour(),
fullTime.getMinute(),
fullTime.getSecond(),
fullTime.getNano());
@@ -1,33 +1,11 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.JavaTestUtil;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.RedundantCompareToJavaTimeInspection;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import org.jetbrains.annotations.NotNull;
//todo just to preserve place
public class RedundantCompareToJavaTimeInspectionTest extends LightJavaCodeInsightFixtureTestCase {
@Override
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
return JAVA_21;
}
public void testCompareTo() {
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";
public void testEmptyMethod() {
}
}
@@ -1,50 +0,0 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.siyeh.ig.redundancy;
import com.intellij.codeInspection.InspectionsBundle;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import com.siyeh.InspectionGadgetsBundle;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class RedundantExplicitChronoFieldInspectionTest extends LightJavaCodeInsightFixtureTestCase {
@Override
protected String getBasePath() {
return "/java/java-tests/testData/ig/com/siyeh/igtest/redundancy/redundant_explicit_chrono_field/";
}
@Override
public void setUp() throws Exception {
super.setUp();
myFixture.enableInspections(new RedundantExplicitChronoFieldInspection());
}
public void testPreview() {
doTest("Replace with 'getNano()' call");
}
public void testOffsetDateTimeConvert() {
doTest(null);
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return JAVA_21;
}
private void doTest(@Nullable String quickFixName) {
myFixture.configureByFile(getTestDataPath() + "before" + getTestName(false) + ".java");
if (quickFixName != null) {
myFixture.checkPreviewAndLaunchAction(myFixture.findSingleIntention(quickFixName));
}
else {
myFixture.launchAction(myFixture.findSingleIntention(InspectionsBundle.message("fix.all.inspection.problems.in.file",
InspectionGadgetsBundle.message(
"inspection.explicit.chrono.field.display.name"))));
}
myFixture.checkResultByFile("after" + getTestName(false) + ".java");
}
}
@@ -8,27 +8,38 @@ import com.siyeh.InspectionGadgetsBundle;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class RedundantCreationJavaTimeInspectionTest extends LightJavaCodeInsightFixtureTestCase {
public class RedundantJavaTimeOperationInspectionTest extends LightJavaCodeInsightFixtureTestCase {
@Override
protected String getBasePath() {
return "/java/java-tests/testData/ig/com/siyeh/igtest/redundancy/redundant_creation_java_time/";
return "/java/java-tests/testData/ig/com/siyeh/igtest/redundancy/redundant_java_time_operations/";
}
@Override
public void setUp() throws Exception {
super.setUp();
myFixture.enableInspections(new RedundantCreationJavaTimeInspection());
myFixture.enableInspections(new RedundantJavaTimeOperationsInspection());
}
public void testPreview() {
doTest("Simplify creation of 'java.time' object");
public void testCreationPreview() {
doTest("Remove 'LocalTime.from()' call");
}
public void testRedundantCreationOfDateTime() {
doTest(null);
}
public void testChronoConverterPreview() {
doTest("Replace with 'getNano()' call");
}
public void testOffsetDateTimeConvert() {
doTest(null);
}
public void testCompareTo() {
doTest(null);
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
@@ -37,13 +48,14 @@ public class RedundantCreationJavaTimeInspectionTest extends LightJavaCodeInsigh
private void doTest(@Nullable String quickFixName) {
myFixture.configureByFile(getTestDataPath() + "before" + getTestName(false) + ".java");
myFixture.testHighlighting(true, false, true);
if (quickFixName != null) {
myFixture.checkPreviewAndLaunchAction(myFixture.findSingleIntention(quickFixName));
}
else {
myFixture.launchAction(myFixture.findSingleIntention(InspectionsBundle.message("fix.all.inspection.problems.in.file",
InspectionGadgetsBundle.message(
"inspection.redundant.creation.java.time.display.name"))));
"inspection.redundant.java.time.operation.display.name"))));
}
myFixture.checkResultByFile("after" + getTestName(false) + ".java");
}