diff --git a/idea/src/org/jetbrains/jet/plugin/JetBundle.properties b/idea/src/org/jetbrains/jet/plugin/JetBundle.properties index c757cb4a736..ee407a64047 100644 --- a/idea/src/org/jetbrains/jet/plugin/JetBundle.properties +++ b/idea/src/org/jetbrains/jet/plugin/JetBundle.properties @@ -24,7 +24,7 @@ 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.nna.call=Replace with non-null asserted (!!.) call +introduce.non.null.assertion=Add 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 diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/ExclExclCallFix.java b/idea/src/org/jetbrains/jet/plugin/quickfix/ExclExclCallFix.java new file mode 100644 index 00000000000..a267e2c5822 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/ExclExclCallFix.java @@ -0,0 +1,174 @@ +/* + * 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.CodeInsightUtilBase; +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.psi.util.PsiTreeUtil; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.lang.psi.JetExpression; +import org.jetbrains.jet.lang.psi.JetFile; +import org.jetbrains.jet.lang.psi.JetPostfixExpression; +import org.jetbrains.jet.lang.psi.JetPsiFactory; +import org.jetbrains.jet.lexer.JetTokens; +import org.jetbrains.jet.plugin.JetBundle; + +/** + * @author slukjanov aka Frostman + */ +@SuppressWarnings("IntentionDescriptionNotFoundInspection") +public class ExclExclCallFix implements IntentionAction { + + private final boolean isRemove; + + private ExclExclCallFix(boolean remove) { + isRemove = remove; + } + + public static ExclExclCallFix removeExclExclCall() { + return new ExclExclCallFix(true); + } + + public static ExclExclCallFix introduceExclExclCall() { + return new ExclExclCallFix(false); + } + + @NotNull + @Override + public String getText() { + return isRemove ? JetBundle.message("remove.unnecessary.non.null.assertion") : JetBundle.message("introduce.non.null.assertion"); + } + + @NotNull + @Override + public String getFamilyName() { + return getText(); + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + if (file instanceof JetFile) { + if (!isRemove) { + return isAvailableForIntroduce(editor, file); + } + else { + return isAvailableForRemove(editor, file); + } + } + + return false; + } + + @Override + public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + if (!CodeInsightUtilBase.prepareFileForWrite(file)) { + return; + } + + if (!isRemove) { + JetExpression modifiedExpression = getExpressionForIntroduceCall(editor, file); + JetExpression exclExclExpression = JetPsiFactory.createExpression(project, modifiedExpression.getText() + "!!"); + modifiedExpression.replace(exclExclExpression); + } + else { + JetPostfixExpression postfixExpression = getExclExclPostfixExpression(editor, file); + JetExpression expression = JetPsiFactory.createExpression(project, postfixExpression.getBaseExpression().getText()); + postfixExpression.replace(expression); + } + } + + @Override + public boolean startInWriteAction() { + return true; + } + + private static boolean isAvailableForIntroduce(Editor editor, PsiFile file) { + return getExpressionForIntroduceCall(editor, file) != null; + } + + private static boolean isAvailableForRemove(Editor editor, PsiFile file) { + return getExclExclPostfixExpression(editor, file) != null; + } + + private static PsiElement getExclExclElement(Editor editor, PsiFile file) { + final PsiElement elementAtCaret = file.findElementAt(editor.getCaretModel().getOffset()); + if (elementAtCaret instanceof LeafPsiElement) { + LeafPsiElement leafElement = (LeafPsiElement) elementAtCaret; + if (leafElement.getElementType() == JetTokens.EXCLEXCL) { + return elementAtCaret; + } + + LeafPsiElement prevLeaf = (LeafPsiElement) PsiTreeUtil.prevLeaf(leafElement); + if (prevLeaf != null && prevLeaf.getElementType() == JetTokens.EXCLEXCL) { + return prevLeaf; + } + } + + return null; + } + + private static JetExpression getExpressionForIntroduceCall(Editor editor, PsiFile file) { + final PsiElement elementAtCaret = file.findElementAt(editor.getCaretModel().getOffset()); + if (elementAtCaret != null) { + JetExpression expression = getExpressionForIntroduceCall(elementAtCaret); + if (expression != null) { + return expression; + } + + // Maybe caret is after error element + expression = getExpressionForIntroduceCall(PsiTreeUtil.prevLeaf(elementAtCaret)); + if (expression != null) { + return expression; + } + } + + return null; + } + + private static JetExpression getExpressionForIntroduceCall(PsiElement problemElement) { + if (problemElement instanceof LeafPsiElement && ((LeafPsiElement) problemElement).getElementType() == JetTokens.DOT) { + PsiElement sibling = problemElement.getPrevSibling(); + if (sibling instanceof JetExpression) { + return (JetExpression) sibling; + } + } + + return null; + } + + private static JetPostfixExpression getExclExclPostfixExpression(Editor editor, PsiFile file) { + PsiElement exclExclElement = getExclExclElement(editor, file); + + if (exclExclElement != null) { + PsiElement parent = exclExclElement.getParent(); + if (parent != null) { + PsiElement operationParent = parent.getParent(); + if (operationParent instanceof JetPostfixExpression) { + return (JetPostfixExpression) operationParent; + } + } + } + + return null; + } +} diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java b/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java index 0b98f37fb27..9745d393651 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java @@ -136,10 +136,8 @@ public class QuickFixes { 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(UNSAFE_CALL, ExclExclCallFix.introduceExclExclCall()); + actions.put(UNNECESSARY_NOT_NULL_ASSERTION, ExclExclCallFix.removeExclExclCall()); actions.put(PUBLIC_MEMBER_SHOULD_SPECIFY_TYPE, new SpecifyTypeExplicitlyFix()); } diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/ReplaceCallFix.java b/idea/src/org/jetbrains/jet/plugin/quickfix/ReplaceCallFix.java index 0faf723cab8..395ed6a9521 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/ReplaceCallFix.java +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/ReplaceCallFix.java @@ -21,12 +21,10 @@ 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; /** @@ -34,48 +32,30 @@ import org.jetbrains.jet.plugin.JetBundle; * @author slukjanov aka Frostman */ public class ReplaceCallFix implements IntentionAction { - private final boolean safe; - private final boolean fromDot; + private final boolean toSafe; - private ReplaceCallFix(boolean safe, boolean fromDot) { - this.safe = safe; - this.fromDot = fromDot; + private ReplaceCallFix(boolean safe) { + this.toSafe = safe; } /** - * @return quickfix for replacing dot call with safe (?.) call + * @return quickfix for replacing dot call with toSafe (?.) call */ public static ReplaceCallFix toSafeCall() { - return new ReplaceCallFix(true, true); + return new ReplaceCallFix(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 + * @return quickfix for replacing unnecessary toSafe (?.) 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); + return new ReplaceCallFix(false); } @NotNull @Override public String getText() { - return fromDot - ? (safe ? JetBundle.message("replace.with.safe.call") : JetBundle.message("replace.with.nna.call")) - : JetBundle.message("replace.with.dot.call"); + return toSafe ? JetBundle.message("replace.with.safe.call") : JetBundle.message("replace.with.dot.call"); } @NotNull @@ -99,29 +79,8 @@ 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() + (fromDot ? (safe ? "?." : "!!.") : ".") + selector.getText()); + project, callExpression.getReceiverExpression().getText() + (toSafe ? "?." : ".") + selector.getText()); callExpression.replace(newElement); } @@ -134,8 +93,7 @@ public class ReplaceCallFix implements IntentionAction { 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); + return PsiTreeUtil.getParentOfType(elementAtCaret, toSafe ? JetDotQualifiedExpression.class : JetSafeQualifiedExpression.class); } private static PsiElement getElementAtCaret(Editor editor, PsiFile file) { diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/UnnecessaryNotNullAssertionFix.java b/idea/src/org/jetbrains/jet/plugin/quickfix/UnnecessaryNotNullAssertionFix.java deleted file mode 100644 index 2256f4897df..00000000000 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/UnnecessaryNotNullAssertionFix.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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.CodeInsightUtilBase; -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.*; -import org.jetbrains.jet.lexer.JetTokens; -import org.jetbrains.jet.plugin.JetBundle; - -/** - * @author slukjanov aka Frostman - */ -@SuppressWarnings("IntentionDescriptionNotFoundInspection") -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) { - if (file instanceof JetFile) { - JetPostfixExpression postfixExpression = getExclExclPostfixExpression(editor, file); - return (postfixExpression != null && postfixExpression.getParent() != null); - } - - return false; - } - - @Override - public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { - if (!CodeInsightUtilBase.prepareFileForWrite(file)) { - return; - } - - final JetPostfixExpression postfixExpression = getExclExclPostfixExpression(editor, file); - - PsiElement parent = postfixExpression.getParent(); - if (parent != null) { - JetExpression expression = JetPsiFactory.createExpression(project, postfixExpression.getBaseExpression().getText()); - postfixExpression.replace(expression); - } - } - - @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; - } - - private static JetPostfixExpression getExclExclPostfixExpression(Editor editor, PsiFile file) { - PsiElement exclExclElement = getExclExclElement(editor, file); - if (exclExclElement != null) { - PsiElement parent = exclExclElement.getParent(); - if (parent instanceof JetSimpleNameExpression) { - PsiElement operationParent = parent.getParent(); - if (operationParent instanceof JetPostfixExpression) { - return (JetPostfixExpression) operationParent; - } - } - } - - return null; - } -} diff --git a/idea/testData/quickfix/expressions/afterUnnecessaryNonNullAssertion2.kt b/idea/testData/quickfix/expressions/afterUnnecessaryNonNullAssertion2.kt index 723fcc294db..1f12846abb6 100644 --- a/idea/testData/quickfix/expressions/afterUnnecessaryNonNullAssertion2.kt +++ b/idea/testData/quickfix/expressions/afterUnnecessaryNonNullAssertion2.kt @@ -1,4 +1,4 @@ -// "Replace with dot call" "true" +// "Remove unnecessary non-null assertion (!!)" "true" fun test(value : String) { value.equals("test") } diff --git a/idea/testData/quickfix/expressions/afterUnnecessaryNonNullAssertion3.kt b/idea/testData/quickfix/expressions/afterUnnecessaryNonNullAssertion3.kt index e5ff0aeab16..9872deea316 100644 --- a/idea/testData/quickfix/expressions/afterUnnecessaryNonNullAssertion3.kt +++ b/idea/testData/quickfix/expressions/afterUnnecessaryNonNullAssertion3.kt @@ -1,4 +1,4 @@ -// "Remove unnecessary non-null assertion (!!)" "false" +// "Remove unnecessary non-null assertion (!!)" "true" fun test(value : String) : Int { - return value!!.length() + return value.length } diff --git a/idea/testData/quickfix/expressions/afterUnsafeCall2.kt b/idea/testData/quickfix/expressions/afterUnsafeCall2.kt index 41a6dd52fee..656198dc506 100644 --- a/idea/testData/quickfix/expressions/afterUnsafeCall2.kt +++ b/idea/testData/quickfix/expressions/afterUnsafeCall2.kt @@ -1,4 +1,4 @@ -// "Replace with non-null asserted (!!.) call" "true" +// "Add non-null asserted (!!) call" "true" fun foo(a: Int?) { a!!.plus(1) } diff --git a/idea/testData/quickfix/expressions/afterUnsafeCall3.kt b/idea/testData/quickfix/expressions/afterUnsafeCall3.kt new file mode 100644 index 00000000000..656198dc506 --- /dev/null +++ b/idea/testData/quickfix/expressions/afterUnsafeCall3.kt @@ -0,0 +1,4 @@ +// "Add non-null asserted (!!) call" "true" +fun foo(a: Int?) { + a!!.plus(1) +} diff --git a/idea/testData/quickfix/expressions/beforeUnnecessaryNonNullAssertion2.kt b/idea/testData/quickfix/expressions/beforeUnnecessaryNonNullAssertion2.kt index 474ea05c733..93a8d4b69cc 100644 --- a/idea/testData/quickfix/expressions/beforeUnnecessaryNonNullAssertion2.kt +++ b/idea/testData/quickfix/expressions/beforeUnnecessaryNonNullAssertion2.kt @@ -1,4 +1,4 @@ -// "Replace with dot call" "true" +// "Remove unnecessary non-null assertion (!!)" "true" fun test(value : String) { value!!.equals("test") } diff --git a/idea/testData/quickfix/expressions/beforeUnnecessaryNonNullAssertion3.kt b/idea/testData/quickfix/expressions/beforeUnnecessaryNonNullAssertion3.kt index e5ff0aeab16..2422453b1cb 100644 --- a/idea/testData/quickfix/expressions/beforeUnnecessaryNonNullAssertion3.kt +++ b/idea/testData/quickfix/expressions/beforeUnnecessaryNonNullAssertion3.kt @@ -1,4 +1,4 @@ -// "Remove unnecessary non-null assertion (!!)" "false" +// "Remove unnecessary non-null assertion (!!)" "true" fun test(value : String) : Int { - return value!!.length() + return value!!.length } diff --git a/idea/testData/quickfix/expressions/beforeUnsafeCall2.kt b/idea/testData/quickfix/expressions/beforeUnsafeCall2.kt index 9dc62d6af9e..25866b37c4b 100644 --- a/idea/testData/quickfix/expressions/beforeUnsafeCall2.kt +++ b/idea/testData/quickfix/expressions/beforeUnsafeCall2.kt @@ -1,4 +1,4 @@ -// "Replace with non-null asserted (!!.) call" "true" +// "Add non-null asserted (!!) call" "true" fun foo(a: Int?) { a.plus(1) } diff --git a/idea/testData/quickfix/expressions/beforeUnsafeCall3.kt b/idea/testData/quickfix/expressions/beforeUnsafeCall3.kt new file mode 100644 index 00000000000..946185f33ea --- /dev/null +++ b/idea/testData/quickfix/expressions/beforeUnsafeCall3.kt @@ -0,0 +1,4 @@ +// "Add non-null asserted (!!) call" "true" +fun foo(a: Int?) { + a.plus(1) +}