IDEA-163909 Inspection to replace computeIfAbsent returning a constant with putIfAbsent

This commit is contained in:
Tagir Valeev
2016-11-30 15:15:58 +07:00
parent 43e5739f04
commit d749f3dc5a
15 changed files with 303 additions and 1 deletions
@@ -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);
}
}
}
@@ -0,0 +1,10 @@
// "Use 'putIfAbsent' method without lambda" "true"
import java.util.Map;
class Test {
public void test(Map<String, String> map, String key) {
/*comment in param*/
/*comment in arrow*/
map.putIfAbsent(key, (/*comment in parens*/"empty"));
}
}
@@ -0,0 +1,17 @@
// "Use 'or' method without lambda" "true"
package com.google.common.base;
interface Supplier<T> {
T supply();
}
abstract class Optional<T> {
abstract T or(T value);
abstract T or(Supplier<? extends T> supplier);
}
class Test {
public void test(Optional<String> opt) {
System.out.println(opt.or(""));
}
}
@@ -0,0 +1,8 @@
// "Use 'orElse' method without lambda" "true"
import java.util.*;
class Test {
public String test(List<String> data) {
return data.stream().filter(Objects::nonNull).findFirst().orElse(null);
}
}
@@ -0,0 +1,8 @@
// "Use 'putIfAbsent' method without lambda" "true"
import java.util.Map;
class Test {
public void test(Map<String, String> map, String key) {
map.computeIfAbsent(key, (/*comment in param*/k) /*comment in arrow*/<caret>-> (/*comment in parens*/"empty"));
}
}
@@ -0,0 +1,8 @@
// "Use 'putIfAbsent' method without lambda" "false"
import java.util.Map;
class Test {
public void test(Map<String, String> map, String key) {
map.computeIfAbsent(key, (/*comment in param*/k) /*comment in arrow*/<caret>-> (/*comment in parens*/"empty"+key));
}
}
@@ -0,0 +1,8 @@
// "Use 'putIfAbsent' method without lambda" "false"
import java.util.Map;
class Test {
public String test(Map<String, String> map, String key) {
return map.computeIfAbsent(key, k <caret>-> ("empty"));
}
}
@@ -0,0 +1,17 @@
// "Use 'or' method without lambda" "true"
package com.google.common.base;
interface Supplier<T> {
T supply();
}
abstract class Optional<T> {
abstract T or(T value);
abstract T or(Supplier<? extends T> supplier);
}
class Test {
public void test(Optional<String> opt) {
System.out.println(opt.or(() <caret>-> ""));
}
}
@@ -0,0 +1,17 @@
// "Use 'or' method without lambda" "false"
package com.google.common.base;
interface Supplier<T> {
T supply();
}
abstract class Optional<T> {
abstract T or(T value);
abstract T or(Supplier<? extends T> supplier);
}
class Test {
public void test(Optional<Supplier<String>> opt) {
System.out.println(opt.or(() <caret>-> ""));
}
}
@@ -0,0 +1,8 @@
// "Use 'orElse' method without lambda" "true"
import java.util.*;
class Test {
public String test(List<String> data) {
return data.stream().filter(Objects::nonNull).findFirst().orElseGet((<caret>) -> null);
}
}
@@ -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";
}
}
@@ -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}
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
@@ -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) {
@@ -0,0 +1,11 @@
<html>
<body>
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.
<!-- tooltip end -->
<p>
For example, <code>Optional.orElseGet(() -> null)</code> can be replaced with <code>Optional.orElse(null)</code>.
</p>
<small>New in 2017.1</small>
</body>
</html>
+5
View File
@@ -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"/>
<localInspection groupPath="Java" language="JAVA" shortName="ExcessiveLambdaUsage"
groupBundle="messages.InspectionsBundle"
groupKey="group.names.verbose.or.redundant.code.constructs" enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.ExcessiveLambdaUsageInspection"
displayName="Excessive lambda usage"/>
<localInspection groupPath="Java" language="JAVA" shortName="SimplifyStreamApiCallChains"
groupBundle="messages.InspectionsBundle"
groupKey="group.names.declaration.redundancy" enabledByDefault="true" level="WARNING"