diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/rendering/DefaultErrorMessages.java index 9f3ece958b3..eea180cf368 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/rendering/DefaultErrorMessages.java @@ -292,7 +292,7 @@ public class DefaultErrorMessages { MAP.put(ANONYMOUS_INITIALIZER_WITHOUT_CONSTRUCTOR, "Anonymous initializers are only allowed in the presence of a primary constructor"); MAP.put(NULLABLE_SUPERTYPE, "A supertype cannot be nullable"); - MAP.put(UNSAFE_CALL, "Only safe calls (?.) are allowed on a nullable receiver of type {0}", RENDER_TYPE); + MAP.put(UNSAFE_CALL, "Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type {0}", RENDER_TYPE); MAP.put(AMBIGUOUS_LABEL, "Ambiguous label"); MAP.put(UNSUPPORTED, "Unsupported [{0}]", TO_STRING); MAP.put(UNNECESSARY_SAFE_CALL, "Unnecessary safe call on a non-null receiver of type {0}", RENDER_TYPE); diff --git a/idea/src/org/jetbrains/jet/plugin/JetBundle.properties b/idea/src/org/jetbrains/jet/plugin/JetBundle.properties index dbd54120d2d..c757cb4a736 100644 --- a/idea/src/org/jetbrains/jet/plugin/JetBundle.properties +++ b/idea/src/org/jetbrains/jet/plugin/JetBundle.properties @@ -23,7 +23,9 @@ remove.elvis.operator=Remove elvis operator replace.operation.in.binary.expression=Replace operation in a binary expression replace.cast.with.static.assert=Replace a cast with a static assert replace.with.dot.call=Replace with dot call -replace.with.safe.call=Replace with safe call +replace.with.safe.call=Replace with safe (?.) call +replace.with.nna.call=Replace with non-null asserted (!!.) call +remove.unnecessary.non.null.assertion=Remove unnecessary non-null assertion (!!) change.to.backing.field=Change reference to backing field implement.members=Implement members new.kotlin.file.action=Kotlin File diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java b/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java index ee3e4874220..0b98f37fb27 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java @@ -133,8 +133,14 @@ public class QuickFixes { actions.put(VAL_WITH_SETTER, changeVariableMutabilityFix); actions.put(VAL_REASSIGNMENT, changeVariableMutabilityFix); - actions.put(UNNECESSARY_SAFE_CALL, new ReplaceCallFix(false)); - actions.put(UNSAFE_CALL, new ReplaceCallFix(true)); + actions.put(UNNECESSARY_SAFE_CALL, ReplaceCallFix.toDotCallFromSafeCall()); + actions.put(UNSAFE_CALL, ReplaceCallFix.toSafeCall()); + + actions.put(UNSAFE_CALL, ReplaceCallFix.toNonNullAssertedCall()); + actions.put(UNNECESSARY_NOT_NULL_ASSERTION, ReplaceCallFix.toDotCallFromNonNullAssertedCall()); + + actions.put(UNNECESSARY_NOT_NULL_ASSERTION, new UnnecessaryNotNullAssertionFix()); + actions.put(PUBLIC_MEMBER_SHOULD_SPECIFY_TYPE, new SpecifyTypeExplicitlyFix()); } -} \ No newline at end of file +} diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/ReplaceCallFix.java b/idea/src/org/jetbrains/jet/plugin/quickfix/ReplaceCallFix.java index 1a6fa7c04a0..0faf723cab8 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/ReplaceCallFix.java +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/ReplaceCallFix.java @@ -21,33 +21,67 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; +import com.intellij.psi.impl.source.tree.LeafPsiElement; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.lang.psi.*; +import org.jetbrains.jet.lexer.JetTokens; import org.jetbrains.jet.plugin.JetBundle; /** * @author svtk + * @author slukjanov aka Frostman */ public class ReplaceCallFix implements IntentionAction { - private final boolean toSafe; + private final boolean safe; + private final boolean fromDot; - public ReplaceCallFix(boolean toSafe) { - this.toSafe = toSafe; + private ReplaceCallFix(boolean safe, boolean fromDot) { + this.safe = safe; + this.fromDot = fromDot; } + /** + * @return quickfix for replacing dot call with safe (?.) call + */ + public static ReplaceCallFix toSafeCall() { + return new ReplaceCallFix(true, true); + } + + /** + * @return quickfix for replacing dot call with non-null asserted (!!.) call + */ + public static ReplaceCallFix toNonNullAssertedCall() { + return new ReplaceCallFix(false, true); + } + + /** + * @return quickfix for replacing unnecessary safe (?.) call with dot call + */ + public static ReplaceCallFix toDotCallFromSafeCall() { + return new ReplaceCallFix(true, false); + } + + /** + * @return quickfix for replacing unnecessary non-null asserted (!!.) call with dot call + */ + public static ReplaceCallFix toDotCallFromNonNullAssertedCall() { + return new ReplaceCallFix(false, false); + } @NotNull @Override public String getText() { - return toSafe ? JetBundle.message("replace.with.safe.call") : JetBundle.message("replace.with.dot.call"); + return fromDot + ? (safe ? JetBundle.message("replace.with.safe.call") : JetBundle.message("replace.with.nna.call")) + : JetBundle.message("replace.with.dot.call"); } @NotNull @Override public String getFamilyName() { - return toSafe ? JetBundle.message("replace.with.safe.call") : JetBundle.message("replace.with.dot.call"); + return getText(); } @Override @@ -58,11 +92,6 @@ public class ReplaceCallFix implements IntentionAction { return false; } - private JetQualifiedExpression getCallExpression(@NotNull Editor editor, @NotNull JetFile file) { - final PsiElement elementAtCaret = file.findElementAt(editor.getCaretModel().getOffset()); - return PsiTreeUtil.getParentOfType(elementAtCaret, toSafe ? JetDotQualifiedExpression.class : JetSafeQualifiedExpression.class); - } - @Override public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { JetQualifiedExpression callExpression = getCallExpression(editor, (JetFile) file); @@ -70,8 +99,29 @@ public class ReplaceCallFix implements IntentionAction { JetExpression selector = callExpression.getSelectorExpression(); if (selector != null) { + if (!fromDot && !safe) { + final PsiElement elementAtCaret = getElementAtCaret(editor, file); + if (elementAtCaret instanceof LeafPsiElement) { + final LeafPsiElement leafElement = (LeafPsiElement) elementAtCaret; + PsiElement exclExclElement = null; + if (leafElement.getElementType() == JetTokens.EXCLEXCL) { + exclExclElement = leafElement; + } + else if (leafElement.getElementType() == JetTokens.DOT) { + PsiElement prevSibling = leafElement.getPrevSibling(); + if (prevSibling != null) { + exclExclElement = prevSibling.getLastChild(); + } + } + if (exclExclElement != null) { + exclExclElement.delete(); + } + } + } + JetQualifiedExpression newElement = (JetQualifiedExpression) JetPsiFactory.createExpression( - project, callExpression.getReceiverExpression().getText() + (toSafe ? "?." : ".") + selector.getText()); + project, + callExpression.getReceiverExpression().getText() + (fromDot ? (safe ? "?." : "!!.") : ".") + selector.getText()); callExpression.replace(newElement); } @@ -81,4 +131,14 @@ public class ReplaceCallFix implements IntentionAction { public boolean startInWriteAction() { return true; } + + private JetQualifiedExpression getCallExpression(@NotNull Editor editor, @NotNull JetFile file) { + final PsiElement elementAtCaret = getElementAtCaret(editor, file); + return PsiTreeUtil + .getParentOfType(elementAtCaret, fromDot || !safe ? JetDotQualifiedExpression.class : JetSafeQualifiedExpression.class); + } + + private static PsiElement getElementAtCaret(Editor editor, PsiFile file) { + return file.findElementAt(editor.getCaretModel().getOffset()); + } } diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/UnnecessaryNotNullAssertionFix.java b/idea/src/org/jetbrains/jet/plugin/quickfix/UnnecessaryNotNullAssertionFix.java new file mode 100644 index 00000000000..c512c589d3a --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/UnnecessaryNotNullAssertionFix.java @@ -0,0 +1,73 @@ +/* + * Copyright 2010-2012 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 org.jetbrains.jet.plugin.quickfix; + +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.impl.source.tree.LeafPsiElement; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.lang.psi.JetFile; +import org.jetbrains.jet.lexer.JetTokens; +import org.jetbrains.jet.plugin.JetBundle; + +/** + * @author slukjanov aka Frostman + */ +public class UnnecessaryNotNullAssertionFix implements IntentionAction { + @NotNull + @Override + public String getText() { + return JetBundle.message("remove.unnecessary.non.null.assertion"); + } + + @NotNull + @Override + public String getFamilyName() { + return getText(); + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + return file instanceof JetFile && getExclExclElement(editor, file) != null; + } + + @Override + public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + final PsiElement exclExcl = getExclExclElement(editor, file); + assert exclExcl != null; + + exclExcl.delete(); + } + + @Override + public boolean startInWriteAction() { + return true; + } + + private static PsiElement getExclExclElement(Editor editor, PsiFile file) { + final PsiElement elementAtCaret = file.findElementAt(editor.getCaretModel().getOffset()); + if (elementAtCaret instanceof LeafPsiElement && ((LeafPsiElement) elementAtCaret).getElementType() == JetTokens.EXCLEXCL) { + return elementAtCaret; + } + + return null; + } +} diff --git a/idea/testData/quickfix/expressions/afterUnnecessaryNonNullAssertion1.kt b/idea/testData/quickfix/expressions/afterUnnecessaryNonNullAssertion1.kt new file mode 100644 index 00000000000..dd12e4a5f8a --- /dev/null +++ b/idea/testData/quickfix/expressions/afterUnnecessaryNonNullAssertion1.kt @@ -0,0 +1,4 @@ +// "Remove unnecessary non-null assertion (!!)" "true" +fun test(value : Int) : Int { + value +} diff --git a/idea/testData/quickfix/expressions/afterUnnecessaryNonNullAssertion2.kt b/idea/testData/quickfix/expressions/afterUnnecessaryNonNullAssertion2.kt new file mode 100644 index 00000000000..9acdb6fb04b --- /dev/null +++ b/idea/testData/quickfix/expressions/afterUnnecessaryNonNullAssertion2.kt @@ -0,0 +1,4 @@ +// "Replace with dot call" "true" +fun test(value : String) : Int { + return value.length() +} diff --git a/idea/testData/quickfix/expressions/afterUnsafeCall.kt b/idea/testData/quickfix/expressions/afterUnsafeCall.kt deleted file mode 100644 index af830372f6e..00000000000 --- a/idea/testData/quickfix/expressions/afterUnsafeCall.kt +++ /dev/null @@ -1,4 +0,0 @@ -// "Replace with safe call" "true" -fun foo(a: Int?) { - a?.plus(1) -} diff --git a/idea/testData/quickfix/expressions/afterUnsafeCall1.kt b/idea/testData/quickfix/expressions/afterUnsafeCall1.kt new file mode 100644 index 00000000000..52682323884 --- /dev/null +++ b/idea/testData/quickfix/expressions/afterUnsafeCall1.kt @@ -0,0 +1,4 @@ +// "Replace with safe (?.) call" "true" +fun foo(a: Int?) { + a?.plus(1) +} diff --git a/idea/testData/quickfix/expressions/afterUnsafeCall2.kt b/idea/testData/quickfix/expressions/afterUnsafeCall2.kt new file mode 100644 index 00000000000..41a6dd52fee --- /dev/null +++ b/idea/testData/quickfix/expressions/afterUnsafeCall2.kt @@ -0,0 +1,4 @@ +// "Replace with non-null asserted (!!.) call" "true" +fun foo(a: Int?) { + a!!.plus(1) +} diff --git a/idea/testData/quickfix/expressions/beforeUnnecessaryNonNullAssertion1.kt b/idea/testData/quickfix/expressions/beforeUnnecessaryNonNullAssertion1.kt new file mode 100644 index 00000000000..fd1312a806e --- /dev/null +++ b/idea/testData/quickfix/expressions/beforeUnnecessaryNonNullAssertion1.kt @@ -0,0 +1,4 @@ +// "Remove unnecessary non-null assertion (!!)" "true" +fun test(value : Int) : Int { + value!! +} diff --git a/idea/testData/quickfix/expressions/beforeUnnecessaryNonNullAssertion2.kt b/idea/testData/quickfix/expressions/beforeUnnecessaryNonNullAssertion2.kt new file mode 100644 index 00000000000..0bfbf91d678 --- /dev/null +++ b/idea/testData/quickfix/expressions/beforeUnnecessaryNonNullAssertion2.kt @@ -0,0 +1,4 @@ +// "Replace with dot call" "true" +fun test(value : String) : Int { + return value!!.length() +} diff --git a/idea/testData/quickfix/expressions/beforeUnsafeCall.kt b/idea/testData/quickfix/expressions/beforeUnsafeCall1.kt similarity index 51% rename from idea/testData/quickfix/expressions/beforeUnsafeCall.kt rename to idea/testData/quickfix/expressions/beforeUnsafeCall1.kt index 468555b4ff0..c15a29f54c4 100644 --- a/idea/testData/quickfix/expressions/beforeUnsafeCall.kt +++ b/idea/testData/quickfix/expressions/beforeUnsafeCall1.kt @@ -1,4 +1,4 @@ -// "Replace with safe call" "true" +// "Replace with safe (?.) call" "true" fun foo(a: Int?) { a.plus(1) } diff --git a/idea/testData/quickfix/expressions/beforeUnsafeCall2.kt b/idea/testData/quickfix/expressions/beforeUnsafeCall2.kt new file mode 100644 index 00000000000..9dc62d6af9e --- /dev/null +++ b/idea/testData/quickfix/expressions/beforeUnsafeCall2.kt @@ -0,0 +1,4 @@ +// "Replace with non-null asserted (!!.) call" "true" +fun foo(a: Int?) { + a.plus(1) +}