diff --git a/java/java-impl/src/com/intellij/codeInspection/ExcessiveLambdaUsageInspection.java b/java/java-impl/src/com/intellij/codeInspection/ExcessiveLambdaUsageInspection.java new file mode 100644 index 000000000000..f2199bbf8ebb --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/ExcessiveLambdaUsageInspection.java @@ -0,0 +1,142 @@ +/* + * 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. + * 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.openapi.project.Project; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.*; +import com.intellij.psi.util.InheritanceUtil; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; +import com.siyeh.ig.psiutils.CommentTracker; +import com.siyeh.ig.psiutils.ExpressionUtils; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +import java.util.stream.Stream; + +/** + * @author Tagir Valeev + */ +public class ExcessiveLambdaUsageInspection extends BaseJavaBatchLocalInspectionTool { + private static final ExcessiveLambdaInfo[] INFOS = { + new ExcessiveLambdaInfo(CommonClassNames.JAVA_UTIL_MAP, "computeIfAbsent", "putIfAbsent", 1, false), + new ExcessiveLambdaInfo(CommonClassNames.JAVA_UTIL_OPTIONAL, "orElseGet", "orElse", 0, true), + new ExcessiveLambdaInfo("java.util.OptionalInt", "orElseGet", "orElse", 0, true), + new ExcessiveLambdaInfo("java.util.OptionalLong", "orElseGet", "orElse", 0, true), + new ExcessiveLambdaInfo("java.util.OptionalDouble", "orElseGet", "orElse", 0, true), + new ExcessiveLambdaInfo("java.util.OptionalDouble", "orElseGet", "orElse", 0, true), + new ExcessiveLambdaInfo("com.google.common.base.Optional", "or", "or", 0, true), + new ExcessiveLambdaInfo("java.util.Objects", "requireNonNull", "requireNonNull", 1, true) + }; + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + if (!PsiUtil.isLanguageLevel8OrHigher(holder.getFile())) { + return PsiElementVisitor.EMPTY_VISITOR; + } + return new JavaElementVisitor() { + @Override + public void visitLambdaExpression(PsiLambdaExpression lambda) { + PsiElement parent = lambda.getParent(); + if (!(parent instanceof PsiExpressionList)) return; + PsiElement gParent = parent.getParent(); + if (!(gParent instanceof PsiMethodCallExpression)) return; + if (!(lambda.getBody() instanceof PsiExpression)) return; + PsiExpression expr = (PsiExpression)lambda.getBody(); + if (!ExpressionUtils.isSimpleExpression(expr)) return; + if (Stream.of(lambda.getParameterList().getParameters()).anyMatch(param -> ExpressionUtils.isReferenceTo(expr, param))) return; + + for (ExcessiveLambdaInfo info : INFOS) { + if(info.isApplicable((PsiMethodCallExpression)gParent, lambda)) { + holder.registerProblem(lambda, InspectionsBundle.message("inspection.excessive.lambda.message"), + ProblemHighlightType.LIKE_UNUSED_SYMBOL, + new TextRange(0, expr.getTextOffset() - lambda.getTextOffset()), new RemoveExcessiveLambdaFix(info)); + } + } + } + }; + } + + static class RemoveExcessiveLambdaFix implements LocalQuickFix { + private final ExcessiveLambdaInfo myInfo; + + public RemoveExcessiveLambdaFix(ExcessiveLambdaInfo info) { + myInfo = info; + } + + @Nls + @NotNull + @Override + public String getName() { + return InspectionsBundle.message("inspection.excessive.lambda.fix.name", myInfo.myConstantMethod); + } + + @Nls + @NotNull + @Override + public String getFamilyName() { + return InspectionsBundle.message("inspection.excessive.lambda.fix.family.name"); + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PsiElement element = descriptor.getStartElement(); + if(!(element instanceof PsiLambdaExpression)) return; + PsiLambdaExpression lambda = (PsiLambdaExpression)element; + PsiElement body = lambda.getBody(); + if(body == null) return; + PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(lambda, PsiMethodCallExpression.class); + if(call == null) return; + + call.getMethodExpression().handleElementRename(myInfo.myConstantMethod); + CommentTracker ct = new CommentTracker(); + PsiElementFactory factory = JavaPsiFacade.getElementFactory(project); + ct.replaceAndRestoreComments(lambda, factory.createExpressionFromText(ct.text(body), lambda)); + } + } + + static class ExcessiveLambdaInfo { + final String myClass; + final String myLambdaMethod; + final String myConstantMethod; + final int myParameterIndex; + final boolean myCanUseReturnValue; + + ExcessiveLambdaInfo(String aClass, String lambdaMethod, String constantMethod, int index, boolean canUseReturnValue) { + myClass = aClass; + myLambdaMethod = lambdaMethod; + myConstantMethod = constantMethod; + myParameterIndex = index; + myCanUseReturnValue = canUseReturnValue; + } + + boolean isApplicable(PsiMethodCallExpression call, PsiLambdaExpression lambda) { + if(!myLambdaMethod.equals(call.getMethodExpression().getReferenceName())) return false; + if(!myCanUseReturnValue && !(call.getParent() instanceof PsiExpressionStatement)) return false; + PsiExpression[] args = call.getArgumentList().getExpressions(); + if(args.length <= myParameterIndex || args[myParameterIndex] != lambda) return false; + PsiMethod method = call.resolveMethod(); + if(method == null) return false; + PsiParameter[] parameters = method.getParameterList().getParameters(); + if(parameters.length <= myParameterIndex) return false; + PsiClass fnClass = PsiUtil.resolveClassInClassTypeOnly(parameters[myParameterIndex].getType()); + return fnClass != null && LambdaUtil.getFunction(fnClass) != null && + InheritanceUtil.isInheritor(method.getContainingClass(), false, myClass); + } + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/afterComputeIfAbsent.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/afterComputeIfAbsent.java new file mode 100644 index 000000000000..8ccb3b259f29 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/afterComputeIfAbsent.java @@ -0,0 +1,10 @@ +// "Use 'putIfAbsent' method without lambda" "true" +import java.util.Map; + +class Test { + public void test(Map map, String key) { + /*comment in param*/ + /*comment in arrow*/ + map.putIfAbsent(key, (/*comment in parens*/"empty")); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/afterGuavaOptional.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/afterGuavaOptional.java new file mode 100644 index 000000000000..d9776bcbd9c6 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/afterGuavaOptional.java @@ -0,0 +1,17 @@ +// "Use 'or' method without lambda" "true" +package com.google.common.base; + +interface Supplier { + T supply(); +} + +abstract class Optional { + abstract T or(T value); + abstract T or(Supplier supplier); +} + +class Test { + public void test(Optional opt) { + System.out.println(opt.or("")); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/afterOptional.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/afterOptional.java new file mode 100644 index 000000000000..5459c23b0c9a --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/afterOptional.java @@ -0,0 +1,8 @@ +// "Use 'orElse' method without lambda" "true" +import java.util.*; + +class Test { + public String test(List data) { + return data.stream().filter(Objects::nonNull).findFirst().orElse(null); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/beforeComputeIfAbsent.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/beforeComputeIfAbsent.java new file mode 100644 index 000000000000..b52c602c07b2 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/beforeComputeIfAbsent.java @@ -0,0 +1,8 @@ +// "Use 'putIfAbsent' method without lambda" "true" +import java.util.Map; + +class Test { + public void test(Map map, String key) { + map.computeIfAbsent(key, (/*comment in param*/k) /*comment in arrow*/-> (/*comment in parens*/"empty")); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/beforeComputeIfAbsentNonTrivial.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/beforeComputeIfAbsentNonTrivial.java new file mode 100644 index 000000000000..7c5d92a83852 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/beforeComputeIfAbsentNonTrivial.java @@ -0,0 +1,8 @@ +// "Use 'putIfAbsent' method without lambda" "false" +import java.util.Map; + +class Test { + public void test(Map map, String key) { + map.computeIfAbsent(key, (/*comment in param*/k) /*comment in arrow*/-> (/*comment in parens*/"empty"+key)); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/beforeComputeIfAbsentUsedResult.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/beforeComputeIfAbsentUsedResult.java new file mode 100644 index 000000000000..b04715f33856 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/beforeComputeIfAbsentUsedResult.java @@ -0,0 +1,8 @@ +// "Use 'putIfAbsent' method without lambda" "false" +import java.util.Map; + +class Test { + public String test(Map map, String key) { + return map.computeIfAbsent(key, k -> ("empty")); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/beforeGuavaOptional.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/beforeGuavaOptional.java new file mode 100644 index 000000000000..6e63a0f4d595 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/beforeGuavaOptional.java @@ -0,0 +1,17 @@ +// "Use 'or' method without lambda" "true" +package com.google.common.base; + +interface Supplier { + T supply(); +} + +abstract class Optional { + abstract T or(T value); + abstract T or(Supplier supplier); +} + +class Test { + public void test(Optional opt) { + System.out.println(opt.or(() -> "")); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/beforeGuavaOptionalSupplier.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/beforeGuavaOptionalSupplier.java new file mode 100644 index 000000000000..0d994979eb46 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/beforeGuavaOptionalSupplier.java @@ -0,0 +1,17 @@ +// "Use 'or' method without lambda" "false" +package com.google.common.base; + +interface Supplier { + T supply(); +} + +abstract class Optional { + abstract T or(T value); + abstract T or(Supplier supplier); +} + +class Test { + public void test(Optional> opt) { + System.out.println(opt.or(() -> "")); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/beforeOptional.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/beforeOptional.java new file mode 100644 index 000000000000..c102cf7eb615 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage/beforeOptional.java @@ -0,0 +1,8 @@ +// "Use 'orElse' method without lambda" "true" +import java.util.*; + +class Test { + public String test(List data) { + return data.stream().filter(Objects::nonNull).findFirst().orElseGet(() -> null); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/ExcessiveLambdaUsageInspectionTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/ExcessiveLambdaUsageInspectionTest.java new file mode 100644 index 000000000000..b11b41cb8f90 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/ExcessiveLambdaUsageInspectionTest.java @@ -0,0 +1,38 @@ +/* + * 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. + * 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.ExcessiveLambdaUsageInspection; +import com.intellij.codeInspection.LocalInspectionTool; +import org.jetbrains.annotations.NotNull; + + +public class ExcessiveLambdaUsageInspectionTest extends LightQuickFixParameterizedTestCase { + @NotNull + @Override + protected LocalInspectionTool[] configureLocalInspectionTools() { + return new LocalInspectionTool[]{ + new ExcessiveLambdaUsageInspection() + }; + } + + public void test() throws Exception { doAllTests(); } + + @Override + protected String getBasePath() { + return "/codeInsight/daemonCodeAnalyzer/quickFix/excessiveLambdaUsage"; + } +} \ No newline at end of file diff --git a/platform/platform-resources-en/src/messages/InspectionsBundle.properties b/platform/platform-resources-en/src/messages/InspectionsBundle.properties index c30c60911856..1c43cfe52b88 100644 --- a/platform/platform-resources-en/src/messages/InspectionsBundle.properties +++ b/platform/platform-resources-en/src/messages/InspectionsBundle.properties @@ -738,4 +738,8 @@ inspection.tool.window.dialog.title=Inspection Tool Window inspection.tool.window.dialog.no.options=Inspection ''{0}'' has no configurable options inspection.tool.window.inspection.dialog.title=Inspection ''{0}'' options nullable.stuff.inspection.navigate.null.argument.usages.fix.family.name=Navigate to 'null' argument usages -nullable.stuff.inspection.navigate.null.argument.usages.view.name=''null'' argument usages for parameter {0} \ No newline at end of file +nullable.stuff.inspection.navigate.null.argument.usages.view.name=''null'' argument usages for parameter {0} + +inspection.excessive.lambda.message=Excessive lambda usage +inspection.excessive.lambda.fix.family.name=Replace lambda with constant +inspection.excessive.lambda.fix.name=Use ''{0}'' method without lambda diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ExpressionUtils.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ExpressionUtils.java index f29467b3c5c1..e02b0c5d42e1 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ExpressionUtils.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ExpressionUtils.java @@ -699,6 +699,7 @@ public class ExpressionUtils { */ @Contract("null -> false") public static boolean isSimpleExpression(@Nullable PsiExpression expression) { + expression = PsiUtil.skipParenthesizedExprDown(expression); if (expression instanceof PsiLiteralExpression || expression instanceof PsiThisExpression || expression instanceof PsiClassObjectAccessExpression) { diff --git a/resources-en/src/inspectionDescriptions/ExcessiveLambdaUsage.html b/resources-en/src/inspectionDescriptions/ExcessiveLambdaUsage.html new file mode 100644 index 000000000000..8185220e6b88 --- /dev/null +++ b/resources-en/src/inspectionDescriptions/ExcessiveLambdaUsage.html @@ -0,0 +1,11 @@ + + +This inspection warns if trivial lambda expression is used where there's an alternative method which behaves the same way but +accepts concrete value instead of lambda. + +

+ For example, Optional.orElseGet(() -> null) can be replaced with Optional.orElse(null). +

+New in 2017.1 + + \ No newline at end of file diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 58982593c684..2190175682b5 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -840,6 +840,11 @@ groupKey="group.names.language.level.specific.issues.and.migration.aids" enabledByDefault="true" level="WARNING" implementationClass="com.intellij.codeInspection.java18api.Java8MapApiInspection" displayName="Replace with single Map method"/> +