Introduce inspection "async result unused" #KT-24433 Fixed

This commit is contained in:
Mikhail Glukhikh
2018-05-15 18:42:12 +03:00
parent d0c045e4ba
commit ffcfa51fbf
13 changed files with 180 additions and 1 deletions
@@ -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)
}
}
@@ -0,0 +1,6 @@
<html>
<body>
This inspection reports <b>async</b> call that is never used,
so all actions inside <b>async</b> are never executed.
</body>
</html>
+10 -1
View File
@@ -2846,7 +2846,7 @@
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.coroutines.RedundantAsyncInspection"
displayName="Redundant async call"
displayName="Redundant 'async' call"
groupPath="Kotlin"
groupName="Style issues"
enabledByDefault="true"
@@ -2854,6 +2854,15 @@
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.coroutines.AsyncResultUnusedInspection"
displayName="Result of 'async' is not used"
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"/>
+9
View File
@@ -2854,6 +2854,15 @@
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.coroutines.AsyncResultUnusedInspection"
displayName="Result of 'async' is not used"
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"/>
+9
View File
@@ -2854,6 +2854,15 @@
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.coroutines.AsyncResultUnusedInspection"
displayName="Result of 'async' is not used"
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"/>
+9
View File
@@ -2855,6 +2855,15 @@
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.coroutines.AsyncResultUnusedInspection"
displayName="Result of 'async' is not used"
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"/>
+9
View File
@@ -2854,6 +2854,15 @@
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.coroutines.AsyncResultUnusedInspection"
displayName="Result of 'async' is not used"
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"/>
+9
View File
@@ -2854,6 +2854,15 @@
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.coroutines.AsyncResultUnusedInspection"
displayName="Result of 'async' is not used"
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"/>
@@ -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")
})
}
@@ -0,0 +1,11 @@
<problems>
<problem>
<file>simple.kt</file>
<line>30</line>
<module>light_idea_test_case</module>
<package>&lt;default&gt;</package>
<entry_point TYPE="file" FQNAME="temp:///src/simple.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Result of 'async' is not used</problem_class>
<description>Result of 'async' is never used</description>
</problem>
</problems>
@@ -0,0 +1 @@
// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.coroutines.AsyncResultUnusedInspection
@@ -0,0 +1,39 @@
package kotlinx.coroutines.experimental
interface Deferred<T> {
suspend fun await(): T
}
interface CoroutineContext
object DefaultContext : CoroutineContext
enum class CoroutineStart {
DEFAULT,
LAZY,
ATOMIC,
UNDISPATCHED
}
interface Job
fun <T> async(
context: CoroutineContext = DefaultContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
parent: Job? = null,
f: suspend () -> T
): Deferred<T> {
TODO()
}
fun test() {
async { 42 }
}
fun useIt(d: Deferred<Int>) {}
fun falsePositives() {
async { 3 }.await()
val res = async { 13 }
useIt(async { 7 })
}
@@ -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");