KT-12100 convert try / finally to use: 'this' cases, implementation via resolved call
This commit is contained in:
+40
-23
@@ -17,19 +17,17 @@
|
|||||||
package org.jetbrains.kotlin.idea.intentions
|
package org.jetbrains.kotlin.idea.intentions
|
||||||
|
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.psi.PsiElement
|
|
||||||
import com.intellij.psi.search.LocalSearchScope
|
|
||||||
import com.intellij.psi.search.searches.ReferencesSearch
|
|
||||||
import org.jetbrains.kotlin.descriptors.VariableDescriptor
|
import org.jetbrains.kotlin.descriptors.VariableDescriptor
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
import org.jetbrains.kotlin.idea.inspections.IntentionBasedInspection
|
import org.jetbrains.kotlin.idea.inspections.IntentionBasedInspection
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.allChildren
|
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.contentRange
|
import org.jetbrains.kotlin.psi.psiUtil.contentRange
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
|
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.callUtil.isSafeCall
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||||
|
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
|
||||||
|
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
|
||||||
import org.jetbrains.kotlin.types.typeUtil.supertypes
|
import org.jetbrains.kotlin.types.typeUtil.supertypes
|
||||||
|
|
||||||
class ConvertTryFinallyToUseCallInspection : IntentionBasedInspection<KtTryExpression>(ConvertTryFinallyToUseCallIntention::class) {
|
class ConvertTryFinallyToUseCallInspection : IntentionBasedInspection<KtTryExpression>(ConvertTryFinallyToUseCallIntention::class) {
|
||||||
@@ -41,17 +39,24 @@ class ConvertTryFinallyToUseCallIntention : SelfTargetingOffsetIndependentIntent
|
|||||||
) {
|
) {
|
||||||
override fun applyTo(element: KtTryExpression, editor: Editor?) {
|
override fun applyTo(element: KtTryExpression, editor: Editor?) {
|
||||||
val finallySection = element.finallyBlock!!
|
val finallySection = element.finallyBlock!!
|
||||||
val finallyDotCall = finallySection.finalExpression.statements.singleOrNull() as KtDotQualifiedExpression
|
val finallyExpression = finallySection.finalExpression.statements.single()
|
||||||
val resourceReference = finallyDotCall.receiverExpression as KtNameReferenceExpression
|
val finallyExpressionReceiver = (finallyExpression as? KtDotQualifiedExpression)?.receiverExpression
|
||||||
|
val resourceReference = finallyExpressionReceiver as? KtNameReferenceExpression
|
||||||
|
val resourceName = resourceReference?.getReferencedNameAsName()
|
||||||
|
|
||||||
val factory = KtPsiFactory(element)
|
val factory = KtPsiFactory(element)
|
||||||
|
|
||||||
val useCallExpression = factory.buildExpression {
|
val useCallExpression = factory.buildExpression {
|
||||||
appendName(resourceReference.getReferencedNameAsName())
|
if (resourceName != null) {
|
||||||
appendFixedText(".use {")
|
appendName(resourceName)
|
||||||
|
appendFixedText(".")
|
||||||
|
}
|
||||||
|
appendFixedText("use {")
|
||||||
|
|
||||||
appendName(resourceReference.getReferencedNameAsName())
|
if (resourceName != null) {
|
||||||
appendFixedText("->")
|
appendName(resourceName)
|
||||||
|
appendFixedText("->")
|
||||||
|
}
|
||||||
|
|
||||||
appendChildRange(element.tryBlock.contentRange())
|
appendChildRange(element.tryBlock.contentRange())
|
||||||
appendFixedText("}")
|
appendFixedText("}")
|
||||||
@@ -63,21 +68,33 @@ class ConvertTryFinallyToUseCallIntention : SelfTargetingOffsetIndependentIntent
|
|||||||
override fun isApplicableTo(element: KtTryExpression): Boolean {
|
override fun isApplicableTo(element: KtTryExpression): Boolean {
|
||||||
// Single statement in finally, no catch blocks
|
// Single statement in finally, no catch blocks
|
||||||
val finallySection = element.finallyBlock ?: return false
|
val finallySection = element.finallyBlock ?: return false
|
||||||
val finallyDotCall = finallySection.finalExpression.statements.singleOrNull() as? KtDotQualifiedExpression ?: return false
|
val finallyExpression = finallySection.finalExpression.statements.singleOrNull() ?: return false
|
||||||
if (element.catchClauses.isNotEmpty()) return false
|
if (element.catchClauses.isNotEmpty()) return false
|
||||||
|
|
||||||
// Like resource.close()
|
val context = element.analyze()
|
||||||
val resourceReference = finallyDotCall.receiverExpression as? KtNameReferenceExpression ?: return false
|
val resolvedCall = finallyExpression.getResolvedCall(context) ?: return false
|
||||||
val resourceCall = finallyDotCall.selectorExpression as? KtCallExpression ?: return false
|
if (resolvedCall.call.isSafeCall()) return false
|
||||||
if (resourceCall.calleeExpression?.text != "close") return false
|
if (resolvedCall.candidateDescriptor.name.asString() != "close") return false
|
||||||
|
val receiver = resolvedCall.dispatchReceiver ?: return false
|
||||||
// resource is Closeable immutable local variable
|
if (receiver.type.supertypes().all {
|
||||||
val resourceDescriptor =
|
|
||||||
element.analyze().get(BindingContext.REFERENCE_TARGET, resourceReference) as? VariableDescriptor ?: return false
|
|
||||||
return !resourceDescriptor.isVar && resourceDescriptor.type.supertypes().any {
|
|
||||||
it.constructor.declarationDescriptor?.fqNameSafe?.asString().let {
|
it.constructor.declarationDescriptor?.fqNameSafe?.asString().let {
|
||||||
it == "java.io.Closeable" || it == "java.lang.AutoCloseable"
|
it != "java.io.Closeable" && it != "java.lang.AutoCloseable"
|
||||||
}
|
}
|
||||||
|
}) return false
|
||||||
|
|
||||||
|
return when (receiver) {
|
||||||
|
is ExpressionReceiver -> {
|
||||||
|
val expression = receiver.expression
|
||||||
|
if (expression is KtThisExpression) true
|
||||||
|
else {
|
||||||
|
val resourceReference = expression as? KtReferenceExpression ?: return false
|
||||||
|
val resourceDescriptor =
|
||||||
|
context[BindingContext.REFERENCE_TARGET, resourceReference] as? VariableDescriptor ?: return false
|
||||||
|
!resourceDescriptor.isVar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is ImplicitReceiver -> true
|
||||||
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
import java.io.File
|
||||||
|
import java.io.BufferedReader
|
||||||
|
|
||||||
|
fun BufferedReader.foo() {
|
||||||
|
try {
|
||||||
|
readLine()
|
||||||
|
}
|
||||||
|
<caret>finally {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
import java.io.File
|
||||||
|
import java.io.BufferedReader
|
||||||
|
|
||||||
|
fun BufferedReader.foo() {
|
||||||
|
use { readLine() }
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
import java.io.File
|
||||||
|
import java.io.BufferedReader
|
||||||
|
|
||||||
|
fun foo(reader: BufferedReader) {
|
||||||
|
try {
|
||||||
|
reader.readLine()
|
||||||
|
}
|
||||||
|
<caret>finally {
|
||||||
|
reader.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
import java.io.File
|
||||||
|
import java.io.BufferedReader
|
||||||
|
|
||||||
|
fun foo(reader: BufferedReader) {
|
||||||
|
reader.use { reader -> reader.readLine() }
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// IS_APPLICABLE: false
|
||||||
|
// WITH_RUNTIME
|
||||||
|
import java.io.File
|
||||||
|
import java.io.BufferedReader
|
||||||
|
|
||||||
|
fun foo(reader: BufferedReader?) {
|
||||||
|
try {
|
||||||
|
reader?.readLine()
|
||||||
|
}
|
||||||
|
<caret>finally {
|
||||||
|
reader?.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
import java.io.File
|
||||||
|
import java.io.BufferedReader
|
||||||
|
|
||||||
|
fun BufferedReader.foo() {
|
||||||
|
try {
|
||||||
|
this.readLine()
|
||||||
|
}
|
||||||
|
<caret>finally {
|
||||||
|
this.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
import java.io.File
|
||||||
|
import java.io.BufferedReader
|
||||||
|
|
||||||
|
fun BufferedReader.foo() {
|
||||||
|
use { this.readLine() }
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
import java.io.Closeable
|
||||||
|
|
||||||
|
class MyCloseable : Closeable {
|
||||||
|
override fun close() {}
|
||||||
|
|
||||||
|
fun process(x: Int) = x
|
||||||
|
|
||||||
|
fun Int.foo() {
|
||||||
|
try {
|
||||||
|
this@MyCloseable.process(this)
|
||||||
|
}
|
||||||
|
<caret>finally {
|
||||||
|
this@MyCloseable.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
import java.io.Closeable
|
||||||
|
|
||||||
|
class MyCloseable : Closeable {
|
||||||
|
override fun close() {}
|
||||||
|
|
||||||
|
fun process(x: Int) = x
|
||||||
|
|
||||||
|
fun Int.foo() {
|
||||||
|
use { this@MyCloseable.process(this) }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6108,6 +6108,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("implicitReceiver.kt")
|
||||||
|
public void testImplicitReceiver() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertTryFinallyToUseCall/implicitReceiver.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("notClose.kt")
|
@TestMetadata("notClose.kt")
|
||||||
public void testNotClose() throws Exception {
|
public void testNotClose() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertTryFinallyToUseCall/notClose.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertTryFinallyToUseCall/notClose.kt");
|
||||||
@@ -6120,12 +6126,36 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("parameter.kt")
|
||||||
|
public void testParameter() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertTryFinallyToUseCall/parameter.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("safeCall.kt")
|
||||||
|
public void testSafeCall() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertTryFinallyToUseCall/safeCall.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("simple.kt")
|
@TestMetadata("simple.kt")
|
||||||
public void testSimple() throws Exception {
|
public void testSimple() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertTryFinallyToUseCall/simple.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertTryFinallyToUseCall/simple.kt");
|
||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("this.kt")
|
||||||
|
public void testThis() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertTryFinallyToUseCall/this.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("thisLabeled.kt")
|
||||||
|
public void testThisLabeled() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertTryFinallyToUseCall/thisLabeled.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("triple.kt")
|
@TestMetadata("triple.kt")
|
||||||
public void testTriple() throws Exception {
|
public void testTriple() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertTryFinallyToUseCall/triple.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertTryFinallyToUseCall/triple.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user