diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java index c5f7de6925c4..75927fc9b872 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java @@ -134,6 +134,21 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { }; analyzeDfaWithNestedClosures(scope, holder, dfaRunner, Arrays.asList(dfaRunner.createMemoryState()), onTheFly); } + + @Nullable + public static Collection getNullableReturn(PsiMethod method) { + final StandardDataFlowRunner dfaRunner = new StandardDataFlowRunner(); + final DataFlowInstructionVisitor visitor = new DataFlowInstructionVisitor(dfaRunner); + final PsiCodeBlock body = method.getBody(); + if (body == null) { + return null; + } + final RunnerResult rc = dfaRunner.analyzeMethod(body, visitor); + if (rc == RunnerResult.OK) { + return visitor.getProblems(NullabilityProblem.nullableReturn); + } + return null; + } private void analyzeDfaWithNestedClosures(PsiElement scope, ProblemsHolder holder, diff --git a/java/java-impl/src/com/intellij/refactoring/extractMethod/ExtractMethodProcessor.java b/java/java-impl/src/com/intellij/refactoring/extractMethod/ExtractMethodProcessor.java index 43f473dd6c4a..0f993afc87a7 100644 --- a/java/java-impl/src/com/intellij/refactoring/extractMethod/ExtractMethodProcessor.java +++ b/java/java-impl/src/com/intellij/refactoring/extractMethod/ExtractMethodProcessor.java @@ -17,13 +17,17 @@ package com.intellij.refactoring.extractMethod; import com.intellij.codeInsight.ChangeContextUtil; import com.intellij.codeInsight.ExceptionUtil; +import com.intellij.codeInsight.NullableNotNullManager; import com.intellij.codeInsight.daemon.impl.analysis.JavaHighlightUtil; import com.intellij.codeInsight.daemon.impl.quickfix.AnonymousTargetClassPreselectionUtil; import com.intellij.codeInsight.highlighting.HighlightManager; +import com.intellij.codeInsight.intention.AddAnnotationFix; +import com.intellij.codeInsight.intention.impl.AddNotNullAnnotationFix; +import com.intellij.codeInsight.intention.impl.AddNotNullAnnotationIntention; +import com.intellij.codeInsight.intention.impl.AddNullableAnnotationFix; +import com.intellij.codeInsight.intention.impl.AddNullableNotNullAnnotationFix; import com.intellij.codeInsight.navigation.NavigationUtil; -import com.intellij.codeInspection.dataFlow.RunnerResult; -import com.intellij.codeInspection.dataFlow.StandardDataFlowRunner; -import com.intellij.codeInspection.dataFlow.StandardInstructionVisitor; +import com.intellij.codeInspection.dataFlow.*; import com.intellij.codeInspection.dataFlow.instructions.BranchingInstruction; import com.intellij.codeInspection.dataFlow.instructions.Instruction; import com.intellij.ide.DataManager; @@ -39,6 +43,7 @@ import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.*; +import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.WindowManager; @@ -862,6 +867,20 @@ public class ExtractMethodProcessor implements MatchProvider { } } + if (isNullabilityCheckApplicable() && !(newMethod.getReturnType() instanceof PsiPrimitiveType) && + PsiUtil.isLanguageLevel5OrHigher(newMethod) && Registry.is("annotate.extracted.method.nullable.when.applicable", true)) { + final NullableNotNullManager manager = NullableNotNullManager.getInstance(myProject); + final PsiClass nullableAnnotationClass = + JavaPsiFacade.getInstance(myProject).findClass(manager.getDefaultNullable(), GlobalSearchScope.allScope(myProject)); + if (nullableAnnotationClass != null) { + final Collection nullableReturn = DataFlowInspectionBase.getNullableReturn(newMethod); + if (nullableReturn != null && !nullableReturn.isEmpty()) { + final AddNullableNotNullAnnotationFix annotationFix = new AddNullableAnnotationFix(newMethod); + annotationFix.invoke(myProject, myTargetClass.getContainingFile(), newMethod, newMethod); + } + } + } + myExtractedMethod = (PsiMethod)myTargetClass.addAfter(newMethod, myAnchor); if (isNeedToChangeCallContext() && myNeedChangeContext) { ChangeContextUtil.decodeContextInfo(myExtractedMethod, myTargetClass, RefactoringChangeUtil.createThisExpression(myManager, null)); @@ -870,7 +889,10 @@ public class ExtractMethodProcessor implements MatchProvider { methodExpression.setQualifierExpression(RefactoringChangeUtil.createThisExpression(myManager, myTargetClass)); } } + } + protected boolean isNullabilityCheckApplicable() { + return true; } protected boolean isNeedToChangeCallContext() { diff --git a/java/java-impl/src/com/intellij/refactoring/extractMethodObject/ExtractMethodObjectProcessor.java b/java/java-impl/src/com/intellij/refactoring/extractMethodObject/ExtractMethodObjectProcessor.java index ce50b53be3c4..0cf023394880 100644 --- a/java/java-impl/src/com/intellij/refactoring/extractMethodObject/ExtractMethodObjectProcessor.java +++ b/java/java-impl/src/com/intellij/refactoring/extractMethodObject/ExtractMethodObjectProcessor.java @@ -657,6 +657,16 @@ public class ExtractMethodObjectProcessor extends BaseRefactoringProcessor { return false; } + @Override + protected boolean isNullabilityCheckApplicable() { + return false; + } + + @Override + protected boolean isNeedToChangeCallContext() { + return false; + } + @Override protected void apply(final AbstractExtractDialog dialog) { super.apply(dialog); @@ -741,11 +751,6 @@ public class ExtractMethodObjectProcessor extends BaseRefactoringProcessor { return myOutputVariables; } - @Override - protected boolean isNeedToChangeCallContext() { - return false; - } - @Override protected void declareNecessaryVariablesAfterCall(final PsiVariable outputVariable) throws IncorrectOperationException { if (myMultipleExitPoints) { diff --git a/java/java-tests/testData/refactoring/extractMethod/ExitPoints8_after.java b/java/java-tests/testData/refactoring/extractMethod/ExitPoints8_after.java index 1bdb53a12d09..07fd497cbfbc 100644 --- a/java/java-tests/testData/refactoring/extractMethod/ExitPoints8_after.java +++ b/java/java-tests/testData/refactoring/extractMethod/ExitPoints8_after.java @@ -1,3 +1,5 @@ +import org.jetbrains.annotations.Nullable; + class C { public Object m() { Object o = newMethod(); @@ -5,6 +7,7 @@ class C { return null; } + @Nullable private Object newMethod() { for (Object o : new ArrayList()) { if (o != null) { diff --git a/java/java-tests/testData/refactoring/extractMethod/ExitPoints9_after.java b/java/java-tests/testData/refactoring/extractMethod/ExitPoints9_after.java index aafa919ba86d..0c50b6d78882 100644 --- a/java/java-tests/testData/refactoring/extractMethod/ExitPoints9_after.java +++ b/java/java-tests/testData/refactoring/extractMethod/ExitPoints9_after.java @@ -1,3 +1,5 @@ +import org.jetbrains.annotations.Nullable; + class Result { private String _message; @@ -17,6 +19,7 @@ class Main { return result; } + @Nullable private static Result newMethod(String name) { Result result; if (name == null) { diff --git a/java/java-tests/testData/refactoring/extractMethod/NullableCheck1_after.java b/java/java-tests/testData/refactoring/extractMethod/NullableCheck1_after.java index 4a2cf1839290..8cee30b744d2 100644 --- a/java/java-tests/testData/refactoring/extractMethod/NullableCheck1_after.java +++ b/java/java-tests/testData/refactoring/extractMethod/NullableCheck1_after.java @@ -1,3 +1,5 @@ +import org.jetbrains.annotations.Nullable; + class Test { String foo(int i, boolean flag) { @@ -8,6 +10,7 @@ class Test { return null; } + @Nullable private String newMethod(int i, boolean flag) { String xxx = ""; if (flag) { diff --git a/java/java-tests/testData/refactoring/extractMethod/NullableCheckBreak_after.java b/java/java-tests/testData/refactoring/extractMethod/NullableCheckBreak_after.java index dab162d77c5b..9b24700a50f2 100644 --- a/java/java-tests/testData/refactoring/extractMethod/NullableCheckBreak_after.java +++ b/java/java-tests/testData/refactoring/extractMethod/NullableCheckBreak_after.java @@ -1,3 +1,5 @@ +import org.jetbrains.annotations.Nullable; + import java.util.List; class Test { @@ -11,6 +13,7 @@ class Test { } } + @Nullable private Pojo newMethod() { Pojo x = things.get(0); diff --git a/java/java-tests/testData/refactoring/extractMethod/NullableCheckVoid_after.java b/java/java-tests/testData/refactoring/extractMethod/NullableCheckVoid_after.java index 311314d752d8..22b614ed60f9 100644 --- a/java/java-tests/testData/refactoring/extractMethod/NullableCheckVoid_after.java +++ b/java/java-tests/testData/refactoring/extractMethod/NullableCheckVoid_after.java @@ -1,3 +1,5 @@ +import org.jetbrains.annotations.Nullable; + class Test { void foo() { Object o = newMethod(); @@ -5,6 +7,7 @@ class Test { System.out.println(o); } + @Nullable private Object newMethod() { Object o = ""; for (int i = 0; i < 5; i++) { diff --git a/java/java-tests/testData/refactoring/extractMethod/NullableCheck_after.java b/java/java-tests/testData/refactoring/extractMethod/NullableCheck_after.java index fa0db6423177..4a7a11497526 100644 --- a/java/java-tests/testData/refactoring/extractMethod/NullableCheck_after.java +++ b/java/java-tests/testData/refactoring/extractMethod/NullableCheck_after.java @@ -1,3 +1,5 @@ +import org.jetbrains.annotations.Nullable; + class Test { Object foo() { Object o = newMethod(); @@ -6,6 +8,7 @@ class Test { return o; } + @Nullable private Object newMethod() { Object o = ""; for (int i = 0; i < 5; i++) { diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index bd09bd415c8b..ded155665ec7 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -499,3 +499,6 @@ editor.config.stop.at.project.root.description=Stops searching for .editorconfig JDK8042508.bug.fixed=false JDK8042508.bug.fixed.description=Disable check for type variable until javac bug is fixed + +annotate.extracted.method.nullable.when.applicable=true +annotate.extracted.method.nullable.when.applicable.description=Enables @Nullable annotation on newly extracted method when applicable