diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/inspections/AbstractResultUnusedChecker.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/inspections/AbstractResultUnusedChecker.kt new file mode 100644 index 00000000000..272cb4d8441 --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/inspections/AbstractResultUnusedChecker.kt @@ -0,0 +1,37 @@ +/* + * 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 + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode + +abstract class AbstractResultUnusedChecker( + private val expressionChecker: (KtExpression) -> Boolean, + private val callChecker: (ResolvedCall<*>) -> Boolean +) : AbstractKotlinInspection() { + protected fun check(expression: KtExpression): Boolean { + // Check whatever possible by PSI + if (!expressionChecker(expression)) return false + var parent: PsiElement? = expression.parent + while (parent != null) { + if (parent is KtBlockExpression || parent is KtFunction || parent is KtFile) break + if (parent is KtValueArgument) return false + if (parent is KtBinaryExpression && parent.operationToken in KtTokens.ALL_ASSIGNMENTS) return false + parent = parent.parent + } + // Then check by call + val context = expression.analyze(BodyResolveMode.PARTIAL_WITH_CFA) + if (expression.isUsedAsExpression(context)) return false + val resolvedCall = expression.getResolvedCall(context) ?: return false + return callChecker(resolvedCall) + } +} \ No newline at end of file diff --git a/idea/resources/inspectionDescriptions/AsyncResultUnused.html b/idea/resources/inspectionDescriptions/AsyncResultUnused.html new file mode 100644 index 00000000000..c6a81a7035d --- /dev/null +++ b/idea/resources/inspectionDescriptions/AsyncResultUnused.html @@ -0,0 +1,6 @@ + + +This inspection reports async call that is never used, +so all actions inside async are never executed. + + \ No newline at end of file diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 136c9eefcd5..a4112b7ca44 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -2846,7 +2846,7 @@ /> + + diff --git a/idea/src/META-INF/plugin.xml.172 b/idea/src/META-INF/plugin.xml.172 index 9f05da7b396..118f58e7470 100644 --- a/idea/src/META-INF/plugin.xml.172 +++ b/idea/src/META-INF/plugin.xml.172 @@ -2854,6 +2854,15 @@ language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.173 b/idea/src/META-INF/plugin.xml.173 index 1f4a6ee522e..be5da4a9c14 100644 --- a/idea/src/META-INF/plugin.xml.173 +++ b/idea/src/META-INF/plugin.xml.173 @@ -2854,6 +2854,15 @@ language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.182 b/idea/src/META-INF/plugin.xml.182 index d6451cde4ec..89eedfb0cf3 100644 --- a/idea/src/META-INF/plugin.xml.182 +++ b/idea/src/META-INF/plugin.xml.182 @@ -2855,6 +2855,15 @@ language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.as31 b/idea/src/META-INF/plugin.xml.as31 index 112fcb64691..53d029d932b 100644 --- a/idea/src/META-INF/plugin.xml.as31 +++ b/idea/src/META-INF/plugin.xml.as31 @@ -2854,6 +2854,15 @@ language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.as32 b/idea/src/META-INF/plugin.xml.as32 index a0af09b96e5..facbd2ef7de 100644 --- a/idea/src/META-INF/plugin.xml.as32 +++ b/idea/src/META-INF/plugin.xml.as32 @@ -2854,6 +2854,15 @@ language="kotlin" /> + + diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/AsyncResultUnusedInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/AsyncResultUnusedInspection.kt new file mode 100644 index 00000000000..a649633c782 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/AsyncResultUnusedInspection.kt @@ -0,0 +1,26 @@ +/* + * 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.ProblemsHolder +import com.intellij.psi.PsiElementVisitor +import org.jetbrains.kotlin.idea.inspections.AbstractResultUnusedChecker +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.callExpressionVisitor +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull + +class AsyncResultUnusedInspection : AbstractResultUnusedChecker( + expressionChecker = fun(expression): Boolean = + expression is KtCallExpression && expression.calleeExpression?.text == "async", + callChecker = fun(resolvedCall): Boolean = + resolvedCall.resultingDescriptor.fqNameOrNull()?.asString() == "kotlinx.coroutines.experimental.async" +) { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor = + callExpressionVisitor(fun(expression) { + if (!check(expression)) return + holder.registerProblem(expression.calleeExpression ?: expression, "Result of 'async' is never used") + }) +} \ No newline at end of file diff --git a/idea/testData/inspections/coroutines/asyncResultUnused/inspectionData/expected.xml b/idea/testData/inspections/coroutines/asyncResultUnused/inspectionData/expected.xml new file mode 100644 index 00000000000..e77788b5f3b --- /dev/null +++ b/idea/testData/inspections/coroutines/asyncResultUnused/inspectionData/expected.xml @@ -0,0 +1,11 @@ + + + simple.kt + 30 + light_idea_test_case + <default> + + Result of 'async' is not used + Result of 'async' is never used + + \ No newline at end of file diff --git a/idea/testData/inspections/coroutines/asyncResultUnused/inspectionData/inspections.test b/idea/testData/inspections/coroutines/asyncResultUnused/inspectionData/inspections.test new file mode 100644 index 00000000000..4b6b3d052a1 --- /dev/null +++ b/idea/testData/inspections/coroutines/asyncResultUnused/inspectionData/inspections.test @@ -0,0 +1 @@ +// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.coroutines.AsyncResultUnusedInspection diff --git a/idea/testData/inspections/coroutines/asyncResultUnused/simple.kt b/idea/testData/inspections/coroutines/asyncResultUnused/simple.kt new file mode 100644 index 00000000000..07c01df416f --- /dev/null +++ b/idea/testData/inspections/coroutines/asyncResultUnused/simple.kt @@ -0,0 +1,39 @@ +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 test() { + async { 42 } +} + +fun useIt(d: Deferred) {} + +fun falsePositives() { + async { 3 }.await() + val res = async { 13 } + useIt(async { 7 }) +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java index c38f2fb3905..7795fc663e0 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java @@ -139,6 +139,11 @@ public class InspectionTestGenerated extends AbstractInspectionTest { runTest("idea/testData/inspections/copyWithoutNamedArguments/inspectionData/inspections.test"); } + @TestMetadata("coroutines/asyncResultUnused/inspectionData/inspections.test") + public void testCoroutines_asyncResultUnused_inspectionData_Inspections_test() throws Exception { + runTest("idea/testData/inspections/coroutines/asyncResultUnused/inspectionData/inspections.test"); + } + @TestMetadata("dataClassPrivateConstructor/inspectionData/inspections.test") public void testDataClassPrivateConstructor_inspectionData_Inspections_test() throws Exception { runTest("idea/testData/inspections/dataClassPrivateConstructor/inspectionData/inspections.test");