Add quick fixes for SMARTCAST_IMPOSSIBLE in 'if' #KT-27184 Fixed
This commit is contained in:
committed by
Mikhail Glukhikh
parent
a0162adbf9
commit
cf3215b96e
+6
-2
@@ -32,11 +32,15 @@ import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getType
|
||||
|
||||
class IfThenToSafeAccessInspection : AbstractApplicabilityBasedInspection<KtIfExpression>(KtIfExpression::class.java) {
|
||||
class IfThenToSafeAccessInspection(
|
||||
private val stableElementNeeded: Boolean
|
||||
) : AbstractApplicabilityBasedInspection<KtIfExpression>(KtIfExpression::class.java) {
|
||||
|
||||
constructor() : this(stableElementNeeded = true)
|
||||
|
||||
override fun isApplicable(element: KtIfExpression): Boolean {
|
||||
val ifThenToSelectData = element.buildSelectTransformationData() ?: return false
|
||||
if (!ifThenToSelectData.receiverExpression.isStableSimpleExpression(ifThenToSelectData.context)) return false
|
||||
if (stableElementNeeded && !ifThenToSelectData.receiverExpression.isStableSimpleExpression(ifThenToSelectData.context)) return false
|
||||
|
||||
return ifThenToSelectData.clausesReplaceableBySafeCall()
|
||||
}
|
||||
|
||||
+4
-2
@@ -42,11 +42,13 @@ class IfThenToElvisInspection : IntentionBasedInspection<KtIfExpression>(
|
||||
if (element.shouldBeTransformed()) super.problemHighlightType(element) else ProblemHighlightType.INFORMATION
|
||||
}
|
||||
|
||||
class IfThenToElvisIntention : SelfTargetingOffsetIndependentIntention<KtIfExpression>(
|
||||
class IfThenToElvisIntention (private val stableElementNeeded: Boolean) : SelfTargetingOffsetIndependentIntention<KtIfExpression>(
|
||||
KtIfExpression::class.java,
|
||||
"Replace 'if' expression with elvis expression"
|
||||
) {
|
||||
|
||||
constructor() : this(stableElementNeeded = true)
|
||||
|
||||
private fun IfThenToSelectData.clausesReplaceableByElvis(): Boolean =
|
||||
when {
|
||||
baseClause == null || negatedClause == null || negatedClause.isNullOrBlockExpression() ->
|
||||
@@ -65,7 +67,7 @@ class IfThenToElvisIntention : SelfTargetingOffsetIndependentIntention<KtIfExpre
|
||||
|
||||
override fun isApplicableTo(element: KtIfExpression): Boolean {
|
||||
val ifThenToSelectData = element.buildSelectTransformationData() ?: return false
|
||||
if (!ifThenToSelectData.receiverExpression.isStableSimpleExpression(ifThenToSelectData.context)) return false
|
||||
if (stableElementNeeded && !ifThenToSelectData.receiverExpression.isStableSimpleExpression(ifThenToSelectData.context)) return false
|
||||
|
||||
val type = element.getType(ifThenToSelectData.context) ?: return false
|
||||
if (KotlinBuiltIns.isUnit(type)) return false
|
||||
|
||||
@@ -370,6 +370,7 @@ class QuickFixRegistrar : QuickFixContributor {
|
||||
|
||||
SMARTCAST_IMPOSSIBLE.registerFactory(SmartCastImpossibleExclExclFixFactory)
|
||||
SMARTCAST_IMPOSSIBLE.registerFactory(CastExpressionFix.SmartCastImpossibleFactory)
|
||||
SMARTCAST_IMPOSSIBLE.registerFactory(SmartCastImpossibleInIfThenFactory)
|
||||
|
||||
PLATFORM_CLASS_MAPPED_TO_KOTLIN.registerFactory(MapPlatformClassToKotlinFix)
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.quickfix
|
||||
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.idea.inspections.branchedTransformations.IfThenToSafeAccessInspection
|
||||
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.IfThenToElvisIntention
|
||||
import org.jetbrains.kotlin.psi.KtContainerNodeForControlStructureBody
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtIfExpression
|
||||
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
|
||||
object SmartCastImpossibleInIfThenFactory : KotlinIntentionActionsFactory() {
|
||||
override fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction> {
|
||||
val element = diagnostic.psiElement as? KtNameReferenceExpression ?: return emptyList()
|
||||
val ifExpression =
|
||||
element.getStrictParentOfType<KtContainerNodeForControlStructureBody>()?.parent as? KtIfExpression ?: return emptyList()
|
||||
|
||||
val ifThenToSafeAccess = IfThenToSafeAccessInspection(stableElementNeeded = false)
|
||||
val ifThenToElvis = IfThenToElvisIntention(stableElementNeeded = false)
|
||||
|
||||
return listOf(
|
||||
createQuickFix(
|
||||
ifExpression,
|
||||
{ ifThenToSafeAccess.fixText(it) },
|
||||
{ ifThenToSafeAccess.isApplicable(it) },
|
||||
{ ifExpr, project, editor -> ifThenToSafeAccess.applyTo(ifExpr.ifKeyword, project, editor) }
|
||||
),
|
||||
createQuickFix(
|
||||
ifExpression,
|
||||
{ ifThenToElvis.text },
|
||||
{ ifThenToElvis.isApplicableTo(it) },
|
||||
{ ifExpr, _, editor -> ifThenToElvis.applyTo(ifExpr, editor) }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun createQuickFix(
|
||||
ifExpression: KtIfExpression,
|
||||
fixText: (KtIfExpression) -> String,
|
||||
isApplicable: (KtIfExpression) -> Boolean,
|
||||
applyTo: (KtIfExpression, project: Project, editor: Editor?) -> Unit
|
||||
): KotlinQuickFixAction<KtIfExpression> {
|
||||
return object : KotlinQuickFixAction<KtIfExpression>(ifExpression) {
|
||||
override fun getText() = fixText(ifExpression)
|
||||
|
||||
override fun getFamilyName() = text
|
||||
|
||||
override fun isAvailable(project: Project, editor: Editor?, file: KtFile) = element?.let { isApplicable(it) } ?: false
|
||||
|
||||
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
||||
element?.also { applyTo(it, project, editor) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Replace 'if' expression with safe access expression" "true"
|
||||
class Test {
|
||||
var x: String? = ""
|
||||
|
||||
fun test() {
|
||||
if (x != null) <caret>x.length
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Replace 'if' expression with safe access expression" "true"
|
||||
class Test {
|
||||
var x: String? = ""
|
||||
|
||||
fun test() {
|
||||
x?.length
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// "Replace 'if' expression with safe access expression" "true"
|
||||
class Test {
|
||||
var x: String? = ""
|
||||
|
||||
fun test() {
|
||||
if (x != null) {
|
||||
<caret>x.length
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Replace 'if' expression with safe access expression" "true"
|
||||
class Test {
|
||||
var x: String? = ""
|
||||
|
||||
fun test() {
|
||||
x?.length
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// "Replace 'if' expression with safe access expression" "true"
|
||||
// WITH_RUNTIME
|
||||
class Test {
|
||||
var x: String? = ""
|
||||
|
||||
fun test() {
|
||||
if (x != null) foo(<caret>x)
|
||||
}
|
||||
|
||||
fun foo(s: String) = 1
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// "Replace 'if' expression with safe access expression" "true"
|
||||
// WITH_RUNTIME
|
||||
class Test {
|
||||
var x: String? = ""
|
||||
|
||||
fun test() {
|
||||
x?.let { foo(it) }
|
||||
}
|
||||
|
||||
fun foo(s: String) = 1
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Replace 'if' expression with safe access expression" "true"
|
||||
class Test {
|
||||
var x: Any? = null
|
||||
|
||||
fun test() {
|
||||
if (x is String) <caret>x.length
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Replace 'if' expression with safe access expression" "true"
|
||||
class Test {
|
||||
var x: Any? = null
|
||||
|
||||
fun test() {
|
||||
(x as? String)?.length
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// "Replace 'if' expression with safe access expression" "true"
|
||||
// WITH_RUNTIME
|
||||
class Test {
|
||||
var x: Any? = null
|
||||
|
||||
fun test() {
|
||||
if (x is String) foo(x<caret>)
|
||||
}
|
||||
|
||||
fun foo(s: String) = 1
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// "Replace 'if' expression with safe access expression" "true"
|
||||
// WITH_RUNTIME
|
||||
class Test {
|
||||
var x: Any? = null
|
||||
|
||||
fun test() {
|
||||
(x as? String)?.let { foo(it) }
|
||||
}
|
||||
|
||||
fun foo(s: String) = 1
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Replace 'if' expression with elvis expression" "true"
|
||||
class Test {
|
||||
var x: String? = ""
|
||||
|
||||
fun test() {
|
||||
val i = if (x != null) <caret>x.length else 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Replace 'if' expression with elvis expression" "true"
|
||||
class Test {
|
||||
var x: String? = ""
|
||||
|
||||
fun test() {
|
||||
val i = x?.length ?: 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// "Replace 'if' expression with elvis expression" "true"
|
||||
class Test {
|
||||
var x: String? = ""
|
||||
|
||||
fun test() {
|
||||
val i = if (x != null) {
|
||||
<caret>x.length
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Replace 'if' expression with elvis expression" "true"
|
||||
class Test {
|
||||
var x: String? = ""
|
||||
|
||||
fun test() {
|
||||
val i = x?.length ?: 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// "Replace 'if' expression with elvis expression" "true"
|
||||
// WITH_RUNTIME
|
||||
class Test {
|
||||
var x: String? = ""
|
||||
|
||||
fun test() {
|
||||
val i = if (x != null) foo(<caret>x) else bar()
|
||||
}
|
||||
|
||||
fun foo(s: String) = 1
|
||||
|
||||
fun bar() = 0
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// "Replace 'if' expression with elvis expression" "true"
|
||||
// WITH_RUNTIME
|
||||
class Test {
|
||||
var x: String? = ""
|
||||
|
||||
fun test() {
|
||||
val i = x?.let { foo(it) } ?: bar()
|
||||
}
|
||||
|
||||
fun foo(s: String) = 1
|
||||
|
||||
fun bar() = 0
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Replace 'if' expression with elvis expression" "true"
|
||||
class Test {
|
||||
var x: Any? = null
|
||||
|
||||
fun test() {
|
||||
val i = if (x is String) <caret>x.length else 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Replace 'if' expression with elvis expression" "true"
|
||||
class Test {
|
||||
var x: Any? = null
|
||||
|
||||
fun test() {
|
||||
val i = (x as? String)?.length ?: 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// "Replace 'if' expression with elvis expression" "true"
|
||||
// WITH_RUNTIME
|
||||
class Test {
|
||||
var x: Any? = null
|
||||
|
||||
fun test() {
|
||||
val i = if (x is String) foo(<caret>x) else bar()
|
||||
}
|
||||
|
||||
fun foo(s: String) = 1
|
||||
|
||||
fun bar() = 0
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// "Replace 'if' expression with elvis expression" "true"
|
||||
// WITH_RUNTIME
|
||||
class Test {
|
||||
var x: Any? = null
|
||||
|
||||
fun test() {
|
||||
val i = (x as? String)?.let { foo(it) } ?: bar()
|
||||
}
|
||||
|
||||
fun foo(s: String) = 1
|
||||
|
||||
fun bar() = 0
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// "Replace 'if' expression with elvis expression" "false"
|
||||
// ACTION: Add non-null asserted (!!) call
|
||||
// ACTION: Introduce local variable
|
||||
// DISABLE-ERRORS
|
||||
class Test {
|
||||
var x: String? = ""
|
||||
|
||||
fun test() {
|
||||
val i = if (x != null) {
|
||||
bar()
|
||||
<caret>x.length
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fun bar() {}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// "Replace 'if' expression with safe access expression" "false"
|
||||
// ACTION: Add non-null asserted (!!) call
|
||||
// ACTION: Introduce local variable
|
||||
// DISABLE-ERRORS
|
||||
class Test {
|
||||
var x: String? = ""
|
||||
|
||||
fun test() {
|
||||
if (x != null) {
|
||||
bar()
|
||||
<caret>x.length
|
||||
}
|
||||
}
|
||||
|
||||
fun bar() {}
|
||||
}
|
||||
+13
@@ -3751,6 +3751,19 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/smartCastImpossibleInIfThen")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class SmartCastImpossibleInIfThen extends AbstractQuickFixMultiFileTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTestWithExtraFile, TargetBackend.ANY, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInSmartCastImpossibleInIfThen() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/smartCastImpossibleInIfThen"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), TargetBackend.ANY, true);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/specifyOverrideExplicitly")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
@@ -10311,6 +10311,79 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/smartCastImpossibleInIfThen")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class SmartCastImpossibleInIfThen extends AbstractQuickFixTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInSmartCastImpossibleInIfThen() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/smartCastImpossibleInIfThen"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("ifThen.kt")
|
||||
public void testIfThen() throws Exception {
|
||||
runTest("idea/testData/quickfix/smartCastImpossibleInIfThen/ifThen.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("ifThen2.kt")
|
||||
public void testIfThen2() throws Exception {
|
||||
runTest("idea/testData/quickfix/smartCastImpossibleInIfThen/ifThen2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("ifThen3.kt")
|
||||
public void testIfThen3() throws Exception {
|
||||
runTest("idea/testData/quickfix/smartCastImpossibleInIfThen/ifThen3.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("ifThen4.kt")
|
||||
public void testIfThen4() throws Exception {
|
||||
runTest("idea/testData/quickfix/smartCastImpossibleInIfThen/ifThen4.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("ifThen5.kt")
|
||||
public void testIfThen5() throws Exception {
|
||||
runTest("idea/testData/quickfix/smartCastImpossibleInIfThen/ifThen5.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("ifThenElse.kt")
|
||||
public void testIfThenElse() throws Exception {
|
||||
runTest("idea/testData/quickfix/smartCastImpossibleInIfThen/ifThenElse.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("ifThenElse2.kt")
|
||||
public void testIfThenElse2() throws Exception {
|
||||
runTest("idea/testData/quickfix/smartCastImpossibleInIfThen/ifThenElse2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("ifThenElse3.kt")
|
||||
public void testIfThenElse3() throws Exception {
|
||||
runTest("idea/testData/quickfix/smartCastImpossibleInIfThen/ifThenElse3.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("ifThenElse4.kt")
|
||||
public void testIfThenElse4() throws Exception {
|
||||
runTest("idea/testData/quickfix/smartCastImpossibleInIfThen/ifThenElse4.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("ifThenElse5.kt")
|
||||
public void testIfThenElse5() throws Exception {
|
||||
runTest("idea/testData/quickfix/smartCastImpossibleInIfThen/ifThenElse5.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("ifThenElseMultiStatement.kt")
|
||||
public void testIfThenElseMultiStatement() throws Exception {
|
||||
runTest("idea/testData/quickfix/smartCastImpossibleInIfThen/ifThenElseMultiStatement.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("ifThenMultiStatement.kt")
|
||||
public void testIfThenMultiStatement() throws Exception {
|
||||
runTest("idea/testData/quickfix/smartCastImpossibleInIfThen/ifThenMultiStatement.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/specifyOverrideExplicitly")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user