From c3b7a9b1c6861d3e68e0465b76fd00cbeb7e2b4c Mon Sep 17 00:00:00 2001 From: "Anna.Kozlova" Date: Tue, 5 Feb 2019 15:22:04 +0100 Subject: [PATCH] optionally introduce local as var type since java 10 (IDEA-179176) --- .../refactoring/JavaRefactoringSettings.java | 1 + .../IntroduceVariableBase.java | 41 +++++++++++++++ .../IntroduceVariableDialog.java | 32 ++++++++++++ .../IntroduceVariableSettings.java | 4 ++ .../JavaVariableInplaceIntroducer.java | 51 ++++++++++++++++--- .../introduceVariable/VariableExtractor.java | 5 ++ .../VarTypeExtractedJava10.after.java | 9 ++++ .../VarTypeExtractedJava10.java | 8 +++ .../refactoring/IntroduceVariableTest.java | 16 ++++++ .../MockIntroduceVariableHandler.java | 6 +++ .../src/messages/RefactoringBundle.properties | 1 + ...dundantExplicitVariableTypeInspection.java | 24 ++------- 12 files changed, 172 insertions(+), 26 deletions(-) create mode 100644 java/java-tests/testData/refactoring/introduceVariable/VarTypeExtractedJava10.after.java create mode 100644 java/java-tests/testData/refactoring/introduceVariable/VarTypeExtractedJava10.java rename {java/java-analysis-impl => plugins/InspectionGadgets}/src/com/intellij/codeInspection/RedundantExplicitVariableTypeInspection.java (72%) diff --git a/java/java-impl/src/com/intellij/refactoring/JavaRefactoringSettings.java b/java/java-impl/src/com/intellij/refactoring/JavaRefactoringSettings.java index 707b2b940b1b..326f45aeb65f 100644 --- a/java/java-impl/src/com/intellij/refactoring/JavaRefactoringSettings.java +++ b/java/java-impl/src/com/intellij/refactoring/JavaRefactoringSettings.java @@ -79,6 +79,7 @@ public class JavaRefactoringSettings implements PersistentStateComponent 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, diff --git a/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceVariableDialog.java b/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceVariableDialog.java index 1d7a6d9a6816..a9f00572cd84 100644 --- a/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceVariableDialog.java +++ b/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceVariableDialog.java @@ -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(); } diff --git a/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceVariableSettings.java b/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceVariableSettings.java index a6d564198ec9..b17d7d6b2dad 100644 --- a/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceVariableSettings.java +++ b/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceVariableSettings.java @@ -24,6 +24,10 @@ public interface IntroduceVariableSettings { boolean isReplaceAllOccurrences(); boolean isDeclareFinal(); + + default boolean isDeclareVarType() { + return false; + } boolean isReplaceLValues(); diff --git a/java/java-impl/src/com/intellij/refactoring/introduceVariable/JavaVariableInplaceIntroducer.java b/java/java-impl/src/com/intellij/refactoring/introduceVariable/JavaVariableInplaceIntroducer.java index 31219e4390de..458dc37870d4 100644 --- a/java/java-impl/src/com/intellij/refactoring/introduceVariable/JavaVariableInplaceIntroducer.java +++ b/java/java-impl/src/com/intellij/refactoring/introduceVariable/JavaVariableInplaceIntroducer.java @@ -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 myPointer; private JCheckBox myCanBeFinalCb; + private JCheckBox myCanBeVarTypeCb; private final IntroduceVariableSettings mySettings; private final SmartPsiElementPointer 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; } diff --git a/java/java-impl/src/com/intellij/refactoring/introduceVariable/VariableExtractor.java b/java/java-impl/src/com/intellij/refactoring/introduceVariable/VariableExtractor.java index 2d190a651640..30cf240c07fe 100644 --- a/java/java-impl/src/com/intellij/refactoring/introduceVariable/VariableExtractor.java +++ b/java/java-impl/src/com/intellij/refactoring/introduceVariable/VariableExtractor.java @@ -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); } diff --git a/java/java-tests/testData/refactoring/introduceVariable/VarTypeExtractedJava10.after.java b/java/java-tests/testData/refactoring/introduceVariable/VarTypeExtractedJava10.after.java new file mode 100644 index 000000000000..60d48d670fb5 --- /dev/null +++ b/java/java-tests/testData/refactoring/introduceVariable/VarTypeExtractedJava10.after.java @@ -0,0 +1,9 @@ +import java.util.*; +class MyTest { + { + var temp = new ArrayList(); + foo(temp); + } + + void foo(List l) {} +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/introduceVariable/VarTypeExtractedJava10.java b/java/java-tests/testData/refactoring/introduceVariable/VarTypeExtractedJava10.java new file mode 100644 index 000000000000..56f79aa99ef1 --- /dev/null +++ b/java/java-tests/testData/refactoring/introduceVariable/VarTypeExtractedJava10.java @@ -0,0 +1,8 @@ +import java.util.*; +class MyTest { + { + foo(new ArrayList<>()); + } + + void foo(List l) {} +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/refactoring/IntroduceVariableTest.java b/java/java-tests/testSrc/com/intellij/java/refactoring/IntroduceVariableTest.java index a5eee78cba49..e6ee22dfe26d 100644 --- a/java/java-tests/testSrc/com/intellij/java/refactoring/IntroduceVariableTest.java +++ b/java/java-tests/testSrc/com/intellij/java/refactoring/IntroduceVariableTest.java @@ -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")); + } + 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"); diff --git a/java/java-tests/testSrc/com/intellij/java/refactoring/MockIntroduceVariableHandler.java b/java/java-tests/testSrc/com/intellij/java/refactoring/MockIntroduceVariableHandler.java index ec307df0af1e..3d90ebc204d3 100644 --- a/java/java-tests/testSrc/com/intellij/java/refactoring/MockIntroduceVariableHandler.java +++ b/java/java-tests/testSrc/com/intellij/java/refactoring/MockIntroduceVariableHandler.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); diff --git a/platform/platform-resources-en/src/messages/RefactoringBundle.properties b/platform/platform-resources-en/src/messages/RefactoringBundle.properties index 59d74dbe0875..60f4d5346ae1 100644 --- a/platform/platform-resources-en/src/messages/RefactoringBundle.properties +++ b/platform/platform-resources-en/src/messages/RefactoringBundle.properties @@ -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 diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/RedundantExplicitVariableTypeInspection.java b/plugins/InspectionGadgets/src/com/intellij/codeInspection/RedundantExplicitVariableTypeInspection.java similarity index 72% rename from java/java-analysis-impl/src/com/intellij/codeInspection/RedundantExplicitVariableTypeInspection.java rename to plugins/InspectionGadgets/src/com/intellij/codeInspection/RedundantExplicitVariableTypeInspection.java index 0ae260304d00..e9589af688e2 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/RedundantExplicitVariableTypeInspection.java +++ b/plugins/InspectionGadgets/src/com/intellij/codeInspection/RedundantExplicitVariableTypeInspection.java @@ -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)); } } }