mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
extract method: prepare method body to have @Nullable/@NotNull before actual extract is performed
This commit is contained in:
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.intellij.refactoring.extractMethod;
|
||||
|
||||
import com.intellij.codeInsight.NullableNotNullManager;
|
||||
import com.intellij.codeInspection.dataFlow.Nullness;
|
||||
import com.intellij.ide.highlighter.JavaFileType;
|
||||
import com.intellij.ide.util.PropertiesComponent;
|
||||
import com.intellij.openapi.editor.event.DocumentAdapter;
|
||||
@@ -69,6 +71,7 @@ public class ExtractMethodDialog extends DialogWrapper implements AbstractExtrac
|
||||
private final PsiType[] myExceptions;
|
||||
private final boolean myStaticFlag;
|
||||
private boolean myCanBeStatic;
|
||||
private final Nullness myNullness;
|
||||
private final PsiElement[] myElementsToExtract;
|
||||
private final String myHelpId;
|
||||
|
||||
@@ -98,6 +101,7 @@ public class ExtractMethodDialog extends DialogWrapper implements AbstractExtrac
|
||||
String initialMethodName,
|
||||
String title,
|
||||
String helpId,
|
||||
Nullness nullness,
|
||||
final PsiElement[] elementsToExtract) {
|
||||
super(project, true);
|
||||
myProject = project;
|
||||
@@ -107,6 +111,7 @@ public class ExtractMethodDialog extends DialogWrapper implements AbstractExtrac
|
||||
myExceptions = exceptions;
|
||||
myStaticFlag = isStatic;
|
||||
myCanBeStatic = canBeStatic;
|
||||
myNullness = nullness;
|
||||
myElementsToExtract = elementsToExtract;
|
||||
myVariableData = inputVariables;
|
||||
myHelpId = helpId;
|
||||
@@ -293,10 +298,15 @@ public class ExtractMethodDialog extends DialogWrapper implements AbstractExtrac
|
||||
optionsPanel.add(myMakeVarargs);
|
||||
}
|
||||
|
||||
if (!(myReturnType instanceof PsiPrimitiveType) && PsiUtil.isLanguageLevel5OrHigher(myTargetClass)) {
|
||||
if (myNullness != null) {
|
||||
final boolean isSelected = PropertiesComponent.getInstance(myProject).getBoolean(EXTRACT_METHOD_GENERATE_ANNOTATIONS, true);
|
||||
myGenerateAnnotations = new JCheckBox("Generate annotations", isSelected);
|
||||
//todo update signature?!
|
||||
myGenerateAnnotations.addItemListener(new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
updateSignature();
|
||||
}
|
||||
});
|
||||
optionsPanel.add(myGenerateAnnotations);
|
||||
}
|
||||
|
||||
@@ -454,6 +464,12 @@ public class ExtractMethodDialog extends DialogWrapper implements AbstractExtrac
|
||||
|
||||
protected String getSignature() {
|
||||
final @NonNls StringBuilder buffer = new StringBuilder();
|
||||
if (myGenerateAnnotations != null && myGenerateAnnotations.isSelected()) {
|
||||
final NullableNotNullManager nullManager = NullableNotNullManager.getInstance(myProject);
|
||||
buffer.append("@");
|
||||
buffer.append(StringUtil.getShortName(myNullness == Nullness.NULLABLE ? nullManager.getDefaultNullable() : nullManager.getDefaultNotNull()));
|
||||
buffer.append("\n");
|
||||
}
|
||||
final String visibilityString = VisibilityUtil.getVisibilityString(myVisibilityPanel.getVisibility());
|
||||
buffer.append(visibilityString);
|
||||
if (buffer.length() > 0) {
|
||||
|
||||
+131
-99
@@ -21,9 +21,7 @@ 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;
|
||||
@@ -44,7 +42,6 @@ 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;
|
||||
@@ -135,6 +132,7 @@ public class ExtractMethodProcessor implements MatchProvider {
|
||||
private PsiMethodCallExpression myMethodCall;
|
||||
private boolean myNullConditionalCheck = false;
|
||||
private boolean myNotNullConditionalCheck = false;
|
||||
private Nullness myNullness;
|
||||
|
||||
public ExtractMethodProcessor(Project project,
|
||||
Editor editor,
|
||||
@@ -490,10 +488,13 @@ public class ExtractMethodProcessor implements MatchProvider {
|
||||
}
|
||||
|
||||
protected AbstractExtractDialog createExtractMethodDialog(final boolean direct) {
|
||||
final List<VariableData> variables = myInputVariables.getInputVariables();
|
||||
myVariableDatum = variables.toArray(new VariableData[variables.size()]);
|
||||
myNullness = initNullness();
|
||||
return new ExtractMethodDialog(myProject, myTargetClass, myInputVariables, myReturnType, getTypeParameterList(),
|
||||
getThrownExceptions(), isStatic(), isCanBeStatic(), myCanBeChainedConstructor,
|
||||
suggestInitialMethodName(),
|
||||
myRefactoringName, myHelpId, myElements) {
|
||||
myRefactoringName, myHelpId, myNullness, myElements) {
|
||||
protected boolean areTypesDirected() {
|
||||
return direct;
|
||||
}
|
||||
@@ -505,6 +506,22 @@ public class ExtractMethodProcessor implements MatchProvider {
|
||||
};
|
||||
}
|
||||
|
||||
private Nullness initNullness() {
|
||||
if (!PsiUtil.isLanguageLevel5OrHigher(myElements[0]) || PsiUtil.resolveClassInType(myReturnType) == null) return null;
|
||||
final PsiMethod emptyMethod = generateEmptyMethod(getThrownExceptions(), isStatic(), "name");
|
||||
prepareMethodBody(emptyMethod, false);
|
||||
final NullableNotNullManager manager = NullableNotNullManager.getInstance(myProject);
|
||||
final PsiClass nullableAnnotationClass = JavaPsiFacade.getInstance(myProject)
|
||||
.findClass(manager.getDefaultNullable(), GlobalSearchScope.allScope(myProject));
|
||||
if (nullableAnnotationClass != null) {
|
||||
if (myNotNullConditionalCheck || myNullConditionalCheck) {
|
||||
return Nullness.NULLABLE;
|
||||
}
|
||||
return DfaUtil.inferMethodNullity(emptyMethod);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected String suggestInitialMethodName() {
|
||||
if (StringUtil.isEmpty(myInitialMethodName)) {
|
||||
final String initialMethodName;
|
||||
@@ -551,10 +568,15 @@ public class ExtractMethodProcessor implements MatchProvider {
|
||||
@TestOnly
|
||||
public void testRun() throws IncorrectOperationException {
|
||||
testPrepare();
|
||||
|
||||
testNullness();
|
||||
ExtractMethodHandler.run(myProject, myEditor, this);
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public void testNullness() {
|
||||
myNullness = initNullness();
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public void testPrepare() {
|
||||
myInputVariables.setFoldingAvailable(myInputVariables.isFoldingSelectedByDefault());
|
||||
@@ -665,70 +687,15 @@ public class ExtractMethodProcessor implements MatchProvider {
|
||||
|
||||
LOG.assertTrue(myElements[0].isValid());
|
||||
|
||||
final PsiStatement exitStatementCopy = prepareMethodBody(newMethod, true);
|
||||
|
||||
if (myExpression == null) {
|
||||
String outVariableName = myOutputVariable != null ? getNewVariableName(myOutputVariable) : null;
|
||||
PsiReturnStatement returnStatement;
|
||||
if (myNullConditionalCheck) {
|
||||
returnStatement = (PsiReturnStatement)myElementFactory.createStatementFromText("return null;", null);
|
||||
} else if (myOutputVariable != null) {
|
||||
returnStatement = (PsiReturnStatement)myElementFactory.createStatementFromText("return " + outVariableName + ";", null);
|
||||
}
|
||||
else if (myGenerateConditionalExit) {
|
||||
returnStatement = (PsiReturnStatement)myElementFactory.createStatementFromText("return true;", null);
|
||||
}
|
||||
else {
|
||||
returnStatement = (PsiReturnStatement)myElementFactory.createStatementFromText("return;", null);
|
||||
}
|
||||
|
||||
boolean hasNormalExit = false;
|
||||
PsiElement lastElement = myElements[myElements.length - 1];
|
||||
if (!(lastElement instanceof PsiReturnStatement || lastElement instanceof PsiBreakStatement ||
|
||||
lastElement instanceof PsiContinueStatement)) {
|
||||
hasNormalExit = true;
|
||||
}
|
||||
|
||||
PsiStatement exitStatementCopy = myNotNullConditionalCheck ? null : myControlFlowWrapper.getExitStatementCopy(returnStatement, myElements);
|
||||
|
||||
|
||||
declareNecessaryVariablesInsideBody(body);
|
||||
|
||||
|
||||
|
||||
if (myNeedChangeContext) {
|
||||
for (PsiElement element : myElements) {
|
||||
ChangeContextUtil.encodeContextInfo(element, false);
|
||||
}
|
||||
}
|
||||
|
||||
body.addRange(myElements[0], myElements[myElements.length - 1]);
|
||||
if (myNullConditionalCheck) {
|
||||
body.add(myElementFactory.createStatementFromText("return " + myOutputVariable.getName() + ";", null));
|
||||
}
|
||||
else if (myNotNullConditionalCheck) {
|
||||
body.add(myElementFactory.createStatementFromText("return null;", null));
|
||||
}
|
||||
else if (myGenerateConditionalExit) {
|
||||
body.add(myElementFactory.createStatementFromText("return false;", null));
|
||||
}
|
||||
else if (!myHasReturnStatement && hasNormalExit && myOutputVariable != null) {
|
||||
final PsiReturnStatement insertedReturnStatement = (PsiReturnStatement)body.add(returnStatement);
|
||||
if (myOutputVariables.length == 1) {
|
||||
final PsiExpression returnValue = insertedReturnStatement.getReturnValue();
|
||||
if (returnValue instanceof PsiReferenceExpression) {
|
||||
final PsiElement resolved = ((PsiReferenceExpression)returnValue).resolve();
|
||||
if (resolved instanceof PsiLocalVariable && Comparing.strEqual(((PsiVariable)resolved).getName(), outVariableName)) {
|
||||
final PsiStatement statement = PsiTreeUtil.getPrevSiblingOfType(insertedReturnStatement, PsiStatement.class);
|
||||
if (statement instanceof PsiDeclarationStatement) {
|
||||
final PsiElement[] declaredElements = ((PsiDeclarationStatement)statement).getDeclaredElements();
|
||||
if (ArrayUtil.find(declaredElements, resolved) != -1) {
|
||||
InlineUtil.inlineVariable((PsiVariable)resolved, ((PsiVariable)resolved).getInitializer(), (PsiReferenceExpression)returnValue);
|
||||
resolved.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (myNullConditionalCheck) {
|
||||
final String varName = myOutputVariable.getName();
|
||||
if (isDeclaredInside(myOutputVariable)) {
|
||||
@@ -796,7 +763,7 @@ public class ExtractMethodProcessor implements MatchProvider {
|
||||
statement = (PsiStatement)addToMethodCallLocation(statement);
|
||||
myMethodCall = (PsiMethodCallExpression)((PsiExpressionStatement)statement).getExpression().replace(myMethodCall);
|
||||
}
|
||||
if (myHasReturnStatement && !myHasReturnStatementOutput && !hasNormalExit) {
|
||||
if (myHasReturnStatement && !myHasReturnStatementOutput && !hasNormalExit()) {
|
||||
PsiStatement statement = myElementFactory.createStatementFromText("return;", null);
|
||||
addToMethodCallLocation(statement);
|
||||
}
|
||||
@@ -811,18 +778,6 @@ public class ExtractMethodProcessor implements MatchProvider {
|
||||
deleteExtracted();
|
||||
}
|
||||
else {
|
||||
declareNecessaryVariablesInsideBody(body);
|
||||
if (myHasExpressionOutput) {
|
||||
PsiReturnStatement returnStatement = (PsiReturnStatement)myElementFactory.createStatementFromText("return x;", null);
|
||||
final PsiExpression returnValue = RefactoringUtil.convertInitializerToNormalExpression(myExpression, myForcedReturnType);
|
||||
returnStatement.getReturnValue().replace(returnValue);
|
||||
body.add(returnStatement);
|
||||
}
|
||||
else {
|
||||
PsiExpressionStatement statement = (PsiExpressionStatement)myElementFactory.createStatementFromText("x;", null);
|
||||
statement.getExpression().replace(myExpression);
|
||||
body.add(statement);
|
||||
}
|
||||
PsiExpression expression2Replace = myExpression;
|
||||
if (myExpression instanceof PsiAssignmentExpression) {
|
||||
expression2Replace = ((PsiAssignmentExpression)myExpression).getRExpression();
|
||||
@@ -868,27 +823,20 @@ public class ExtractMethodProcessor implements MatchProvider {
|
||||
}
|
||||
}
|
||||
|
||||
if (isNullabilityCheckApplicable() && !(newMethod.getReturnType() instanceof PsiPrimitiveType) && PsiUtil.isLanguageLevel5OrHigher(newMethod) &&
|
||||
PropertiesComponent.getInstance(myProject).getBoolean(ExtractMethodDialog.EXTRACT_METHOD_GENERATE_ANNOTATIONS, true)) {
|
||||
final NullableNotNullManager manager = NullableNotNullManager.getInstance(myProject);
|
||||
final PsiClass nullableAnnotationClass =
|
||||
JavaPsiFacade.getInstance(myProject).findClass(manager.getDefaultNullable(), GlobalSearchScope.allScope(myProject));
|
||||
if (nullableAnnotationClass != null) {
|
||||
final Nullness nullness = DfaUtil.inferMethodNullity(newMethod);
|
||||
AddNullableNotNullAnnotationFix annotationFix;
|
||||
switch (nullness) {
|
||||
case NOT_NULL:
|
||||
annotationFix = new AddNotNullAnnotationFix(newMethod);
|
||||
break;
|
||||
case NULLABLE:
|
||||
annotationFix = new AddNullableAnnotationFix(newMethod);
|
||||
break;
|
||||
default:
|
||||
annotationFix = null;
|
||||
}
|
||||
if (annotationFix != null) {
|
||||
annotationFix.invoke(myProject, myTargetClass.getContainingFile(), newMethod, newMethod);
|
||||
}
|
||||
if (myNullness != null && PropertiesComponent.getInstance(myProject).getBoolean(ExtractMethodDialog.EXTRACT_METHOD_GENERATE_ANNOTATIONS, true)) {
|
||||
AddNullableNotNullAnnotationFix annotationFix;
|
||||
switch (myNullness) {
|
||||
case NOT_NULL:
|
||||
annotationFix = new AddNotNullAnnotationFix(newMethod);
|
||||
break;
|
||||
case NULLABLE:
|
||||
annotationFix = new AddNullableAnnotationFix(newMethod);
|
||||
break;
|
||||
default:
|
||||
annotationFix = null;
|
||||
}
|
||||
if (annotationFix != null) {
|
||||
annotationFix.invoke(myProject, myTargetClass.getContainingFile(), newMethod, newMethod);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -902,8 +850,86 @@ public class ExtractMethodProcessor implements MatchProvider {
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isNullabilityCheckApplicable() {
|
||||
return true;
|
||||
@Nullable
|
||||
private PsiStatement prepareMethodBody(PsiMethod newMethod, boolean doExtract) {
|
||||
PsiCodeBlock body = newMethod.getBody();
|
||||
if (myExpression != null) {
|
||||
declareNecessaryVariablesInsideBody(body);
|
||||
if (myHasExpressionOutput) {
|
||||
PsiReturnStatement returnStatement = (PsiReturnStatement)myElementFactory.createStatementFromText("return x;", null);
|
||||
final PsiExpression returnValue = RefactoringUtil.convertInitializerToNormalExpression(myExpression, myForcedReturnType);
|
||||
returnStatement.getReturnValue().replace(returnValue);
|
||||
body.add(returnStatement);
|
||||
}
|
||||
else {
|
||||
PsiExpressionStatement statement = (PsiExpressionStatement)myElementFactory.createStatementFromText("x;", null);
|
||||
statement.getExpression().replace(myExpression);
|
||||
body.add(statement);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
final boolean hasNormalExit = hasNormalExit();
|
||||
String outVariableName = myOutputVariable != null ? getNewVariableName(myOutputVariable) : null;
|
||||
PsiReturnStatement returnStatement;
|
||||
if (myNullConditionalCheck) {
|
||||
returnStatement = (PsiReturnStatement)myElementFactory.createStatementFromText("return null;", null);
|
||||
} else if (myOutputVariable != null) {
|
||||
returnStatement = (PsiReturnStatement)myElementFactory.createStatementFromText("return " + outVariableName + ";", null);
|
||||
}
|
||||
else if (myGenerateConditionalExit) {
|
||||
returnStatement = (PsiReturnStatement)myElementFactory.createStatementFromText("return true;", null);
|
||||
}
|
||||
else {
|
||||
returnStatement = (PsiReturnStatement)myElementFactory.createStatementFromText("return;", null);
|
||||
}
|
||||
|
||||
PsiStatement exitStatementCopy = !doExtract || myNotNullConditionalCheck ? null : myControlFlowWrapper.getExitStatementCopy(returnStatement, myElements);
|
||||
|
||||
|
||||
declareNecessaryVariablesInsideBody(body);
|
||||
|
||||
body.addRange(myElements[0], myElements[myElements.length - 1]);
|
||||
if (myNullConditionalCheck) {
|
||||
body.add(myElementFactory.createStatementFromText("return " + myOutputVariable.getName() + ";", null));
|
||||
}
|
||||
else if (myNotNullConditionalCheck) {
|
||||
body.add(myElementFactory.createStatementFromText("return null;", null));
|
||||
}
|
||||
else if (myGenerateConditionalExit) {
|
||||
body.add(myElementFactory.createStatementFromText("return false;", null));
|
||||
}
|
||||
else if (!myHasReturnStatement && hasNormalExit && myOutputVariable != null) {
|
||||
final PsiReturnStatement insertedReturnStatement = (PsiReturnStatement)body.add(returnStatement);
|
||||
if (myOutputVariables.length == 1) {
|
||||
final PsiExpression returnValue = insertedReturnStatement.getReturnValue();
|
||||
if (returnValue instanceof PsiReferenceExpression) {
|
||||
final PsiElement resolved = ((PsiReferenceExpression)returnValue).resolve();
|
||||
if (resolved instanceof PsiLocalVariable && Comparing.strEqual(((PsiVariable)resolved).getName(), outVariableName)) {
|
||||
final PsiStatement statement = PsiTreeUtil.getPrevSiblingOfType(insertedReturnStatement, PsiStatement.class);
|
||||
if (statement instanceof PsiDeclarationStatement) {
|
||||
final PsiElement[] declaredElements = ((PsiDeclarationStatement)statement).getDeclaredElements();
|
||||
if (ArrayUtil.find(declaredElements, resolved) != -1) {
|
||||
InlineUtil.inlineVariable((PsiVariable)resolved, ((PsiVariable)resolved).getInitializer(),
|
||||
(PsiReferenceExpression)returnValue);
|
||||
resolved.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return exitStatementCopy;
|
||||
}
|
||||
|
||||
private boolean hasNormalExit() {
|
||||
boolean hasNormalExit = false;
|
||||
PsiElement lastElement = myElements[myElements.length - 1];
|
||||
if (!(lastElement instanceof PsiReturnStatement || lastElement instanceof PsiBreakStatement ||
|
||||
lastElement instanceof PsiContinueStatement)) {
|
||||
hasNormalExit = true;
|
||||
}
|
||||
return hasNormalExit;
|
||||
}
|
||||
|
||||
protected boolean isNeedToChangeCallContext() {
|
||||
@@ -1085,12 +1111,18 @@ public class ExtractMethodProcessor implements MatchProvider {
|
||||
}
|
||||
|
||||
private PsiMethod generateEmptyMethod(PsiClassType[] exceptions, boolean isStatic) throws IncorrectOperationException {
|
||||
return generateEmptyMethod(exceptions, isStatic, myMethodName);
|
||||
}
|
||||
|
||||
private PsiMethod generateEmptyMethod(PsiClassType[] exceptions,
|
||||
boolean isStatic,
|
||||
String methodName) throws IncorrectOperationException {
|
||||
PsiMethod newMethod;
|
||||
if (myIsChainedConstructor) {
|
||||
newMethod = myElementFactory.createConstructor();
|
||||
}
|
||||
else {
|
||||
newMethod = myElementFactory.createMethod(myMethodName, myReturnType);
|
||||
newMethod = myElementFactory.createMethod(methodName, myReturnType);
|
||||
PsiUtil.setModifierProperty(newMethod, PsiModifier.STATIC, isStatic);
|
||||
}
|
||||
PsiUtil.setModifierProperty(newMethod, myMethodVisibility, true);
|
||||
|
||||
-5
@@ -665,11 +665,6 @@ public class ExtractMethodObjectProcessor extends BaseRefactoringProcessor {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isNullabilityCheckApplicable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isNeedToChangeCallContext() {
|
||||
return false;
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
class Test {
|
||||
void foo() {
|
||||
@@ -11,7 +11,7 @@ class Test {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Nullable
|
||||
private String newMethod() {
|
||||
final String str = "";
|
||||
if (str == "") {
|
||||
|
||||
@@ -706,6 +706,7 @@ public class ExtractMethodTest extends LightCodeInsightTestCase {
|
||||
|
||||
if (doRefactor) {
|
||||
processor.testPrepare();
|
||||
processor.testNullness();
|
||||
if (disabledParams != null) {
|
||||
for (int param : disabledParams) {
|
||||
processor.doNotPassParameter(param);
|
||||
|
||||
Reference in New Issue
Block a user