diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/JetPsiUtil.java b/compiler/frontend/src/org/jetbrains/kotlin/psi/JetPsiUtil.java
index 5534a08893e..624c74440c2 100644
--- a/compiler/frontend/src/org/jetbrains/kotlin/psi/JetPsiUtil.java
+++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/JetPsiUtil.java
@@ -551,6 +551,11 @@ public class JetPsiUtil {
return true;
}
+ // 'x ?: ...' case
+ if (parentExpression instanceof JetBinaryExpression && parentOperation == JetTokens.ELVIS && currentInner == ((JetBinaryExpression) parentExpression).getRight()) {
+ return false;
+ }
+
int innerPriority = getPriority(innerExpression);
int parentPriority = getPriority(parentExpression);
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index 564652f7729..9e6f4a48cd7 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -498,6 +498,11 @@
language="jet"
implementationClass="org.jetbrains.kotlin.idea.search.ideaExtensions.JetTargetElementEvaluator" />
+
+ org.jetbrains.kotlin.idea.intentions.IfNullToElvisIntention
+ Kotlin
+
+
org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyAction
Kotlin
diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/IfNullToElvisIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/IfNullToElvisIntention.kt
new file mode 100644
index 00000000000..5f4f576b94a
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/intentions/IfNullToElvisIntention.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2010-2015 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.kotlin.idea.intentions
+
+import com.intellij.openapi.editor.Editor
+import com.intellij.psi.PsiComment
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiExpression
+import com.intellij.psi.PsiRecursiveElementVisitor
+import org.jetbrains.kotlin.JetNodeTypes
+import org.jetbrains.kotlin.idea.caches.resolve.analyze
+import org.jetbrains.kotlin.idea.util.isNothing
+import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.psi.psiUtil.replaced
+import org.jetbrains.kotlin.psi.psiUtil.siblings
+import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
+import java.util.ArrayList
+
+public class IfNullToElvisIntention : JetSelfTargetingIntention(javaClass(), "Replace 'if' with elvis operator"){
+ override fun isApplicableTo(element: JetIfExpression, caretOffset: Int): Boolean {
+ //TODO: range!
+ val data = calcData(element) ?: return false
+
+ val type = data.ifNullExpression.analyze().getType(data.ifNullExpression) ?: return false
+ if (!type.isNothing()) return false
+
+ return true
+ }
+
+ override fun applyTo(element: JetIfExpression, editor: Editor) {
+ val (initializer, statement, ifNullExpr) = calcData(element)!!
+ val factory = JetPsiFactory(element)
+
+ // do not loose any comments!
+ val comments = element.extractComments(ifNullExpr)
+
+ for (comment in comments) {
+ statement.add(factory.createWhiteSpace())
+ statement.add(comment)
+ }
+
+ val elvis = factory.createExpression("a ?: b") as JetBinaryExpression
+ elvis.getLeft()!!.replace(initializer)
+ elvis.getRight()!!.replace(ifNullExpr)
+ val newElvis = initializer.replaced(elvis)
+ element.delete()
+
+ editor.getCaretModel().moveToOffset(newElvis.getRight()!!.getTextOffset())
+ }
+
+ private data class Data(
+ val initializer: JetExpression,
+ val statement: JetExpression,
+ val ifNullExpression: JetExpression
+ )
+
+ private fun calcData(ifExpression: JetIfExpression): Data? {
+ if (ifExpression.getElse() != null) return null
+
+ val binaryExpression = ifExpression.getCondition() as? JetBinaryExpression ?: return null
+ if (binaryExpression.getRight()?.getNode()?.getElementType() != JetNodeTypes.NULL) return null
+ val left = binaryExpression.getLeft() as? JetSimpleNameExpression ?: return null
+
+ if (ifExpression.getParent() !is JetBlockExpression) return null
+ val prevStatement = ifExpression.siblings(forward = false, withItself = false)
+ .firstIsInstanceOrNull() ?: return null
+ if (prevStatement !is JetVariableDeclaration) return null
+ if (prevStatement.getNameAsName() != left.getReferencedNameAsName()) return null
+ val initializer = prevStatement.getInitializer() ?: return null
+ val then = ifExpression.getThen() ?: return null
+
+ if (then is JetBlockExpression) {
+ val statement = then.getStatements().singleOrNull() as? JetExpression ?: return null
+ return Data(initializer, prevStatement, statement)
+ }
+ else {
+ return Data(initializer, prevStatement, then)
+ }
+ }
+
+ private fun PsiElement.extractComments(skipElement: PsiElement): List {
+ val comments = ArrayList()
+ accept(object : PsiRecursiveElementVisitor() {
+ override fun visitElement(element: PsiElement) {
+ if (element == skipElement) return
+ super.visitElement(element)
+ }
+
+ override fun visitComment(comment: PsiComment) {
+ comments.add(comment)
+ }
+ })
+ return comments
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/ifNullToElvis/.intention b/idea/testData/intentions/ifNullToElvis/.intention
new file mode 100644
index 00000000000..f7463a9e35f
--- /dev/null
+++ b/idea/testData/intentions/ifNullToElvis/.intention
@@ -0,0 +1 @@
+org.jetbrains.kotlin.idea.intentions.IfNullToElvisIntention
diff --git a/idea/testData/intentions/ifNullToElvis/Break.kt b/idea/testData/intentions/ifNullToElvis/Break.kt
new file mode 100644
index 00000000000..7bfc3082fd4
--- /dev/null
+++ b/idea/testData/intentions/ifNullToElvis/Break.kt
@@ -0,0 +1,6 @@
+fun foo(p: List) {
+ for (i in 1..10) {
+ val v = p[i]
+ if (v == null) break
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/ifNullToElvis/Break.kt.after b/idea/testData/intentions/ifNullToElvis/Break.kt.after
new file mode 100644
index 00000000000..00a52987e9b
--- /dev/null
+++ b/idea/testData/intentions/ifNullToElvis/Break.kt.after
@@ -0,0 +1,5 @@
+fun foo(p: List) {
+ for (i in 1..10) {
+ val v = p[i] ?: break
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/ifNullToElvis/CommentInBlock.kt b/idea/testData/intentions/ifNullToElvis/CommentInBlock.kt
new file mode 100644
index 00000000000..1d4f323f9ce
--- /dev/null
+++ b/idea/testData/intentions/ifNullToElvis/CommentInBlock.kt
@@ -0,0 +1,8 @@
+fun foo(p: List): Int {
+ val v = p[0]
+ if (v == null) {
+ // return -1 if null
+ return -1
+ }
+ return v.length()
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/ifNullToElvis/CommentInBlock.kt.after b/idea/testData/intentions/ifNullToElvis/CommentInBlock.kt.after
new file mode 100644
index 00000000000..d8079d811ef
--- /dev/null
+++ b/idea/testData/intentions/ifNullToElvis/CommentInBlock.kt.after
@@ -0,0 +1,4 @@
+fun foo(p: List): Int {
+ val v = p[0] ?: return -1 // return -1 if null
+ return v.length()
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/ifNullToElvis/Comments.kt b/idea/testData/intentions/ifNullToElvis/Comments.kt
new file mode 100644
index 00000000000..2aa1c6bdaef
--- /dev/null
+++ b/idea/testData/intentions/ifNullToElvis/Comments.kt
@@ -0,0 +1,6 @@
+fun foo(p: List): Int {
+ val v = p[0]
+ // now check if v is null
+ if (v == null/* null */) return -1 // return -1
+ return v.length()
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/ifNullToElvis/Comments.kt.after b/idea/testData/intentions/ifNullToElvis/Comments.kt.after
new file mode 100644
index 00000000000..690207a2def
--- /dev/null
+++ b/idea/testData/intentions/ifNullToElvis/Comments.kt.after
@@ -0,0 +1,6 @@
+fun foo(p: List): Int {
+ val v = p[0] ?: return -1 /* null */
+ // now check if v is null
+ // return -1
+ return v.length()
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/ifNullToElvis/Comments2.kt b/idea/testData/intentions/ifNullToElvis/Comments2.kt
new file mode 100644
index 00000000000..ce462b54c9a
--- /dev/null
+++ b/idea/testData/intentions/ifNullToElvis/Comments2.kt
@@ -0,0 +1,8 @@
+fun foo(p: List): Int {
+ val v = p[0]
+ if (v == null) { // v is null
+ // we should do something with it
+ return -1 // let's return -1
+ } // end of if
+ return v.length()
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/ifNullToElvis/Comments2.kt.after b/idea/testData/intentions/ifNullToElvis/Comments2.kt.after
new file mode 100644
index 00000000000..55cc5df7eba
--- /dev/null
+++ b/idea/testData/intentions/ifNullToElvis/Comments2.kt.after
@@ -0,0 +1,5 @@
+fun foo(p: List): Int {
+ val v = p[0] ?: return -1 // v is null // we should do something with it // let's return -1
+ // end of if
+ return v.length()
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/ifNullToElvis/Continue.kt b/idea/testData/intentions/ifNullToElvis/Continue.kt
new file mode 100644
index 00000000000..42ee0bdaec9
--- /dev/null
+++ b/idea/testData/intentions/ifNullToElvis/Continue.kt
@@ -0,0 +1,6 @@
+fun foo(p: List) {
+ for (i in 1..10) {
+ val v = p[i]
+ if (v == null) continue
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/ifNullToElvis/Continue.kt.after b/idea/testData/intentions/ifNullToElvis/Continue.kt.after
new file mode 100644
index 00000000000..c87fb239232
--- /dev/null
+++ b/idea/testData/intentions/ifNullToElvis/Continue.kt.after
@@ -0,0 +1,5 @@
+fun foo(p: List) {
+ for (i in 1..10) {
+ val v = p[i] ?: continue
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/ifNullToElvis/MultiStatementBlock.kt b/idea/testData/intentions/ifNullToElvis/MultiStatementBlock.kt
new file mode 100644
index 00000000000..45e0b97ac0c
--- /dev/null
+++ b/idea/testData/intentions/ifNullToElvis/MultiStatementBlock.kt
@@ -0,0 +1,10 @@
+// IS_APPLICABLE: false
+fun foo(p: List) {
+ val v = p[0]
+ if (v == null) {
+ bar()
+ return
+ }
+}
+
+fun bar(){}
\ No newline at end of file
diff --git a/idea/testData/intentions/ifNullToElvis/NotExit.kt b/idea/testData/intentions/ifNullToElvis/NotExit.kt
new file mode 100644
index 00000000000..ce91eaaefdf
--- /dev/null
+++ b/idea/testData/intentions/ifNullToElvis/NotExit.kt
@@ -0,0 +1,8 @@
+// IS_APPLICABLE: false
+fun foo(p: List): Int? {
+ val v = p[0]
+ if (v == null) bar()
+ return v?.length()
+}
+
+fun bar(){}
\ No newline at end of file
diff --git a/idea/testData/intentions/ifNullToElvis/OtherVar1.kt b/idea/testData/intentions/ifNullToElvis/OtherVar1.kt
new file mode 100644
index 00000000000..f956f4365dc
--- /dev/null
+++ b/idea/testData/intentions/ifNullToElvis/OtherVar1.kt
@@ -0,0 +1,11 @@
+// IS_APPLICABLE: false
+class C {
+ var v: String? = null
+
+ fun foo(p: List): Int {
+ val v = p[0]
+ if (this.v == null) return -1
+ return v!!.length()
+ }
+}
+
diff --git a/idea/testData/intentions/ifNullToElvis/OtherVar2.kt b/idea/testData/intentions/ifNullToElvis/OtherVar2.kt
new file mode 100644
index 00000000000..f0764475964
--- /dev/null
+++ b/idea/testData/intentions/ifNullToElvis/OtherVar2.kt
@@ -0,0 +1,11 @@
+// IS_APPLICABLE: false
+class C {
+ var x: String? = null
+
+ fun foo(p: List): Int {
+ val v = p[0]
+ if (x == null) return -1
+ return v!!.length()
+ }
+}
+
diff --git a/idea/testData/intentions/ifNullToElvis/Return.kt b/idea/testData/intentions/ifNullToElvis/Return.kt
new file mode 100644
index 00000000000..a2eb900e9d3
--- /dev/null
+++ b/idea/testData/intentions/ifNullToElvis/Return.kt
@@ -0,0 +1,5 @@
+fun foo(p: List): Int {
+ val v = p[0]
+ if (v == null) return -1
+ return v.length()
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/ifNullToElvis/Return.kt.after b/idea/testData/intentions/ifNullToElvis/Return.kt.after
new file mode 100644
index 00000000000..d0c49ad031c
--- /dev/null
+++ b/idea/testData/intentions/ifNullToElvis/Return.kt.after
@@ -0,0 +1,4 @@
+fun foo(p: List): Int {
+ val v = p[0] ?: return -1
+ return v.length()
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/ifNullToElvis/ThrowInBlock.kt b/idea/testData/intentions/ifNullToElvis/ThrowInBlock.kt
new file mode 100644
index 00000000000..8b1a17d6be8
--- /dev/null
+++ b/idea/testData/intentions/ifNullToElvis/ThrowInBlock.kt
@@ -0,0 +1,8 @@
+// WITH_RUNTIME
+
+fun foo(p: List) {
+ val v = p[0]
+ if (v == null) {
+ throw RuntimeException()
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/ifNullToElvis/ThrowInBlock.kt.after b/idea/testData/intentions/ifNullToElvis/ThrowInBlock.kt.after
new file mode 100644
index 00000000000..20a03b81934
--- /dev/null
+++ b/idea/testData/intentions/ifNullToElvis/ThrowInBlock.kt.after
@@ -0,0 +1,5 @@
+// WITH_RUNTIME
+
+fun foo(p: List) {
+ val v = p[0] ?: throw RuntimeException()
+}
\ No newline at end of file
diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java
index 538013f9420..67441fd1573 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java
@@ -4094,6 +4094,81 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
}
}
+ @TestMetadata("idea/testData/intentions/ifNullToElvis")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(JUnit3RunnerWithInners.class)
+ public static class IfNullToElvis extends AbstractIntentionTest {
+ public void testAllFilesPresentInIfNullToElvis() throws Exception {
+ JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/ifNullToElvis"), Pattern.compile("^(.+)\\.kt$"), true);
+ }
+
+ @TestMetadata("Break.kt")
+ public void testBreak() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/ifNullToElvis/Break.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("CommentInBlock.kt")
+ public void testCommentInBlock() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/ifNullToElvis/CommentInBlock.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("Comments.kt")
+ public void testComments() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/ifNullToElvis/Comments.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("Comments2.kt")
+ public void testComments2() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/ifNullToElvis/Comments2.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("Continue.kt")
+ public void testContinue() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/ifNullToElvis/Continue.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("MultiStatementBlock.kt")
+ public void testMultiStatementBlock() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/ifNullToElvis/MultiStatementBlock.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("NotExit.kt")
+ public void testNotExit() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/ifNullToElvis/NotExit.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("OtherVar1.kt")
+ public void testOtherVar1() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/ifNullToElvis/OtherVar1.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("OtherVar2.kt")
+ public void testOtherVar2() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/ifNullToElvis/OtherVar2.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("Return.kt")
+ public void testReturn() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/ifNullToElvis/Return.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("ThrowInBlock.kt")
+ public void testThrowInBlock() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/ifNullToElvis/ThrowInBlock.kt");
+ doTest(fileName);
+ }
+ }
+
@TestMetadata("idea/testData/intentions/insertCurlyBracesToTemplate")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)