Redundant async inspection: support case with GlobalScope
Partial implementation of KT-28504
This commit is contained in:
+11
-10
@@ -25,7 +25,7 @@ import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.PsiChildRange
|
||||
|
||||
class SimplifyCallChainFix(private val newCallText: String) : LocalQuickFix {
|
||||
class SimplifyCallChainFix(private val newCallText: String, private val removeReceiverOfFirstCall: Boolean = false) : LocalQuickFix {
|
||||
private val shortenedText = newCallText.split("(").joinToString(separator = "(") {
|
||||
it.substringAfterLast(".")
|
||||
}
|
||||
@@ -34,20 +34,21 @@ class SimplifyCallChainFix(private val newCallText: String) : LocalQuickFix {
|
||||
|
||||
override fun getFamilyName() = name
|
||||
|
||||
private fun apply(secondQualifiedExpression: KtQualifiedExpression) {
|
||||
val factory = KtPsiFactory(secondQualifiedExpression)
|
||||
val firstExpression = secondQualifiedExpression.receiverExpression
|
||||
fun apply(qualifiedExpression: KtQualifiedExpression) {
|
||||
val factory = KtPsiFactory(qualifiedExpression)
|
||||
val firstExpression = qualifiedExpression.receiverExpression
|
||||
|
||||
val operationSign = when (firstExpression) {
|
||||
val operationSign = if (removeReceiverOfFirstCall) "" else when (firstExpression) {
|
||||
is KtSafeQualifiedExpression -> "?."
|
||||
is KtQualifiedExpression -> "."
|
||||
else -> ""
|
||||
}
|
||||
|
||||
val receiverExpression = (firstExpression as? KtQualifiedExpression)?.receiverExpression
|
||||
val receiverExpressionOrEmptyString: Any =
|
||||
if (!removeReceiverOfFirstCall && firstExpression is KtQualifiedExpression) firstExpression.receiverExpression else ""
|
||||
|
||||
val firstCallExpression = AbstractCallChainChecker.getCallExpression(firstExpression) ?: return
|
||||
val secondCallExpression = secondQualifiedExpression.selectorExpression as? KtCallExpression ?: return
|
||||
val secondCallExpression = qualifiedExpression.selectorExpression as? KtCallExpression ?: return
|
||||
|
||||
val lastArgumentName = if (newCallText.startsWith("joinTo")) Name.identifier("transform") else null
|
||||
if (lastArgumentName != null) {
|
||||
@@ -82,7 +83,7 @@ class SimplifyCallChainFix(private val newCallText: String) : LocalQuickFix {
|
||||
|
||||
val newQualifiedExpression = if (lambdaExpression != null) factory.createExpressionByPattern(
|
||||
"$0$1$2 $3 $4",
|
||||
receiverExpression ?: "",
|
||||
receiverExpressionOrEmptyString,
|
||||
operationSign,
|
||||
newCallText,
|
||||
argumentsText,
|
||||
@@ -90,13 +91,13 @@ class SimplifyCallChainFix(private val newCallText: String) : LocalQuickFix {
|
||||
)
|
||||
else factory.createExpressionByPattern(
|
||||
"$0$1$2 $3",
|
||||
receiverExpression ?: "",
|
||||
receiverExpressionOrEmptyString,
|
||||
operationSign,
|
||||
newCallText,
|
||||
argumentsText
|
||||
)
|
||||
|
||||
val result = secondQualifiedExpression.replaced(newQualifiedExpression)
|
||||
val result = qualifiedExpression.replaced(newQualifiedExpression)
|
||||
ShortenReferences.DEFAULT.process(result)
|
||||
}
|
||||
|
||||
|
||||
+19
-3
@@ -7,20 +7,23 @@ package org.jetbrains.kotlin.idea.inspections.coroutines
|
||||
|
||||
import com.intellij.codeInspection.ProblemHighlightType
|
||||
import com.intellij.codeInspection.ProblemsHolder
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.inspections.collections.AbstractCallChainChecker
|
||||
import org.jetbrains.kotlin.idea.inspections.collections.SimplifyCallChainFix
|
||||
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
|
||||
import org.jetbrains.kotlin.psi.KtQualifiedExpression
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import org.jetbrains.kotlin.psi.qualifiedExpressionVisitor
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
|
||||
class RedundantAsyncInspection : AbstractCallChainChecker() {
|
||||
|
||||
private fun generateConversion(expression: KtQualifiedExpression): Conversion? {
|
||||
var defaultContext: Boolean? = null
|
||||
var defaultStart: Boolean? = null
|
||||
// Temporary forbid cases with explicit scope
|
||||
if (expression.receiverExpression is KtQualifiedExpression) return null
|
||||
|
||||
var conversion = findQualifiedConversion(expression, conversionGroups) check@{ _, firstResolvedCall, _, _ ->
|
||||
for ((parameterDescriptor, valueArgument) in firstResolvedCall.valueArguments) {
|
||||
@@ -36,6 +39,17 @@ class RedundantAsyncInspection : AbstractCallChainChecker() {
|
||||
defaultStart ?: return null
|
||||
if (defaultContext!! && !defaultStart!!) return null
|
||||
|
||||
val receiverExpression = expression.receiverExpression
|
||||
val scopeExpression = (receiverExpression as? KtQualifiedExpression)?.receiverExpression
|
||||
if (scopeExpression != null) {
|
||||
val context = scopeExpression.analyze(BodyResolveMode.PARTIAL)
|
||||
val scopeDescriptor = (scopeExpression as? KtNameReferenceExpression)?.let { context[BindingContext.REFERENCE_TARGET, it] }
|
||||
if (scopeDescriptor?.fqNameSafe?.toString() != globalScope) {
|
||||
// Temporary forbid cases with explicit non-global scope
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultContext!! && defaultStart!!) {
|
||||
conversion = conversion.withArgumentList(
|
||||
if (conversion === conversions[0]) {
|
||||
@@ -58,7 +72,7 @@ class RedundantAsyncInspection : AbstractCallChainChecker() {
|
||||
"Redundant 'async' call may be reduced to '$fullReplacement'",
|
||||
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
|
||||
isOnTheFly,
|
||||
SimplifyCallChainFix(fullReplacement)
|
||||
SimplifyCallChainFix(fullReplacement, removeReceiverOfFirstCall = true)
|
||||
)
|
||||
holder.registerProblem(descriptor)
|
||||
})
|
||||
@@ -79,6 +93,8 @@ class RedundantAsyncInspection : AbstractCallChainChecker() {
|
||||
)
|
||||
)
|
||||
|
||||
private const val globalScope = "kotlinx.coroutines.GlobalScope"
|
||||
|
||||
private const val defaultAsyncArgument = "$COROUTINE_PACKAGE.Dispatchers.Default"
|
||||
|
||||
private const val defaultAsyncArgumentExperimental = "$COROUTINE_EXPERIMENTAL_PACKAGE.DefaultDispatcher"
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
// PROBLEM: none
|
||||
|
||||
package kotlinx.coroutines
|
||||
|
||||
suspend fun test(ctx: CoroutineContext) {
|
||||
// Does not work yet (1.3.11)
|
||||
GlobalScope.<caret>async(ctx) { 42 }.await()
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
package kotlinx.coroutines
|
||||
|
||||
suspend fun test(ctx: CoroutineContext) {
|
||||
withContext(ctx) { 42 }
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
package kotlinx.coroutines
|
||||
|
||||
suspend fun test() {
|
||||
GlobalScope.<caret>async() { 42 }.await()
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
package kotlinx.coroutines
|
||||
|
||||
suspend fun test() {
|
||||
withContext(Dispatchers.Default) { 42 }
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.async
|
||||
|
||||
suspend fun test() {
|
||||
GlobalScope.<caret>async() { 42 }.await()
|
||||
}
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
suspend fun test() {
|
||||
withContext(Dispatchers.Default) { 42 }
|
||||
}
|
||||
+10
@@ -2080,6 +2080,16 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
|
||||
runTest("idea/testData/inspectionsLocal/coroutines/redundantAsync/globalScope.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("globalScopeNoContext.kt")
|
||||
public void testGlobalScopeNoContext() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/coroutines/redundantAsync/globalScopeNoContext.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("globalScopeNoContextNoPackage.kt")
|
||||
public void testGlobalScopeNoContextNoPackage() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/coroutines/redundantAsync/globalScopeNoContextNoPackage.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/coroutines/redundantAsync/simple.kt");
|
||||
|
||||
Reference in New Issue
Block a user