diff --git a/idea/resources/inspectionDescriptions/RedundantAsync.html b/idea/resources/inspectionDescriptions/RedundantAsync.html
new file mode 100644
index 00000000000..80fc8d5af2f
--- /dev/null
+++ b/idea/resources/inspectionDescriptions/RedundantAsync.html
@@ -0,0 +1,6 @@
+
+
+This inspection reports async call that is immediately followed by await.
+Such a call can be replaced with a kind of blocking call.
+
+
\ No newline at end of file
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index 0613d807c03..d6c0e4a9726 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -2839,6 +2839,15 @@
language="kotlin"
/>
+
+
diff --git a/idea/src/META-INF/plugin.xml.172 b/idea/src/META-INF/plugin.xml.172
index 81376c12a6c..517a196ea2b 100644
--- a/idea/src/META-INF/plugin.xml.172
+++ b/idea/src/META-INF/plugin.xml.172
@@ -2834,6 +2834,15 @@
language="kotlin"
/>
+
+
diff --git a/idea/src/META-INF/plugin.xml.173 b/idea/src/META-INF/plugin.xml.173
index 1a34f7f8814..11742499373 100644
--- a/idea/src/META-INF/plugin.xml.173
+++ b/idea/src/META-INF/plugin.xml.173
@@ -2834,6 +2834,15 @@
language="kotlin"
/>
+
+
diff --git a/idea/src/META-INF/plugin.xml.182 b/idea/src/META-INF/plugin.xml.182
index 564f726effd..2b91d6afc02 100644
--- a/idea/src/META-INF/plugin.xml.182
+++ b/idea/src/META-INF/plugin.xml.182
@@ -2840,6 +2840,15 @@
language="kotlin"
/>
+
+
diff --git a/idea/src/META-INF/plugin.xml.as31 b/idea/src/META-INF/plugin.xml.as31
index c5eb6b4a718..820c3d58d40 100644
--- a/idea/src/META-INF/plugin.xml.as31
+++ b/idea/src/META-INF/plugin.xml.as31
@@ -2834,6 +2834,15 @@
language="kotlin"
/>
+
+
diff --git a/idea/src/META-INF/plugin.xml.as32 b/idea/src/META-INF/plugin.xml.as32
index 67d9a2ea248..b9d588cd0b1 100644
--- a/idea/src/META-INF/plugin.xml.as32
+++ b/idea/src/META-INF/plugin.xml.as32
@@ -2834,6 +2834,15 @@
language="kotlin"
/>
+
+
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/collections/SimplifyCallChainFix.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/SimplifyCallChainFix.kt
index 882c6608454..1ed03ccfb8c 100644
--- a/idea/src/org/jetbrains/kotlin/idea/inspections/collections/SimplifyCallChainFix.kt
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/SimplifyCallChainFix.kt
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.idea.inspections.collections
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
+import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
@@ -66,7 +67,8 @@ class SimplifyCallChainFix(val newName: String) : LocalQuickFix {
argumentsText
)
- secondQualifiedExpression.replaced(newQualifiedExpression)
+ val result = secondQualifiedExpression.replaced(newQualifiedExpression)
+ ShortenReferences.DEFAULT.process(result)
}
}
}
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/RedundantAsyncInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/RedundantAsyncInspection.kt
new file mode 100644
index 00000000000..87ea5e995a8
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/RedundantAsyncInspection.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
+ * that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.idea.inspections.coroutines
+
+import com.intellij.codeInspection.ProblemHighlightType
+import com.intellij.codeInspection.ProblemsHolder
+import org.jetbrains.kotlin.idea.inspections.collections.AbstractCallChainChecker
+import org.jetbrains.kotlin.idea.inspections.collections.SimplifyCallChainFix
+import org.jetbrains.kotlin.psi.psiUtil.startOffset
+import org.jetbrains.kotlin.psi.qualifiedExpressionVisitor
+import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
+
+class RedundantAsyncInspection : AbstractCallChainChecker() {
+ override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) =
+ qualifiedExpressionVisitor(fun(expression) {
+ var defaultContext: Boolean? = null
+ var defaultStart: Boolean? = null
+ var defaultParent: Boolean? = null
+ val conversion = findQualifiedConversion(expression, conversionGroups) check@{ _, firstResolvedCall, _, _ ->
+ for ((parameterDescriptor, valueArgument) in firstResolvedCall.valueArguments) {
+ val default = valueArgument is DefaultValueArgument
+ when (parameterDescriptor.name.asString()) {
+ "context" -> defaultContext = default
+ "start" -> defaultStart = default
+ "parent" -> defaultParent = default
+ }
+ }
+ true
+ } ?: return
+ defaultContext ?: return
+ defaultStart ?: return
+ if (defaultParent != true) return
+ if (defaultContext!! && !defaultStart!!) return
+
+ val replacement =
+ if (defaultContext!! && defaultStart!!) "kotlinx.coroutines.experimental.runBlocking" else conversion.replacement
+ val descriptor = holder.manager.createProblemDescriptor(
+ expression,
+ expression.firstCalleeExpression()!!.textRange.shiftRight(-expression.startOffset),
+ "Redundant 'async' call may be reduced to '$replacement'",
+ ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
+ isOnTheFly,
+ SimplifyCallChainFix(replacement)
+ )
+ holder.registerProblem(descriptor)
+ })
+
+ private val conversionGroups = conversions.group()
+
+ companion object {
+
+ private val conversions = listOf(
+ Conversion(
+ "kotlinx.coroutines.experimental.async",
+ "kotlinx.coroutines.experimental.Deferred.await",
+ "kotlinx.coroutines.experimental.withContext"
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/redundantAsync/.inspection b/idea/testData/inspectionsLocal/coroutines/redundantAsync/.inspection
new file mode 100644
index 00000000000..4e59c34818d
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/redundantAsync/.inspection
@@ -0,0 +1 @@
+org.jetbrains.kotlin.idea.inspections.coroutines.RedundantAsyncInspection
diff --git a/idea/testData/inspectionsLocal/coroutines/redundantAsync/simple.kt b/idea/testData/inspectionsLocal/coroutines/redundantAsync/simple.kt
new file mode 100644
index 00000000000..a0cff42a150
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/redundantAsync/simple.kt
@@ -0,0 +1,48 @@
+// WITH_RUNTIME
+
+package kotlinx.coroutines.experimental
+
+interface Deferred {
+ suspend fun await(): T
+}
+
+interface CoroutineContext
+
+object DefaultContext : CoroutineContext
+
+enum class CoroutineStart {
+ DEFAULT,
+ LAZY,
+ ATOMIC,
+ UNDISPATCHED
+}
+
+interface Job
+
+fun async(
+ context: CoroutineContext = DefaultContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ parent: Job? = null,
+ f: suspend () -> T
+): Deferred {
+ TODO()
+}
+
+fun runBlocking(
+ context: CoroutineContext = DefaultContext,
+ f: suspend () -> T
+) {
+ TODO()
+}
+
+suspend fun withContext(
+ context: CoroutineContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ f: suspend () -> T
+) {
+ TODO()
+}
+
+suspend fun test(ctx: CoroutineContext) {
+ async(ctx) { 42 }.await()
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/redundantAsync/simple.kt.after b/idea/testData/inspectionsLocal/coroutines/redundantAsync/simple.kt.after
new file mode 100644
index 00000000000..ea3468a8619
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/redundantAsync/simple.kt.after
@@ -0,0 +1,48 @@
+// WITH_RUNTIME
+
+package kotlinx.coroutines.experimental
+
+interface Deferred {
+ suspend fun await(): T
+}
+
+interface CoroutineContext
+
+object DefaultContext : CoroutineContext
+
+enum class CoroutineStart {
+ DEFAULT,
+ LAZY,
+ ATOMIC,
+ UNDISPATCHED
+}
+
+interface Job
+
+fun async(
+ context: CoroutineContext = DefaultContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ parent: Job? = null,
+ f: suspend () -> T
+): Deferred {
+ TODO()
+}
+
+fun runBlocking(
+ context: CoroutineContext = DefaultContext,
+ f: suspend () -> T
+) {
+ TODO()
+}
+
+suspend fun withContext(
+ context: CoroutineContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ f: suspend () -> T
+) {
+ TODO()
+}
+
+suspend fun test(ctx: CoroutineContext) {
+ withContext(ctx) { 42 }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/redundantAsync/simplest.kt b/idea/testData/inspectionsLocal/coroutines/redundantAsync/simplest.kt
new file mode 100644
index 00000000000..61372604ada
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/redundantAsync/simplest.kt
@@ -0,0 +1,48 @@
+// WITH_RUNTIME
+
+package kotlinx.coroutines.experimental
+
+interface Deferred {
+ suspend fun await(): T
+}
+
+interface CoroutineContext
+
+object DefaultContext : CoroutineContext
+
+enum class CoroutineStart {
+ DEFAULT,
+ LAZY,
+ ATOMIC,
+ UNDISPATCHED
+}
+
+interface Job
+
+fun async(
+ context: CoroutineContext = DefaultContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ parent: Job? = null,
+ f: suspend () -> T
+): Deferred {
+ TODO()
+}
+
+fun runBlocking(
+ context: CoroutineContext = DefaultContext,
+ f: suspend () -> T
+) {
+ TODO()
+}
+
+suspend fun withContext(
+ context: CoroutineContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ f: suspend () -> T
+) {
+ TODO()
+}
+
+suspend fun test() {
+ async { 42 }.await()
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/redundantAsync/simplest.kt.after b/idea/testData/inspectionsLocal/coroutines/redundantAsync/simplest.kt.after
new file mode 100644
index 00000000000..d6bf79b72bf
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/redundantAsync/simplest.kt.after
@@ -0,0 +1,48 @@
+// WITH_RUNTIME
+
+package kotlinx.coroutines.experimental
+
+interface Deferred {
+ suspend fun await(): T
+}
+
+interface CoroutineContext
+
+object DefaultContext : CoroutineContext
+
+enum class CoroutineStart {
+ DEFAULT,
+ LAZY,
+ ATOMIC,
+ UNDISPATCHED
+}
+
+interface Job
+
+fun async(
+ context: CoroutineContext = DefaultContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ parent: Job? = null,
+ f: suspend () -> T
+): Deferred {
+ TODO()
+}
+
+fun runBlocking(
+ context: CoroutineContext = DefaultContext,
+ f: suspend () -> T
+) {
+ TODO()
+}
+
+suspend fun withContext(
+ context: CoroutineContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ f: suspend () -> T
+) {
+ TODO()
+}
+
+suspend fun test() {
+ runBlocking { 42 }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/redundantAsync/withParent.kt b/idea/testData/inspectionsLocal/coroutines/redundantAsync/withParent.kt
new file mode 100644
index 00000000000..7759437c987
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/redundantAsync/withParent.kt
@@ -0,0 +1,49 @@
+// PROBLEM: none
+// WITH_RUNTIME
+
+package kotlinx.coroutines.experimental
+
+interface Deferred {
+ suspend fun await(): T
+}
+
+interface CoroutineContext
+
+object DefaultContext : CoroutineContext
+
+enum class CoroutineStart {
+ DEFAULT,
+ LAZY,
+ ATOMIC,
+ UNDISPATCHED
+}
+
+interface Job
+
+fun async(
+ context: CoroutineContext = DefaultContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ parent: Job? = null,
+ f: suspend () -> T
+): Deferred {
+ TODO()
+}
+
+fun runBlocking(
+ context: CoroutineContext = DefaultContext,
+ f: suspend () -> T
+) {
+ TODO()
+}
+
+suspend fun withContext(
+ context: CoroutineContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ f: suspend () -> T
+) {
+ TODO()
+}
+
+suspend fun test(ctx: CoroutineContext) {
+ async(parent = null) { 42 }.await()
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/redundantAsync/withStartAndContext.kt b/idea/testData/inspectionsLocal/coroutines/redundantAsync/withStartAndContext.kt
new file mode 100644
index 00000000000..fac0fcac8b7
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/redundantAsync/withStartAndContext.kt
@@ -0,0 +1,48 @@
+// WITH_RUNTIME
+
+package kotlinx.coroutines.experimental
+
+interface Deferred {
+ suspend fun await(): T
+}
+
+interface CoroutineContext
+
+object DefaultContext : CoroutineContext
+
+enum class CoroutineStart {
+ DEFAULT,
+ LAZY,
+ ATOMIC,
+ UNDISPATCHED
+}
+
+interface Job
+
+fun async(
+ context: CoroutineContext = DefaultContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ parent: Job? = null,
+ f: suspend () -> T
+): Deferred {
+ TODO()
+}
+
+fun runBlocking(
+ context: CoroutineContext = DefaultContext,
+ f: suspend () -> T
+) {
+ TODO()
+}
+
+suspend fun withContext(
+ context: CoroutineContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ f: suspend () -> T
+) {
+ TODO()
+}
+
+suspend fun test(ctx: CoroutineContext) {
+ async(ctx, CoroutineStart.LAZY) { 42 }.await()
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/redundantAsync/withStartAndContext.kt.after b/idea/testData/inspectionsLocal/coroutines/redundantAsync/withStartAndContext.kt.after
new file mode 100644
index 00000000000..f9c2bb60107
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/redundantAsync/withStartAndContext.kt.after
@@ -0,0 +1,48 @@
+// WITH_RUNTIME
+
+package kotlinx.coroutines.experimental
+
+interface Deferred {
+ suspend fun await(): T
+}
+
+interface CoroutineContext
+
+object DefaultContext : CoroutineContext
+
+enum class CoroutineStart {
+ DEFAULT,
+ LAZY,
+ ATOMIC,
+ UNDISPATCHED
+}
+
+interface Job
+
+fun async(
+ context: CoroutineContext = DefaultContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ parent: Job? = null,
+ f: suspend () -> T
+): Deferred {
+ TODO()
+}
+
+fun runBlocking(
+ context: CoroutineContext = DefaultContext,
+ f: suspend () -> T
+) {
+ TODO()
+}
+
+suspend fun withContext(
+ context: CoroutineContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ f: suspend () -> T
+) {
+ TODO()
+}
+
+suspend fun test(ctx: CoroutineContext) {
+ withContext(ctx, CoroutineStart.LAZY) { 42 }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/redundantAsync/withStartNoContext.kt b/idea/testData/inspectionsLocal/coroutines/redundantAsync/withStartNoContext.kt
new file mode 100644
index 00000000000..5fc9d46d605
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/redundantAsync/withStartNoContext.kt
@@ -0,0 +1,49 @@
+// PROBLEM: none
+// WITH_RUNTIME
+
+package kotlinx.coroutines.experimental
+
+interface Deferred {
+ suspend fun await(): T
+}
+
+interface CoroutineContext
+
+object DefaultContext : CoroutineContext
+
+enum class CoroutineStart {
+ DEFAULT,
+ LAZY,
+ ATOMIC,
+ UNDISPATCHED
+}
+
+interface Job
+
+fun async(
+ context: CoroutineContext = DefaultContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ parent: Job? = null,
+ f: suspend () -> T
+): Deferred {
+ TODO()
+}
+
+fun runBlocking(
+ context: CoroutineContext = DefaultContext,
+ f: suspend () -> T
+) {
+ TODO()
+}
+
+suspend fun withContext(
+ context: CoroutineContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ f: suspend () -> T
+) {
+ TODO()
+}
+
+suspend fun test(ctx: CoroutineContext) {
+ async(start = CoroutineStart.LAZY) { 42 }.await()
+}
\ 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 ca59ffaf2d4..2467ef82db3 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
@@ -1226,6 +1226,57 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
}
}
+ @TestMetadata("idea/testData/inspectionsLocal/coroutines")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(JUnit3RunnerWithInners.class)
+ public static class Coroutines extends AbstractLocalInspectionTest {
+ private void runTest(String testDataFilePath) throws Exception {
+ KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
+ }
+
+ public void testAllFilesPresentInCoroutines() throws Exception {
+ KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/coroutines"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
+ }
+
+ @TestMetadata("idea/testData/inspectionsLocal/coroutines/redundantAsync")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(JUnit3RunnerWithInners.class)
+ public static class RedundantAsync extends AbstractLocalInspectionTest {
+ private void runTest(String testDataFilePath) throws Exception {
+ KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
+ }
+
+ public void testAllFilesPresentInRedundantAsync() throws Exception {
+ KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/coroutines/redundantAsync"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
+ }
+
+ @TestMetadata("simple.kt")
+ public void testSimple() throws Exception {
+ runTest("idea/testData/inspectionsLocal/coroutines/redundantAsync/simple.kt");
+ }
+
+ @TestMetadata("simplest.kt")
+ public void testSimplest() throws Exception {
+ runTest("idea/testData/inspectionsLocal/coroutines/redundantAsync/simplest.kt");
+ }
+
+ @TestMetadata("withParent.kt")
+ public void testWithParent() throws Exception {
+ runTest("idea/testData/inspectionsLocal/coroutines/redundantAsync/withParent.kt");
+ }
+
+ @TestMetadata("withStartAndContext.kt")
+ public void testWithStartAndContext() throws Exception {
+ runTest("idea/testData/inspectionsLocal/coroutines/redundantAsync/withStartAndContext.kt");
+ }
+
+ @TestMetadata("withStartNoContext.kt")
+ public void testWithStartNoContext() throws Exception {
+ runTest("idea/testData/inspectionsLocal/coroutines/redundantAsync/withStartNoContext.kt");
+ }
+ }
+ }
+
@TestMetadata("idea/testData/inspectionsLocal/deprecatedCallableAddReplaceWith")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)