In progress: KT-7442 Inspection + intention to replace "if (xxx == null) return ..' with elvis

This commit is contained in:
Valentin Kipyatkov
2015-04-28 23:21:51 +03:00
parent c52f69538d
commit 8d887f8780
23 changed files with 316 additions and 0 deletions
@@ -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);
+5
View File
@@ -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)