Quick-fix to insert .filter(Objects::nonNull) (IDEA-176699)

This commit is contained in:
Tagir Valeev
2017-08-08 12:10:18 +07:00
parent 82eac480cf
commit a454ab1c33
17 changed files with 411 additions and 2 deletions

View File

@@ -265,6 +265,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
if (qualifier == null || expression == null) return fixes;
try {
ContainerUtil.addIfNotNull(fixes, createAddStreamFilterNotNullFix(qualifier));
if (isVolatileFieldReference(qualifier)) {
ContainerUtil.addIfNotNull(fixes, createIntroduceVariableFix(qualifier));
}
@@ -318,6 +319,10 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
return null;
}
protected LocalQuickFix createAddStreamFilterNotNullFix(PsiElement reference) {
return null;
}
protected void addSurroundWithIfFix(PsiExpression qualifier, List<LocalQuickFix> fixes, boolean onTheFly) {
}
@@ -336,7 +341,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
for (PsiElement element : visitor.getProblems(NullabilityProblem.callNPE)) {
if (reportedAnchors.add(element)) {
if (element instanceof PsiMethodReferenceExpression) {
holder.registerProblem(element, InspectionsBundle.message("dataflow.message.npe.methodref.invocation"));
holder.registerProblem(element, InspectionsBundle.message("dataflow.message.npe.methodref.invocation"), createAddStreamFilterNotNullFix(element));
}
else {
reportCallMayProduceNpe(holder, (PsiMethodCallExpression)element, holder.isOnTheFly());
@@ -712,7 +717,9 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
if (!reportedAnchors.add(expr)) continue;
if (expr.getParent() instanceof PsiMethodReferenceExpression) {
holder.registerProblem(expr.getParent(), InspectionsBundle.message("dataflow.message.passing.nullable.argument.methodref"));
PsiMethodReferenceExpression methodRef = (PsiMethodReferenceExpression)expr.getParent();
holder.registerProblem(methodRef, InspectionsBundle.message("dataflow.message.passing.nullable.argument.methodref"),
createAddStreamFilterNotNullFix(methodRef));
}
else {
final String text = isNullLiteralExpression(expr)

View File

@@ -0,0 +1,106 @@
/*
* 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;
import com.intellij.codeInsight.intention.HighPriorityAction;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.SuggestedNameInfo;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ArrayUtil;
import com.siyeh.ig.psiutils.StreamApiUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.util.ObjectUtils.tryCast;
public class StreamFilterNotNullFix implements LocalQuickFix, HighPriorityAction {
@Override
@NotNull
public String getFamilyName() {
return InspectionsBundle.message("inspection.data.flow.filter.notnull.quickfix");
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiFunctionalExpression function = findFunction(descriptor.getStartElement());
if (function == null) return;
PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(function, PsiMethodCallExpression.class);
if (call == null) return;
PsiExpression qualifier = call.getMethodExpression().getQualifierExpression();
if (qualifier == null) return;
String name = suggestVariableName(function, qualifier);
// We create first lambda, then convert to method reference as user code style might be set to prefer lambdas
PsiExpression replacement = JavaPsiFacade.getElementFactory(project)
.createExpressionFromText(qualifier.getText() + ".filter(" + name + "->" + name + "!=null)", qualifier);
PsiMethodCallExpression result = (PsiMethodCallExpression)qualifier.replace(replacement);
LambdaCanBeMethodReferenceInspection.replaceAllLambdasWithMethodReferences(result.getArgumentList());
}
@NotNull
private static String suggestVariableName(@NotNull PsiFunctionalExpression function, @NotNull PsiExpression qualifier) {
String name = null;
if (function instanceof PsiLambdaExpression) {
PsiParameter parameter = ArrayUtil.getFirstElement(((PsiLambdaExpression)function).getParameterList().getParameters());
if (parameter != null) {
name = parameter.getName();
}
}
PsiType type = StreamApiUtil.getStreamElementType(qualifier.getType());
JavaCodeStyleManager javaCodeStyleManager = JavaCodeStyleManager.getInstance(function.getProject());
SuggestedNameInfo info = javaCodeStyleManager.suggestVariableName(VariableKind.PARAMETER, name, null, type, true);
name = ArrayUtil.getFirstElement(info.names);
return javaCodeStyleManager.suggestUniqueVariableName(name == null ? "obj" : name, qualifier, false);
}
@Nullable
private static PsiFunctionalExpression findFunction(PsiElement reference) {
if (reference instanceof PsiFunctionalExpression) {
return (PsiFunctionalExpression)reference;
}
if (reference instanceof PsiIdentifier) {
// in "str.trim()" go from "trim" to "str"
reference = reference.getParent();
if (reference instanceof PsiReferenceExpression) {
reference = PsiUtil.skipParenthesizedExprDown(((PsiReferenceExpression)reference).getQualifierExpression());
}
}
if (reference instanceof PsiReferenceExpression) {
PsiParameter parameter = tryCast(((PsiReferenceExpression)reference).resolve(), PsiParameter.class);
if (parameter == null) return null;
PsiParameterList parameterList = tryCast(parameter.getParent(), PsiParameterList.class);
if (parameterList == null || parameterList.getParametersCount() != 1) return null;
return tryCast(parameterList.getParent(), PsiLambdaExpression.class);
}
return null;
}
public static StreamFilterNotNullFix makeFix(PsiElement reference) {
PsiFunctionalExpression fn = findFunction(reference);
if (fn == null) return null;
PsiExpressionList args = tryCast(PsiUtil.skipParenthesizedExprUp(fn.getParent()), PsiExpressionList.class);
if (args == null || args.getExpressions().length != 1) return null;
PsiMethodCallExpression call = tryCast(args.getParent(), PsiMethodCallExpression.class);
if (call == null) return null;
PsiExpression qualifier = call.getMethodExpression().getQualifierExpression();
if (qualifier == null || !InheritanceUtil.isInheritor(qualifier.getType(), CommonClassNames.JAVA_UTIL_STREAM_STREAM)) return null;
return new StreamFilterNotNullFix();
}
}

View File

@@ -66,6 +66,11 @@ public class DataFlowInspection extends DataFlowInspectionBase {
return new ReplaceWithTrivialLambdaFix(value);
}
@Override
protected LocalQuickFix createAddStreamFilterNotNullFix(PsiElement reference) {
return StreamFilterNotNullFix.makeFix(reference);
}
@Override
protected LocalQuickFix createIntroduceVariableFix(final PsiExpression expression) {
return new IntroduceVariableFix(true);

View File

@@ -0,0 +1,21 @@
// "Insert 'filter(Objects::nonNull)' step" "true"
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Objects;
class MyClass {
@Nullable String convert(String s) {
return s;
}
String accept(@NotNull String s) {
return s;
}
void test(List<String> list, @Nullable String s1) {
list.stream().map(this::convert).filter(Objects::nonNull).map(s -> accept(s)).forEach(System.out::println);
}
}

View File

@@ -0,0 +1,16 @@
// "Insert 'filter(Objects::nonNull)' step" "true"
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Objects;
class MyClass {
@Nullable String convert(String s) {
return s;
}
void test(List<String> list, @Nullable String s1) {
list.stream().map(this::convert).filter(Objects::nonNull).map(s -> s.trim()).forEach(System.out::println);
}
}

View File

@@ -0,0 +1,21 @@
// "Insert 'filter(Objects::nonNull)' step" "true"
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Objects;
class MyClass {
@Nullable String convert(String s) {
return s;
}
String accept(@NotNull String s) {
return s;
}
void test(List<String> list, @Nullable String s1) {
list.stream().map(this::convert).filter(Objects::nonNull).map(this::accept).forEach(System.out::println);
}
}

View File

@@ -0,0 +1,16 @@
// "Insert 'filter(Objects::nonNull)' step" "true"
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Objects;
class MyClass {
@Nullable String convert(String s) {
return s;
}
void test(List<String> list, @Nullable String s1) {
list.stream().map(this::convert).filter(Objects::nonNull).map(String::trim).forEach(System.out::println);
}
}

View File

@@ -0,0 +1,26 @@
// "Insert 'filter(Objects::nonNull)' step" "true"
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
class MyClass {
@Nullable String convert(String s) {
return s;
}
String accept(@NotNull String s) {
return s;
}
String execute(Supplier<String> op) {
return op.get();
}
void test(List<String> list, @Nullable String s1) {
list.stream().map(this::convert).filter(Objects::nonNull).map(s -> execute(() -> accept(s))).forEach(System.out::println);
}
}

View File

@@ -0,0 +1,20 @@
// "Insert 'filter(Objects::nonNull)' step" "true"
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
class MyClass {
@Nullable String convert(String s) {
return s;
}
String accept(@NotNull String s) {
return s;
}
void test(List<String> list, @Nullable String s1) {
list.stream().map(this::convert).map(s -> accept(s<caret>)).forEach(System.out::println);
}
}

View File

@@ -0,0 +1,20 @@
// "Insert 'filter(Objects::nonNull)' step" "false"
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
class MyClass {
@Nullable String convert(String s) {
return s;
}
String accept(@NotNull String s) {
return s;
}
void test(List<String> list, @Nullable String s1) {
list.stream().map(this::convert).map(s -> accept(s1<caret>)).forEach(System.out::println);
}
}

View File

@@ -0,0 +1,15 @@
// "Insert 'filter(Objects::nonNull)' step" "true"
import org.jetbrains.annotations.Nullable;
import java.util.List;
class MyClass {
@Nullable String convert(String s) {
return s;
}
void test(List<String> list, @Nullable String s1) {
list.stream().map(this::convert).map(s -> s.t<caret>rim()).forEach(System.out::println);
}
}

View File

@@ -0,0 +1,20 @@
// "Insert 'filter(Objects::nonNull)' step" "true"
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
class MyClass {
@Nullable String convert(String s) {
return s;
}
String accept(@NotNull String s) {
return s;
}
void test(List<String> list, @Nullable String s1) {
list.stream().map(this::convert).map(this:<caret>:accept).forEach(System.out::println);
}
}

View File

@@ -0,0 +1,15 @@
// "Insert 'filter(Objects::nonNull)' step" "true"
import org.jetbrains.annotations.Nullable;
import java.util.List;
class MyClass {
@Nullable String convert(String s) {
return s;
}
void test(List<String> list, @Nullable String s1) {
list.stream().map(this::convert).map(String:<caret>:trim).forEach(System.out::println);
}
}

View File

@@ -0,0 +1,25 @@
// "Insert 'filter(Objects::nonNull)' step" "true"
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.function.Supplier;
class MyClass {
@Nullable String convert(String s) {
return s;
}
String accept(@NotNull String s) {
return s;
}
String execute(Supplier<String> op) {
return op.get();
}
void test(List<String> list, @Nullable String s1) {
list.stream().map(this::convert).map(s -> execute(() -> accept(s<caret>))).forEach(System.out::println);
}
}

View File

@@ -0,0 +1,17 @@
// "Insert 'filter(Objects::nonNull)' step" "false"
import org.jetbrains.annotations.Nullable;
import java.util.List;
class MyClass {
interface MyFunction {
String apply(@Nullable String s);
}
void doSmth(MyFunction fn) {}
void test(List<String> list, @Nullable String s1) {
this.doSmth(s -> s.t<caret>rim());
}
}

View File

@@ -0,0 +1,58 @@
/*
* 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.codeInsight.daemon.quickFix;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.dataFlow.DataFlowInspection;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.testFramework.IdeaTestUtil;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.PsiTestUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class StreamFilterNotNullFixTest extends LightQuickFixParameterizedTestCase {
private static final LightProjectDescriptor DESCRIPTOR = new LightProjectDescriptor() {
@Nullable
@Override
public Sdk getSdk() {
return PsiTestUtil.addJdkAnnotations(IdeaTestUtil.getMockJdk18());
}
};
@NotNull
@Override
protected LocalInspectionTool[] configureLocalInspectionTools() {
return new LocalInspectionTool[]{new DataFlowInspection()};
}
public void test() throws Exception {
doAllTests();
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return DESCRIPTOR;
}
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/streamFilterNotNull";
}
}

View File

@@ -47,6 +47,7 @@ inspection.data.flow.true.asserts.option=Don't report assertions with condition
inspection.data.flow.redundant.instanceof.quickfix=Replace with != null
inspection.data.flow.simplify.boolean.expression.quickfix=Simplify boolean expression
inspection.data.flow.simplify.to.assignment.quickfix.name=Simplify to normal assignment
inspection.data.flow.filter.notnull.quickfix=Insert 'filter(Objects::nonNull)' step
configure.annotations.option=Configure annotations
configure.checker.option.button=Configure Assert/Check Methods
configure.checker.option.main.dialog.title=Assert/Check Method Configuration