Java inspection: convert the intention for "Replace method reference with lambda" into an INFORMATION-level inspection

This commit is contained in:
Pavel Dolgov
2016-06-23 12:08:34 +03:00
parent 1fa4233a00
commit 72e58a3abc
60 changed files with 194 additions and 123 deletions
@@ -2551,6 +2551,10 @@
key="replace.lambda.with.anonymous.name" groupBundle="messages.InspectionsBundle"
groupKey="group.names.code.style.issues" enabledByDefault="true" level="INFORMATION"
implementationClass="com.siyeh.ig.style.ReplaceLambdaWithAnonymousInspection"/>
<localInspection groupPath="Java" language="JAVA" shortName="ReplaceMethodRefWithLambda" bundle="com.siyeh.InspectionGadgetsBundle"
key="replace.method.ref.with.lambda.name" groupBundle="messages.InspectionsBundle"
groupKey="group.names.code.style.issues" enabledByDefault="true" level="INFORMATION"
implementationClass="com.siyeh.ig.style.ReplaceMethodRefWithLambdaInspection"/>
<!--group.names.threading.issues-->
<localInspection groupPath="Java" language="JAVA" suppressId="AccessToNonThreadSafeStaticField" shortName="AccessToNonThreadSafeStaticFieldFromInstance"
@@ -2184,4 +2184,6 @@ infer.lambda.parameter.type.descriptor=Expand lambda to {0} -> '{'...'}'
replace.diamond.with.explicit.type.arguments.name=Replace diamond with explicit type arguments
replace.diamond.with.explicit.type.arguments.descriptor=Replace '<>' with explicit type arguments
replace.lambda.with.anonymous.name=Replace lambda with anonymous class
replace.lambda.with.anonymous.descriptor=Replace lambda with anonymous class
replace.lambda.with.anonymous.descriptor=Replace lambda with anonymous class
replace.method.ref.with.lambda.name=Replace method reference with lambda
replace.method.ref.with.lambda.descriptor=Replace method reference with lambda
@@ -236,14 +236,14 @@ public class ReplaceLambdaWithAnonymousInspection extends BaseInspection {
@NotNull
@Override
public String getName() {
return InspectionGadgetsBundle.message("replace.lambda.with.anonymous.name");
return InspectionGadgetsBundle.message("replace.lambda.with.anonymous.descriptor");
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return InspectionGadgetsBundle.message("replace.lambda.with.anonymous.descriptor");
return InspectionGadgetsBundle.message("replace.lambda.with.anonymous.name");
}
@Override
@@ -0,0 +1,166 @@
/*
* Copyright 2011 Bas Leijdekkers
*
* 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.siyeh.ig.style;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.ide.DataManager;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.AsyncResult;
import com.intellij.openapi.util.Factory;
import com.intellij.psi.*;
import com.intellij.refactoring.util.LambdaRefactoringUtil;
import com.intellij.util.Consumer;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.BaseInspection;
import com.siyeh.ig.BaseInspectionVisitor;
import com.siyeh.ig.InspectionGadgetsFix;
import com.siyeh.ig.psiutils.SideEffectChecker;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
public class ReplaceMethodRefWithLambdaInspection extends BaseInspection {
private static final Logger LOG = Logger.getInstance("#" + ReplaceMethodRefWithLambdaInspection.class.getName());
@Nls
@NotNull
@Override
public String getDisplayName() {
return InspectionGadgetsBundle.message("replace.method.ref.with.lambda.name");
}
@NotNull
@Override
protected String buildErrorString(Object... infos) {
return InspectionGadgetsBundle.message("replace.method.ref.with.lambda.descriptor");
}
@Override
public BaseInspectionVisitor buildVisitor() {
return new MethodRefToLambdaVisitor();
}
@Nullable
@Override
protected InspectionGadgetsFix buildFix(Object... infos) {
if (infos.length == 1) {
final Object info = infos[0];
if (info instanceof FixFactory) {
return ((FixFactory)info).create();
}
}
return null;
}
private static class MethodRefToLambdaVisitor extends BaseInspectionVisitor {
@Override
public void visitMethodReferenceExpression(PsiMethodReferenceExpression methodReferenceExpression) {
super.visitMethodReferenceExpression(methodReferenceExpression);
final PsiType interfaceType = methodReferenceExpression.getFunctionalInterfaceType();
if (interfaceType != null &&
LambdaUtil.getFunctionalInterfaceMethod(interfaceType) != null &&
methodReferenceExpression.resolve() != null) {
registerError(methodReferenceExpression, getFixFactory(isWithSideEffects(methodReferenceExpression), isOnTheFly()));
}
}
private static FixFactory getFixFactory(boolean withSideEffects, boolean onTheFly) {
if (!withSideEffects) return MethodRefToLambdaFix::new;
if (onTheFly || ApplicationManager.getApplication().isUnitTestMode()) return SideEffectsMethodRefToLambdaFix::new;
return null;
}
private static boolean isWithSideEffects(PsiMethodReferenceExpression methodReferenceExpression) {
final PsiExpression qualifierExpression = methodReferenceExpression.getQualifierExpression();
if (qualifierExpression != null) {
final List<PsiElement> sideEffects = new ArrayList<PsiElement>();
SideEffectChecker.checkSideEffects(qualifierExpression, sideEffects);
return !sideEffects.isEmpty();
}
return false;
}
}
private static class MethodRefToLambdaFix extends InspectionGadgetsFix {
@Nls
@NotNull
@Override
public String getName() {
return InspectionGadgetsBundle.message("replace.method.ref.with.lambda.descriptor");
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return InspectionGadgetsBundle.message("replace.method.ref.with.lambda.name");
}
@Override
protected void doFix(Project project, ProblemDescriptor descriptor) {
final PsiElement element = descriptor.getPsiElement();
if (element instanceof PsiMethodReferenceExpression) {
doFix(project, (PsiMethodReferenceExpression)element);
}
}
protected void doFix(Project project, @NotNull PsiMethodReferenceExpression methodReferenceExpression) {
LambdaRefactoringUtil.convertMethodReferenceToLambda(methodReferenceExpression, false, true);
}
}
private static class SideEffectsMethodRefToLambdaFix extends MethodRefToLambdaFix {
@Override
public boolean startInWriteAction() {
return false;
}
@Override
protected void doFix(Project project, @NotNull PsiMethodReferenceExpression methodReferenceExpression) {
final AsyncResult<DataContext> contextFromFocus = DataManager.getInstance().getDataContextFromFocus();
contextFromFocus.doWhenDone((Consumer<DataContext>)context -> {
final Editor editor = CommonDataKeys.EDITOR.getData(context);
if (editor != null) {
CommandProcessor.getInstance()
.executeCommand(project, () -> doFixAndRemoveSideEffects(editor, methodReferenceExpression), getFamilyName(), null);
}
});
}
private static void doFixAndRemoveSideEffects(@NotNull Editor editor, @NotNull PsiMethodReferenceExpression methodReferenceExpression) {
if (!FileModificationService.getInstance().preparePsiElementsForWrite(methodReferenceExpression)) return;
final PsiLambdaExpression lambdaExpression =
WriteAction.compute(() -> LambdaRefactoringUtil.convertMethodReferenceToLambda(methodReferenceExpression, false, true));
if (lambdaExpression != null) {
LambdaRefactoringUtil.removeSideEffectsFromLambdaBody(editor, lambdaExpression);
}
}
}
private interface FixFactory extends Factory<InspectionGadgetsFix> {
}
}
@@ -0,0 +1,5 @@
<html>
<body>
This inspection expands method reference to lambda expression
</body>
</html>
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
* Copyright 2000-2016 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.
@@ -13,23 +13,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.siyeh.ipp.types;
package com.siyeh.ig.fixes.style;
import com.intellij.testFramework.LightProjectDescriptor;
import com.siyeh.IntentionPowerPackBundle;
import com.siyeh.ipp.IPPTestCase;
import org.jetbrains.annotations.NotNull;
import com.intellij.openapi.roots.ModuleRootModificationUtil;
import com.intellij.testFramework.IdeaTestUtil;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.IGQuickFixesTestCase;
import com.siyeh.ig.style.ReplaceMethodRefWithLambdaInspection;
public class ReplaceMethodReferenceWithLambdaIntentionTest extends IPPTestCase {
public class ReplaceMethodReferenceWithLambdaFixTest extends IGQuickFixesTestCase {
@Override
protected String getIntentionName() {
return IntentionPowerPackBundle.message("replace.method.ref.with.lambda.intention.name");
protected void setUp() throws Exception {
super.setUp();
ModuleRootModificationUtil.setModuleSdk(myModule, IdeaTestUtil.getMockJdk18());
myFixture.enableInspections(new ReplaceMethodRefWithLambdaInspection());
myDefaultHint = InspectionGadgetsBundle.message("replace.method.ref.with.lambda.descriptor");
}
@Override
protected String getRelativePath() {
return "types/methodRefs2lambda";
return "style/methodRefs2lambda";
}
public void testRedundantCast() throws Exception {
@@ -127,10 +130,4 @@ public class ReplaceMethodReferenceWithLambdaIntentionTest extends IPPTestCase {
public void testPreserveExpressionQualifier() throws Exception {
doTest();
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return JAVA_8;
}
}
@@ -259,12 +259,6 @@
<categoryKey>intention.category.declaration</categoryKey>
</intentionAction>
<intentionAction>
<className>com.siyeh.ipp.types.ReplaceMethodRefWithLambdaIntention</className>
<bundleName>com.siyeh.IntentionPowerPackBundle</bundleName>
<categoryKey>intention.category.declaration</categoryKey>
</intentionAction>
<intentionAction>
<className>com.siyeh.ipp.decls.ChangeVariableTypeToRhsTypeIntention</className>
<bundleName>com.siyeh.IntentionPowerPackBundle</bundleName>
@@ -162,8 +162,6 @@ split.try.with.multiple.resources.intention.name=Split 'try' statement with mult
split.try.with.multiple.resources.intention.family.name=Split Try Statement with Multiple Resources
obscure.thrown.exceptions.intention.family.name=Replace Exceptions in Throws Clause with Single More General Exception
add.array.creation.expression.intention.family.name=Add Array Creation Expression
replace.method.ref.with.lambda.intention.name=Replace method reference with lambda
replace.method.ref.with.lambda.intention.family.name=Replace method reference with lambda
make.public.intention.name=Make 'public'
make.public.intention.family.name=Make Public
make.package.private.intention.name=Make package-private
@@ -1,70 +0,0 @@
/*
* Copyright 2011 Bas Leijdekkers
*
* 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.siyeh.ipp.types;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.util.LambdaRefactoringUtil;
import com.siyeh.ipp.base.Intention;
import com.siyeh.ipp.base.PsiElementPredicate;
import org.jetbrains.annotations.NotNull;
public class ReplaceMethodRefWithLambdaIntention extends Intention {
private static final Logger LOG = Logger.getInstance("#" + ReplaceMethodRefWithLambdaIntention.class.getName());
@NotNull
@Override
protected PsiElementPredicate getElementPredicate() {
return new MethodRefPredicate();
}
@Override
protected void processIntention(@NotNull PsiElement element) {}
@Override
public boolean startInWriteAction() {
return false;
}
@Override
protected void processIntention(final Editor editor, @NotNull PsiElement element) {
PsiMethodReferenceExpression ref = PsiTreeUtil.getParentOfType(element, PsiMethodReferenceExpression.class);
PsiLambdaExpression expr = ref != null
? WriteAction.compute(() -> LambdaRefactoringUtil.convertMethodReferenceToLambda(ref, false, true))
: null;
if (expr == null) return;
LambdaRefactoringUtil.removeSideEffectsFromLambdaBody(editor, expr);
}
private static class MethodRefPredicate implements PsiElementPredicate {
@Override
public boolean satisfiedBy(PsiElement element) {
final PsiMethodReferenceExpression methodReferenceExpression = PsiTreeUtil.getParentOfType(element, PsiMethodReferenceExpression.class);
if (methodReferenceExpression != null) {
final PsiType interfaceType = methodReferenceExpression.getFunctionalInterfaceType();
if (interfaceType != null &&
LambdaUtil.getFunctionalInterfaceMethod(interfaceType) != null &&
methodReferenceExpression.resolve() != null) {
return true;
}
}
return false;
}
}
}
@@ -1,10 +0,0 @@
class X {
interface I {
void _();
}
static void m() {}
{
I i = () -> m();
}
}
@@ -1,10 +0,0 @@
class X {
interface I {
void _();
}
static void m() {}
{
I i = <spot>X :: m</spot>;
}
}
@@ -1,5 +0,0 @@
<html>
<body>
This intention expands method reference to lambda expression
</body>
</html>