diff --git a/java/java-impl-inspections/src/com/intellij/codeInspection/CastCanBeReplacedWithVariableInspection.java b/java/java-impl-inspections/src/com/intellij/codeInspection/CastCanBeReplacedWithVariableInspection.java new file mode 100644 index 000000000000..3b2d572f2ca5 --- /dev/null +++ b/java/java-impl-inspections/src/com/intellij/codeInspection/CastCanBeReplacedWithVariableInspection.java @@ -0,0 +1,187 @@ +// 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.openapi.progress.ProgressIndicatorProvider; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.*; +import com.intellij.psi.controlFlow.*; +import com.intellij.psi.util.JavaPsiPatternUtil; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; +import com.siyeh.InspectionGadgetsBundle; +import com.siyeh.ig.psiutils.CommentTracker; +import com.siyeh.ig.psiutils.EquivalenceChecker; +import com.siyeh.ig.psiutils.InstanceOfUtils; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Objects; + +/** + * @author Danila Ponomarenko + */ +public class CastCanBeReplacedWithVariableInspection extends AbstractBaseJavaLocalInspectionTool + implements CleanupLocalInspectionTool { + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + return new JavaElementVisitor() { + @Override + public void visitTypeCastExpression(@NotNull PsiTypeCastExpression typeCastExpression) { + final PsiMethod method = PsiTreeUtil.getParentOfType(typeCastExpression, PsiMethod.class); + + if (method == null) { + return; + } + + final PsiExpression operand = PsiUtil.skipParenthesizedExprDown(typeCastExpression.getOperand()); + if (!(operand instanceof PsiReferenceExpression operandReference)) { + return; + } + + final PsiElement resolved = operandReference.resolve(); + if (!(resolved instanceof PsiParameter) && !(resolved instanceof PsiLocalVariable)) { + return; + } + + final PsiVariable replacement = findReplacement(method, (PsiVariable)resolved, typeCastExpression); + if (replacement == null) { + return; + } + + final String variableName = replacement.getName(); + final String castExpressionText = typeCastExpression.getText(); + final LocalQuickFix fix = new ReplaceCastWithVariableFix(castExpressionText, replacement); + holder.registerProblem(typeCastExpression, + InspectionGadgetsBundle.message("inspection.cast.can.be.replaced.with.variable.message", + variableName, castExpressionText), fix); + } + }; + } + + @Nullable + private static PsiVariable findReplacement(@NotNull PsiMethod method, + @NotNull PsiVariable castedVar, + @NotNull PsiTypeCastExpression expression) { + final TextRange expressionTextRange = expression.getTextRange(); + if (InstanceOfUtils.isUncheckedCast(expression)) return null; + PsiExpression operand = Objects.requireNonNull(PsiUtil.skipParenthesizedExprDown(expression.getOperand())); + PsiType castType = Objects.requireNonNull(expression.getCastType()).getType(); + List found = + SyntaxTraverser.psiTraverser(method) + .filter(PsiTypeCastExpression.class) + .filter(cast -> EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(cast.getOperand(), operand)) + .filter(cast -> { + PsiTypeElement typeElement = cast.getCastType(); + return typeElement != null && InstanceOfUtils.typeCompatible(typeElement.getType(), castType, operand); + }) + .toList(); + PsiResolveHelper resolveHelper = PsiResolveHelper.getInstance(method.getProject()); + final PsiCodeBlock methodBody = method.getBody(); + if (methodBody == null) return null; + for (PsiTypeCastExpression occurrence : found) { + ProgressIndicatorProvider.checkCanceled(); + final TextRange occurrenceTextRange = occurrence.getTextRange(); + if (occurrence == expression || occurrenceTextRange.getEndOffset() >= expressionTextRange.getStartOffset()) { + continue; + } + + final PsiLocalVariable variable = getVariable(occurrence); + + if (variable != null && + resolveHelper.resolveReferencedVariable(variable.getName(), expression) == variable && + !isChangedBetween(castedVar, methodBody, occurrence, expression) && + !isChangedBetween(variable, methodBody, occurrence, expression)) { + return variable; + } + } + + PsiInstanceOfExpression instanceOf = InstanceOfUtils.findPatternCandidate(expression); + if (instanceOf != null) { + PsiPattern pattern = instanceOf.getPattern(); + PsiPatternVariable patternVariable = JavaPsiPatternUtil.getPatternVariable(pattern); + if (patternVariable != null && + !isChangedBetween(castedVar, methodBody, instanceOf, expression) && + !isChangedBetween(patternVariable, methodBody, instanceOf, expression)) { + return patternVariable; + } + } + + return null; + } + + private static boolean isChangedBetween(@NotNull final PsiVariable variable, + @NotNull final PsiElement scope, + @NotNull final PsiElement start, + @NotNull final PsiElement end) { + if (variable.hasModifierProperty(PsiModifier.FINAL)) { + return false; + } + + ControlFlow controlFlow; + try { + controlFlow = ControlFlowFactory.getInstance(variable.getProject()).getControlFlow(scope, new LocalsControlFlowPolicy(scope), true); + } + catch (AnalysisCanceledException ignored) { + controlFlow = ControlFlow.EMPTY; + } + int startOffset = controlFlow.getEndOffset(start) + 1; + int endOffset = controlFlow.getEndOffset(end); + return ControlFlowUtil.getWrittenVariables(controlFlow, startOffset, endOffset, true).contains(variable); + } + + @Nullable + private static PsiLocalVariable getVariable(@NotNull PsiExpression occurrence) { + final PsiElement parent = PsiUtil.skipParenthesizedExprUp(occurrence.getParent()); + + if (parent instanceof PsiLocalVariable localVariable) { + return localVariable; + } + + if (parent instanceof PsiAssignmentExpression assignmentExpression && + assignmentExpression.getLExpression() instanceof PsiReferenceExpression referenceExpression && + referenceExpression.resolve() instanceof PsiLocalVariable localVariable) { + return localVariable; + } + + return null; + } + + private static class ReplaceCastWithVariableFix implements LocalQuickFix { + private final @NotNull String myText; + private final @NotNull String myVariableName; + + private ReplaceCastWithVariableFix(@NotNull String text, @NotNull PsiVariable variable) { + myText = text; + myVariableName = Objects.requireNonNull(variable.getName()); + } + + @Nls(capitalization = Nls.Capitalization.Sentence) + @NotNull + @Override + public String getName() { + return CommonQuickFixBundle.message("fix.replace.x.with.y", myText, myVariableName); + } + + @Nls(capitalization = Nls.Capitalization.Sentence) + @NotNull + @Override + public String getFamilyName() { + return InspectionGadgetsBundle.message("inspection.cast.can.be.replaced.with.variable.family.name"); + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + final PsiElement element = descriptor.getPsiElement(); + if (element instanceof PsiTypeCastExpression typeCastExpression) { + final PsiElement toReplace = + typeCastExpression.getParent() instanceof PsiParenthesizedExpression ? typeCastExpression.getParent() : typeCastExpression; + new CommentTracker().replaceAndRestoreComments(toReplace, myVariableName); + } + } + } +} diff --git a/java/java-impl/src/META-INF/JavaPlugin.xml b/java/java-impl/src/META-INF/JavaPlugin.xml index 3c1f96f79792..f96dbb11953c 100644 --- a/java/java-impl/src/META-INF/JavaPlugin.xml +++ b/java/java-impl/src/META-INF/JavaPlugin.xml @@ -1952,12 +1952,6 @@ messages.JavaBundle intention.category.strings - - JAVA - com.intellij.codeInsight.intention.impl.ReplaceCastWithVariableAction - messages.JavaBundle - intention.category.declaration - JAVA com.intellij.codeInsight.intention.impl.SortContentAction diff --git a/java/java-impl/src/com/intellij/codeInsight/intention/impl/ReplaceCastWithVariableAction.java b/java/java-impl/src/com/intellij/codeInsight/intention/impl/ReplaceCastWithVariableAction.java deleted file mode 100644 index ec2671c8197f..000000000000 --- a/java/java-impl/src/com/intellij/codeInsight/intention/impl/ReplaceCastWithVariableAction.java +++ /dev/null @@ -1,176 +0,0 @@ -// 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.codeInsight.intention.impl; - -import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction; -import com.intellij.java.JavaBundle; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.progress.ProgressIndicatorProvider; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.util.TextRange; -import com.intellij.psi.*; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.util.PsiUtil; -import com.intellij.util.IncorrectOperationException; -import com.siyeh.ig.psiutils.CommentTracker; -import com.siyeh.ig.psiutils.EquivalenceChecker; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; - -/** - * @author Danila Ponomarenko - */ -public class ReplaceCastWithVariableAction extends PsiElementBaseIntentionAction { - private String myReplaceVariableName = ""; - - @Override - public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) { - final PsiTypeCastExpression typeCastExpression = PsiTreeUtil.getParentOfType(element, PsiTypeCastExpression.class); - final PsiMethod method = PsiTreeUtil.getParentOfType(element, PsiMethod.class); - - if (typeCastExpression == null || method == null) { - return false; - } - - final PsiExpression operand = PsiUtil.skipParenthesizedExprDown(typeCastExpression.getOperand()); - if (!(operand instanceof PsiReferenceExpression)) { - return false; - } - - final PsiReferenceExpression operandReference = (PsiReferenceExpression)operand; - final PsiElement resolved = operandReference.resolve(); - if (!(resolved instanceof PsiParameter) && !(resolved instanceof PsiLocalVariable)) { - return false; - } - - final PsiLocalVariable replacement = findReplacement(method, (PsiVariable)resolved, typeCastExpression); - if (replacement == null) { - return false; - } - - myReplaceVariableName = replacement.getName(); - setText(JavaBundle.message("intention.replace.cast.with.var.text", typeCastExpression.getText(), myReplaceVariableName)); - - return true; - } - - @Override - public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException { - final PsiTypeCastExpression typeCastExpression = PsiTreeUtil.getParentOfType(element, PsiTypeCastExpression.class); - - if (typeCastExpression == null) { - return; - } - - final PsiElement toReplace = typeCastExpression.getParent() instanceof PsiParenthesizedExpression ? typeCastExpression.getParent() : typeCastExpression; - final PsiElementFactory factory = JavaPsiFacade.getElementFactory(project); - new CommentTracker().replaceAndRestoreComments(toReplace, factory.createExpressionFromText(myReplaceVariableName, toReplace)); - } - - @Nullable - private static PsiLocalVariable findReplacement(@NotNull PsiMethod method, - @NotNull PsiVariable castedVar, - @NotNull PsiTypeCastExpression expression) { - final TextRange expressionTextRange = expression.getTextRange(); - PsiExpression operand = PsiUtil.skipParenthesizedExprDown(expression.getOperand()); - List found = - SyntaxTraverser.psiTraverser(method) - .filter(PsiTypeCastExpression.class) - .filter(cast -> EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(cast.getOperand(), operand)) - .toList(); - PsiResolveHelper resolveHelper = PsiResolveHelper.getInstance(method.getProject()); - for (PsiTypeCastExpression occurrence : found) { - ProgressIndicatorProvider.checkCanceled(); - final TextRange occurrenceTextRange = occurrence.getTextRange(); - if (occurrence == expression || occurrenceTextRange.getEndOffset() >= expressionTextRange.getStartOffset()) { - continue; - } - - final PsiLocalVariable variable = getVariable(occurrence); - - final PsiCodeBlock methodBody = method.getBody(); - if (variable != null && methodBody != null && - resolveHelper.resolveReferencedVariable(variable.getName(), expression) == variable && - !isChangedBetween(castedVar, methodBody, occurrence, expression) && - !isChangedBetween(variable, methodBody, occurrence, expression)) { - return variable; - } - } - - - return null; - } - - private static boolean isChangedBetween(@NotNull final PsiVariable variable, - @NotNull final PsiElement scope, - @NotNull final PsiElement start, - @NotNull final PsiElement end) { - if (variable.hasModifierProperty(PsiModifier.FINAL)) { - return false; - } - - final Ref result = new Ref<>(); - - scope.accept( - new JavaRecursiveElementWalkingVisitor() { - private boolean inScope; - - @Override - public void visitElement(@NotNull PsiElement element) { - if (element == start) { - inScope = true; - } - if (element == end) { - inScope = false; - stopWalking(); - } - super.visitElement(element); - } - - @Override - public void visitAssignmentExpression(@NotNull PsiAssignmentExpression expression) { - if (inScope && expression.getLExpression() instanceof PsiReferenceExpression) { - final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)expression.getLExpression(); - - if (variable.equals(referenceExpression.resolve())) { - result.set(true); - stopWalking(); - } - } - super.visitAssignmentExpression(expression); - } - } - ); - return result.get() == Boolean.TRUE; - } - - @Nullable - private static PsiLocalVariable getVariable(@NotNull PsiExpression occurrence) { - final PsiElement parent = PsiUtil.skipParenthesizedExprUp(occurrence.getParent()); - - if (parent instanceof PsiLocalVariable) { - return (PsiLocalVariable)parent; - } - - if (parent instanceof PsiAssignmentExpression) { - final PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression)parent; - if (assignmentExpression.getLExpression() instanceof PsiReferenceExpression) { - final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)assignmentExpression.getLExpression(); - final PsiElement resolved = referenceExpression.resolve(); - if (resolved instanceof PsiLocalVariable) { - return (PsiLocalVariable)resolved; - } - } - } - - return null; - } - - @NotNull - @Override - public String getFamilyName() { - return JavaBundle.message("intention.replace.cast.with.var.family"); - } -} diff --git a/java/java-impl/src/inspectionDescriptions/CastCanBeReplacedWithVariable.html b/java/java-impl/src/inspectionDescriptions/CastCanBeReplacedWithVariable.html new file mode 100644 index 000000000000..dac5f65184af --- /dev/null +++ b/java/java-impl/src/inspectionDescriptions/CastCanBeReplacedWithVariable.html @@ -0,0 +1,21 @@ + + +Reports type cast operations that can be replaced with existing local or pattern variables with the same value. +

Example:

+

+  void foo(Object obj) {
+    String s = (String) obj;
+    System.out.println(((String) obj).trim());
+  }
+
+

After the quick-fix is applied:

+

+  void foo(Object obj) {
+    String s = (String) obj;
+    System.out.println(s.trim());
+  }
+
+ +

New in 2022.3

+ + \ No newline at end of file diff --git a/java/java-impl/src/intentionDescriptions/ReplaceCastWithVariableAction/after.java.template b/java/java-impl/src/intentionDescriptions/ReplaceCastWithVariableAction/after.java.template deleted file mode 100644 index 222b38c4c26f..000000000000 --- a/java/java-impl/src/intentionDescriptions/ReplaceCastWithVariableAction/after.java.template +++ /dev/null @@ -1,2 +0,0 @@ -FooBar foobar = (FooBar) foo; -return foobar.baz; \ No newline at end of file diff --git a/java/java-impl/src/intentionDescriptions/ReplaceCastWithVariableAction/before.java.template b/java/java-impl/src/intentionDescriptions/ReplaceCastWithVariableAction/before.java.template deleted file mode 100644 index 38fb40e0a4e6..000000000000 --- a/java/java-impl/src/intentionDescriptions/ReplaceCastWithVariableAction/before.java.template +++ /dev/null @@ -1,2 +0,0 @@ -FooBar foobar = (FooBar) foo; -return ((FooBar) foo).baz; \ No newline at end of file diff --git a/java/java-impl/src/intentionDescriptions/ReplaceCastWithVariableAction/description.html b/java/java-impl/src/intentionDescriptions/ReplaceCastWithVariableAction/description.html deleted file mode 100644 index 0bb05f061c16..000000000000 --- a/java/java-impl/src/intentionDescriptions/ReplaceCastWithVariableAction/description.html +++ /dev/null @@ -1,5 +0,0 @@ - - -Replaces a type cast expression with an existing local variable with the same value. - - \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/afterChangedAfterCastingVar.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterChangedAfterCastingVar.java similarity index 100% rename from java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/afterChangedAfterCastingVar.java rename to java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterChangedAfterCastingVar.java diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/afterChangedAfterVar.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterChangedAfterVar.java similarity index 100% rename from java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/afterChangedAfterVar.java rename to java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterChangedAfterVar.java diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/afterChangedBeforeCastingVar.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterChangedBeforeCastingVar.java similarity index 100% rename from java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/afterChangedBeforeCastingVar.java rename to java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterChangedBeforeCastingVar.java diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/afterChangedBeforeVar.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterChangedBeforeVar.java similarity index 100% rename from java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/afterChangedBeforeVar.java rename to java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterChangedBeforeVar.java diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/afterChangedTwice.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterChangedTwice.java similarity index 100% rename from java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/afterChangedTwice.java rename to java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterChangedTwice.java diff --git a/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterPatternVar.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterPatternVar.java new file mode 100644 index 000000000000..02ce6095165d --- /dev/null +++ b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterPatternVar.java @@ -0,0 +1,8 @@ +// "Replace '(String) obj' with 's'" "true-preview" + +class X { + void foo(Object obj) { + String s = (String) obj; + System.out.println(s.trim()); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/afterSimple.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterSimple.java similarity index 100% rename from java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/afterSimple.java rename to java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterSimple.java diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/afterTwoVars.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterTwoVars.java similarity index 100% rename from java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/afterTwoVars.java rename to java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterTwoVars.java diff --git a/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterUnchangedCastingVar.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterUnchangedCastingVar.java new file mode 100644 index 000000000000..17b85180bc53 --- /dev/null +++ b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterUnchangedCastingVar.java @@ -0,0 +1,12 @@ +// "Replace '(String) o' with 's1'" "true-preview" + +class C { + void foo(Object o) { + String s1 = (String) o; + if (Math.random() > 0.5) { + o = null; + return; + } + String s2 = s1; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterUnchangedVar.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterUnchangedVar.java new file mode 100644 index 000000000000..9d2231062bf8 --- /dev/null +++ b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterUnchangedVar.java @@ -0,0 +1,12 @@ +// "Replace '(String) o' with 's1'" "true-preview" + +class C { + void foo(Object o) { + String s1 = (String) o; + if (Math.random() > 0.5) { + s1 = null; + return; + } + String s2 = s1; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/beforeChangedAfterCastingVar.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeChangedAfterCastingVar.java similarity index 100% rename from java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/beforeChangedAfterCastingVar.java rename to java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeChangedAfterCastingVar.java diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/beforeChangedAfterVar.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeChangedAfterVar.java similarity index 100% rename from java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/beforeChangedAfterVar.java rename to java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeChangedAfterVar.java diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/beforeChangedBeforeCastingVar.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeChangedBeforeCastingVar.java similarity index 100% rename from java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/beforeChangedBeforeCastingVar.java rename to java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeChangedBeforeCastingVar.java diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/beforeChangedBeforeVar.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeChangedBeforeVar.java similarity index 100% rename from java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/beforeChangedBeforeVar.java rename to java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeChangedBeforeVar.java diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/beforeChangedCastingVar.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeChangedCastingVar.java similarity index 100% rename from java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/beforeChangedCastingVar.java rename to java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeChangedCastingVar.java diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/beforeChangedTwice.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeChangedTwice.java similarity index 100% rename from java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/beforeChangedTwice.java rename to java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeChangedTwice.java diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/beforeChangedVar.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeChangedVar1.java similarity index 100% rename from java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/beforeChangedVar.java rename to java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeChangedVar1.java diff --git a/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeChangedVar2.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeChangedVar2.java new file mode 100644 index 000000000000..340cf35bd5c5 --- /dev/null +++ b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeChangedVar2.java @@ -0,0 +1,8 @@ +// "Replace '(String) obj' with 's1'" "false" + +class X { + void test(Object obj) { + String s1 = (String) obj, s2 = s1 = "blah blah blah"; + String s3 = (String) obj; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeChangedVar3.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeChangedVar3.java new file mode 100644 index 000000000000..6127c16c286a --- /dev/null +++ b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeChangedVar3.java @@ -0,0 +1,9 @@ +// "Replace '(String) obj' with 's1'" "false" + +class X { + void test(Object obj, String s) { + if (obj instanceof String s1 && (s1 = "blah blah blah").equals(s)) { + String s2 = (String) obj; + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeIncompatibleTypes.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeIncompatibleTypes.java new file mode 100644 index 000000000000..7ec3c07e1d6d --- /dev/null +++ b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeIncompatibleTypes.java @@ -0,0 +1,8 @@ +// "Replace '(Integer) obj' with 's'" "false" + +class X { + void test(Object obj) { + String s = (String) obj; + Integer i = ((Integer) obj).intValue(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/beforeOtherBranch.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeOtherBranch.java similarity index 100% rename from java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/beforeOtherBranch.java rename to java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeOtherBranch.java diff --git a/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforePatternVar.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforePatternVar.java new file mode 100644 index 000000000000..d712e0f9a7f6 --- /dev/null +++ b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforePatternVar.java @@ -0,0 +1,8 @@ +// "Replace '(String) obj' with 's'" "true-preview" + +class X { + void foo(Object obj) { + String s = (String) obj; + System.out.println(((String) obj).trim()); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/beforeSimple.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeSimple.java similarity index 100% rename from java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/beforeSimple.java rename to java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeSimple.java diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/beforeTwoVars.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeTwoVars.java similarity index 100% rename from java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceCastWithVariable/beforeTwoVars.java rename to java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeTwoVars.java diff --git a/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeUnchangedCastingVar.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeUnchangedCastingVar.java new file mode 100644 index 000000000000..05ecd337a43d --- /dev/null +++ b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeUnchangedCastingVar.java @@ -0,0 +1,12 @@ +// "Replace '(String) o' with 's1'" "true-preview" + +class C { + void foo(Object o) { + String s1 = (String) o; + if (Math.random() > 0.5) { + o = null; + return; + } + String s2 = (String) o; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeUnchangedVar.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeUnchangedVar.java new file mode 100644 index 000000000000..5cf7a5cb73d6 --- /dev/null +++ b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeUnchangedVar.java @@ -0,0 +1,12 @@ +// "Replace '(String) o' with 's1'" "true-preview" + +class C { + void foo(Object o) { + String s1 = (String) o; + if (Math.random() > 0.5) { + s1 = null; + return; + } + String s2 = (String) o; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeUnsafeCast.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeUnsafeCast.java new file mode 100644 index 000000000000..5bb5f3a8b465 --- /dev/null +++ b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeUnsafeCast.java @@ -0,0 +1,10 @@ +// "Replace '(ArrayList) obj' with 'arrayList'" "false" + +import java.util.*; + +class X { + void test(List obj) { + ArrayList arrayList = (ArrayList) obj; + Integer list = ((ArrayList) obj).get(0); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/CastCanBeReplacedWithVariableInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/CastCanBeReplacedWithVariableInspectionTest.java new file mode 100644 index 000000000000..bf03217b056b --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/CastCanBeReplacedWithVariableInspectionTest.java @@ -0,0 +1,26 @@ +// 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.codeInspection; + +import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase; +import com.intellij.codeInspection.CastCanBeReplacedWithVariableInspection; +import com.intellij.codeInspection.LocalInspectionTool; +import com.intellij.testFramework.LightProjectDescriptor; +import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase; +import org.jetbrains.annotations.NotNull; + +public class CastCanBeReplacedWithVariableInspectionTest extends LightQuickFixParameterizedTestCase { + @Override + protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() { + return new LocalInspectionTool[]{new CastCanBeReplacedWithVariableInspection()}; + } + + @Override + protected String getBasePath() { + return "/inspection/castCanBeReplacedWithVariable"; + } + + @Override + protected @NotNull LightProjectDescriptor getProjectDescriptor() { + return LightJavaCodeInsightFixtureTestCase.JAVA_16; + } +} diff --git a/java/openapi/resources/messages/JavaBundle.properties b/java/openapi/resources/messages/JavaBundle.properties index ad52b31263ee..831202811436 100644 --- a/java/openapi/resources/messages/JavaBundle.properties +++ b/java/openapi/resources/messages/JavaBundle.properties @@ -859,8 +859,6 @@ intention.move.field.assignment.to.declaration=Move assignment to field declarat intention.move.initializer.to.constructor=Move initializer to constructor intention.move.initializer.to.set.up=Move initializer to setUp method intention.override.method.text=Override method ''{0}'' -intention.replace.cast.with.var.family=Replace cast with variable -intention.replace.cast.with.var.text=Replace ''{0}'' with ''{1}'' intention.replace.concatenation.with.formatted.output.family=Replace concatenation with formatted output intention.replace.concatenation.with.formatted.output.text=Replace '+' with 'java.text.MessageFormat.format()' intention.split.declaration.assignment.text=Split into declaration and assignment diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/resources/messages/InspectionGadgetsBundle.properties b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/resources/messages/InspectionGadgetsBundle.properties index d9f0fe33bc2d..d71e7d18bbc6 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/resources/messages/InspectionGadgetsBundle.properties +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/resources/messages/InspectionGadgetsBundle.properties @@ -2303,6 +2303,10 @@ inspection.pattern.variable.can.be.used.existing.message=Existing pattern variab inspection.pattern.variable.can.be.used.existing.fix.family.name=Replace with existing pattern variable inspection.pattern.variable.can.be.used.existing.fix.name=Replace ''{0}'' with existing pattern variable ''{1}'' +inspection.cast.can.be.replaced.with.variable.display.name=Cast can be replaced with variable +inspection.cast.can.be.replaced.with.variable.message=Variable ''{0}'' can be used instead of ''{1}'' +inspection.cast.can.be.replaced.with.variable.family.name=Replace cast with variable + array.hash.code.fix.family.name=Replace with 'Arrays.hashCode()' call objects.hash.fix.family.name=Wrap with 'Arrays.hashCode()' unqualified.static.access.fix.family.name=Qualify static access diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/InstanceOfUtils.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/InstanceOfUtils.java index 6f7e75cfffb2..734b4b0f5bad 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/InstanceOfUtils.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/InstanceOfUtils.java @@ -183,16 +183,20 @@ public final class InstanceOfUtils { */ @Nullable public static PsiInstanceOfExpression findPatternCandidate(@NotNull PsiTypeCastExpression cast) { - PsiTypeElement castType = cast.getCastType(); - if (castType == null) return null; - PsiExpression castOperand = cast.getOperand(); - if (castOperand == null) return null; - PsiType type = castOperand.getType(); - if (type == null) return null; - if (JavaGenericsUtil.isUncheckedCast(castType.getType(), type)) return null; + if (isUncheckedCast(cast)) return null; return findCorrespondingInstanceOf(cast); } + public static boolean isUncheckedCast(@NotNull PsiTypeCastExpression cast) { + PsiTypeElement castType = cast.getCastType(); + if (castType == null) return true; + PsiExpression castOperand = cast.getOperand(); + if (castOperand == null) return true; + PsiType type = castOperand.getType(); + if (type == null) return true; + return JavaGenericsUtil.isUncheckedCast(castType.getType(), type); + } + /** * @param cast a cast expression to find parent instanceof for * @return an instanceof expression that checks for the same raw type as the cast. @@ -376,7 +380,7 @@ public final class InstanceOfUtils { return null; } - private static boolean typeCompatible(@NotNull PsiType instanceOfType, @NotNull PsiType castType, @NotNull PsiExpression castOperand) { + public static boolean typeCompatible(@NotNull PsiType instanceOfType, @NotNull PsiType castType, @NotNull PsiExpression castOperand) { if (instanceOfType.equals(castType)) return true; if (castType instanceof PsiClassType) { PsiClassType rawType = ((PsiClassType)castType).rawType(); diff --git a/plugins/InspectionGadgets/src/META-INF/InspectionGadgets.xml b/plugins/InspectionGadgets/src/META-INF/InspectionGadgets.xml index 364a9e66da12..e3a46060baec 100644 --- a/plugins/InspectionGadgets/src/META-INF/InspectionGadgets.xml +++ b/plugins/InspectionGadgets/src/META-INF/InspectionGadgets.xml @@ -2769,6 +2769,10 @@ bundle="messages.InspectionGadgetsBundle" key="inspection.pattern.variable.can.be.used.display.name" groupBundle="messages.InspectionsBundle" groupKey="group.names.language.level.specific.issues.and.migration.aids16" implementationClass="com.intellij.codeInspection.PatternVariableCanBeUsedInspection" cleanupTool="true"/> +