anonymous can be method reference inspection

This commit is contained in:
anna
2012-10-09 20:22:06 +02:00
parent 2e599091fd
commit fced71498b
10 changed files with 401 additions and 110 deletions

View File

@@ -0,0 +1,135 @@
/*
* Copyright 2000-2012 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;
import com.intellij.codeInsight.daemon.GroupNames;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.RedundantCastUtil;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
/**
* User: anna
*/
public class AnonymousCanBeMethodReferenceInspection extends BaseJavaLocalInspectionTool {
public static final Logger LOG = Logger.getInstance("#" + AnonymousCanBeMethodReferenceInspection.class.getName());
@Nls
@NotNull
@Override
public String getGroupDisplayName() {
return GroupNames.LANGUAGE_LEVEL_SPECIFIC_GROUP_NAME;
}
@Nls
@NotNull
@Override
public String getDisplayName() {
return "Anonymous type can be replaced with method reference";
}
@Override
public boolean isEnabledByDefault() {
return true;
}
@NotNull
@Override
public String getShortName() {
return "Anonymous2MethodRef";
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new JavaElementVisitor() {
@Override
public void visitAnonymousClass(PsiAnonymousClass aClass) {
super.visitAnonymousClass(aClass);
if (PsiUtil.getLanguageLevel(aClass).isAtLeast(LanguageLevel.JDK_1_8)) {
final PsiClassType baseClassType = aClass.getBaseClassType();
final String functionalInterfaceErrorMessage = LambdaUtil.checkInterfaceFunctional(baseClassType);
if (functionalInterfaceErrorMessage == null) {
final PsiMethod[] methods = aClass.getMethods();
if (methods.length == 1 && aClass.getFields().length == 0) {
final PsiCodeBlock body = methods[0].getBody();
final PsiCallExpression callExpression =
LambdaCanBeMethReferenceInspection.canBeMethodReferenceProblem(body, methods[0].getParameterList().getParameters());
if (callExpression != null && callExpression.resolveMethod() != methods[0]) {
final PsiElement parent = aClass.getParent();
if (parent instanceof PsiNewExpression) {
final PsiJavaCodeReferenceElement classReference = ((PsiNewExpression)parent).getClassOrAnonymousClassReference();
if (classReference != null) {
holder.registerProblem(classReference,
"Anonymous type can be replaced with method reference", new ReplaceWithMethodRefFix());
}
}
}
}
}
}
}
};
}
private static class ReplaceWithMethodRefFix implements LocalQuickFix {
@NotNull
@Override
public String getName() {
return "Replace with method reference";
}
@NotNull
@Override
public String getFamilyName() {
return getName();
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
final PsiElement element = descriptor.getPsiElement();
final PsiAnonymousClass anonymousClass = PsiTreeUtil.getParentOfType(element, PsiAnonymousClass.class);
if (anonymousClass == null) return;
final PsiMethod[] methods = anonymousClass.getMethods();
if (methods.length != 1) return;
final PsiParameter[] parameters = methods[0].getParameterList().getParameters();
final PsiCallExpression callExpression = LambdaCanBeMethReferenceInspection.canBeMethodReferenceProblem(methods[0].getBody(), parameters);
if (callExpression == null) return;
final String methodRefText =
LambdaCanBeMethReferenceInspection.createMethodReferenceText(callExpression, parameters);
if (methodRefText != null) {
final String canonicalText = anonymousClass.getBaseClassType().getCanonicalText();
final PsiExpression psiExpression = JavaPsiFacade.getElementFactory(project).createExpressionFromText("(" + canonicalText + ")" + methodRefText, anonymousClass);
PsiElement castExpr = anonymousClass.getParent().replace(psiExpression);
if (RedundantCastUtil.isCastRedundant((PsiTypeCastExpression)castExpr)) {
final PsiExpression operand = ((PsiTypeCastExpression)castExpr).getOperand();
LOG.assertTrue(operand != null);
castExpr = castExpr.replace(operand);
}
JavaCodeStyleManager.getInstance(project).shortenClassReferences(castExpr);
}
}
}
}

View File

@@ -25,6 +25,7 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* User: anna
@@ -66,87 +67,148 @@ public class LambdaCanBeMethReferenceInspection extends BaseJavaLocalInspectionT
super.visitLambdaExpression(expression);
if (PsiUtil.getLanguageLevel(expression).isAtLeast(LanguageLevel.JDK_1_8)) {
final PsiElement body = expression.getBody();
PsiCallExpression methodCall = null;
if (body instanceof PsiCallExpression) {
methodCall = (PsiCallExpression)body;
} else if (body instanceof PsiCodeBlock) {
final PsiStatement[] statements = ((PsiCodeBlock)body).getStatements();
if (statements.length == 1) {
if (statements[0] instanceof PsiReturnStatement) {
final PsiExpression returnValue = ((PsiReturnStatement)statements[0]).getReturnValue();
if (returnValue instanceof PsiCallExpression) {
methodCall = (PsiCallExpression)returnValue;
}
} else if (statements[0] instanceof PsiExpressionStatement) {
final PsiExpression expr = ((PsiExpressionStatement)statements[0]).getExpression();
if (expr instanceof PsiCallExpression) {
methodCall = (PsiCallExpression)expr;
}
}
}
}
if (methodCall != null) {
final PsiExpressionList argumentList = methodCall.getArgumentList();
if (argumentList != null) {
final PsiParameter[] parameters = expression.getParameterList().getParameters();
final PsiExpression[] expressions = argumentList.getExpressions();
final PsiMethod psiMethod = methodCall.resolveMethod();
final PsiClass containingClass;
boolean isConstructor;
if (psiMethod == null) {
isConstructor = true;
if (!(methodCall instanceof PsiNewExpression)) return;
final PsiJavaCodeReferenceElement classReference = ((PsiNewExpression)methodCall).getClassOrAnonymousClassReference();
if (classReference == null) return;
containingClass = (PsiClass)classReference.resolve();
} else {
containingClass = psiMethod.getContainingClass();
isConstructor = psiMethod.isConstructor();
}
if (containingClass == null) return;
boolean isReceiverType = parameters.length > 0 && LambdaUtil.isReceiverType(parameters[0].getType(), containingClass, PsiUtil.resolveGenericsClassInType(parameters[0].getType()).getSubstitutor());
final boolean staticOrValidConstructorRef;
if (isConstructor) {
staticOrValidConstructorRef =
(containingClass.getContainingClass() == null || containingClass.hasModifierProperty(PsiModifier.STATIC));
} else {
staticOrValidConstructorRef = psiMethod.hasModifierProperty(PsiModifier.STATIC);
}
final int offset = isReceiverType && !staticOrValidConstructorRef ? 1 : 0;
if (parameters.length != expressions.length + offset) return;
for (int i = 0; i < expressions.length; i++) {
PsiExpression psiExpression = expressions[i];
if (!(psiExpression instanceof PsiReferenceExpression)) return;
final PsiElement resolve = ((PsiReferenceExpression)psiExpression).resolve();
if (resolve == null) return;
if (parameters[i + offset] != resolve) return;
}
if (offset > 0) {
final PsiExpression qualifierExpression;
if (methodCall instanceof PsiMethodCallExpression) {
qualifierExpression = ((PsiMethodCallExpression)methodCall).getMethodExpression().getQualifierExpression();
} else if (methodCall instanceof PsiNewExpression) {
qualifierExpression = ((PsiNewExpression)methodCall).getQualifier();
} else {
qualifierExpression = null;
}
if (!(qualifierExpression instanceof PsiReferenceExpression) || ((PsiReferenceExpression)qualifierExpression).resolve() != parameters[0]) return;
}
holder.registerProblem(methodCall,
"Can be replaced with method reference",
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new ReplaceWithMethodRefFix());
}
final PsiCallExpression callExpression = canBeMethodReferenceProblem(body, expression.getParameterList().getParameters());
if (callExpression != null) {
holder.registerProblem(callExpression,
"Can be replaced with method reference",
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new ReplaceWithMethodRefFix());
}
}
}
};
}
@Nullable
protected static PsiCallExpression canBeMethodReferenceProblem(@Nullable final PsiElement body, final PsiParameter[] parameters) {
PsiCallExpression methodCall = null;
if (body instanceof PsiCallExpression) {
methodCall = (PsiCallExpression)body;
}
else if (body instanceof PsiCodeBlock) {
final PsiStatement[] statements = ((PsiCodeBlock)body).getStatements();
if (statements.length == 1) {
if (statements[0] instanceof PsiReturnStatement) {
final PsiExpression returnValue = ((PsiReturnStatement)statements[0]).getReturnValue();
if (returnValue instanceof PsiCallExpression) {
methodCall = (PsiCallExpression)returnValue;
}
}
else if (statements[0] instanceof PsiExpressionStatement) {
final PsiExpression expr = ((PsiExpressionStatement)statements[0]).getExpression();
if (expr instanceof PsiCallExpression) {
methodCall = (PsiCallExpression)expr;
}
}
}
}
if (methodCall != null) {
final PsiExpressionList argumentList = methodCall.getArgumentList();
if (argumentList != null) {
final PsiExpression[] expressions = argumentList.getExpressions();
final PsiMethod psiMethod = methodCall.resolveMethod();
final PsiClass containingClass;
boolean isConstructor;
if (psiMethod == null) {
isConstructor = true;
if (!(methodCall instanceof PsiNewExpression)) return null;
final PsiJavaCodeReferenceElement classReference = ((PsiNewExpression)methodCall).getClassOrAnonymousClassReference();
if (classReference == null) return null;
containingClass = (PsiClass)classReference.resolve();
}
else {
containingClass = psiMethod.getContainingClass();
isConstructor = psiMethod.isConstructor();
}
if (containingClass == null) return null;
boolean isReceiverType = parameters.length > 0 && LambdaUtil.isReceiverType(parameters[0].getType(), containingClass, PsiUtil
.resolveGenericsClassInType(parameters[0].getType()).getSubstitutor());
final boolean staticOrValidConstructorRef;
if (isConstructor) {
staticOrValidConstructorRef =
(containingClass.getContainingClass() == null || containingClass.hasModifierProperty(PsiModifier.STATIC));
}
else {
staticOrValidConstructorRef = psiMethod.hasModifierProperty(PsiModifier.STATIC);
}
final int offset = isReceiverType && !staticOrValidConstructorRef ? 1 : 0;
if (parameters.length != expressions.length + offset) return null;
for (int i = 0; i < expressions.length; i++) {
PsiExpression psiExpression = expressions[i];
if (!(psiExpression instanceof PsiReferenceExpression)) return null;
final PsiElement resolve = ((PsiReferenceExpression)psiExpression).resolve();
if (resolve == null) return null;
if (parameters[i + offset] != resolve) return null;
}
if (offset > 0) {
final PsiExpression qualifierExpression;
if (methodCall instanceof PsiMethodCallExpression) {
qualifierExpression = ((PsiMethodCallExpression)methodCall).getMethodExpression().getQualifierExpression();
}
else if (methodCall instanceof PsiNewExpression) {
qualifierExpression = ((PsiNewExpression)methodCall).getQualifier();
}
else {
qualifierExpression = null;
}
if (!(qualifierExpression instanceof PsiReferenceExpression) ||
((PsiReferenceExpression)qualifierExpression).resolve() != parameters[0]) {
return null;
}
}
return methodCall;
}
}
return null;
}
@Nullable
protected static String createMethodReferenceText(PsiElement element, final PsiParameter[] parameters) {
String methodRefText = null;
if (element instanceof PsiMethodCallExpression) {
final PsiMethodCallExpression methodCall = (PsiMethodCallExpression)element;
final PsiMethod psiMethod = methodCall.resolveMethod();
LOG.assertTrue(psiMethod != null);
final PsiClass containingClass = psiMethod.getContainingClass();
LOG.assertTrue(containingClass != null);
final PsiReferenceExpression methodExpression = methodCall.getMethodExpression();
final PsiExpression qualifierExpression = methodExpression.getQualifierExpression();
final String methodReferenceName = methodExpression.getReferenceName();
if (qualifierExpression != null) {
boolean isReceiverType = parameters.length > 0 && LambdaUtil.isReceiverType(parameters[0].getType(), containingClass, PsiUtil
.resolveGenericsClassInType(parameters[0].getType()).getSubstitutor());
methodRefText = (isReceiverType ? containingClass.getQualifiedName() : qualifierExpression.getText()) + "::" + methodReferenceName;
}
else {
methodRefText =
(psiMethod.hasModifierProperty(PsiModifier.STATIC) ? containingClass.getQualifiedName() : "this") + "::" + methodReferenceName;
}
}
else if (element instanceof PsiNewExpression) {
final PsiMethod constructor = ((PsiNewExpression)element).resolveConstructor();
if (constructor != null) {
final PsiClass containingClass = constructor.getContainingClass();
LOG.assertTrue(containingClass != null);
methodRefText = containingClass.getQualifiedName() + "::new";
}
else {
final PsiJavaCodeReferenceElement classReference = ((PsiNewExpression)element).getClassOrAnonymousClassReference();
if (classReference != null) {
final JavaResolveResult resolve = classReference.advancedResolve(false);
final PsiElement containingClass = resolve.getElement();
if (containingClass instanceof PsiClass) {
methodRefText = ((PsiClass)containingClass).getQualifiedName() + "::new";
}
}
}
}
return methodRefText;
}
private static class ReplaceWithMethodRefFix implements LocalQuickFix {
@NotNull
@Override
@@ -165,43 +227,11 @@ public class LambdaCanBeMethReferenceInspection extends BaseJavaLocalInspectionT
final PsiElement element = descriptor.getPsiElement();
final PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(element, PsiLambdaExpression.class);
if (lambdaExpression == null) return;
String methodRefText = null;
if (element instanceof PsiMethodCallExpression) {
final PsiMethodCallExpression methodCall = (PsiMethodCallExpression)element;
final PsiMethod psiMethod = methodCall.resolveMethod();
LOG.assertTrue(psiMethod != null);
final PsiClass containingClass = psiMethod.getContainingClass();
LOG.assertTrue(containingClass != null);
final PsiReferenceExpression methodExpression = methodCall.getMethodExpression();
final PsiExpression qualifierExpression = methodExpression.getQualifierExpression();
final String methodReferenceName = methodExpression.getReferenceName();
if (qualifierExpression != null) {
final PsiParameter[] parameters = lambdaExpression.getParameterList().getParameters();
boolean isReceiverType = parameters.length > 0 && LambdaUtil.isReceiverType(parameters[0].getType(), containingClass, PsiUtil.resolveGenericsClassInType(parameters[0].getType()).getSubstitutor());
methodRefText = (isReceiverType ? containingClass.getQualifiedName() : qualifierExpression.getText()) + "::" + methodReferenceName;
} else {
methodRefText = (psiMethod.hasModifierProperty(PsiModifier.STATIC) ? containingClass.getQualifiedName() : "this") + "::" + methodReferenceName;
}
} else if (element instanceof PsiNewExpression) {
final PsiMethod constructor = ((PsiNewExpression)element).resolveConstructor();
if (constructor != null) {
final PsiClass containingClass = constructor.getContainingClass();
LOG.assertTrue(containingClass != null);
methodRefText = containingClass.getQualifiedName() + "::new";
} else {
final PsiJavaCodeReferenceElement classReference = ((PsiNewExpression)element).getClassOrAnonymousClassReference();
if (classReference != null) {
final JavaResolveResult resolve = classReference.advancedResolve(false);
final PsiElement containingClass = resolve.getElement();
if (containingClass instanceof PsiClass) {
methodRefText = ((PsiClass)containingClass).getQualifiedName() + "::new";
}
}
}
}
final String methodRefText = createMethodReferenceText(element, lambdaExpression.getParameterList().getParameters());
if (methodRefText != null) {
final PsiExpression psiExpression = JavaPsiFacade.getElementFactory(project).createExpressionFromText(methodRefText, lambdaExpression);
final PsiExpression psiExpression =
JavaPsiFacade.getElementFactory(project).createExpressionFromText(methodRefText, lambdaExpression);
JavaCodeStyleManager.getInstance(project).shortenClassReferences(lambdaExpression.replace(psiExpression));
}
}

View File

@@ -0,0 +1,13 @@
// "Replace with method reference" "true"
class Test {
interface Bar {
int compare(String o1, String o2);
}
static int c(String o1, String o2) {
return 0;
}
{
Bar bar2 = Test::c;
}
}

View File

@@ -0,0 +1,14 @@
// "Replace with method reference" "true"
class Test {
interface I {}
interface Bar extends I {
int compare(String o1, String o2);
}
static int c(String o1, String o2) {
return 0;
}
{
I bar2 = (Bar) Test::c;
}
}

View File

@@ -0,0 +1,18 @@
// "Replace with method reference" "true"
class Test {
interface Bar {
int compare(String o1, String o2);
}
static int c(String o1, String o2) {
return 0;
}
{
Bar bar2 = new B<caret>ar() {
@Override
public int compare(final String o1, final String o2) {
return c(o1, o2);
}
};
}
}

View File

@@ -0,0 +1,19 @@
// "Replace with method reference" "true"
class Test {
interface I {}
interface Bar extends I {
int compare(String o1, String o2);
}
static int c(String o1, String o2) {
return 0;
}
{
I bar2 = new B<caret>ar() {
@Override
public int compare(final String o1, final String o2) {
return c(o1, o2);
}
};
}
}

View File

@@ -0,0 +1,15 @@
// "Replace with method reference" "false"
class Test {
public interface I {
int m();
}
{
I i = new <caret>I() {
@Override
public int m() {
return m();
}
};
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2000-2012 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.codeInsight.daemon.quickFix;
import com.intellij.codeInspection.AnonymousCanBeMethodReferenceInspection;
import com.intellij.codeInspection.LocalInspectionTool;
public class Anonymous2MethodReferenceInspectionTest extends LightQuickFixTestCase {
@Override
protected LocalInspectionTool[] configureLocalInspectionTools() {
return new LocalInspectionTool[]{
new AnonymousCanBeMethodReferenceInspection(),
};
}
public void test() throws Exception { doAllTests(); }
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/anonymous2methodReference";
}
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
This inspection reports anonymous types which can be replaced with method references
<p>
Method references syntax is not supported under Java 1.7 or earlier JVMs.
</body>
</html>

View File

@@ -540,6 +540,9 @@
<localInspection language="JAVA" shortName="Convert2Lambda" displayName="Anonymous type can be replaced with lambda"
groupName="Java language level migration aids" enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.AnonymousCanBeLambdaInspection" />
<localInspection language="JAVA" shortName="Anonymous2MethodRef" displayName="Anonymous type can be replaced with method reference"
groupName="Java language level migration aids" enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.AnonymousCanBeMethodReferenceInspection" />
<localInspection language="JAVA" shortName="Convert2MethodRef" displayName="Lambda can be replaced with method reference"
groupName="Java language level migration aids" enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.LambdaCanBeMethReferenceInspection" />