optionally introduce local as var type since java 10 (IDEA-179176)

This commit is contained in:
Anna.Kozlova
2019-02-05 15:23:14 +01:00
parent 9958e534b3
commit c3b7a9b1c6
12 changed files with 172 additions and 26 deletions
@@ -79,6 +79,7 @@ public class JavaRefactoringSettings implements PersistentStateComponent<JavaRef
public boolean CONVERT_TO_INSTANCE_METHOD_PREVIEW_USAGES = true;
public Boolean INTRODUCE_LOCAL_CREATE_FINALS;
public Boolean INTRODUCE_LOCAL_CREATE_VAR_TYPE = false;
public Boolean INTRODUCE_PARAMETER_CREATE_FINALS;
public boolean INLINE_CLASS_SEARCH_IN_COMMENTS = true;
@@ -770,6 +770,17 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase {
return wasSucceed[0];
}
public static boolean canBeExtractedWithoutExplicitType(PsiExpression expr) {
if (PsiUtil.isLanguageLevel10OrHigher(expr)) {
PsiType type = expr.getType();
return type != null &&
!PsiType.NULL.equals(type) &&
PsiTypesUtil.isDenotableType(type, expr) &&
(expr instanceof PsiNewExpression || type.equals(((PsiExpression)expr.copy()).getType()));
}
return false;
}
@Nullable
private static PsiElement getAnchor(PsiElement place) {
PsiElement anchorStatement = RefactoringUtil.getParentStatement(place, false);
@@ -935,6 +946,25 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase {
return initializer;
}
/**
* Ensure that diamond inside initializer is expanded, then replace variable type with var
*/
public static PsiElement expandDiamondsAndReplaceExplicitTypeWithVar(PsiTypeElement typeElement, PsiElement context) {
PsiElement parent = typeElement.getParent();
if (parent instanceof PsiVariable) {
PsiExpression copyVariableInitializer = ((PsiVariable)parent).getInitializer();
if (copyVariableInitializer instanceof PsiNewExpression) {
final PsiDiamondType.DiamondInferenceResult diamondResolveResult =
PsiDiamondTypeImpl.resolveInferredTypesNoCheck((PsiNewExpression)copyVariableInitializer, copyVariableInitializer);
if (!diamondResolveResult.getInferredTypes().isEmpty()) {
PsiDiamondTypeUtil.expandTopLevelDiamondsInside(copyVariableInitializer);
}
}
}
return typeElement.replace(JavaPsiFacade.getElementFactory(context.getProject()).createTypeElementFromText("var", context));
}
public static PsiElement replace(final PsiExpression expr1, final PsiExpression ref, final Project project)
throws IncorrectOperationException {
final PsiExpression expr2;
@@ -1021,6 +1051,7 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase {
final SuggestedNameInfo suggestedName = getSuggestedName(typeSelectorManager.getDefaultType(), expr, anchor);
final String variableName = suggestedName.names.length > 0 ? suggestedName.names[0] : "";
final boolean declareFinal = replaceAll && declareFinalIfAll || !anyAssignmentLHS && createFinals(anchor.getContainingFile());
final boolean declareVarType = canBeExtractedWithoutExplicitType(expr) && createVarType();
final boolean replaceWrite = anyAssignmentLHS && replaceChoice.isAll();
return new IntroduceVariableSettings() {
@Override
@@ -1038,6 +1069,11 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase {
return declareFinal;
}
@Override
public boolean isDeclareVarType() {
return declareVarType;
}
@Override
public boolean isReplaceLValues() {
return replaceWrite;
@@ -1063,6 +1099,11 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase {
createFinals.booleanValue();
}
public static boolean createVarType() {
final Boolean createVarType = JavaRefactoringSettings.getInstance().INTRODUCE_LOCAL_CREATE_VAR_TYPE;
return createVarType != null && createVarType.booleanValue();
}
public static boolean checkAnchorBeforeThisOrSuper(final Project project,
final Editor editor,
final PsiElement tempAnchorElement,
@@ -3,6 +3,7 @@ package com.intellij.refactoring.introduceVariable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.util.Comparing;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiNameHelper;
@@ -37,6 +38,7 @@ class IntroduceVariableDialog extends DialogWrapper implements IntroduceVariable
private StateRestoringCheckBox myCbReplaceWrite;
private JCheckBox myCbFinal;
private boolean myCbFinalState;
private JCheckBox myCbVarType;
private TypeSelector myTypeSelector;
private NameSuggestionsManager myNameSuggestionsManager;
private static final String REFACTORING_NAME = RefactoringBundle.message("introduce.variable.title");
@@ -104,6 +106,11 @@ class IntroduceVariableDialog extends DialogWrapper implements IntroduceVariable
}
}
@Override
public boolean isDeclareVarType() {
return myCbVarType.isVisible() && myCbVarType.isEnabled() && myCbVarType.isSelected();
}
@Override
public PsiType getSelectedType() {
return myTypeSelector.getSelectedType();
@@ -216,6 +223,24 @@ class IntroduceVariableDialog extends DialogWrapper implements IntroduceVariable
};
myCbFinal.addItemListener(myFinalListener);
myCbVarType = new NonFocusableCheckBox(RefactoringBundle.message("declare.var.type"));
boolean toVarType = IntroduceVariableBase.canBeExtractedWithoutExplicitType(myExpression);
if (toVarType) {
myTypeSelector.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
myCbVarType.setEnabled(Comparing.equal(myTypeSelector.getSelectedType(), myExpression.getType()));
}
}
});
}
myCbVarType.setVisible(toVarType);
myCbVarType.setSelected(IntroduceVariableBase.createVarType());
gbConstraints.gridy++;
panel.add(myCbVarType, gbConstraints);
updateControls();
return panel;
@@ -247,6 +272,10 @@ class IntroduceVariableDialog extends DialogWrapper implements IntroduceVariable
myCbFinal.setEnabled(true);
myCbFinal.setSelected(myCbFinalState);
}
if (myCbVarType != null) {
myCbVarType.setEnabled(Comparing.equal(myTypeSelector.getSelectedType(), myExpression.getType()));
}
}
@Override
@@ -257,6 +286,9 @@ class IntroduceVariableDialog extends DialogWrapper implements IntroduceVariable
if (myCbFinal.isEnabled()) {
JavaRefactoringSettings.getInstance().INTRODUCE_LOCAL_CREATE_FINALS = myCbFinal.isSelected();
}
if (myCbVarType.isVisible() && myCbVarType.isEnabled()) {
JavaRefactoringSettings.getInstance().INTRODUCE_LOCAL_CREATE_VAR_TYPE = myCbVarType.isSelected();
}
super.doOKAction();
}
@@ -24,6 +24,10 @@ public interface IntroduceVariableSettings {
boolean isReplaceAllOccurrences();
boolean isDeclareFinal();
default boolean isDeclareVarType() {
return false;
}
boolean isReplaceLValues();
@@ -42,6 +42,7 @@ import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.refactoring.JavaRefactoringSettings;
import com.intellij.refactoring.RefactoringActionHandler;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.introduceParameter.AbstractJavaInplaceIntroducer;
import com.intellij.refactoring.rename.ResolveSnapshotProvider;
import com.intellij.refactoring.rename.inplace.VariableInplaceRenamer;
@@ -65,6 +66,7 @@ public class JavaVariableInplaceIntroducer extends AbstractJavaInplaceIntroducer
private SmartPsiElementPointer<? extends PsiElement> myPointer;
private JCheckBox myCanBeFinalCb;
private JCheckBox myCanBeVarTypeCb;
private final IntroduceVariableSettings mySettings;
private final SmartPsiElementPointer<PsiElement> myChosenAnchor;
private final boolean myCantChangeFinalModifier;
@@ -75,6 +77,7 @@ public class JavaVariableInplaceIntroducer extends AbstractJavaInplaceIntroducer
private boolean myDeleteSelf = true;
private final boolean mySkipTypeExpressionOnStart;
private final PsiFile myFile;
private final boolean myCanBeVarType;
public JavaVariableInplaceIntroducer(final Project project,
IntroduceVariableSettings settings, PsiElement chosenAnchor, final Editor editor,
@@ -100,6 +103,7 @@ public class JavaVariableInplaceIntroducer extends AbstractJavaInplaceIntroducer
PsiElement parent = myExpr.getParent();
myReplaceSelf = parent instanceof PsiExpressionStatement && !(parent.getParent() instanceof PsiSwitchLabeledRuleStatement);
mySkipTypeExpressionOnStart = !(myExpr instanceof PsiFunctionalExpression && myReplaceSelf);
myCanBeVarType = IntroduceVariableBase.canBeExtractedWithoutExplicitType(myExpr);
}
@Override
@@ -189,6 +193,10 @@ public class JavaVariableInplaceIntroducer extends AbstractJavaInplaceIntroducer
JavaRefactoringSettings.getInstance().INTRODUCE_LOCAL_CREATE_FINALS = psiVariable.hasModifierProperty(PsiModifier.FINAL);
}
if (myCanBeVarTypeCb != null) {
JavaRefactoringSettings.getInstance().INTRODUCE_LOCAL_CREATE_VAR_TYPE = myCanBeVarTypeCb.isSelected();
}
final Document document = myEditor.getDocument();
LOG.assertTrue(psiVariable.isValid());
adjustLine(psiVariable, document);
@@ -235,6 +243,7 @@ public class JavaVariableInplaceIntroducer extends AbstractJavaInplaceIntroducer
@Override
@Nullable
protected JComponent getComponent() {
if (myCantChangeFinalModifier && !(myCanBeVarType && getVariable() instanceof PsiLocalVariable)) return null;
if (!myCantChangeFinalModifier) {
myCanBeFinalCb = new NonFocusableCheckBox("Declare final");
myCanBeFinalCb.setSelected(createFinals());
@@ -252,19 +261,49 @@ public class JavaVariableInplaceIntroducer extends AbstractJavaInplaceIntroducer
});
}
});
} else {
return null;
}
if (myCanBeVarType && getVariable() instanceof PsiLocalVariable) {
myCanBeVarTypeCb = new NonFocusableCheckBox(RefactoringBundle.message("declare.var.type"));
myCanBeVarTypeCb.setSelected(IntroduceVariableBase.createVarType());
myCanBeVarTypeCb.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
WriteCommandAction.writeCommandAction(myProject).withName(getCommandName()).withGroupId(getCommandName()).run(() -> {
final PsiVariable variable = getVariable();
if (variable != null) {
PsiTypeElement typeElement = variable.getTypeElement();
LOG.assertTrue(typeElement != null);
if (myCanBeVarTypeCb.isSelected()) {
IntroduceVariableBase.expandDiamondsAndReplaceExplicitTypeWithVar(typeElement, variable);
}
else {
typeElement = PsiTypesUtil.replaceWithExplicitType(typeElement);
if (typeElement != null) { //simplify as it was before `var`
IntroduceVariableBase.simplifyVariableInitializer(variable.getInitializer(), typeElement.getType());
}
}
}
});
}
});
}
final JPanel panel = new JPanel(new GridBagLayout());
panel.setBorder(null);
GridBagConstraints gridBagConstraints = new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL,
JBUI.insets(5), 0, 0);
if (myCanBeFinalCb != null) {
panel.add(myCanBeFinalCb, new GridBagConstraints(0, 1, 1, 1, 1, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL,
JBUI.insets(5), 0, 0));
panel.add(myCanBeFinalCb, gridBagConstraints);
}
panel.add(Box.createVerticalBox(), new GridBagConstraints(0, 2, 1, 1, 1, 1, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH,
JBUI.emptyInsets(), 0, 0));
if (myCanBeVarTypeCb != null) {
panel.add(myCanBeVarTypeCb, gridBagConstraints);
}
gridBagConstraints.fill = GridBagConstraints.BOTH;
panel.add(Box.createVerticalBox(), gridBagConstraints);
return panel;
}
@@ -106,6 +106,11 @@ class VariableExtractor {
highlight(var);
PsiUtil.setModifierProperty(var, PsiModifier.FINAL, mySettings.isDeclareFinal());
if (mySettings.isDeclareVarType()) {
PsiTypeElement typeElement = var.getTypeElement();
LOG.assertTrue(typeElement != null);
IntroduceVariableBase.expandDiamondsAndReplaceExplicitTypeWithVar(typeElement, var);
}
myFieldConflictsResolver.fix();
return SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(var);
}
@@ -0,0 +1,9 @@
import java.util.*;
class MyTest {
{
var temp = new ArrayList<String>();
foo(temp);
}
void foo(List<String> l) {}
}
@@ -0,0 +1,8 @@
import java.util.*;
class MyTest {
{
foo(<selection>new ArrayList<>()</selection>);
}
void foo(List<String> l) {}
}
@@ -9,6 +9,7 @@ import com.intellij.psi.CommonClassNames;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiType;
import com.intellij.refactoring.JavaRefactoringSettings;
import com.intellij.refactoring.introduceVariable.InputValidator;
import com.intellij.refactoring.introduceVariable.IntroduceVariableBase;
import com.intellij.refactoring.introduceVariable.IntroduceVariableSettings;
@@ -263,6 +264,10 @@ public class IntroduceVariableTest extends LightCodeInsightTestCase {
doTest(new MockIntroduceVariableHandler("temp", true, false, false, CommonClassNames.JAVA_LANG_STRING));
}
public void testVarTypeExtractedJava10() {
doTestWithVarType(new MockIntroduceVariableHandler("temp", true, false, false, "java.util.ArrayList<java.lang.String>"));
}
public void testDeclareTernary() {
doTest(new MockIntroduceVariableHandler("temp", true, false, false, CommonClassNames.JAVA_LANG_STRING));
}
@@ -681,6 +686,17 @@ public class IntroduceVariableTest extends LightCodeInsightTestCase {
public void testChooseTypeExpressionWhenNotDenotable() { doTest(new MockIntroduceVariableHandler("m", false, false, false, "Foo")); }
public void testChooseTypeExpressionWhenNotDenotable1() { doTest(new MockIntroduceVariableHandler("m", false, false, false, "Foo<?>")); }
private void doTestWithVarType(IntroduceVariableBase testMe) {
Boolean asVarType = JavaRefactoringSettings.getInstance().INTRODUCE_LOCAL_CREATE_VAR_TYPE;
try {
JavaRefactoringSettings.getInstance().INTRODUCE_LOCAL_CREATE_VAR_TYPE = true;
doTest(testMe);
}
finally {
JavaRefactoringSettings.getInstance().INTRODUCE_LOCAL_CREATE_VAR_TYPE = asVarType;
}
}
private void doTest(IntroduceVariableBase testMe) {
String baseName = "/refactoring/introduceVariable/" + getTestName(false);
configureByFile(baseName + ".java");
@@ -52,6 +52,7 @@ class MockIntroduceVariableHandler extends IntroduceVariableBase {
PsiType defaultType = typeSelectorManager.getDefaultType();
PsiType type = myLookForType ? findType(typeSelectorManager.getTypesForAll(), defaultType) : defaultType;
assertEquals(myExpectedTypeText, type.getInternalCanonicalText());
boolean isDeclareVarType = canBeExtractedWithoutExplicitType(expr) && createVarType();
IntroduceVariableSettings introduceVariableSettings = new IntroduceVariableSettings() {
@Override
public String getEnteredName() {
@@ -82,6 +83,11 @@ class MockIntroduceVariableHandler extends IntroduceVariableBase {
public boolean isOK() {
return true;
}
@Override
public boolean isDeclareVarType() {
return isDeclareVarType;
}
};
boolean validationResult = validator.isOK(introduceVariableSettings);
assertValidationResult(validationResult);
@@ -126,6 +126,7 @@ extractSuperInterface.javadoc=JavaDoc
no.interface.name.specified=No interface name specified
replace.all.occurences=Replace &all occurrences ({0} occurrences)
declare.final=Declare &final
declare.var.type=Declare &var type
introduce.parameter.title=Extract Parameter
parameter.of.type=Parameter of &type:
use.variable.initializer.to.initialize.parameter=Use variable &initializer to initialize parameter
@@ -1,12 +1,12 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInspection;
import com.intellij.openapi.project.Project;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.impl.PsiDiamondTypeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.introduceVariable.IntroduceVariableBase;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
@@ -46,7 +46,7 @@ public class RedundantExplicitVariableTypeInspection extends AbstractBaseJavaLoc
PsiTypeElement element2Highlight) {
PsiTypeElement typeElementCopy = copyVariable.getTypeElement();
if (typeElementCopy != null) {
replaceExplicitTypeWithVar(typeElementCopy, variable);
IntroduceVariableBase.expandDiamondsAndReplaceExplicitTypeWithVar(typeElementCopy, variable);
if (variable.getType().equals(copyVariable.getType())) {
holder.registerProblem(element2Highlight,
"Explicit type of local variable can be omitted",
@@ -58,22 +58,6 @@ public class RedundantExplicitVariableTypeInspection extends AbstractBaseJavaLoc
};
}
private static PsiElement replaceExplicitTypeWithVar(PsiTypeElement typeElement, PsiElement context) {
PsiElement parent = typeElement.getParent();
if (parent instanceof PsiVariable) {
PsiExpression copyVariableInitializer = ((PsiVariable)parent).getInitializer();
if (copyVariableInitializer instanceof PsiNewExpression) {
final PsiDiamondType.DiamondInferenceResult diamondResolveResult =
PsiDiamondTypeImpl.resolveInferredTypesNoCheck((PsiNewExpression)copyVariableInitializer, copyVariableInitializer);
if (!diamondResolveResult.getInferredTypes().isEmpty()) {
PsiDiamondTypeUtil.expandTopLevelDiamondsInside(copyVariableInitializer);
}
}
}
return typeElement.replace(JavaPsiFacade.getElementFactory(context.getProject()).createTypeElementFromText("var", context));
}
private static class ReplaceWithVarFix implements LocalQuickFix {
@Nls
@NotNull
@@ -87,7 +71,7 @@ public class RedundantExplicitVariableTypeInspection extends AbstractBaseJavaLoc
PsiElement element = descriptor.getPsiElement();
if (element instanceof PsiTypeElement) {
CodeStyleManager.getInstance(project)
.reformat(replaceExplicitTypeWithVar((PsiTypeElement)element, element));
.reformat(IntroduceVariableBase.expandDiamondsAndReplaceExplicitTypeWithVar((PsiTypeElement)element, element));
}
}
}