Introduce "redundant suspend" inspection #KT-19103 Fixed
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body>
|
||||
This inspection reports 'suspend' modifier as redundant if no other suspend functions are called inside
|
||||
</body>
|
||||
</html>
|
||||
@@ -2691,6 +2691,16 @@
|
||||
level="WEAK WARNING"
|
||||
language="kotlin"
|
||||
/>
|
||||
|
||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.RedundantSuspendModifierInspection"
|
||||
displayName="Redundant 'suspend' modifier"
|
||||
groupPath="Kotlin"
|
||||
groupName="Redundant constructs"
|
||||
enabledByDefault="true"
|
||||
level="WARNING"
|
||||
language="kotlin"
|
||||
/>
|
||||
|
||||
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
||||
|
||||
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
||||
|
||||
+22
-21
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.idea.highlighter
|
||||
import com.intellij.codeHighlighting.Pass
|
||||
import com.intellij.codeInsight.daemon.LineMarkerInfo
|
||||
import com.intellij.codeInsight.daemon.LineMarkerProvider
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.editor.markup.GutterIconRenderer
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.psi.PsiElement
|
||||
@@ -43,19 +44,12 @@ class KotlinSuspendCallLineMarkerProvider : LineMarkerProvider {
|
||||
) {
|
||||
override fun createGutterRenderer(): GutterIconRenderer? {
|
||||
return object : LineMarkerInfo.LineMarkerGutterIconRenderer<KtElement>(this) {
|
||||
override fun getClickAction() = null
|
||||
override fun getClickAction(): AnAction? = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getLineMarkerInfo(element: PsiElement) = null
|
||||
|
||||
private fun isValidCandidateExpression(expression: KtExpression): Boolean {
|
||||
if (expression is KtOperationReferenceExpression || expression is KtForExpression) return true
|
||||
val parent = expression.parent
|
||||
if (parent is KtCallExpression && parent.calleeExpression == expression) return true
|
||||
return false
|
||||
}
|
||||
override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? = null
|
||||
|
||||
override fun collectSlowLineMarkers(
|
||||
elements: MutableList<PsiElement>,
|
||||
@@ -67,27 +61,34 @@ class KotlinSuspendCallLineMarkerProvider : LineMarkerProvider {
|
||||
ProgressManager.checkCanceled()
|
||||
|
||||
if (element !is KtExpression) continue
|
||||
if (!isValidCandidateExpression(element)) continue
|
||||
|
||||
val lineNumber = element.getLineNumber()
|
||||
if (lineNumber in markedLineNumbers) continue
|
||||
|
||||
val bindingContext = element.analyze(BodyResolveMode.PARTIAL)
|
||||
val resolvedCall = if (element is KtForExpression) {
|
||||
bindingContext[BindingContext.LOOP_RANGE_NEXT_RESOLVED_CALL, element.loopRange]
|
||||
} else {
|
||||
element.getResolvedCall(bindingContext)
|
||||
} ?: continue
|
||||
val calleeDescriptor = resolvedCall.resultingDescriptor as? FunctionDescriptor ?: continue
|
||||
if (!calleeDescriptor.isSuspend) continue
|
||||
if (!element.hasSuspendCalls()) continue
|
||||
|
||||
markedLineNumbers += lineNumber
|
||||
result += if (element is KtForExpression) {
|
||||
SuspendCallMarkerInfo(element.loopRange!!, "Suspending iteration")
|
||||
|
||||
} else {
|
||||
SuspendCallMarkerInfo(element, "Suspend function call")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtExpression.isValidCandidateExpression(): Boolean {
|
||||
if (this is KtOperationReferenceExpression || this is KtForExpression) return true
|
||||
val parent = parent
|
||||
if (parent is KtCallExpression && parent.calleeExpression == this) return true
|
||||
return false
|
||||
}
|
||||
|
||||
fun KtExpression.hasSuspendCalls(bindingContext: BindingContext = analyze(BodyResolveMode.PARTIAL)): Boolean {
|
||||
if (!isValidCandidateExpression()) return false
|
||||
|
||||
val resolvedCall = if (this is KtForExpression) {
|
||||
bindingContext[BindingContext.LOOP_RANGE_NEXT_RESOLVED_CALL, loopRange]
|
||||
} else {
|
||||
this.getResolvedCall(bindingContext)
|
||||
} ?: return false
|
||||
return (resolvedCall.resultingDescriptor as? FunctionDescriptor)?.isSuspend == true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.inspections
|
||||
|
||||
import com.intellij.codeInspection.IntentionWrapper
|
||||
import com.intellij.codeInspection.LocalInspectionToolSession
|
||||
import com.intellij.codeInspection.ProblemHighlightType
|
||||
import com.intellij.codeInspection.ProblemsHolder
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import org.jetbrains.kotlin.config.LanguageFeature
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully
|
||||
import org.jetbrains.kotlin.idea.highlighter.hasSuspendCalls
|
||||
import org.jetbrains.kotlin.idea.project.languageVersionSettings
|
||||
import org.jetbrains.kotlin.idea.quickfix.RemoveModifierFix
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||
import org.jetbrains.kotlin.psi.KtVisitorVoid
|
||||
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
|
||||
|
||||
class RedundantSuspendModifierInspection : AbstractKotlinInspection() {
|
||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
|
||||
return object : KtVisitorVoid() {
|
||||
override fun visitNamedFunction(function: KtNamedFunction) {
|
||||
super.visitNamedFunction(function)
|
||||
if (!function.languageVersionSettings.supportsFeature(LanguageFeature.Coroutines)) return
|
||||
|
||||
val suspendModifier = function.modifierList?.getModifier(KtTokens.SUSPEND_KEYWORD) ?: return
|
||||
if (!function.hasBody()) return
|
||||
|
||||
val context = function.analyzeFully()
|
||||
|
||||
if (function.anyDescendantOfType<KtExpression> { it.hasSuspendCalls(context) }) {
|
||||
return
|
||||
}
|
||||
|
||||
holder.registerProblem(suspendModifier,
|
||||
"Redundant 'suspend' modifier",
|
||||
ProblemHighlightType.LIKE_UNUSED_SYMBOL,
|
||||
IntentionWrapper(RemoveModifierFix(function, KtTokens.SUSPEND_KEYWORD, isRedundant = true),
|
||||
function.containingFile))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
<problems>
|
||||
<problem>
|
||||
<file>test.kt</file>
|
||||
<line>13</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Redundant 'suspend' modifier</problem_class>
|
||||
<description>Redundant 'suspend' modifier</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>test.kt</file>
|
||||
<line>33</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Redundant 'suspend' modifier</problem_class>
|
||||
<description>Redundant 'suspend' modifier</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>test.kt</file>
|
||||
<line>35</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Redundant 'suspend' modifier</problem_class>
|
||||
<description>Redundant 'suspend' modifier</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>test.kt</file>
|
||||
<line>40</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Redundant 'suspend' modifier</problem_class>
|
||||
<description>Redundant 'suspend' modifier</description>
|
||||
</problem>
|
||||
<!-- FIXME: Incorrect problem -->
|
||||
<problem>
|
||||
<file>test.kt</file>
|
||||
<line>57</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Redundant 'suspend' modifier</problem_class>
|
||||
<description>Redundant 'suspend' modifier</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>operators.kt</file>
|
||||
<line>5</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/operators.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Redundant 'suspend' modifier</problem_class>
|
||||
<description>Redundant 'suspend' modifier</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>operators.kt</file>
|
||||
<line>23</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/operators.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Redundant 'suspend' modifier</problem_class>
|
||||
<description>Redundant 'suspend' modifier</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>operators.kt</file>
|
||||
<line>37</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/operators.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Redundant 'suspend' modifier</problem_class>
|
||||
<description>Redundant 'suspend' modifier</description>
|
||||
</problem>
|
||||
</problems>
|
||||
+1
@@ -0,0 +1 @@
|
||||
// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.RedundantSuspendModifierInspection
|
||||
@@ -0,0 +1,43 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
class A(val x: Int) {
|
||||
// Redundant
|
||||
suspend operator fun plus(a: A): A {
|
||||
return A(x + a.x)
|
||||
}
|
||||
}
|
||||
|
||||
// Not redundant
|
||||
suspend fun foo(a1: A, a2: A): A {
|
||||
return a1 + a2
|
||||
}
|
||||
// Not redundant
|
||||
suspend fun bar(a1: A, a2: A): A {
|
||||
var result = a1
|
||||
result += a2
|
||||
return result
|
||||
}
|
||||
|
||||
class B(var x: Int) {
|
||||
// Redundant
|
||||
suspend operator fun minusAssign(b: B) {
|
||||
x -= b.x
|
||||
}
|
||||
}
|
||||
|
||||
// Not redundant
|
||||
suspend fun foo(b1: B, b2: B): B {
|
||||
val result = b1
|
||||
result -= b2
|
||||
return result
|
||||
}
|
||||
|
||||
class C(val x: Int, val y: Int) {
|
||||
// Redundant
|
||||
suspend operator fun invoke() = x + y
|
||||
}
|
||||
|
||||
// Not redundant
|
||||
suspend fun foo(c1: C, c2: C): Int {
|
||||
return c1() + c2()
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun coroutine(block: suspend () -> Unit) {}
|
||||
|
||||
// Not redundant
|
||||
suspend fun rootSuspend() {
|
||||
coroutine {
|
||||
empty()
|
||||
}
|
||||
}
|
||||
|
||||
// Redundant
|
||||
suspend fun empty() {}
|
||||
|
||||
suspend fun suspendUser() = rootSuspend() // not redundant
|
||||
|
||||
open class My {
|
||||
// Not redundant
|
||||
open suspend fun baseSuspend() {
|
||||
rootSuspend()
|
||||
}
|
||||
}
|
||||
|
||||
class Your : My() {
|
||||
override fun baseSuspend() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class SIterable {
|
||||
operator fun iterator() = this
|
||||
// Redundant
|
||||
suspend operator fun hasNext(): Boolean = false
|
||||
// Redundant
|
||||
suspend operator fun next(): Int = 0
|
||||
}
|
||||
|
||||
class SIterator {
|
||||
// Redundant
|
||||
suspend operator fun iterator() = this
|
||||
operator fun hasNext(): Boolean = false
|
||||
operator fun next(): Int = 0
|
||||
|
||||
}
|
||||
|
||||
// Not redundant
|
||||
suspend fun foo() {
|
||||
val iterable = SIterable()
|
||||
coroutine {
|
||||
for (x in iterable) {
|
||||
println(x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not redundant
|
||||
suspend fun bar() {
|
||||
val iterator = SIterator()
|
||||
coroutine {
|
||||
for (x in iterator) {
|
||||
println(x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Suspended {
|
||||
// Not redundant
|
||||
suspend fun bar()
|
||||
}
|
||||
@@ -323,6 +323,12 @@ public class InspectionTestGenerated extends AbstractInspectionTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("redundantSuspendModifier/inspectionData/inspections.test")
|
||||
public void testRedundantSuspendModifier_inspectionData_Inspections_test() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/redundantSuspendModifier/inspectionData/inspections.test");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("redundantUnitReturnType/inspectionData/inspections.test")
|
||||
public void testRedundantUnitReturnType_inspectionData_Inspections_test() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/redundantUnitReturnType/inspectionData/inspections.test");
|
||||
|
||||
Reference in New Issue
Block a user