Introduce DeferredIsResultInspection #KT-25620 Fixed
This commit is contained in:
@@ -3136,6 +3136,15 @@
|
||||
language="kotlin"
|
||||
/>
|
||||
|
||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.coroutines.DeferredIsResultInspection"
|
||||
displayName="Function returning Deferred directly"
|
||||
groupPath="Kotlin"
|
||||
groupName="Style issues"
|
||||
enabledByDefault="true"
|
||||
level="WEAK WARNING"
|
||||
language="kotlin"
|
||||
/>
|
||||
|
||||
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
||||
|
||||
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<body>
|
||||
This inspection reports functions with <b>kotlinx.coroutines.Deferred</b> result.
|
||||
|
||||
Functions which use <b>Deferred</b> as return type should have a name with suffix <b>Async</b>.
|
||||
Otherwise, it's recommended to turn a function into a suspend function and unwrap <b>Deferred</b>.
|
||||
</body>
|
||||
</html>
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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 com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||
import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
|
||||
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getReturnTypeReference
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
|
||||
abstract class AbstractIsResultInspection(
|
||||
private val typeShortName: String,
|
||||
private val typeFullName: String,
|
||||
private val allowedSuffix: String,
|
||||
private val allowedNames: Set<String>,
|
||||
private val suggestedFunctionNameToCall: String,
|
||||
private val shouldMakeSuspend: Boolean = false,
|
||||
private val simplify: (KtExpression) -> Unit = {}
|
||||
) : AbstractKotlinInspection() {
|
||||
|
||||
protected fun analyzeFunction(function: KtFunction, toReport: PsiElement, holder: ProblemsHolder) {
|
||||
if (function is KtConstructor<*>) return
|
||||
val returnTypeText = function.getReturnTypeReference()?.text
|
||||
if (returnTypeText != null && typeShortName !in returnTypeText) return
|
||||
val name = (function as? KtNamedFunction)?.nameAsName?.asString()
|
||||
if (name in allowedNames) return
|
||||
if (function is KtNamedFunction) {
|
||||
val receiverTypeReference = function.receiverTypeReference
|
||||
// Filter given type extensions
|
||||
if (receiverTypeReference != null && typeShortName in receiverTypeReference.text) return
|
||||
}
|
||||
if (function is KtFunctionLiteral || returnTypeText == null) {
|
||||
// Heuristics to save performance: check if something creates given type in function text
|
||||
val text = function.bodyExpression?.text
|
||||
if (text != null && allowedNames.none { it in text } && typeShortName !in text && allowedSuffix !in text) return
|
||||
}
|
||||
|
||||
val descriptor = function.resolveToDescriptorIfAny() as? FunctionDescriptor ?: return
|
||||
val returnType = descriptor.returnType ?: return
|
||||
val returnTypeClass = returnType.constructor.declarationDescriptor as? ClassDescriptor ?: return
|
||||
if (returnTypeClass.fqNameSafe.asString() != typeFullName) return
|
||||
|
||||
if (name != null && name.endsWith(allowedSuffix)) {
|
||||
analyzeFunctionWithAllowedSuffix(name, descriptor, toReport, holder)
|
||||
} else {
|
||||
holder.registerProblem(
|
||||
toReport,
|
||||
"Function returning $typeShortName with a name that does not end with $allowedSuffix",
|
||||
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
|
||||
*listOfNotNull(
|
||||
name?.let { RenameToFix("$it$allowedSuffix") },
|
||||
AddCallOrUnwrapTypeFix(
|
||||
withBody = function.hasBody(),
|
||||
functionName = suggestedFunctionNameToCall,
|
||||
typeName = typeShortName,
|
||||
shouldMakeSuspend = shouldMakeSuspend,
|
||||
simplify = simplify
|
||||
)
|
||||
).toTypedArray()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
||||
return object : KtVisitorVoid() {
|
||||
override fun visitNamedFunction(function: KtNamedFunction) {
|
||||
analyzeFunction(function, function.nameIdentifier ?: function.funKeyword ?: function, holder)
|
||||
}
|
||||
|
||||
override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) {
|
||||
analyzeFunction(lambdaExpression.functionLiteral, lambdaExpression.functionLiteral.lBrace, holder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open fun analyzeFunctionWithAllowedSuffix(name: String, descriptor: FunctionDescriptor, toReport: PsiElement, holder: ProblemsHolder) {}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.LocalQuickFix
|
||||
import com.intellij.codeInspection.ProblemDescriptor
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent
|
||||
import org.jetbrains.kotlin.idea.core.replaced
|
||||
import org.jetbrains.kotlin.idea.core.setType
|
||||
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getReturnTypeReference
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
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.bindingContextUtil.getTargetFunctionDescriptor
|
||||
|
||||
class AddCallOrUnwrapTypeFix(
|
||||
val withBody: Boolean,
|
||||
val functionName: String,
|
||||
val typeName: String,
|
||||
val shouldMakeSuspend: Boolean,
|
||||
val simplify: (KtExpression) -> Unit
|
||||
) : LocalQuickFix {
|
||||
override fun getName(): String =
|
||||
if (withBody) "Add '.$functionName()' to function result (breaks use-sites!)"
|
||||
else "Unwrap '$typeName' return type (breaks use-sites!)"
|
||||
|
||||
override fun getFamilyName(): String = name
|
||||
|
||||
private fun KtExpression.addCallAndSimplify(factory: KtPsiFactory) {
|
||||
val newCallExpression = factory.createExpressionByPattern("$0.$functionName()", this)
|
||||
val result = replaced(newCallExpression)
|
||||
simplify(result)
|
||||
}
|
||||
|
||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||
val function = descriptor.psiElement.getNonStrictParentOfType<KtFunction>() ?: return
|
||||
val returnTypeReference = function.getReturnTypeReference()
|
||||
val context = function.analyzeWithContent()
|
||||
val functionDescriptor = context[BindingContext.FUNCTION, function] ?: return
|
||||
if (shouldMakeSuspend) {
|
||||
function.addModifier(KtTokens.SUSPEND_KEYWORD)
|
||||
}
|
||||
if (returnTypeReference != null) {
|
||||
val returnType = functionDescriptor.returnType ?: return
|
||||
val returnTypeArgument = returnType.arguments.firstOrNull()?.type ?: return
|
||||
function.setType(returnTypeArgument)
|
||||
}
|
||||
if (!withBody) return
|
||||
val factory = KtPsiFactory(project)
|
||||
val bodyExpression = function.bodyExpression
|
||||
bodyExpression?.forEachDescendantOfType<KtReturnExpression> {
|
||||
if (it.getTargetFunctionDescriptor(context) == functionDescriptor) {
|
||||
it.returnedExpression?.addCallAndSimplify(factory)
|
||||
}
|
||||
}
|
||||
if (function is KtFunctionLiteral) {
|
||||
val lastStatement = function.bodyExpression?.statements?.lastOrNull()
|
||||
if (lastStatement != null && lastStatement !is KtReturnExpression) {
|
||||
lastStatement.addCallAndSimplify(factory)
|
||||
}
|
||||
} else if (!function.hasBlockBody()) {
|
||||
bodyExpression?.addCallAndSimplify(factory)
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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 org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtQualifiedExpression
|
||||
|
||||
class DeferredIsResultInspection : AbstractIsResultInspection(
|
||||
typeShortName = "Deferred",
|
||||
typeFullName = "kotlinx.coroutines.Deferred",
|
||||
allowedSuffix = "Async",
|
||||
allowedNames = setOf("async"),
|
||||
suggestedFunctionNameToCall = "await",
|
||||
shouldMakeSuspend = true,
|
||||
simplify = fun(expression: KtExpression) {
|
||||
val qualifiedExpression = expression as? KtQualifiedExpression ?: return
|
||||
val redundantAsyncInspection = RedundantAsyncInspection()
|
||||
val conversion = redundantAsyncInspection.generateConversion(qualifiedExpression) ?: return
|
||||
redundantAsyncInspection.generateFix(conversion).apply(qualifiedExpression)
|
||||
}
|
||||
)
|
||||
+23
-136
@@ -5,35 +5,25 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.inspections.coroutines
|
||||
|
||||
import com.intellij.codeInspection.LocalQuickFix
|
||||
import com.intellij.codeInspection.ProblemDescriptor
|
||||
import com.intellij.codeInspection.ProblemHighlightType
|
||||
import com.intellij.codeInspection.ProblemsHolder
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import com.intellij.refactoring.rename.RenameProcessor
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||
import org.jetbrains.kotlin.idea.core.setType
|
||||
import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
|
||||
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getReturnTypeReference
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
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.bindingContextUtil.getTargetFunctionDescriptor
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
|
||||
class DirectUseOfResultTypeInspection : AbstractKotlinInspection() {
|
||||
class DirectUseOfResultTypeInspection : AbstractIsResultInspection(
|
||||
typeShortName = SHORT_NAME,
|
||||
typeFullName = "kotlin.Result",
|
||||
allowedSuffix = CATCHING,
|
||||
allowedNames = setOf("success", "failure", "runCatching"),
|
||||
suggestedFunctionNameToCall = "getOrThrow"
|
||||
) {
|
||||
|
||||
private fun MemberScope.hasCorrespondingNonCatchingFunction(
|
||||
nameWithoutCatching: String,
|
||||
@@ -76,130 +66,27 @@ class DirectUseOfResultTypeInspection : AbstractKotlinInspection() {
|
||||
return false
|
||||
}
|
||||
|
||||
private fun analyzeFunction(function: KtFunction, toReport: PsiElement, holder: ProblemsHolder) {
|
||||
if (function is KtConstructor<*>) return
|
||||
val returnTypeText = function.getReturnTypeReference()?.text
|
||||
if (returnTypeText != null && SHORT_NAME !in returnTypeText) return
|
||||
val name = (function as? KtNamedFunction)?.nameAsName?.asString()
|
||||
// Filter names from stdlib
|
||||
if (name in ALLOWED_NAMES) return
|
||||
if (function is KtNamedFunction) {
|
||||
val receiverTypeReference = function.receiverTypeReference
|
||||
// Filter Result extensions
|
||||
if (receiverTypeReference != null && SHORT_NAME in receiverTypeReference.text) return
|
||||
}
|
||||
if (function is KtFunctionLiteral || returnTypeText == null) {
|
||||
// Heuristics to save performance
|
||||
val text = function.bodyExpression?.text
|
||||
// Check there is something creating Result in function text
|
||||
if (text != null && ALLOWED_NAMES.none { it in text } && SHORT_NAME !in text && CATCHING !in text) return
|
||||
}
|
||||
|
||||
val descriptor = function.resolveToDescriptorIfAny() as? FunctionDescriptor ?: return
|
||||
override fun analyzeFunctionWithAllowedSuffix(
|
||||
name: String,
|
||||
descriptor: FunctionDescriptor,
|
||||
toReport: PsiElement,
|
||||
holder: ProblemsHolder
|
||||
) {
|
||||
val returnType = descriptor.returnType ?: return
|
||||
val returnTypeClass = returnType.constructor.declarationDescriptor as? ClassDescriptor ?: return
|
||||
if (returnTypeClass.fqNameSafe.asString() != FULL_NAME) return
|
||||
|
||||
if (name != null && name.endsWith(CATCHING)) {
|
||||
val nameWithoutCatching = name.substringBeforeLast(CATCHING)
|
||||
if (descriptor.hasCorrespondingNonCatchingFunction(returnType, nameWithoutCatching) == false) {
|
||||
val returnTypeArgument = returnType.arguments.firstOrNull()?.type
|
||||
val typeName = returnTypeArgument?.constructor?.declarationDescriptor?.name?.asString() ?: "T"
|
||||
holder.registerProblem(
|
||||
toReport,
|
||||
"Function '$name' returning '$SHORT_NAME<$typeName>' without the corresponding " +
|
||||
"function '$nameWithoutCatching' returning '$typeName'",
|
||||
ProblemHighlightType.GENERIC_ERROR_OR_WARNING
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val nameWithoutCatching = name.substringBeforeLast(CATCHING)
|
||||
if (descriptor.hasCorrespondingNonCatchingFunction(returnType, nameWithoutCatching) == false) {
|
||||
val returnTypeArgument = returnType.arguments.firstOrNull()?.type
|
||||
val typeName = returnTypeArgument?.constructor?.declarationDescriptor?.name?.asString() ?: "T"
|
||||
holder.registerProblem(
|
||||
toReport,
|
||||
"Function returning $SHORT_NAME with a name that does not end with $CATCHING",
|
||||
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
|
||||
*listOfNotNull(
|
||||
AddGetOrThrowFix(withBody = function.hasBody()),
|
||||
name?.let { RenameToCatchingFix("$it$CATCHING") }
|
||||
).toTypedArray()
|
||||
"Function '$name' returning '$SHORT_NAME<$typeName>' without the corresponding " +
|
||||
"function '$nameWithoutCatching' returning '$typeName'",
|
||||
ProblemHighlightType.GENERIC_ERROR_OR_WARNING
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
||||
return object : KtVisitorVoid() {
|
||||
override fun visitNamedFunction(function: KtNamedFunction) {
|
||||
analyzeFunction(function, function.nameIdentifier ?: function.funKeyword ?: function, holder)
|
||||
}
|
||||
private const val SHORT_NAME = "Result"
|
||||
|
||||
override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) {
|
||||
analyzeFunction(lambdaExpression.functionLiteral, lambdaExpression.functionLiteral.lBrace, holder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class AddGetOrThrowFix(val withBody: Boolean) : LocalQuickFix {
|
||||
override fun getName(): String =
|
||||
if (withBody) "Add '.$GET_OR_THROW()' to function result (breaks use-sites!)"
|
||||
else "Unwrap '$SHORT_NAME' return type (breaks use-sites!)"
|
||||
|
||||
override fun getFamilyName(): String = name
|
||||
|
||||
private fun KtExpression.addGetOrThrow(factory: KtPsiFactory) {
|
||||
val getOrThrowExpression = factory.createExpressionByPattern("$0.$GET_OR_THROW()", this)
|
||||
replace(getOrThrowExpression)
|
||||
}
|
||||
|
||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||
val function = descriptor.psiElement.getNonStrictParentOfType<KtFunction>() ?: return
|
||||
val returnTypeReference = function.getReturnTypeReference()
|
||||
val context = function.analyze()
|
||||
val functionDescriptor = context[BindingContext.FUNCTION, function] ?: return
|
||||
if (returnTypeReference != null) {
|
||||
val returnType = functionDescriptor.returnType ?: return
|
||||
val returnTypeArgument = returnType.arguments.firstOrNull()?.type ?: return
|
||||
function.setType(returnTypeArgument)
|
||||
}
|
||||
if (!withBody) return
|
||||
val factory = KtPsiFactory(project)
|
||||
val bodyExpression = function.bodyExpression
|
||||
bodyExpression?.forEachDescendantOfType<KtReturnExpression> {
|
||||
if (it.getTargetFunctionDescriptor(context) == functionDescriptor) {
|
||||
it.returnedExpression?.addGetOrThrow(factory)
|
||||
}
|
||||
}
|
||||
if (function is KtFunctionLiteral) {
|
||||
val lastStatement = function.bodyExpression?.statements?.lastOrNull()
|
||||
if (lastStatement != null && lastStatement !is KtReturnExpression) {
|
||||
lastStatement.addGetOrThrow(factory)
|
||||
}
|
||||
} else if (!function.hasBlockBody()) {
|
||||
bodyExpression?.addGetOrThrow(factory)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class RenameToCatchingFix(val newName: String) : LocalQuickFix {
|
||||
override fun getName(): String = "Rename to '$newName'"
|
||||
|
||||
override fun getFamilyName(): String = name
|
||||
|
||||
override fun startInWriteAction(): Boolean = false
|
||||
|
||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||
val function = descriptor.psiElement.getNonStrictParentOfType<KtFunction>() ?: return
|
||||
RenameProcessor(project, function, newName, false, false).run()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SHORT_NAME = "Result"
|
||||
|
||||
private const val FULL_NAME = "kotlin.$SHORT_NAME"
|
||||
|
||||
private const val CATCHING = "Catching"
|
||||
|
||||
private const val GET_OR_THROW = "getOrThrow"
|
||||
|
||||
private val ALLOWED_NAMES = setOf("success", "failure", "runCatching")
|
||||
}
|
||||
}
|
||||
private const val CATCHING = "Catching"
|
||||
|
||||
+28
-24
@@ -23,7 +23,7 @@ import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
|
||||
class RedundantAsyncInspection : AbstractCallChainChecker() {
|
||||
|
||||
private fun generateConversion(expression: KtQualifiedExpression): Conversion? {
|
||||
fun generateConversion(expression: KtQualifiedExpression): Conversion? {
|
||||
var defaultContext: Boolean? = null
|
||||
var defaultStart: Boolean? = null
|
||||
|
||||
@@ -63,38 +63,42 @@ class RedundantAsyncInspection : AbstractCallChainChecker() {
|
||||
return conversion
|
||||
}
|
||||
|
||||
fun generateFix(conversion: Conversion): SimplifyCallChainFix {
|
||||
val contextArgument = conversion.additionalArgument
|
||||
return SimplifyCallChainFix(conversion, removeReceiverOfFirstCall = true, runOptimizeImports = true) { callExpression ->
|
||||
if (contextArgument != null) {
|
||||
val call = callExpression.resolveToCall()
|
||||
if (call != null) {
|
||||
for (argument in callExpression.valueArguments) {
|
||||
val mapping = call.getArgumentMapping(argument) as? ArgumentMatch ?: continue
|
||||
if (mapping.valueParameter.name.asString() == CONTEXT_ARGUMENT_NAME) {
|
||||
val name = argument.getArgumentName()?.asName
|
||||
val expressionText = contextArgument + " + " + argument.getArgumentExpression()!!.text
|
||||
argument.replace(
|
||||
if (name == null) {
|
||||
createArgument(expressionText)
|
||||
} else {
|
||||
createArgument("$name = $expressionText")
|
||||
}
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) =
|
||||
qualifiedExpressionVisitor(fun(expression) {
|
||||
val conversion = generateConversion(expression) ?: return
|
||||
val contextArgument = conversion.additionalArgument
|
||||
val descriptor = holder.manager.createProblemDescriptor(
|
||||
expression,
|
||||
expression.firstCalleeExpression()!!.textRange.shiftRight(-expression.startOffset),
|
||||
"Redundant 'async' call may be reduced to '${conversion.replacement}'",
|
||||
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
|
||||
isOnTheFly,
|
||||
SimplifyCallChainFix(conversion, removeReceiverOfFirstCall = true, runOptimizeImports = true) { callExpression ->
|
||||
if (contextArgument != null) {
|
||||
val call = callExpression.resolveToCall()
|
||||
if (call != null) {
|
||||
for (argument in callExpression.valueArguments) {
|
||||
val mapping = call.getArgumentMapping(argument) as? ArgumentMatch ?: continue
|
||||
if (mapping.valueParameter.name.asString() == CONTEXT_ARGUMENT_NAME) {
|
||||
val name = argument.getArgumentName()?.asName
|
||||
val expressionText = contextArgument + " + " + argument.getArgumentExpression()!!.text
|
||||
argument.replace(
|
||||
if (name == null) {
|
||||
createArgument(expressionText)
|
||||
} else {
|
||||
createArgument("$name = $expressionText")
|
||||
}
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
generateFix(conversion)
|
||||
)
|
||||
holder.registerProblem(descriptor)
|
||||
})
|
||||
|
||||
@@ -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.LocalQuickFix
|
||||
import com.intellij.codeInspection.ProblemDescriptor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.refactoring.rename.RenameProcessor
|
||||
import org.jetbrains.kotlin.psi.KtFunction
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
|
||||
|
||||
class RenameToFix(val newName: String) : LocalQuickFix {
|
||||
override fun getName(): String = "Rename to '$newName'"
|
||||
|
||||
override fun getFamilyName(): String = name
|
||||
|
||||
override fun startInWriteAction(): Boolean = false
|
||||
|
||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||
val function = descriptor.psiElement.getNonStrictParentOfType<KtFunction>() ?: return
|
||||
RenameProcessor(project, function, newName, false, false).run()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.idea.inspections.coroutines.DeferredIsResultInspection
|
||||
@@ -0,0 +1,8 @@
|
||||
// WITH_RUNTIME
|
||||
// FIX: Unwrap 'Deferred' return type (breaks use-sites!)
|
||||
|
||||
package kotlinx.coroutines
|
||||
|
||||
interface My {
|
||||
fun <caret>function(): Deferred<Int>
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// WITH_RUNTIME
|
||||
// FIX: Unwrap 'Deferred' return type (breaks use-sites!)
|
||||
|
||||
package kotlinx.coroutines
|
||||
|
||||
interface My {
|
||||
suspend fun <caret>function(): Int
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// WITH_RUNTIME
|
||||
// FIX: Add '.await()' to function result (breaks use-sites!)
|
||||
|
||||
package kotlinx.coroutines
|
||||
|
||||
// TODO: this test contains strange formatting bug (see 0 -> return in *.after file). To be fixed.
|
||||
fun <caret>myFunction(context: CoroutineContext, switch: Int): Deferred<Int> {
|
||||
with (GlobalScope) {
|
||||
when (switch) {
|
||||
0 -> return async {
|
||||
val x = 123
|
||||
x * x
|
||||
}
|
||||
1 -> return async(context) { -1 }
|
||||
else -> return async() { 9 }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// WITH_RUNTIME
|
||||
// FIX: Add '.await()' to function result (breaks use-sites!)
|
||||
|
||||
package kotlinx.coroutines
|
||||
|
||||
// TODO: this test contains strange formatting bug (see 0 -> return in *.after file). To be fixed.
|
||||
suspend fun myFunction(context: CoroutineContext, switch: Int): Int {
|
||||
with (GlobalScope) {
|
||||
when (switch) {
|
||||
0 -> return withContext(Dispatchers.Default) {
|
||||
val x = 123
|
||||
x * x
|
||||
}
|
||||
1 -> return withContext(context) { -1 }
|
||||
else -> return withContext(Dispatchers.Default) { 9 }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// WITH_RUNTIME
|
||||
// FIX: Rename to 'myFunctionAsync'
|
||||
|
||||
package kotlinx.coroutines
|
||||
|
||||
fun <caret>myFunction(): Deferred<Int> {
|
||||
return GlobalScope.async { 42 }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// WITH_RUNTIME
|
||||
// FIX: Rename to 'myFunctionAsync'
|
||||
|
||||
package kotlinx.coroutines
|
||||
|
||||
fun myFunctionAsync(): Deferred<Int> {
|
||||
return GlobalScope.async { 42 }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// WITH_RUNTIME
|
||||
// FIX: Add '.await()' to function result (breaks use-sites!)
|
||||
|
||||
package kotlinx.coroutines
|
||||
|
||||
fun <caret>myFunction(): Deferred<Int> {
|
||||
return GlobalScope.async { 42 }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// WITH_RUNTIME
|
||||
// FIX: Add '.await()' to function result (breaks use-sites!)
|
||||
|
||||
package kotlinx.coroutines
|
||||
|
||||
suspend fun myFunction(): Int {
|
||||
return withContext(Dispatchers.Default) { 42 }
|
||||
}
|
||||
@@ -34,7 +34,7 @@ fun <T> CoroutineScope.async(
|
||||
suspend fun <T> withContext(
|
||||
context: CoroutineContext,
|
||||
block: suspend CoroutineScope.() -> T
|
||||
) {
|
||||
): T {
|
||||
TODO()
|
||||
}
|
||||
|
||||
|
||||
+33
@@ -1990,6 +1990,39 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/coroutines"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/inspectionsLocal/coroutines/deferredIsResult")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class DeferredIsResult extends AbstractLocalInspectionTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
|
||||
}
|
||||
|
||||
@TestMetadata("abstract.kt")
|
||||
public void testAbstract() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/coroutines/deferredIsResult/abstract.kt");
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInDeferredIsResult() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/coroutines/deferredIsResult"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("complex.kt")
|
||||
public void testComplex() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/coroutines/deferredIsResult/complex.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("rename.kt")
|
||||
public void testRename() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/coroutines/deferredIsResult/rename.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/coroutines/deferredIsResult/simple.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/inspectionsLocal/coroutines/directUseOfResultType")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user