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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 'x ?: ...' case
|
||||||
|
if (parentExpression instanceof JetBinaryExpression && parentOperation == JetTokens.ELVIS && currentInner == ((JetBinaryExpression) parentExpression).getRight()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
int innerPriority = getPriority(innerExpression);
|
int innerPriority = getPriority(innerExpression);
|
||||||
int parentPriority = getPriority(parentExpression);
|
int parentPriority = getPriority(parentExpression);
|
||||||
|
|
||||||
|
|||||||
@@ -498,6 +498,11 @@
|
|||||||
language="jet"
|
language="jet"
|
||||||
implementationClass="org.jetbrains.kotlin.idea.search.ideaExtensions.JetTargetElementEvaluator" />
|
implementationClass="org.jetbrains.kotlin.idea.search.ideaExtensions.JetTargetElementEvaluator" />
|
||||||
|
|
||||||
|
<intentionAction>
|
||||||
|
<className>org.jetbrains.kotlin.idea.intentions.IfNullToElvisIntention</className>
|
||||||
|
<category>Kotlin</category>
|
||||||
|
</intentionAction>
|
||||||
|
|
||||||
<intentionAction>
|
<intentionAction>
|
||||||
<className>org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyAction</className>
|
<className>org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyAction</className>
|
||||||
<category>Kotlin</category>
|
<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")
|
@TestMetadata("idea/testData/intentions/insertCurlyBracesToTemplate")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user