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")