diff --git a/idea/src/META-INF/plugin-common.xml b/idea/src/META-INF/plugin-common.xml
index cd984da64e0..c7b1ddaa2ad 100644
--- a/idea/src/META-INF/plugin-common.xml
+++ b/idea/src/META-INF/plugin-common.xml
@@ -761,6 +761,7 @@
+
diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/FoldInitializerAndIfToElvisIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/FoldInitializerAndIfToElvisIntention.kt
index 8bec7c36609..53ce7b632b9 100644
--- a/idea/src/org/jetbrains/kotlin/idea/intentions/FoldInitializerAndIfToElvisIntention.kt
+++ b/idea/src/org/jetbrains/kotlin/idea/intentions/FoldInitializerAndIfToElvisIntention.kt
@@ -43,111 +43,126 @@ import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
class FoldInitializerAndIfToElvisInspection : IntentionBasedInspection(FoldInitializerAndIfToElvisIntention::class)
-class FoldInitializerAndIfToElvisIntention : SelfTargetingRangeIntention(KtIfExpression::class.java, "Replace 'if' with elvis operator"){
+class FoldInitializerAndIfToElvisIntention :
+ SelfTargetingRangeIntention(KtIfExpression::class.java, "Replace 'if' with elvis operator") {
+
override fun applicabilityRange(element: KtIfExpression): TextRange? {
- val data = calcData(element) ?: return null
-
- val type = data.ifNullExpression.analyze().getType(data.ifNullExpression) ?: return null
- if (!type.isNothing()) return null
-
- val rParen = element.rightParenthesis ?: return null
- return TextRange(element.startOffset, rParen.endOffset)
+ return FoldInitializerAndIfToElvisIntention.applicabilityRange(element)
}
override fun applyTo(element: KtIfExpression, editor: Editor?) {
- val (initializer, declaration, ifNullExpr, typeReference) = calcData(element)!!
- val factory = KtPsiFactory(element)
-
- val explicitTypeToSet = when {
- // for var with no explicit type, add it so that the actual change won't change
- declaration.isVar && declaration.typeReference == null -> initializer.analyze(BodyResolveMode.PARTIAL).getType(initializer)
-
- // for val with explicit type, change it to non-nullable
- !declaration.isVar && declaration.typeReference != null -> initializer.analyze(BodyResolveMode.PARTIAL).getType(initializer)?.makeNotNullable()
-
- else -> null
- }
-
- val childRangeBefore = PsiChildRange(declaration, element)
- val commentSaver = CommentSaver(childRangeBefore)
- val childRangeAfter = childRangeBefore.withoutLastStatement()
-
- val pattern = if (element.then?.hasComments() == true)
- "$0\n?: $1"
- else
- "$0 ?: $1"
-
- val elvis = factory.createExpressionByPattern(pattern, initializer, ifNullExpr) as KtBinaryExpression
- if (typeReference != null) {
- elvis.left!!.replace(factory.createExpressionByPattern("$0 as? $1", initializer, typeReference))
- }
- val newElvis = initializer.replaced(elvis)
- element.delete()
-
- if (explicitTypeToSet != null && !explicitTypeToSet.isError) {
- declaration.setType(explicitTypeToSet)
- }
-
- commentSaver.restore(childRangeAfter)
-
+ val newElvis = FoldInitializerAndIfToElvisIntention.applyTo(element)
editor?.caretModel?.moveToOffset(newElvis.right!!.textOffset)
}
- private fun PsiElement.hasComments(): Boolean {
- var result = false
- accept(object : KtTreeVisitorVoid() {
- override fun visitComment(comment: PsiComment?) {
- result = true
+ companion object {
+ private fun applicabilityRange(element: KtIfExpression): TextRange? {
+ val data = calcData(element) ?: return null
+
+ val type = data.ifNullExpression.analyze().getType(data.ifNullExpression) ?: return null
+ if (!type.isNothing()) return null
+
+ val rParen = element.rightParenthesis ?: return null
+ return TextRange(element.startOffset, rParen.endOffset)
+ }
+
+ fun isApplicable(element: KtIfExpression): Boolean = applicabilityRange(element) != null
+
+ fun applyTo(element: KtIfExpression): KtBinaryExpression {
+ val (initializer, declaration, ifNullExpr, typeReference) = calcData(element)!!
+ val factory = KtPsiFactory(element)
+
+ val explicitTypeToSet = when {
+ // for var with no explicit type, add it so that the actual change won't change
+ declaration.isVar && declaration.typeReference == null -> initializer.analyze(BodyResolveMode.PARTIAL).getType(initializer)
+
+ // for val with explicit type, change it to non-nullable
+ !declaration.isVar && declaration.typeReference != null -> initializer.analyze(BodyResolveMode.PARTIAL).getType(initializer)?.makeNotNullable()
+
+ else -> null
}
- })
- return result
+
+ val childRangeBefore = PsiChildRange(declaration, element)
+ val commentSaver = CommentSaver(childRangeBefore)
+ val childRangeAfter = childRangeBefore.withoutLastStatement()
+
+ val pattern = if (element.then?.hasComments() == true)
+ "$0\n?: $1"
+ else
+ "$0 ?: $1"
+
+ val elvis = factory.createExpressionByPattern(pattern, initializer, ifNullExpr) as KtBinaryExpression
+ if (typeReference != null) {
+ elvis.left!!.replace(factory.createExpressionByPattern("$0 as? $1", initializer, typeReference))
+ }
+ val newElvis = initializer.replaced(elvis)
+ element.delete()
+
+ if (explicitTypeToSet != null && !explicitTypeToSet.isError) {
+ declaration.setType(explicitTypeToSet)
+ }
+
+ commentSaver.restore(childRangeAfter)
+
+ return newElvis
+ }
+
+ private fun PsiElement.hasComments(): Boolean {
+ var result = false
+ accept(object : KtTreeVisitorVoid() {
+ override fun visitComment(comment: PsiComment?) {
+ result = true
+ }
+ })
+ return result
+ }
+
+ private fun calcData(ifExpression: KtIfExpression): Data? {
+ if (ifExpression.`else` != null) return null
+
+ val operationExpression = ifExpression.condition as? KtOperationExpression ?: return null
+ val value = when (operationExpression) {
+ is KtBinaryExpression -> {
+ if (operationExpression.operationToken != KtTokens.EQEQ) return null
+ operationExpression.expressionComparedToNull()
+ }
+ is KtIsExpression -> {
+ if (!operationExpression.isNegated) return null
+ if (operationExpression.typeReference?.typeElement is KtNullableType) return null
+ operationExpression.leftHandSide
+ }
+ else -> return null
+ } as? KtNameReferenceExpression ?: return null
+
+ if (ifExpression.parent !is KtBlockExpression) return null
+ val prevStatement = (ifExpression.siblings(forward = false, withItself = false)
+ .firstIsInstanceOrNull() ?: return null) as? KtVariableDeclaration
+ prevStatement ?: return null
+ if (prevStatement.nameAsName != value.getReferencedNameAsName()) return null
+ val initializer = prevStatement.initializer ?: return null
+ val then = ifExpression.then ?: return null
+ val typeReference = (operationExpression as? KtIsExpression)?.typeReference
+
+ val statement = if (then is KtBlockExpression) then.statements.singleOrNull() else then
+ statement ?: return null
+
+ if (ReferencesSearch.search(prevStatement, LocalSearchScope(statement)).findFirst() != null) {
+ return null
+ }
+
+ return Data(initializer, prevStatement, statement, typeReference)
+ }
+
+ private fun PsiChildRange.withoutLastStatement(): PsiChildRange {
+ val newLast = last!!.siblings(forward = false, withItself = false).first { it !is PsiWhiteSpace }
+ return PsiChildRange(first, newLast)
+ }
}
private data class Data(
- val initializer: KtExpression,
- val declaration: KtVariableDeclaration,
- val ifNullExpression: KtExpression,
- val typeChecked: KtTypeReference? = null
+ val initializer: KtExpression,
+ val declaration: KtVariableDeclaration,
+ val ifNullExpression: KtExpression,
+ val typeChecked: KtTypeReference? = null
)
-
- private fun calcData(ifExpression: KtIfExpression): Data? {
- if (ifExpression.`else` != null) return null
-
- val operationExpression = ifExpression.condition as? KtOperationExpression ?: return null
- val value = when (operationExpression) {
- is KtBinaryExpression -> {
- if (operationExpression.operationToken != KtTokens.EQEQ) return null
- operationExpression.expressionComparedToNull()
- }
- is KtIsExpression -> {
- if (!operationExpression.isNegated) return null
- if (operationExpression.typeReference?.typeElement is KtNullableType) return null
- operationExpression.leftHandSide
- }
- else -> return null
- } as? KtNameReferenceExpression ?: return null
-
- if (ifExpression.parent !is KtBlockExpression) return null
- val prevStatement = (ifExpression.siblings(forward = false, withItself = false)
- .firstIsInstanceOrNull() ?: return null) as? KtVariableDeclaration
- prevStatement ?: return null
- if (prevStatement.nameAsName != value.getReferencedNameAsName()) return null
- val initializer = prevStatement.initializer ?: return null
- val then = ifExpression.then ?: return null
- val typeReference = (operationExpression as? KtIsExpression)?.typeReference
-
- val statement = if (then is KtBlockExpression) then.statements.singleOrNull() else then
- statement ?: return null
-
- if (ReferencesSearch.search(prevStatement, LocalSearchScope(statement)).findFirst() != null) {
- return null
- }
-
- return Data(initializer, prevStatement, statement, typeReference)
- }
-
- private fun PsiChildRange.withoutLastStatement(): PsiChildRange {
- val newLast = last!!.siblings(forward = false, withItself = false).first { it !is PsiWhiteSpace }
- return PsiChildRange(first, newLast)
- }
}
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/kotlin/idea/joinLines/JoinInitializerAndIfToElvisHandler.kt b/idea/src/org/jetbrains/kotlin/idea/joinLines/JoinInitializerAndIfToElvisHandler.kt
new file mode 100644
index 00000000000..675dd462a04
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/joinLines/JoinInitializerAndIfToElvisHandler.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010-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/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.idea.joinLines
+
+import com.intellij.codeInsight.editorActions.JoinRawLinesHandlerDelegate
+import com.intellij.openapi.editor.Document
+import com.intellij.psi.PsiFile
+import org.jetbrains.kotlin.idea.intentions.FoldInitializerAndIfToElvisIntention
+import org.jetbrains.kotlin.psi.KtFile
+import org.jetbrains.kotlin.psi.KtIfExpression
+import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespaceAndComments
+import org.jetbrains.kotlin.psi.psiUtil.siblings
+
+class JoinInitializerAndIfToElvisHandler : JoinRawLinesHandlerDelegate {
+
+ override fun tryJoinRawLines(document: Document, file: PsiFile, start: Int, end: Int): Int {
+ if (file !is KtFile) return -1
+ val lineBreak = file.findElementAt(start)
+ ?.siblings(forward = true, withItself = true)
+ ?.firstOrNull { it.textContains('\n') }
+ ?: return -1
+ val ifExpression = lineBreak.getNextSiblingIgnoringWhitespaceAndComments() as? KtIfExpression ?: return -1
+ if (!FoldInitializerAndIfToElvisIntention.isApplicable(ifExpression)) return -1
+ return FoldInitializerAndIfToElvisIntention.applyTo(ifExpression).textRange.startOffset
+ }
+
+ override fun tryJoinLines(document: Document, file: PsiFile, start: Int, end: Int) = -1
+}
\ No newline at end of file
diff --git a/idea/testData/joinLines/initializerAndIfToElvis/simple.kt b/idea/testData/joinLines/initializerAndIfToElvis/simple.kt
new file mode 100644
index 00000000000..5f2bac9c9c2
--- /dev/null
+++ b/idea/testData/joinLines/initializerAndIfToElvis/simple.kt
@@ -0,0 +1,5 @@
+fun foo(list: List): Int {
+ val first = list.firstOrNull()
+ if (first == null) return -1
+ return first.length
+}
\ No newline at end of file
diff --git a/idea/testData/joinLines/initializerAndIfToElvis/simple.kt.after b/idea/testData/joinLines/initializerAndIfToElvis/simple.kt.after
new file mode 100644
index 00000000000..086246c21c7
--- /dev/null
+++ b/idea/testData/joinLines/initializerAndIfToElvis/simple.kt.after
@@ -0,0 +1,4 @@
+fun foo(list: List): Int {
+ val first = list.firstOrNull() ?: return -1
+ return first.length
+}
\ No newline at end of file
diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/declarations/JoinLinesTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/declarations/JoinLinesTestGenerated.java
index c3f2aed70df..1e08e527810 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/intentions/declarations/JoinLinesTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/declarations/JoinLinesTestGenerated.java
@@ -225,6 +225,24 @@ public class JoinLinesTestGenerated extends AbstractJoinLinesTest {
}
}
+ @TestMetadata("idea/testData/joinLines/initializerAndIfToElvis")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(JUnit3RunnerWithInners.class)
+ public static class InitializerAndIfToElvis extends AbstractJoinLinesTest {
+ private void runTest(String testDataFilePath) throws Exception {
+ KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
+ }
+
+ public void testAllFilesPresentInInitializerAndIfToElvis() throws Exception {
+ KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/joinLines/initializerAndIfToElvis"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
+ }
+
+ @TestMetadata("simple.kt")
+ public void testSimple() throws Exception {
+ runTest("idea/testData/joinLines/initializerAndIfToElvis/simple.kt");
+ }
+ }
+
@TestMetadata("idea/testData/joinLines/nestedIfs")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)