Java: more accurate preview for "Method parameter always has the same value" inspection

and add test

GitOrigin-RevId: 957be987fbc9414e1d099ee86c72193bcfc43c19
This commit is contained in:
Bas Leijdekkers
2023-05-10 14:19:36 +00:00
committed by intellij-monorepo-bot
parent 63d274bf59
commit d8ba8bd567
4 changed files with 135 additions and 77 deletions
@@ -4,13 +4,14 @@ package com.intellij.codeInspection.sameParameterValue;
import com.intellij.analysis.AnalysisScope;
import com.intellij.codeInsight.daemon.impl.UnusedSymbolUtil;
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
import com.intellij.codeInsight.intention.preview.IntentionPreviewUtils;
import com.intellij.codeInsight.options.JavaInspectionControls;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.deadCode.UnusedDeclarationInspectionBase;
import com.intellij.codeInspection.options.OptPane;
import com.intellij.codeInspection.reference.*;
import com.intellij.java.JavaBundle;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.EmptyProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
@@ -18,6 +19,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.OverridingMethodsSearch;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiFormatUtil;
@@ -65,10 +67,6 @@ public class SameParameterValueInspection extends GlobalJavaBatchInspectionTool
);
}
protected LocalQuickFix createFix(String paramName, String value) {
return new InlineParameterValueFix(paramName, value);
}
@Override
public CommonProblemDescriptor @Nullable [] checkElement(@NotNull RefEntity refEntity,
@NotNull AnalysisScope scope,
@@ -76,7 +74,7 @@ public class SameParameterValueInspection extends GlobalJavaBatchInspectionTool
@NotNull GlobalInspectionContext globalContext,
@NotNull ProblemDescriptionsProcessor processor) {
List<ProblemDescriptor> problems = null;
if (refEntity instanceof final RefMethod refMethod) {
if (refEntity instanceof RefMethod refMethod) {
if (refMethod.hasSuperMethods() ||
VisibilityUtil.compare(refMethod.getAccessModifier(), highestModifier) < 0 ||
@@ -109,13 +107,8 @@ public class SameParameterValueInspection extends GlobalJavaBatchInspectionTool
@Nullable
@Contract("null -> null; !null -> !null")
private static List<Object> valueToList(@Nullable Object rootValue) {
if (rootValue == null) return null;
if (rootValue instanceof List<?>) {
//noinspection unchecked
return (List<Object>)rootValue;
} else {
return new SmartList<>(rootValue);
}
//noinspection unchecked
return rootValue == null || rootValue instanceof List<?> ? (List<Object>)rootValue : new SmartList<>(rootValue);
}
@Override
@@ -161,9 +154,7 @@ public class SameParameterValueInspection extends GlobalJavaBatchInspectionTool
if (hint == null) return null;
final int spaceIdx = hint.indexOf(' ');
if (spaceIdx == -1 || spaceIdx >= hint.length() - 1) return null; //invalid hint
final String paramName = hint.substring(0, spaceIdx);
final String value = hint.substring(spaceIdx + 1);
return createFix(paramName, value);
return new InlineParameterValueFix(hint.substring(0, spaceIdx), hint.substring(spaceIdx + 1));
}
@Override
@@ -178,15 +169,14 @@ public class SameParameterValueInspection extends GlobalJavaBatchInspectionTool
return new LocalSameParameterValueInspection(this);
}
private @Nullable ProblemDescriptor registerProblem(@NotNull InspectionManager manager,
@NotNull UParameter parameter,
Object value,
boolean suggestFix) {
private static @Nullable ProblemDescriptor registerProblem(@NotNull InspectionManager manager,
@NotNull UParameter parameter,
Object value,
boolean suggestFix) {
final String name = parameter.getName();
if (name == null || name.isEmpty()) return null;
String shortName;
String stringPresentation;
if (value instanceof PsiType) {
stringPresentation = ((PsiType)value).getCanonicalText() + ".class";
shortName = ((PsiType)value).getPresentableText() + ".class";
@@ -209,12 +199,13 @@ public class SameParameterValueInspection extends GlobalJavaBatchInspectionTool
}
PsiElement anchor = ObjectUtils.notNull(UDeclarationKt.getAnchorPsi(parameter), parameter);
if (!anchor.isPhysical()) return null;
String value1 = stringPresentation.startsWith("\"\"")
? stringPresentation
: StringUtil.escapeLineBreak(stringPresentation);
return manager.createProblemDescriptor(anchor,
JavaBundle.message("inspection.same.parameter.problem.descriptor",
StringUtil.unquoteString(shortName)),
suggestFix ? createFix(name, stringPresentation.startsWith("\"\"")
? stringPresentation
: StringUtil.escapeLineBreak(stringPresentation)) : null,
suggestFix ? new InlineParameterValueFix(name, value1) : null,
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, false);
}
@@ -256,42 +247,22 @@ public class SameParameterValueInspection extends GlobalJavaBatchInspectionTool
final PsiElement element = descriptor.getPsiElement();
final PsiMethod method = PsiTreeUtil.getParentOfType(element, PsiMethod.class);
if (method == null) return;
PsiParameter parameter = findParameter(method, element);
PsiParameter parameter = PsiTreeUtil.getParentOfType(element, PsiParameter.class, false);
if (parameter == null) {
parameter =
ContainerUtil.find(method.getParameterList().getParameters(), (param) -> Comparing.strEqual(param.getName(), myParameterName));
}
if (parameter == null) return;
final PsiExpression defToInline;
try {
defToInline = JavaPsiFacade.getElementFactory(project).createExpressionFromText(myValue, parameter);
}
catch (IncorrectOperationException e) {
return;
}
inlineSameParameterValue(method, parameter, defToInline);
final PsiExpression expression = JavaPsiFacade.getElementFactory(project).createExpressionFromText(myValue, parameter);
inlineSameParameterValue(method, parameter, expression);
}
@Override
public @NotNull IntentionPreviewInfo generatePreview(@NotNull Project project, @NotNull ProblemDescriptor previewDescriptor) {
final PsiElement element = previewDescriptor.getPsiElement();
final PsiMethod method = PsiTreeUtil.getParentOfType(element, PsiMethod.class);
if (method == null) return IntentionPreviewInfo.EMPTY;
final PsiParameter parameter = findParameterByName(method);
if (parameter == null) return IntentionPreviewInfo.EMPTY;
Collection<PsiReference> references = ReferencesSearch.search(parameter).findAll();
final PsiExpression defToInline = JavaPsiFacade.getElementFactory(project).createExpressionFromText(myValue, parameter);
references.forEach((ref) -> ref.getElement().replace(defToInline));
parameter.delete();
applyFix(project, previewDescriptor);
return IntentionPreviewInfo.DIFF;
}
private @Nullable PsiParameter findParameter(PsiMethod method, PsiElement descriptorElement) {
PsiParameter parameter = PsiTreeUtil.getParentOfType(descriptorElement, PsiParameter.class, false);
if (parameter == null) parameter = findParameterByName(method);
return parameter;
}
private @Nullable PsiParameter findParameterByName(PsiMethod method) {
return ContainerUtil.find(method.getParameterList().getParameters(), (param) -> Comparing.strEqual(param.getName(), myParameterName));
}
@Override
public boolean startInWriteAction() {
return false;
@@ -302,12 +273,17 @@ public class SameParameterValueInspection extends GlobalJavaBatchInspectionTool
Collection<PsiMethod> methods = new ArrayList<>();
methods.add(method);
Project project = method.getProject();
if (!ProgressManager.getInstance()
.runProcessWithProgressSynchronously(() -> { methods.addAll(OverridingMethodsSearch.search(method).findAll()); },
JavaBundle.message("progress.title.search.for.overriding.methods"), true, project)) {
return;
boolean preview = IntentionPreviewUtils.isIntentionPreviewActive();
if (!preview) {
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(
() -> {
methods.addAll(OverridingMethodsSearch.search(method).findAll());
},
JavaBundle.message("progress.title.search.for.overriding.methods"), true, project)) {
return;
}
if (!CommonRefactoringUtil.checkReadOnlyStatus(project, methods, true)) return;
}
if (!CommonRefactoringUtil.checkReadOnlyStatus(project, methods, true)) return;
int parameterIndex = method.getParameterList().getParameterIndex(parameter);
Map<PsiParameter, Collection<PsiReference>> paramsToInline = new HashMap<>();
@@ -326,29 +302,50 @@ public class SameParameterValueInspection extends GlobalJavaBatchInspectionTool
}
if (!BaseRefactoringProcessor.processConflicts(project, conflicts)) return;
ApplicationManager.getApplication().runWriteAction(() -> {
for (Map.Entry<PsiParameter, Collection<PsiReference>> entry : paramsToInline.entrySet()) {
Collection<PsiReference> refsToInline = entry.getValue();
try {
PsiExpression[] exprs = new PsiExpression[refsToInline.size()];
int idx = 0;
for (PsiReference reference : refsToInline) {
if (reference instanceof PsiJavaCodeReferenceElement) {
exprs[idx++] = CommonJavaInlineUtil.getInstance().inlineVariable(entry.getKey(), defToInline, (PsiJavaCodeReferenceElement)reference, null);
}
if (preview) {
inlineParameters(defToInline, paramsToInline);
final boolean vararg = parameter.isVarArgs();
parameter.delete();
Collection<PsiReference> calls = ReferencesSearch.search(method, new LocalSearchScope(method.getContainingFile())).findAll();
for (PsiReference call : calls) {
PsiElement parent = call.getElement().getParent();
if (parent instanceof PsiMethodCallExpression methodCallExpression) {
PsiExpression[] arguments = methodCallExpression.getArgumentList().getExpressions();
if (vararg) {
methodCallExpression.deleteChildRange(arguments[parameterIndex], arguments[arguments.length - 1]);
}
for (PsiExpression expr : exprs) {
if (expr != null) CommonJavaRefactoringUtil.tryToInlineArrayCreationForVarargs(expr);
else {
arguments[parameterIndex].delete();
}
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
});
}
else {
WriteAction.run(() -> inlineParameters(defToInline, paramsToInline));
removeParameter(method, parameter);
}
}
removeParameter(method, parameter);
private static void inlineParameters(PsiExpression defToInline, Map<PsiParameter, Collection<PsiReference>> paramsToInline) {
for (Map.Entry<PsiParameter, Collection<PsiReference>> entry : paramsToInline.entrySet()) {
Collection<PsiReference> refsToInline = entry.getValue();
try {
PsiExpression[] exprs = new PsiExpression[refsToInline.size()];
int idx = 0;
for (PsiReference reference : refsToInline) {
if (reference instanceof PsiJavaCodeReferenceElement) {
exprs[idx++] = CommonJavaInlineUtil.getInstance().inlineVariable(entry.getKey(), defToInline, (PsiJavaCodeReferenceElement)reference, null);
}
}
for (PsiExpression expr : exprs) {
if (expr != null) CommonJavaRefactoringUtil.tryToInlineArrayCreationForVarargs(expr);
}
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
}
public static void removeParameter(PsiMethod method, PsiParameter parameter) {
@@ -473,8 +470,8 @@ public class SameParameterValueInspection extends GlobalJavaBatchInspectionTool
if (Boolean.FALSE.equals(isFixAvailable) && myGlobal.ignoreWhenRefactoringIsComplicated) return true;
Object presentableValue = value;
if (valueList != null && valueList.size() == 1 && valueList.get(0) == null) presentableValue = valueList.get(0);
ProblemDescriptor descriptor =
myGlobal.registerProblem(holder.getManager(), parameter, presentableValue, Boolean.TRUE.equals(isFixAvailable));
ProblemDescriptor descriptor =
registerProblem(holder.getManager(), parameter, presentableValue, Boolean.TRUE.equals(isFixAvailable));
if (descriptor != null) {
holder.registerProblem(descriptor);
}
@@ -0,0 +1,10 @@
class Simple {
void x() {
System.out.println(10);
}
void y() {
x();
}
}
@@ -0,0 +1,10 @@
class Simple {
void x(int <warning descr="Actual value of parameter 'i' is always '10'"><caret>i</warning>) {
System.out.println(i);
}
void y() {
x(10);
}
}
@@ -0,0 +1,41 @@
// Copyright 2000-2023 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.JavaTestUtil;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.sameParameterValue.SameParameterValueInspection;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* @author Bas Leijdekkers
*/
public final class SameParameterValueQuickFixTest extends LightJavaCodeInsightFixtureTestCase {
public void testSimple() {
doTest();
}
private void doTest() {
doNamedTest(getTestName(false));
}
private void doNamedTest(String name) {
LocalInspectionTool inspection = new SameParameterValueInspection().getSharedLocalInspectionTool();
myFixture.enableInspections(inspection);
myFixture.configureByFile(name + ".java");
myFixture.testHighlighting(true, false, false);
final @NotNull List<IntentionAction> intentions = myFixture.filterAvailableIntentions("Inline value");
assertEquals("intention not found", 1, intentions.size());
myFixture.checkPreviewAndLaunchAction(intentions.get(0));
myFixture.checkResultByFile(name + ".after.java");
}
@Override
protected String getTestDataPath() {
return JavaTestUtil.getJavaTestDataPath() + "/inspection/sameParameterValueQuickFix";
}
}