In progress: KT-7442 Inspection + intention to replace "if (xxx == null) return ..' with elvis
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -498,6 +498,11 @@
|
||||
language="jet"
|
||||
implementationClass="org.jetbrains.kotlin.idea.search.ideaExtensions.JetTargetElementEvaluator" />
|
||||
|
||||
<intentionAction>
|
||||
<className>org.jetbrains.kotlin.idea.intentions.IfNullToElvisIntention</className>
|
||||
<category>Kotlin</category>
|
||||
</intentionAction>
|
||||
|
||||
<intentionAction>
|
||||
<className>org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyAction</className>
|
||||
<category>Kotlin</category>
|
||||
|
||||
@@ -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<JetIfExpression>(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<JetExpression>() ?: 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<PsiComment> {
|
||||
val comments = ArrayList<PsiComment>()
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.idea.intentions.IfNullToElvisIntention
|
||||
@@ -0,0 +1,6 @@
|
||||
fun foo(p: List<String?>) {
|
||||
for (i in 1..10) {
|
||||
val v = p[i]
|
||||
<caret>if (v == null) break
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(p: List<String?>) {
|
||||
for (i in 1..10) {
|
||||
val v = p[i] ?: <caret>break
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fun foo(p: List<String?>): Int {
|
||||
val v = p[0]
|
||||
<caret>if (v == null) {
|
||||
// return -1 if null
|
||||
return -1
|
||||
}
|
||||
return v.length()
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
fun foo(p: List<String?>): Int {
|
||||
val v = p[0] ?: <caret>return -1 // return -1 if null
|
||||
return v.length()
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
fun foo(p: List<String?>): Int {
|
||||
val v = p[0]
|
||||
// now check if v is null
|
||||
<caret>if (v == null/* null */) return -1 // return -1
|
||||
return v.length()
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
fun foo(p: List<String?>): Int {
|
||||
val v = p[0] ?: <caret>return -1 /* null */
|
||||
// now check if v is null
|
||||
// return -1
|
||||
return v.length()
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fun foo(p: List<String?>): Int {
|
||||
val v = p[0]
|
||||
<caret>if (v == null) { // v is null
|
||||
// we should do something with it
|
||||
return -1 // let's return -1
|
||||
} // end of if
|
||||
return v.length()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(p: List<String?>): Int {
|
||||
val v = p[0] ?: <caret>return -1 // v is null // we should do something with it // let's return -1
|
||||
// end of if
|
||||
return v.length()
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
fun foo(p: List<String?>) {
|
||||
for (i in 1..10) {
|
||||
val v = p[i]
|
||||
<caret>if (v == null) continue
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(p: List<String?>) {
|
||||
for (i in 1..10) {
|
||||
val v = p[i] ?: <caret>continue
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// IS_APPLICABLE: false
|
||||
fun foo(p: List<String?>) {
|
||||
val v = p[0]
|
||||
<caret>if (v == null) {
|
||||
bar()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fun bar(){}
|
||||
@@ -0,0 +1,8 @@
|
||||
// IS_APPLICABLE: false
|
||||
fun foo(p: List<String?>): Int? {
|
||||
val v = p[0]
|
||||
<caret>if (v == null) bar()
|
||||
return v?.length()
|
||||
}
|
||||
|
||||
fun bar(){}
|
||||
@@ -0,0 +1,11 @@
|
||||
// IS_APPLICABLE: false
|
||||
class C {
|
||||
var v: String? = null
|
||||
|
||||
fun foo(p: List<String?>): Int {
|
||||
val v = p[0]
|
||||
<caret>if (this.v == null) return -1
|
||||
return v!!.length()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// IS_APPLICABLE: false
|
||||
class C {
|
||||
var x: String? = null
|
||||
|
||||
fun foo(p: List<String?>): Int {
|
||||
val v = p[0]
|
||||
<caret>if (x == null) return -1
|
||||
return v!!.length()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(p: List<String?>): Int {
|
||||
val v = p[0]
|
||||
<caret>if (v == null) return -1
|
||||
return v.length()
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
fun foo(p: List<String?>): Int {
|
||||
val v = p[0] ?: <caret>return -1
|
||||
return v.length()
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun foo(p: List<String?>) {
|
||||
val v = p[0]
|
||||
<caret>if (v == null) {
|
||||
throw RuntimeException()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun foo(p: List<String?>) {
|
||||
val v = p[0] ?: <caret>throw RuntimeException()
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user