quick fix for deprecation inspection: replace method call with suggested in javadoc if no red code produced IDEA-130816

This commit is contained in:
Dmitry Batkovich
2017-09-18 19:42:50 +03:00
parent f37223b2f0
commit 583b83e553
12 changed files with 374 additions and 4 deletions
@@ -19,9 +19,7 @@ import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.daemon.JavaErrorMessages;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightMessageUtil;
import com.intellij.codeInsight.daemon.impl.analysis.JavaHighlightUtil;
import com.intellij.codeInspection.BaseJavaBatchLocalInspectionTool;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.openapi.application.ApplicationManager;
@@ -31,7 +29,14 @@ import com.intellij.psi.*;
import com.intellij.psi.impl.JavaConstantExpressionEvaluator;
import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.infos.MethodCandidateInfo;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.javadoc.PsiDocTag;
import com.intellij.psi.javadoc.PsiInlineDocTag;
import com.intellij.psi.util.*;
import com.intellij.refactoring.util.RefactoringChangeUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ObjectUtils;
import one.util.streamex.MoreCollectors;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -275,7 +280,16 @@ abstract class DeprecationInspectionBase extends BaseJavaBatchLocalInspectionToo
String description = JavaErrorMessages.message(forRemoval ? "marked.for.removal.symbol" : "deprecated.symbol",
HighlightMessageUtil.getSymbolName(refElement, PsiSubstitutor.EMPTY));
holder.registerProblem(elementToHighlight, getDescription(description, forRemoval, highlightType), highlightType, rangeInElement);
LocalQuickFix quickFix = null;
PsiMethodCallExpression methodCall = getMethodCall(elementToHighlight);
if (refElement instanceof PsiMethod && methodCall != null) {
PsiMethod replacement = findReplacementInJavaDoc((PsiMethod)refElement, methodCall);
if (replacement != null) {
quickFix = new ReplaceMethodCallFix((PsiMethodCallExpression)elementToHighlight.getParent().getParent(), replacement);
}
}
holder.registerProblem(elementToHighlight, getDescription(description, forRemoval, highlightType), highlightType, rangeInElement, quickFix);
}
private static boolean isMarkedForRemoval(PsiModifierListOwner element, boolean forRemoval) {
@@ -326,4 +340,80 @@ abstract class DeprecationInspectionBase extends BaseJavaBatchLocalInspectionToo
}
return description;
}
private static PsiMethod findReplacementInJavaDoc(@NotNull PsiMethod method, @NotNull PsiMethodCallExpression call) {
if (method instanceof PsiConstructorCall) return null;
PsiDocComment doc = method.getDocComment();
if (doc == null) return null;
PsiDocTag[] docTags = PsiTreeUtil.getChildrenOfType(doc, PsiInlineDocTag.class);
PsiDocTag[] tags = doc.getTags();
PsiDocTag[] allTags = docTags == null ? tags : ArrayUtil.mergeArrays(docTags, tags);
if (allTags.length == 0) return null;
PsiMethod tagMethod = (PsiMethod)Arrays
.stream(allTags)
.filter(t -> {
String name = t.getName();
return "link".equals(name) || "see".equals(name);
})
.collect(MoreCollectors.onlyOne())
.map(tag -> tag.getValueElement())
.map(value -> value.getReference())
.map(reference -> reference.resolve())
.filter(resolved -> resolved instanceof PsiMethod)
.orElse(null);
return tagMethod == null || tagMethod.isDeprecated() || tagMethod.isEquivalentTo(method) || !areReplaceable(method, tagMethod, call)
? null
: tagMethod;
}
private static boolean areReplaceable(@NotNull PsiMethod initial,
@NotNull PsiMethod suggestedReplacement,
@NotNull PsiMethodCallExpression call) {
if (!PsiResolveHelper.SERVICE.getInstance(call.getProject()).isAccessible(suggestedReplacement, call, null)) {
return false;
}
boolean isInitialStatic = initial.hasModifierProperty(PsiModifier.STATIC);
boolean isSuggestedStatic = suggestedReplacement.hasModifierProperty(PsiModifier.STATIC);
if (isInitialStatic && !isSuggestedStatic) {
return false;
}
if (!isInitialStatic && !isSuggestedStatic && !InheritanceUtil.isInheritorOrSelf(getQualifierClass(call), suggestedReplacement.getContainingClass(), true)) {
return false;
}
String qualifierText;
if (isInitialStatic) {
qualifierText = ObjectUtils.notNull(suggestedReplacement.getContainingClass()).getQualifiedName() + ".";
} else {
PsiExpression qualifierExpression = call.getMethodExpression().getQualifierExpression();
qualifierText = qualifierExpression == null ? "" : (qualifierExpression.getText() + ".");
}
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(initial.getProject());
PsiExpressionList arguments = call.getArgumentList();
PsiMethodCallExpression suggestedCall = (PsiMethodCallExpression)elementFactory
.createExpressionFromText(qualifierText + suggestedReplacement.getName() + arguments.getText(), call);
MethodCandidateInfo result = ObjectUtils.tryCast(suggestedCall.resolveMethodGenerics(), MethodCandidateInfo.class);
return result != null && result.isApplicable();
}
@Nullable
private static PsiClass getQualifierClass(@NotNull PsiMethodCallExpression call) {
PsiExpression expression = call.getMethodExpression().getQualifierExpression();
if (expression == null) {
return RefactoringChangeUtil.getThisClass(call);
}
return PsiUtil.resolveClassInType(expression.getType());
}
@Nullable
private static PsiMethodCallExpression getMethodCall(@NotNull PsiElement element) {
if (!(element instanceof PsiIdentifier)) return null;
PsiElement parent = element.getParent();
if (!(parent instanceof PsiReferenceExpression)) return null;
return ObjectUtils.tryCast(parent.getParent(), PsiMethodCallExpression.class);
}
}
@@ -0,0 +1,63 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInspection.deprecation
import com.intellij.codeInspection.LocalQuickFixOnPsiElement
import com.intellij.openapi.project.Project
import com.intellij.psi.*
import com.intellij.psi.codeStyle.JavaCodeStyleManager
import com.intellij.psi.util.PsiFormatUtil
import com.intellij.psi.util.PsiFormatUtilBase
import com.intellij.util.ObjectUtils
import org.jetbrains.annotations.Nls
internal class ReplaceMethodCallFix(expr: PsiMethodCallExpression, replacementMethod: PsiMethod) : LocalQuickFixOnPsiElement(expr) {
private val myReplacementMethodPointer =
SmartPointerManager.getInstance(replacementMethod.project).createSmartPsiElementPointer(replacementMethod)
private val myReplacementText =
PsiFormatUtil.formatMethod(replacementMethod, PsiSubstitutor.EMPTY, PsiFormatUtilBase.SHOW_CONTAINING_CLASS or PsiFormatUtilBase.SHOW_NAME, 0)
override fun getText(): String {
return "Replace method call with " + myReplacementText
}
@Nls
override fun getFamilyName(): String {
return "Replace Method Call"
}
override fun invoke(project: Project, file: PsiFile, startElement: PsiElement, endElement: PsiElement) {
val expr = ObjectUtils.tryCast(startElement, PsiMethodCallExpression::class.java) ?: return
val replacementMethod = myReplacementMethodPointer.element ?: return
val qualifierExpression = expr.methodExpression.qualifierExpression
val isReplacementStatic = replacementMethod.hasModifierProperty(PsiModifier.STATIC)
val qualifierText = if (qualifierExpression != null && !isReplacementStatic) {
qualifierExpression.text + "."
}
else if (isReplacementStatic) {
replacementMethod.containingClass!!.qualifiedName!! + "."
}
else {
""
}
val elementFactory = JavaPsiFacade.getElementFactory(project)
val newMethodCall = elementFactory.createExpressionFromText(qualifierText + replacementMethod.name + expr.argumentList.text, expr)
val replaced = expr.replace(newMethodCall) as PsiMethodCallExpression
JavaCodeStyleManager.getInstance(project).shortenClassReferences(replaced.methodExpression)
}
}
@@ -0,0 +1,19 @@
// "Replace method call with Test.nnn" "true"
class Test<T> {
static void example() {
Test<String> t = new Test<>();
t.nnn("");
}
/**
* {@link Test#nnn(Object)}
*
*/
@Deprecated
void mmm(T t) {
}
void nnn(T t) {
}
}
@@ -0,0 +1,23 @@
// "Replace method call with Test2.nnn" "true"
class Test extends Test2 {
static void example() {
Test t = new Test();
t.nnn("");
}
/**
* {@link Test2#nnn(String)}
*
*/
@Deprecated
void mmm(String t) {
}
}
class Test2 {
void nnn(String t) {
}
}
@@ -0,0 +1,18 @@
// "Replace method call with Test.nnn" "true"
public class Test {
void foo() {
nnn("");
}
/**
* {@link Test#nnn(CharSequence)}
*
*/
@Deprecated
void mmm(String s) {
}
void nnn(CharSequence ss) {
}
}
@@ -0,0 +1,19 @@
// "Replace method call with Test.mmm1" "true"
class Test {
static void example() {
Test t = new Test();
Test.mmm1("");
}
/**
* {@link Test#mmm1(String)}
*
*/
@Deprecated
void mmm(String t) {
}
static void mmm1(String string) {
}
}
@@ -0,0 +1,19 @@
// "Replace method call with Test.nnn" "true"
class Test<T> {
static void example() {
Test<String> t = new Test<>();
t.m<caret>mm("");
}
/**
* {@link Test#nnn(Object)}
*
*/
@Deprecated
void mmm(T t) {
}
void nnn(T t) {
}
}
@@ -0,0 +1,23 @@
// "Replace method call with Test2.nnn" "true"
class Test extends Test2 {
static void example() {
Test t = new Test();
t.m<caret>mm("");
}
/**
* {@link Test2#nnn(String)}
*
*/
@Deprecated
void mmm(String t) {
}
}
class Test2 {
void nnn(String t) {
}
}
@@ -0,0 +1,23 @@
// "Replace method call with Test2.nnn" "false"
class Test {
static void example() {
Test t = new Test();
t.m<caret>mm("");
}
/**
* {@link Test2#nnn(String)}
*
*/
@Deprecated
void mmm(String t) {
}
}
class Test2 {
void nnn(String t) {
}
}
@@ -0,0 +1,18 @@
// "Replace method call with Test.nnn" "true"
public class Test {
void foo() {
m<caret>mm("");
}
/**
* {@link Test#nnn(CharSequence)}
*
*/
@Deprecated
void mmm(String s) {
}
void nnn(CharSequence ss) {
}
}
@@ -0,0 +1,19 @@
// "Replace method call with Test.mmm1" "true"
class Test {
static void example() {
Test t = new Test();
t.m<caret>mm("");
}
/**
* {@link Test#mmm1(String)}
*
*/
@Deprecated
void mmm(String t) {
}
static void mmm1(String string) {
}
}
@@ -0,0 +1,36 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.java.codeInspection;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.deprecation.DeprecationInspection;
import org.jetbrains.annotations.NotNull;
public class DeprecationInspectionFixTest extends LightQuickFixParameterizedTestCase {
@NotNull
@Override
protected LocalInspectionTool[] configureLocalInspectionTools() {
return new LocalInspectionTool[]{new DeprecationInspection()};
}
public void test() { doAllTests(); }
@Override
protected String getBasePath() {
return "/inspection/deprecation/quickFix";
}
}