wrap with optional IDEA-122553

This commit is contained in:
Dmitry Batkovich
2016-03-25 13:50:03 +03:00
parent c06a613289
commit 6f49be7fcc
18 changed files with 367 additions and 5 deletions
@@ -270,4 +270,9 @@ public abstract class QuickFixFactory {
@NotNull
public abstract IntentionAction createWrapLongWithMathToIntExactFix(@Nullable PsiType type, @NotNull PsiExpression expression);
@NotNull
public IntentionAction createWrapWithOptionalFix(@Nullable PsiType type, @NotNull PsiExpression expression) {
throw new UnsupportedOperationException();
};
}
@@ -607,6 +607,7 @@ public class HighlightMethodUtil {
CastMethodArgumentFix.REGISTRAR.registerCastActions(candidates, methodCall, info, fixRange);
WrapArrayToArraysAsListFix.REGISTAR.registerCastActions(candidates, methodCall, info, fixRange);
WrapLongWithMathToIntExactFix.REGISTAR.registerCastActions(candidates, methodCall, info, fixRange);
WrapObjectWithOptionalOfNullableFix.REGISTAR.registerCastActions(candidates, methodCall, info, fixRange);
PermuteArgumentsFix.registerFix(info, methodCall, candidates, fixRange);
WrapExpressionFix.registerWrapAction(candidates, list.getExpressions(), info);
registerChangeParameterClassFix(methodCall, list, info);
@@ -694,6 +695,7 @@ public class HighlightMethodUtil {
CastMethodArgumentFix.REGISTRAR.registerCastActions(candidates, methodCall, info, fixRange);
WrapArrayToArraysAsListFix.REGISTAR.registerCastActions(candidates, methodCall, info, fixRange);
WrapLongWithMathToIntExactFix.REGISTAR.registerCastActions(candidates, methodCall, info, fixRange);
WrapObjectWithOptionalOfNullableFix.REGISTAR.registerCastActions(candidates, methodCall, info, fixRange);
PermuteArgumentsFix.registerFix(info, methodCall, candidates, fixRange);
WrapExpressionFix.registerWrapAction(candidates, list.getExpressions(), info);
registerChangeParameterClassFix(methodCall, list, info);
@@ -729,6 +731,7 @@ public class HighlightMethodUtil {
AddTypeArgumentsFix.REGISTRAR.registerCastActions(methodCandidates, methodCall, highlightInfo, fixRange);
WrapArrayToArraysAsListFix.REGISTAR.registerCastActions(methodCandidates, methodCall, highlightInfo, fixRange);
WrapLongWithMathToIntExactFix.REGISTAR.registerCastActions(methodCandidates, methodCall, highlightInfo, fixRange);
WrapObjectWithOptionalOfNullableFix.REGISTAR.registerCastActions(methodCandidates, methodCall, highlightInfo, fixRange);
registerMethodAccessLevelIntentions(methodCandidates, methodCall, list, highlightInfo);
registerChangeMethodSignatureFromUsageIntentions(methodCandidates, list, highlightInfo, fixRange);
RemoveRedundantArgumentsFix.registerIntentions(methodCandidates, list, highlightInfo, fixRange);
@@ -568,6 +568,7 @@ public class HighlightUtil extends HighlightUtilBase {
}
if (expression != null) {
QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createWrapLongWithMathToIntExactFix(lType, expression));
QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createWrapWithOptionalFix(lType, expression));
QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createWrapExpressionFix(lType, expression));
AddTypeArgumentsConditionalFix.register(highlightInfo, expression, lType);
}
@@ -91,8 +91,10 @@ public abstract class ArgumentFixerActionFactory {
if (!GenericsUtil.isFromExternalTypeLanguage(parameterType)) continue;
if (suggestedCasts.contains(parameterType.getCanonicalText())) continue;
if (exprType instanceof PsiPrimitiveType && parameterType instanceof PsiClassType) {
parameterType = PsiPrimitiveType.getUnboxedType(parameterType);
if (parameterType == null) continue;
PsiType unboxedParameterType = PsiPrimitiveType.getUnboxedType(parameterType);
if (unboxedParameterType != null) {
parameterType = unboxedParameterType;
}
}
// strict compare since even widening cast may help
if (Comparing.equal(exprType, parameterType)) continue;
@@ -45,5 +45,6 @@ public class ConstructorParametersFixer {
AddTypeArgumentsFix.REGISTRAR.registerCastActions(candidates, constructorCall, highlightInfo, fixRange);
WrapArrayToArraysAsListFix.REGISTAR.registerCastActions(candidates, constructorCall, highlightInfo, fixRange);
WrapLongWithMathToIntExactFix.REGISTAR.registerCastActions(candidates, constructorCall, highlightInfo, fixRange);
WrapObjectWithOptionalOfNullableFix.REGISTAR.registerCastActions(candidates, constructorCall, highlightInfo, fixRange);
}
}
@@ -0,0 +1,205 @@
/*
* Copyright 2000-2016 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.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.intention.HighPriorityAction;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement;
import com.intellij.codeInspection.dataFlow.DfaPsiUtil;
import com.intellij.codeInspection.dataFlow.Nullness;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* @author Dmitry Batkovich
*/
public class WrapObjectWithOptionalOfNullableFix extends MethodArgumentFix implements HighPriorityAction {
private static final Logger LOG = Logger.getInstance(WrapObjectWithOptionalOfNullableFix.class);
public static final ArgumentFixerActionFactory REGISTAR = new MyFixerActionFactory();
protected WrapObjectWithOptionalOfNullableFix(final @NotNull PsiExpressionList list,
final int i,
final @NotNull PsiType toType,
final @NotNull ArgumentFixerActionFactory fixerActionFactory) {
super(list, i, toType, fixerActionFactory);
}
@NotNull
@Override
public String getText() {
if (myArgList.getExpressions().length == 1) {
return QuickFixBundle.message("wrap.with.optional.single.parameter.text");
}
else {
return QuickFixBundle.message("wrap.with.optional.parameter.text", myIndex + 1);
}
}
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
return PsiUtil.isLanguageLevel8OrHigher(file) && super.isAvailable(project, editor, file);
}
public static class MyIntentionAction extends LocalQuickFixAndIntentionActionOnPsiElement implements HighPriorityAction {
@Nullable private final PsiType myType;
protected MyIntentionAction(@NotNull PsiExpression element, @Nullable PsiType type) {
super(element);
myType = type;
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return QuickFixBundle.message("wrap.with.optional.single.parameter.text");
}
@Override
public void invoke(@NotNull Project project,
@NotNull PsiFile file,
@Nullable("is null when called from inspection") Editor editor,
@NotNull PsiElement startElement,
@NotNull PsiElement endElement) {
startElement.replace(getModifiedExpression((PsiExpression)getStartElement()));
}
@Override
public boolean isAvailable(@NotNull Project project,
@NotNull PsiFile file,
@NotNull PsiElement startElement,
@NotNull PsiElement endElement) {
return startElement.isValid() &&
startElement.getManager().isInProject(startElement) &&
PsiUtil.isLanguageLevel8OrHigher(startElement) && areConvertible(((PsiExpression) startElement).getType(), myType);
}
@NotNull
@Override
public String getText() {
return getFamilyName();
}
}
public static IntentionAction createFix(@Nullable PsiType type, @NotNull PsiExpression expression) {
class MyFix extends LocalQuickFixAndIntentionActionOnPsiElement implements HighPriorityAction {
protected MyFix(@Nullable PsiElement element) {
super(element);
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return QuickFixBundle.message("wrap.with.optional.single.parameter.text");
}
@Override
public void invoke(@NotNull Project project,
@NotNull PsiFile file,
@Nullable("is null when called from inspection") Editor editor,
@NotNull PsiElement startElement,
@NotNull PsiElement endElement) {
startElement.replace(getModifiedExpression((PsiExpression)getStartElement()));
}
@Override
public boolean isAvailable(@NotNull Project project,
@NotNull PsiFile file,
@NotNull PsiElement startElement,
@NotNull PsiElement endElement) {
return startElement.isValid() &&
startElement.getManager().isInProject(startElement) &&
PsiUtil.isLanguageLevel8OrHigher(startElement) && areConvertible(expression.getType(), type);
}
@NotNull
@Override
public String getText() {
return getFamilyName();
}
}
return new MyFix(expression);
}
public static class MyFixerActionFactory extends ArgumentFixerActionFactory {
@Nullable
@Override
protected PsiExpression getModifiedArgument(final PsiExpression expression, final PsiType toType) throws IncorrectOperationException {
return getModifiedExpression(expression);
}
@Override
public boolean areTypesConvertible(final PsiType exprType, final PsiType parameterType, final PsiElement context) {
return parameterType.isConvertibleFrom(exprType) || areConvertible(exprType, parameterType);
}
@Override
public MethodArgumentFix createFix(final PsiExpressionList list, final int i, final PsiType toType) {
return new WrapObjectWithOptionalOfNullableFix(list, i, toType, this);
}
}
private static boolean areConvertible(@Nullable PsiType exprType, @Nullable PsiType parameterType) {
if (exprType == null || !(parameterType instanceof PsiClassType)) {
return false;
}
final PsiClassType.ClassResolveResult resolve = ((PsiClassType)parameterType).resolveGenerics();
final PsiClass resolvedClass = resolve.getElement();
if (resolvedClass == null || !CommonClassNames.JAVA_UTIL_OPTIONAL.equals(resolvedClass.getQualifiedName())) return false;
final Collection<PsiType> values = resolve.getSubstitutor().getSubstitutionMap().values();
if (values.size() == 0) return true;
if (values.size() > 1) return false;
final PsiType optionalTypeParameter = ContainerUtil.getFirstItem(values);
if (optionalTypeParameter == null) return false;
return TypeConversionUtil.isAssignable(optionalTypeParameter, exprType);
}
@NotNull
private static PsiExpression getModifiedExpression(PsiExpression expression) {
final Project project = expression.getProject();
PsiModifierListOwner toCheckNullability = null;
if (expression instanceof PsiMethodCallExpression) {
toCheckNullability = ((PsiMethodCallExpression)expression).resolveMethod();
}
else if (expression instanceof PsiReferenceExpression) {
final PsiElement resolved = ((PsiReferenceExpression)expression).resolve();
if (resolved instanceof PsiModifierListOwner) {
toCheckNullability = (PsiModifierListOwner)resolved;
}
}
final Nullness nullability = toCheckNullability == null ? Nullness.NOT_NULL : DfaPsiUtil
.getElementNullability(expression.getType(), toCheckNullability);
String methodName = nullability == Nullness.NOT_NULL ? "of" : "ofNullable";
final String newExpressionText = CommonClassNames.JAVA_UTIL_OPTIONAL + "." + methodName + "(" + expression.getText() + ")";
return JavaPsiFacade.getElementFactory(project).createExpressionFromText(newExpressionText, expression);
}
}
@@ -620,4 +620,10 @@ public class EmptyQuickFixFactory extends QuickFixFactory {
public IntentionAction createWrapLongWithMathToIntExactFix(@Nullable PsiType type, @NotNull PsiExpression expression) {
return QuickFixes.EMPTY_FIX;
}
@NotNull
@Override
public IntentionAction createWrapWithOptionalFix(@Nullable PsiType type, @NotNull PsiExpression expression) {
return QuickFixes.EMPTY_FIX;
}
}
@@ -780,6 +780,12 @@ public class QuickFixFactoryImpl extends QuickFixFactory {
return new WrapLongWithMathToIntExactFix(type, expression);
}
@NotNull
@Override
public IntentionAction createWrapWithOptionalFix(@Nullable PsiType type, @NotNull PsiExpression expression) {
return WrapObjectWithOptionalOfNullableFix.createFix(type, expression);
}
private static boolean timeToOptimizeImports(@NotNull PsiFile file) {
if (!CodeInsightSettings.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY) return false;
@@ -0,0 +1,10 @@
// "Wrap using 'java.util.Optional'" "true"
import java.util.Optional;
public class Test {
void m() {
Optional<String> o = Optional.of("some string value");
}
}
@@ -0,0 +1,15 @@
// "Wrap 2nd parameter using 'java.util.Optional'" "true"
import java.util.Optional;
public class Test {
void m() {
long ll = 10;
f(10, Optional.ofNullable(ll), 10);
}
void f(long j, Optional<Number> o, int i) {
}
}
@@ -0,0 +1,14 @@
// "Wrap using 'java.util.Optional'" "true"
import java.util.Optional;
public class Test {
void m(String ss) {
f(Optional.ofNullable(ss));
}
void f(Optional<String> o) {
}
}
@@ -0,0 +1,10 @@
// "Wrap using 'java.util.Optional'" "true"
import java.util.Optional;
public class Test {
void m() {
Optional<String> o = "some <caret>string value";
}
}
@@ -0,0 +1,14 @@
// "Wrap using 'java.util.Optional'" "false"
import java.util.Optional;
public class Test {
void m(String ss) {
f(ss);
}
void f(Optional<Long> o) {
}
}
@@ -0,0 +1,15 @@
// "Wrap 2nd parameter using 'java.util.Optional'" "true"
import java.util.Optional;
public class Test {
void m() {
long ll = 10;
f(10, l<caret>l, 10);
}
void f(long j, Optional<Number> o, int i) {
}
}
@@ -0,0 +1,14 @@
// "Wrap using 'java.util.Optional'" "true"
import java.util.Optional;
public class Test {
void m(String ss) {
f(s<caret>s);
}
void f(Optional<String> o) {
}
}
@@ -0,0 +1,38 @@
/*
* Copyright 2000-2016 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.codeInsight.daemon.quickFix;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.testFramework.IdeaTestUtil;
/**
* @author Dmitry Batkovich
*/
public class WrapObjectWithOptionalFixTest extends LightQuickFixParameterizedTestCase {
public void test() throws Exception {
doAllTests();
}
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/wrapObjectWithOptional";
}
@Override
protected Sdk getProjectJDK() {
return IdeaTestUtil.getMockJdk18();
}
}
@@ -16,6 +16,7 @@
package com.intellij.ui.treeStructure.treetable;
import com.intellij.ui.table.JBTable;
import com.intellij.util.ui.JBUI;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
@@ -52,7 +53,6 @@ public class TreeTable extends JBTable {
public TreeTable(TreeTableModel treeTableModel) {
super();
setModel(treeTableModel);
}
@@ -92,7 +92,7 @@ public class TreeTable extends JBTable {
// And update the height of the trees row to match that of the table.
if (myTree.getRowHeight() < 1) {
setRowHeight(18); // Metal looks better like this.
setRowHeight(JBUI.scale(18)); // Metal looks better like this.
}
else {
setRowHeight(getRowHeight());
@@ -282,4 +282,7 @@ add.exception.from.field.initializer.to.constructor.throws.text=Add exception to
add.exception.from.field.initializer.to.constructor.throws.family.text=Add exception to class constructors signature
java.8.collections.api.inspection.description=If statement could be replaced with single method
java.8.collections.api.inspection.fix.family.name=Replace with single method call
java.8.collections.api.inspection.fix.text=Replace with ''{0}'' method call
java.8.collections.api.inspection.fix.text=Replace with ''{0}'' method call
wrap.with.optional.parameter.text=Wrap {0, choice, 1#1st|2#2nd|3#3rd|4#{0,number}th} parameter using ''java.util.Optional''
wrap.with.optional.single.parameter.text=Wrap using 'java.util.Optional'