blob: 1961fe5adacc3db2040ed3dbb92796632d2873cb [file] [log] [blame]
/*
* 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) {}
}