Add "Ambiguous context due to scope receiver in suspend fun" inspection
#KT-28696 Fixed
(cherry picked from commit a15b47c93c8217af3430d9a7a2af97c5ce6c7d6f)
diff --git a/idea/resources/META-INF/plugin-common.xml b/idea/resources/META-INF/plugin-common.xml
index 6a05a33..260a923 100644
--- a/idea/resources/META-INF/plugin-common.xml
+++ b/idea/resources/META-INF/plugin-common.xml
@@ -3173,6 +3173,15 @@
language="kotlin"
/>
+ <localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.coroutines.SuspendFunctionOnCoroutineScopeInspection"
+ displayName="Ambiguous coroutineContext due to CoroutineScope receiver of suspend function"
+ groupPath="Kotlin"
+ groupName="Probable bugs"
+ enabledByDefault="true"
+ level="WARNING"
+ language="kotlin"
+ />
+
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
diff --git a/idea/resources/inspectionDescriptions/SuspendFunctionOnCoroutineScope.html b/idea/resources/inspectionDescriptions/SuspendFunctionOnCoroutineScope.html
new file mode 100644
index 0000000..0401a74
--- /dev/null
+++ b/idea/resources/inspectionDescriptions/SuspendFunctionOnCoroutineScope.html
@@ -0,0 +1,17 @@
+<html>
+<body>
+This inspection reports calls & accesses of <b>CoroutineScope</b> extensions or members
+inside suspend functions with <b>CoroutineScope</b> receiver.
+
+Both suspend functions and <b>CoroutineScope</b> members & extensions have access to <b>coroutineContext</b>.
+When some function is simultaneously suspend <b>and</b> has <b>CoroutineScope</b> receiver,
+it has ambiguous access to <b>CoroutineContext</b>:
+first via <b>kotlin.coroutines.coroutineContext</b> and second via <b>CoroutineScope.coroutineContext</b>,
+and two these contexts are different in general case.
+So when we call some <b>CoroutineScope</b> extension or access <b>coroutineContext</b> from such a function,
+it's unclear which from these two context do we have in mind.
+
+Normal ways to fix this are to wrap suspicious call inside <b>coroutineScope { ... }</b> or
+to get rid of <b>CoroutineScope</b> function receiver.
+</body>
+</html>
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedReceiverParameterInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedReceiverParameterInspection.kt
index 0024951..f6c9449 100644
--- a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedReceiverParameterInspection.kt
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedReceiverParameterInspection.kt
@@ -36,7 +36,6 @@
import org.jetbrains.kotlin.idea.refactoring.changeSignature.runChangeSignature
import org.jetbrains.kotlin.idea.refactoring.explicateAsTextForReceiver
import org.jetbrains.kotlin.idea.refactoring.getThisLabelName
-import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.idea.util.getThisReceiverOwner
import org.jetbrains.kotlin.lexer.KtTokens
@@ -119,45 +118,53 @@
receiverTypeReference,
KotlinBundle.message("unused.receiver.parameter"),
ProblemHighlightType.LIKE_UNUSED_SYMBOL,
- MyQuickFix(inSameClass)
+ RemoveReceiverFix(inSameClass)
)
}
}
}
- private class MyQuickFix(private val inSameClass: Boolean) : LocalQuickFix {
- override fun getName(): String = KotlinBundle.message("unused.receiver.parameter.remove")
-
- private fun configureChangeSignature() = object : KotlinChangeSignatureConfiguration {
- override fun performSilently(affectedFunctions: Collection<PsiElement>) = true
- override fun configure(originalDescriptor: KotlinMethodDescriptor) = originalDescriptor.modify { it.removeParameter(0) }
- }
+ class RemoveReceiverFix(private val inSameClass: Boolean) : LocalQuickFix {
+ override fun getName(): String = actionName
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val element = descriptor.psiElement as? KtTypeReference ?: return
if (!FileModificationService.getInstance().preparePsiElementForWrite(element)) return
- val function = element.parent as? KtCallableDeclaration ?: return
- val callableDescriptor = function.resolveToDescriptorIfAny(BodyResolveMode.FULL) as? CallableDescriptor ?: return
-
- val typeParameters = RemoveUnusedFunctionParameterFix.typeParameters(element)
- if (inSameClass) {
- runWriteAction {
- val explicateAsTextForReceiver = callableDescriptor.explicateAsTextForReceiver()
- function.forEachDescendantOfType<KtThisExpression> {
- if (it.text == explicateAsTextForReceiver) it.labelQualifier?.delete()
- }
- function.setReceiverTypeReference(null)
- }
- } else {
- runChangeSignature(project, callableDescriptor, configureChangeSignature(), element, name)
- }
- RemoveUnusedFunctionParameterFix.runRemoveUnusedTypeParameters(typeParameters)
+ apply(element, project, inSameClass)
}
- override fun getFamilyName(): String = name
+ override fun getFamilyName(): String = actionName
override fun startInWriteAction() = false
+
+ companion object {
+ private val actionName = KotlinBundle.message("unused.receiver.parameter.remove")
+
+ private fun configureChangeSignature() = object : KotlinChangeSignatureConfiguration {
+ override fun performSilently(affectedFunctions: Collection<PsiElement>) = true
+ override fun configure(originalDescriptor: KotlinMethodDescriptor) = originalDescriptor.modify { it.removeParameter(0) }
+ }
+
+ fun apply(element: KtTypeReference, project: Project, inSameClass: Boolean = false) {
+ val function = element.parent as? KtCallableDeclaration ?: return
+ val callableDescriptor = function.resolveToDescriptorIfAny(BodyResolveMode.FULL) as? CallableDescriptor ?: return
+
+ val typeParameters = RemoveUnusedFunctionParameterFix.typeParameters(element)
+ if (inSameClass) {
+ runWriteAction {
+ val explicateAsTextForReceiver = callableDescriptor.explicateAsTextForReceiver()
+ function.forEachDescendantOfType<KtThisExpression> {
+ if (it.text == explicateAsTextForReceiver) it.labelQualifier?.delete()
+ }
+ function.setReceiverTypeReference(null)
+ }
+ } else {
+ runChangeSignature(project, callableDescriptor, configureChangeSignature(), element, actionName)
+ }
+ RemoveUnusedFunctionParameterFix.runRemoveUnusedTypeParameters(typeParameters)
+ }
+ }
}
}
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/SuspendFunctionOnCoroutineScopeInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/SuspendFunctionOnCoroutineScopeInspection.kt
new file mode 100644
index 0000000..38db77c
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/SuspendFunctionOnCoroutineScopeInspection.kt
@@ -0,0 +1,216 @@
+/*
+ * 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.inspections.coroutines
+
+import com.intellij.codeInsight.FileModificationService
+import com.intellij.codeInspection.*
+import com.intellij.codeInspection.ProblemHighlightType.*
+import com.intellij.openapi.project.Project
+import com.intellij.psi.PsiElementVisitor
+import com.intellij.psi.codeStyle.CodeStyleManager
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
+import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent
+import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
+import org.jetbrains.kotlin.idea.core.ShortenReferences
+import org.jetbrains.kotlin.idea.core.replaced
+import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
+import org.jetbrains.kotlin.idea.inspections.UnusedReceiverParameterInspection
+import org.jetbrains.kotlin.idea.intentions.ConvertReceiverToParameterIntention
+import org.jetbrains.kotlin.idea.intentions.MoveMemberToCompanionObjectIntention
+import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
+import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
+import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
+import org.jetbrains.kotlin.resolve.BindingContext
+import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
+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.resolve.scopes.receivers.ReceiverValue
+import org.jetbrains.kotlin.types.KotlinType
+
+class SuspendFunctionOnCoroutineScopeInspection : AbstractKotlinInspection() {
+
+ override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
+ return namedFunctionVisitor(fun(function: KtNamedFunction) {
+ if (!function.hasModifier(KtTokens.SUSPEND_KEYWORD)) return
+ if (!function.hasBody()) return
+
+ val context = function.analyzeWithContent()
+ val descriptor = context[BindingContext.FUNCTION, function] ?: return
+ val (extensionOfCoroutineScope, memberOfCoroutineScope) = with(descriptor) {
+ extensionReceiverParameter.ofCoroutineScopeType() to dispatchReceiverParameter.ofCoroutineScopeType()
+ }
+ if (!extensionOfCoroutineScope && !memberOfCoroutineScope) return
+
+ fun DeclarationDescriptor.isReceiverOfAnalyzedFunction(): Boolean {
+ if (extensionOfCoroutineScope && this == descriptor) return true
+ if (memberOfCoroutineScope && this == descriptor.containingDeclaration) return true
+ return false
+ }
+
+ fun checkSuspiciousReceiver(receiver: ReceiverValue, problemExpression: KtExpression) {
+ when (receiver) {
+ is ImplicitReceiver -> if (!receiver.declarationDescriptor.isReceiverOfAnalyzedFunction()) return
+ is ExpressionReceiver -> {
+ val receiverThisExpression = receiver.expression as? KtThisExpression ?: return
+ if (receiverThisExpression.getTargetLabel() != null) {
+ val instanceReference = receiverThisExpression.instanceReference
+ if (context[BindingContext.REFERENCE_TARGET, instanceReference]?.isReceiverOfAnalyzedFunction() != true) return
+ }
+ }
+ }
+ val fixes = mutableListOf<LocalQuickFix>()
+ val reportElement = (problemExpression as? KtCallExpression)?.calleeExpression ?: problemExpression
+ holder.registerProblem(
+ reportElement,
+ MESSAGE,
+ GENERIC_ERROR_OR_WARNING,
+ WrapWithCoroutineScopeFix(removeReceiver = false, wrapCallOnly = true)
+ )
+ fixes += WrapWithCoroutineScopeFix(removeReceiver = extensionOfCoroutineScope, wrapCallOnly = false)
+ val file = function.containingKtFile
+ if (extensionOfCoroutineScope) {
+ fixes += IntentionWrapper(ConvertReceiverToParameterIntention(), file)
+ }
+ if (memberOfCoroutineScope) {
+ val containingDeclaration = function.containingClassOrObject
+ if (containingDeclaration is KtClass && !containingDeclaration.isInterface() && function.hasBody()) {
+ fixes += IntentionWrapper(MoveMemberToCompanionObjectIntention(), file)
+ }
+ }
+
+ holder.registerProblem(
+ with(function) { receiverTypeReference ?: nameIdentifier ?: funKeyword ?: this },
+ MESSAGE,
+ GENERIC_ERROR_OR_WARNING,
+ *fixes.toTypedArray()
+ )
+ }
+
+ function.forEachDescendantOfType(fun(callExpression: KtCallExpression) {
+ val resolvedCall = callExpression.getResolvedCall(context) ?: return
+ val extensionReceiverParameter = resolvedCall.resultingDescriptor.extensionReceiverParameter ?: return
+ if (!extensionReceiverParameter.type.isCoroutineScope()) return
+ val extensionReceiver = resolvedCall.extensionReceiver ?: return
+ checkSuspiciousReceiver(extensionReceiver, callExpression)
+ })
+ function.forEachDescendantOfType(fun(nameReferenceExpression: KtNameReferenceExpression) {
+ if (nameReferenceExpression.getReferencedName() != COROUTINE_CONTEXT) return
+ val resolvedCall = nameReferenceExpression.getResolvedCall(context) ?: return
+ if (resolvedCall.resultingDescriptor.fqNameSafe.asString() == "$COROUTINE_SCOPE.$COROUTINE_CONTEXT") {
+ val dispatchReceiver = resolvedCall.dispatchReceiver ?: return
+ checkSuspiciousReceiver(dispatchReceiver, nameReferenceExpression)
+ }
+ })
+ })
+ }
+
+ private class WrapWithCoroutineScopeFix(
+ private val removeReceiver: Boolean,
+ private val wrapCallOnly: Boolean
+ ) : LocalQuickFix {
+ override fun getFamilyName(): String = "Wrap with coroutineScope"
+
+ override fun getName(): String =
+ when {
+ removeReceiver && !wrapCallOnly -> "Remove receiver & wrap with 'coroutineScope { ... }'"
+ wrapCallOnly -> "Wrap call with 'coroutineScope { ... }'"
+ else -> "Wrap function body with 'coroutineScope { ... }'"
+ }
+
+ override fun startInWriteAction() = false
+
+ override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
+ val problemElement = descriptor.psiElement ?: return
+ val function = problemElement.getNonStrictParentOfType<KtNamedFunction>() ?: return
+ val functionDescriptor = function.resolveToDescriptorIfAny()
+ if (!FileModificationService.getInstance().preparePsiElementForWrite(function)) return
+ val bodyExpression = function.bodyExpression
+
+ fun getExpressionToWrapCall(): KtExpression? {
+ var result = problemElement as? KtExpression ?: return null
+ while (result.parent is KtQualifiedExpression || result.parent is KtCallExpression) {
+ result = result.parent as KtExpression
+ }
+ return result
+ }
+
+ var expressionToWrap = when {
+ wrapCallOnly -> getExpressionToWrapCall()
+ else -> bodyExpression
+ } ?: return
+ if (functionDescriptor?.extensionReceiverParameter.ofCoroutineScopeType()) {
+ val context = function.analyzeWithContent()
+ expressionToWrap.forEachDescendantOfType<KtDotQualifiedExpression> {
+ val receiverExpression = it.receiverExpression as? KtThisExpression
+ val selectorExpression = it.selectorExpression
+ if (receiverExpression?.getTargetLabel() != null && selectorExpression != null) {
+ if (context[BindingContext.REFERENCE_TARGET, receiverExpression.instanceReference] == functionDescriptor) {
+ if (it === expressionToWrap) {
+ expressionToWrap = it.replaced(selectorExpression)
+ } else {
+ it.replace(selectorExpression)
+ }
+ }
+ }
+ }
+ }
+
+ val factory = KtPsiFactory(function)
+ val blockExpression = function.bodyBlockExpression
+ project.executeWriteCommand(name, this) {
+ val result = when {
+ expressionToWrap != bodyExpression -> expressionToWrap.replaced(
+ factory.createExpressionByPattern("$COROUTINE_SCOPE_WRAPPER { $0 }", expressionToWrap)
+ )
+ blockExpression == null -> bodyExpression.replaced(
+ factory.createExpressionByPattern("$COROUTINE_SCOPE_WRAPPER { $0 }", bodyExpression)
+ )
+ else -> {
+ val bodyText = buildString {
+ for (statement in blockExpression.statements) {
+ append(statement.text)
+ }
+ }
+ blockExpression.replaced(
+ factory.createBlock("$COROUTINE_SCOPE_WRAPPER { $bodyText }")
+ )
+ }
+ }
+ val reformatted = CodeStyleManager.getInstance(project).reformat(result)
+ ShortenReferences.DEFAULT.process(reformatted as KtElement)
+ }
+
+ val receiverTypeReference = function.receiverTypeReference
+ if (removeReceiver && !wrapCallOnly && receiverTypeReference != null) {
+ UnusedReceiverParameterInspection.RemoveReceiverFix.apply(receiverTypeReference, project)
+ }
+ }
+ }
+
+ companion object {
+ private const val COROUTINE_SCOPE = "kotlinx.coroutines.CoroutineScope"
+
+ private const val COROUTINE_SCOPE_WRAPPER = "kotlinx.coroutines.coroutineScope"
+
+ private const val COROUTINE_CONTEXT = "coroutineContext"
+
+ private const val MESSAGE = "Ambiguous coroutineContext due to CoroutineScope receiver of suspend function"
+
+ private fun KotlinType.isCoroutineScope(): Boolean =
+ constructor.declarationDescriptor?.fqNameSafe?.asString() == COROUTINE_SCOPE
+
+ private fun ReceiverParameterDescriptor?.ofCoroutineScopeType(): Boolean {
+ if (this == null) return false
+ if (type.isCoroutineScope()) return true
+ return type.constructor.supertypes.reversed().any { it.isCoroutineScope() }
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/.inspection b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/.inspection
new file mode 100644
index 0000000..29c4137
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/.inspection
@@ -0,0 +1 @@
+org.jetbrains.kotlin.idea.inspections.coroutines.SuspendFunctionOnCoroutineScopeInspection
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/anonymous.kt b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/anonymous.kt
new file mode 100644
index 0000000..f4c046a
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/anonymous.kt
@@ -0,0 +1,12 @@
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+
+fun doSomething() {}
+
+fun foo() {
+ val scope = object : CoroutineScope {
+ suspend fun foo() {
+ <caret>async { doSomething() }
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/anonymous.kt.after b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/anonymous.kt.after
new file mode 100644
index 0000000..0595234
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/anonymous.kt.after
@@ -0,0 +1,13 @@
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
+
+fun doSomething() {}
+
+fun foo() {
+ val scope = object : CoroutineScope {
+ suspend fun foo() {
+ coroutineScope { async { doSomething() } }
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/body.kt b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/body.kt
new file mode 100644
index 0000000..c087092
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/body.kt
@@ -0,0 +1,8 @@
+// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+
+fun calcSomething() = 42
+
+suspend fun <caret>CoroutineScope.foo() = async { calcSomething() }
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/body.kt.after b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/body.kt.after
new file mode 100644
index 0000000..e490d77
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/body.kt.after
@@ -0,0 +1,8 @@
+// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
+
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
+
+fun calcSomething() = 42
+
+suspend fun foo() = coroutineScope { async { calcSomething() } }
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/call.kt b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/call.kt
new file mode 100644
index 0000000..e3e853a
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/call.kt
@@ -0,0 +1,15 @@
+// FIX: Wrap call with 'coroutineScope { ... }'
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+
+fun calcSomething() {}
+
+suspend fun CoroutineScope.foo() {
+ async {
+ calcSomething()
+ }
+ <caret>async {
+ calcSomething()
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/call.kt.after b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/call.kt.after
new file mode 100644
index 0000000..959fc42
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/call.kt.after
@@ -0,0 +1,18 @@
+// FIX: Wrap call with 'coroutineScope { ... }'
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
+
+fun calcSomething() {}
+
+suspend fun CoroutineScope.foo() {
+ async {
+ calcSomething()
+ }
+ coroutineScope {
+ async {
+ calcSomething()
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/context.kt b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/context.kt
new file mode 100644
index 0000000..f0bdae6
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/context.kt
@@ -0,0 +1,10 @@
+// FIX: Wrap call with 'coroutineScope { ... }'
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineContext
+
+fun use(context: CoroutineContext) {}
+
+suspend fun CoroutineScope.foo() {
+ use(<caret>coroutineContext)
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/context.kt.after b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/context.kt.after
new file mode 100644
index 0000000..05fc051
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/context.kt.after
@@ -0,0 +1,11 @@
+// FIX: Wrap call with 'coroutineScope { ... }'
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineContext
+import kotlinx.coroutines.coroutineScope
+
+fun use(context: CoroutineContext) {}
+
+suspend fun CoroutineScope.foo() {
+ use(coroutineScope { coroutineContext })
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/contextWithLabeledThis.kt b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/contextWithLabeledThis.kt
new file mode 100644
index 0000000..46d01e7
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/contextWithLabeledThis.kt
@@ -0,0 +1,10 @@
+// FIX: Wrap call with 'coroutineScope { ... }'
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineContext
+
+fun use(context: CoroutineContext) {}
+
+suspend fun CoroutineScope.foo() {
+ use(this@foo.<caret>coroutineContext)
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/contextWithLabeledThis.kt.after b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/contextWithLabeledThis.kt.after
new file mode 100644
index 0000000..05fc051
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/contextWithLabeledThis.kt.after
@@ -0,0 +1,11 @@
+// FIX: Wrap call with 'coroutineScope { ... }'
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineContext
+import kotlinx.coroutines.coroutineScope
+
+fun use(context: CoroutineContext) {}
+
+suspend fun CoroutineScope.foo() {
+ use(coroutineScope { coroutineContext })
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/contextWithThis.kt b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/contextWithThis.kt
new file mode 100644
index 0000000..2fe983b
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/contextWithThis.kt
@@ -0,0 +1,10 @@
+// FIX: Wrap call with 'coroutineScope { ... }'
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineContext
+
+fun use(context: CoroutineContext) {}
+
+suspend fun CoroutineScope.foo() {
+ use(this.<caret>coroutineContext)
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/contextWithThis.kt.after b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/contextWithThis.kt.after
new file mode 100644
index 0000000..9bb7a24
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/contextWithThis.kt.after
@@ -0,0 +1,11 @@
+// FIX: Wrap call with 'coroutineScope { ... }'
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineContext
+import kotlinx.coroutines.coroutineScope
+
+fun use(context: CoroutineContext) {}
+
+suspend fun CoroutineScope.foo() {
+ use(coroutineScope { this.coroutineContext })
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/convertReceiver.kt b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/convertReceiver.kt
new file mode 100644
index 0000000..1330597d
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/convertReceiver.kt
@@ -0,0 +1,8 @@
+// FIX: Convert receiver to parameter
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+
+fun calcSomething() = 42
+
+suspend fun <caret>CoroutineScope.foo() = async { calcSomething() }
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/convertReceiver.kt.after b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/convertReceiver.kt.after
new file mode 100644
index 0000000..fe239b6
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/convertReceiver.kt.after
@@ -0,0 +1,8 @@
+// FIX: Convert receiver to parameter
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+
+fun calcSomething() = 42
+
+suspend fun foo(coroutineScope: CoroutineScope) = coroutineScope.async { calcSomething() }
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/coroutines.lib.kt b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/coroutines.lib.kt
new file mode 100644
index 0000000..e7a3450
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/coroutines.lib.kt
@@ -0,0 +1,46 @@
+package kotlinx.coroutines
+
+interface Deferred<T> {
+ suspend fun await(): T
+}
+
+interface CoroutineContext
+
+object Dispatchers {
+ object Default : CoroutineContext
+}
+
+enum class CoroutineStart {
+ DEFAULT,
+ LAZY,
+ ATOMIC,
+ UNDISPATCHED
+}
+
+interface CoroutineScope {
+ val coroutineContext: CoroutineContext get() = Dispatchers.Default
+}
+
+object GlobalScope : CoroutineScope
+
+fun <T> CoroutineScope.async(
+ context: CoroutineContext = Dispatchers.Default,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ block: suspend CoroutineScope.() -> T
+): Deferred<T> {
+ TODO()
+}
+
+suspend fun <T> withContext(
+ context: CoroutineContext,
+ block: suspend CoroutineScope.() -> T
+): T {
+ TODO()
+}
+
+suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R = GlobalScope.block()
+
+operator fun CoroutineContext.plus(other: CoroutineContext): CoroutineContext {
+ TODO()
+}
+
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/declaration.kt b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/declaration.kt
new file mode 100644
index 0000000..3ac1d87
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/declaration.kt
@@ -0,0 +1,10 @@
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+
+fun calcSomething() = 42
+
+suspend fun CoroutineScope.foo() {
+ val deferred = <caret>async {
+ calcSomething()
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/declaration.kt.after b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/declaration.kt.after
new file mode 100644
index 0000000..529611f
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/declaration.kt.after
@@ -0,0 +1,13 @@
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
+
+fun calcSomething() = 42
+
+suspend fun CoroutineScope.foo() {
+ val deferred = coroutineScope {
+ async {
+ calcSomething()
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/inner.kt b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/inner.kt
new file mode 100644
index 0000000..2e39394
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/inner.kt
@@ -0,0 +1,15 @@
+// PROBLEM: none
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineContext
+
+fun use(context: CoroutineContext) {}
+
+abstract class MyCoroutineScope : CoroutineScope {
+ inner class Inner {
+ suspend fun foo() {
+ // Does not work yet (could work)
+ use(<caret>coroutineContext)
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/lots.kt b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/lots.kt
new file mode 100644
index 0000000..acb67a7
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/lots.kt
@@ -0,0 +1,21 @@
+// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+
+fun calcSomething() {}
+
+suspend fun <caret>CoroutineScope.foo() {
+ async {
+ calcSomething()
+ calcSomething() // ...
+
+ /* ... */
+
+ calcSomething() /* ... */
+
+ calcSomething()
+ if (true) // ...
+ calcSomething()
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/lots.kt.after b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/lots.kt.after
new file mode 100644
index 0000000..8d7fa39
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/lots.kt.after
@@ -0,0 +1,23 @@
+// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
+
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
+
+fun calcSomething() {}
+
+suspend fun foo() {
+ coroutineScope {
+ async {
+ calcSomething()
+ calcSomething() // ...
+
+ /* ... */
+
+ calcSomething() /* ... */
+
+ calcSomething()
+ if (true) // ...
+ calcSomething()
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/member.kt b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/member.kt
new file mode 100644
index 0000000..b14e1e1
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/member.kt
@@ -0,0 +1,10 @@
+// FIX: Move to companion object
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+
+fun calcSomething() = 42
+
+abstract class MyCoroutineScope : CoroutineScope {
+ suspend fun <caret>foo() = async { calcSomething() }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/member.kt.after b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/member.kt.after
new file mode 100644
index 0000000..1f814aa
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/member.kt.after
@@ -0,0 +1,12 @@
+// FIX: Move to companion object
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+
+fun calcSomething() = 42
+
+abstract class MyCoroutineScope : CoroutineScope {
+ companion object {
+ suspend fun foo(myCoroutineScope: MyCoroutineScope) = myCoroutineScope.async { calcSomething() }
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/object.kt b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/object.kt
new file mode 100644
index 0000000..aae26e4
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/object.kt
@@ -0,0 +1,8 @@
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+
+fun calcSomething() = 42
+
+object MyCoroutineScope : CoroutineScope {
+ suspend fun <caret>foo() = async { calcSomething() }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/object.kt.after b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/object.kt.after
new file mode 100644
index 0000000..a662c27
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/object.kt.after
@@ -0,0 +1,9 @@
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
+
+fun calcSomething() = 42
+
+object MyCoroutineScope : CoroutineScope {
+ suspend fun foo() = coroutineScope { async { calcSomething() } }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/simple.kt b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/simple.kt
new file mode 100644
index 0000000..ff40e7e
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/simple.kt
@@ -0,0 +1,12 @@
+// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+
+fun calcSomething() {}
+
+suspend fun <caret>CoroutineScope.foo() {
+ async {
+ calcSomething()
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/simple.kt.after b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/simple.kt.after
new file mode 100644
index 0000000..be7f362
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/simple.kt.after
@@ -0,0 +1,14 @@
+// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
+
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
+
+fun calcSomething() {}
+
+suspend fun foo() {
+ coroutineScope {
+ async {
+ calcSomething()
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/this.kt b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/this.kt
new file mode 100644
index 0000000..d3d887f
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/this.kt
@@ -0,0 +1,12 @@
+// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+
+fun calcSomething() {}
+
+suspend fun <caret>CoroutineScope.foo() {
+ this.async {
+ calcSomething()
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/this.kt.after b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/this.kt.after
new file mode 100644
index 0000000..767ce83
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/this.kt.after
@@ -0,0 +1,14 @@
+// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
+
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
+
+fun calcSomething() {}
+
+suspend fun foo() {
+ coroutineScope {
+ this.async {
+ calcSomething()
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/thisInMember.kt b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/thisInMember.kt
new file mode 100644
index 0000000..11597e5
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/thisInMember.kt
@@ -0,0 +1,10 @@
+// FIX: Wrap function body with 'coroutineScope { ... }'
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+
+fun calcSomething() = 42
+
+abstract class MyCoroutineScope : CoroutineScope {
+ suspend fun <caret>foo() = this.async { calcSomething() }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/thisInMember.kt.after b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/thisInMember.kt.after
new file mode 100644
index 0000000..992939c
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/thisInMember.kt.after
@@ -0,0 +1,11 @@
+// FIX: Wrap function body with 'coroutineScope { ... }'
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
+
+fun calcSomething() = 42
+
+abstract class MyCoroutineScope : CoroutineScope {
+ suspend fun foo() = coroutineScope { this.async { calcSomething() } }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/thisLabeled.kt b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/thisLabeled.kt
new file mode 100644
index 0000000..bab18f0
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/thisLabeled.kt
@@ -0,0 +1,14 @@
+// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+
+fun calcSomething() {}
+
+class My {
+ suspend fun <caret>CoroutineScope.foo() {
+ this@foo.async {
+ calcSomething()
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/thisLabeled.kt.after b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/thisLabeled.kt.after
new file mode 100644
index 0000000..183c4aa
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/thisLabeled.kt.after
@@ -0,0 +1,16 @@
+// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
+
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
+
+fun calcSomething() {}
+
+class My {
+ suspend fun foo() {
+ coroutineScope {
+ async {
+ calcSomething()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/thisWrongLabeled.kt b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/thisWrongLabeled.kt
new file mode 100644
index 0000000..b8c5763
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/thisWrongLabeled.kt
@@ -0,0 +1,15 @@
+// PROBLEM: none
+// DISABLE-ERRORS
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+
+fun calcSomething() {}
+
+class My {
+ suspend fun <caret>CoroutineScope.foo() {
+ this@My.async {
+ calcSomething()
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/useSite.kt b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/useSite.kt
new file mode 100644
index 0000000..e5ecb01
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/useSite.kt
@@ -0,0 +1,17 @@
+// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.async
+
+fun calcSomething() {}
+
+suspend fun <caret>CoroutineScope.foo() {
+ async {
+ calcSomething()
+ }
+}
+
+suspend fun test() {
+ GlobalScope.foo()
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/useSite.kt.after b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/useSite.kt.after
new file mode 100644
index 0000000..2fbb8cb
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/useSite.kt.after
@@ -0,0 +1,18 @@
+// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
+
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
+
+fun calcSomething() {}
+
+suspend fun foo() {
+ coroutineScope {
+ async {
+ calcSomething()
+ }
+ }
+}
+
+suspend fun test() {
+ foo()
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/withVariable.kt b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/withVariable.kt
new file mode 100644
index 0000000..9c8634f
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/withVariable.kt
@@ -0,0 +1,12 @@
+// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+
+fun calcSomething() = 42
+
+suspend fun <caret>CoroutineScope.foo() {
+ val deferred = async {
+ calcSomething()
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/withVariable.kt.after b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/withVariable.kt.after
new file mode 100644
index 0000000..06a130a
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/withVariable.kt.after
@@ -0,0 +1,14 @@
+// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
+
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
+
+fun calcSomething() = 42
+
+suspend fun foo() {
+ coroutineScope {
+ val deferred = async {
+ calcSomething()
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
index 02462b5..c6987ee 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
@@ -2239,6 +2239,114 @@
runTest("idea/testData/inspectionsLocal/coroutines/redundantRunCatching/simple.kt");
}
}
+
+ @TestMetadata("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(JUnit3RunnerWithInners.class)
+ public static class SuspendFunctionOnCoroutineScope extends AbstractLocalInspectionTest {
+ private void runTest(String testDataFilePath) throws Exception {
+ KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
+ }
+
+ public void testAllFilesPresentInSuspendFunctionOnCoroutineScope() throws Exception {
+ KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
+ }
+
+ @TestMetadata("anonymous.kt")
+ public void testAnonymous() throws Exception {
+ runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/anonymous.kt");
+ }
+
+ @TestMetadata("body.kt")
+ public void testBody() throws Exception {
+ runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/body.kt");
+ }
+
+ @TestMetadata("call.kt")
+ public void testCall() throws Exception {
+ runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/call.kt");
+ }
+
+ @TestMetadata("context.kt")
+ public void testContext() throws Exception {
+ runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/context.kt");
+ }
+
+ @TestMetadata("contextWithLabeledThis.kt")
+ public void testContextWithLabeledThis() throws Exception {
+ runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/contextWithLabeledThis.kt");
+ }
+
+ @TestMetadata("contextWithThis.kt")
+ public void testContextWithThis() throws Exception {
+ runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/contextWithThis.kt");
+ }
+
+ @TestMetadata("convertReceiver.kt")
+ public void testConvertReceiver() throws Exception {
+ runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/convertReceiver.kt");
+ }
+
+ @TestMetadata("declaration.kt")
+ public void testDeclaration() throws Exception {
+ runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/declaration.kt");
+ }
+
+ @TestMetadata("inner.kt")
+ public void testInner() throws Exception {
+ runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/inner.kt");
+ }
+
+ @TestMetadata("lots.kt")
+ public void testLots() throws Exception {
+ runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/lots.kt");
+ }
+
+ @TestMetadata("member.kt")
+ public void testMember() throws Exception {
+ runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/member.kt");
+ }
+
+ @TestMetadata("object.kt")
+ public void testObject() throws Exception {
+ runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/object.kt");
+ }
+
+ @TestMetadata("simple.kt")
+ public void testSimple() throws Exception {
+ runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/simple.kt");
+ }
+
+ @TestMetadata("this.kt")
+ public void testThis() throws Exception {
+ runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/this.kt");
+ }
+
+ @TestMetadata("thisInMember.kt")
+ public void testThisInMember() throws Exception {
+ runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/thisInMember.kt");
+ }
+
+ @TestMetadata("thisLabeled.kt")
+ public void testThisLabeled() throws Exception {
+ runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/thisLabeled.kt");
+ }
+
+ @TestMetadata("thisWrongLabeled.kt")
+ public void testThisWrongLabeled() throws Exception {
+ runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/thisWrongLabeled.kt");
+ }
+
+ @TestMetadata("useSite.kt")
+ public void testUseSite() throws Exception {
+ runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/useSite.kt");
+ }
+
+ @TestMetadata("withVariable.kt")
+ public void testWithVariable() throws Exception {
+ runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/withVariable.kt");
+ }
+ }
}
@TestMetadata("idea/testData/inspectionsLocal/delegationToVarProperty")