Merge remote-tracking branch 'console/master'

This commit is contained in:
Him188 2021-02-05 23:34:32 +08:00
commit 99701a2434
24 changed files with 969 additions and 155 deletions

View File

@ -44,18 +44,34 @@
groupKey="group.names.plugin.service.issues" enabledByDefault="true" level="WARNING"
implementationClass="net.mamoe.mirai.console.intellij.diagnostics.PluginMainServiceNotConfiguredInspection"/>
<localInspection groupPath="Mirai console" language="kotlin" shortName="ResourceNotClosed"
bundle="messages.InspectionGadgetsBundle"
key="resource.not.closed.display.name" groupBundle="messages.InspectionsBundle"
groupKey="group.names.mirai.core.issues" enabledByDefault="true" level="WARNING"
implementationClass="net.mamoe.mirai.console.intellij.diagnostics.ResourceNotClosedInspection"/>
<localInspection groupPath="Mirai console" language="JAVA" shortName="ResourceNotClosedJava"
bundle="messages.InspectionGadgetsBundle"
key="resource.not.closed.display.name" groupBundle="messages.InspectionsBundle"
groupKey="group.names.mirai.core.issues" enabledByDefault="true" level="WARNING"
implementationClass="net.mamoe.mirai.console.intellij.diagnostics.ResourceNotClosedInspection"/>
<localInspection groupPath="Mirai console" language="kotlin" shortName="UsingStringPlusMessage"
bundle="messages.InspectionGadgetsBundle"
key="using.string.plus.message.display.name" groupBundle="messages.InspectionsBundle"
groupKey="group.names.message.issues" enabledByDefault="true" level="WARNING"
implementationClass="net.mamoe.mirai.console.intellij.diagnostics.UsingStringPlusMessageInspection"/>
<!--
<intentionAction>
<className>net.mamoe.mirai.console.intellij.diagnostics.fix.AbuseYellowIntention</className>
<category>Mirai</category>
<className>net.mamoe.mirai.console.intellij.diagnostics.fix.WrapWithResourceUseCallIntention</className>
<category>Mirai console</category>
</intentionAction>
-->
<intentionAction>
<className>net.mamoe.mirai.console.intellij.diagnostics.fix.WrapWithResourceUseCallJavaIntention</className>
<category>Mirai console</category>
</intentionAction>
</extensions>
<extensions defaultExtensionNs="org.jetbrains.kotlin">

View File

@ -0,0 +1,8 @@
<html>
<body>
<p>检查 ExternalResource 没有 close 的情况。
</p>
<!-- tooltip end -->
<!--<p>Text after this comment will only be shown in the settings of the inspection.</p>-->
</body>
</html>

View File

@ -0,0 +1 @@
resource.use { it.uploadAsImage(contact) }

View File

@ -0,0 +1,8 @@
<html>
<body>
<p>将资源直接使用转换为 `.use { ... }`
</p>
<!-- tooltip end -->
<p>将 `resource.sendAsImageTo(contact)` 转换为 `resource.use { it.sendAsImageTo(contact) }` </p>
</body>
</html>

View File

@ -0,0 +1,4 @@
ExternalResource resource;
Contact contact;
resource.use { it.uploadAsImage(contact) }

View File

@ -0,0 +1,4 @@
ExternalResource resource;
Contact contact;
resource.uploadAsImage(contact)

View File

@ -0,0 +1,8 @@
<html>
<body>
<p>将资源直接使用转换为 `.use { ... }`
</p>
<!-- tooltip end -->
<p>将 `resource.sendAsImageTo(contact)` 转换为 `resource.use { it.sendAsImageTo(contact) }` </p>
</body>
</html>

View File

@ -7,4 +7,5 @@
# https://github.com/mamoe/mirai/blob/master/LICENSE
#
plugin.service.not.configured.display.name=Plugin main not configured
using.string.plus.message.display.name=Using string plus message
using.string.plus.message.display.name=Using string plus message
resource.not.closed.display.name=Resource not closed

View File

@ -7,4 +7,5 @@
# https://github.com/mamoe/mirai/blob/master/LICENSE
#
group.names.plugin.service.issues=Plugin service issues
group.names.message.issues=Message issues
group.names.message.issues=Message issues
group.names.mirai.core.issues=Mirai core issues

View File

@ -1,7 +1,7 @@
plugins {
kotlin("jvm") version "1.4.20"
kotlin("plugin.serialization") version "1.4.20"
id("net.mamoe.mirai-console") version "2.0.0"
id("net.mamoe.mirai-console") version "2.3.2"
java
}
@ -9,7 +9,6 @@ group = "org.example"
version = "1.0-SNAPSHOT"
repositories {
mavenLocal()
jcenter()
mavenCentral()
}

View File

@ -0,0 +1,31 @@
package test;
import net.mamoe.mirai.contact.Contact;
import net.mamoe.mirai.message.data.Image;
import net.mamoe.mirai.utils.ExternalResource;
import org.example.myplugin.ResourceNotClosedInspectionTestKt;
import java.io.File;
import java.io.IOException;
import static org.example.myplugin.ResourceNotClosedInspectionTestKt.magic;
public class ResourceNotClosedInspectionTestJava {
public static void main(String[] args) {
File file = magic();
Contact contact = magic();
// useImage(contact.uploadImage(ExternalResource.create(file)));
useImage(Contact.uploadImage(contact, ExternalResource.create(file)));
useImage(Contact.uploadImage(contact, file));
try (final ExternalResource resource = ExternalResource.create(file)) {
useImage(contact.uploadImage(resource));
}
}
static void useImage(Image image) {
}
}

View File

@ -0,0 +1,32 @@
package org.example.myplugin
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Contact.Companion.sendImage
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.Image.Key.queryUrl
import net.mamoe.mirai.utils.ExternalResource
import net.mamoe.mirai.utils.ExternalResource.Companion.sendAsImageTo
import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource
import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsImage
import java.io.File
class ResourceNotClosedInspectionTest {
suspend fun useResource() {
val file = magic<File>()
val contact = magic<Contact>()
contact.uploadImage(file.toExternalResource()) // should report warning
contact.sendImage(file) // should report warning
//file.toExternalResource().uploadAsImage(contact)
file.toExternalResource().uploadAsImage(contact)
file.toExternalResource().sendAsImageTo(contact)
// contact.uploadImage(file) // should ok
// replace to net.mamoe.mirai.contact.Contact.Companion.uploadImage
}
}
fun <T> magic(): T = null!!

View File

@ -16,5 +16,16 @@ fun main() {
val x: String = ""
val plain: PlainText = PlainText("")
PlainText(x) + plain
x + plain
// x + plain
run { x } + plain
run { x.apply { x.let { also { x } } } } + plain
run { x.apply { x.let { also { x } } } }.plus(plain)
x + plain
1 + plain
x.plus(plain)
1.plus(plain)
}

View File

@ -0,0 +1,18 @@
package org.example.myplugin
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.utils.ExternalResource
import net.mamoe.mirai.utils.ExternalResource.Companion.sendAsImageTo
import java.io.File
class WrapWithResourceUseCallIntentionTest {
suspend fun test() {
val file = magic<File>()
val contact = magic<Contact>()
val resource = magic<ExternalResource>()
resource.sendAsImageTo(contact)
resource.run { sendAsImageTo(contact) }
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.intellij.diagnostics
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.idea.inspections.KotlinUniversalQuickFix
import org.jetbrains.kotlin.idea.quickfix.KotlinCrossLanguageQuickFixAction
fun <T: PsiElement> LocalQuickFix(text: String, element: T, invokeAction: QuickFixInvoke<T>.() -> Unit): LocalQuickFix {
return object: KotlinCrossLanguageQuickFixAction<T>(element), KotlinUniversalQuickFix {
@Suppress("DialogTitleCapitalization")
override fun getFamilyName(): String = "Mirai console"
override fun getText(): String = text
override fun invokeImpl(project: Project, editor: Editor?, file: PsiFile) {
invokeAction(QuickFixInvoke(project, editor ?: return, file, this.element ?: return))
}
}
}
class QuickFixInvoke<T>(
val project: Project,
val editor: Editor,
val file: PsiFile,
val element: T,
)

View File

@ -0,0 +1,299 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.intellij.diagnostics
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.*
import net.mamoe.mirai.console.intellij.resolve.*
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
import org.jetbrains.kotlin.idea.inspections.KotlinUniversalQuickFix
import org.jetbrains.kotlin.idea.quickfix.KotlinCrossLanguageQuickFixAction
import org.jetbrains.kotlin.idea.search.getKotlinFqName
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.nj2k.postProcessing.resolve
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.referenceExpression
import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import kotlin.contracts.contract
/*
private val bundle by lazy {
BundleUtil.loadLanguageBundle(PluginMainServiceNotConfiguredInspection::class.java.classLoader, "messages.InspectionGadgetsBundle")!!
}*/
/**
* @since 2.4
*/
class ResourceNotClosedInspection : AbstractKotlinInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : KtVisitorVoid() {
override fun visitCallExpression(callExpression: KtCallExpression) {
for (processor in ResourceNotClosedInspectionProcessors.processors) {
processor.visitKtExpr(holder, isOnTheFly, callExpression)
}
}
override fun visitElement(element: PsiElement) {
if (element is PsiCallExpression) {
for (processor in ResourceNotClosedInspectionProcessors.processors) {
processor.visitPsiExpr(holder, isOnTheFly, element)
}
}
}
}
}
}
val CONTACT_FQ_NAME = FqName("net.mamoe.mirai.contact.Contact")
val CONTACT_COMPANION_FQ_NAME = FqName("net.mamoe.mirai.contact.Contact.Companion")
fun KtReferenceExpression.resolveCalleeFunction(): KtNamedFunction? {
val originalCallee = getCalleeExpressionIfAny()?.referenceExpression()?.resolve() ?: return null
if (originalCallee !is KtNamedFunction) return null
return originalCallee
}
fun KtNamedFunction.isNamedMemberFunctionOf(className: String, functionName: String, extensionReceiver: String? = null): Boolean {
if (extensionReceiver != null) {
if (this.receiverTypeReference?.resolveReferencedType()?.getKotlinFqName()?.toString() != extensionReceiver) return false
}
return this.name == functionName && this.containingClassOrObject?.allSuperTypes?.any { it.getKotlinFqName()?.toString() == className } == true
}
@Suppress("DialogTitleCapitalization")
object ResourceNotClosedInspectionProcessors {
val processors = arrayOf(
FirstArgumentProcessor,
KtExtensionProcessor
)
interface Processor {
fun visitKtExpr(holder: ProblemsHolder, isOnTheFly: Boolean, callExpr: KtCallExpression)
fun visitPsiExpr(holder: ProblemsHolder, isOnTheFly: Boolean, expr: PsiCallExpression)
}
object KtExtensionProcessor : Processor {
// net.mamoe.mirai.utils.ExternalResource.Companion.sendAsImage(net.mamoe.mirai.utils.ExternalResource, C, kotlin.coroutines.Continuation<? super net.mamoe.mirai.message.MessageReceipt<? extends C>>)
val SEND_AS_IMAGE_TO = FunctionSignature {
name("sendAsImageTo")
dispatchReceiver("net.mamoe.mirai.utils.ExternalResource.Companion")
extensionReceiver("net.mamoe.mirai.utils.ExternalResource")
}
val UPLOAD_AS_IMAGE = FunctionSignature {
name("uploadAsImage")
dispatchReceiver("net.mamoe.mirai.utils.ExternalResource.Companion")
extensionReceiver("net.mamoe.mirai.utils.ExternalResource")
}
override fun visitKtExpr(holder: ProblemsHolder, isOnTheFly: Boolean, callExpr: KtCallExpression) {
val parent = callExpr.parent
if (parent !is KtDotQualifiedExpression) return
val callee = callExpr.resolveCalleeFunction() ?: return
if (!parent.receiverExpression.isCallingExternalResourceCreators()) return
class Fix(private val functionName: String) : KotlinCrossLanguageQuickFixAction<KtDotQualifiedExpression>(parent), KotlinUniversalQuickFix {
override fun getFamilyName(): String = FAMILY_NAME
override fun getText(): String = "修复 $functionName"
override fun invokeImpl(project: Project, editor: Editor?, file: PsiFile) {
if (editor == null) return
val uploadImageExpression = element ?: return
val toExternalExpression = uploadImageExpression.receiverExpression
val toExternalReceiverExpression = toExternalExpression.dotReceiverExpression() ?: return
toExternalExpression.replace(toExternalReceiverExpression)
}
}
when {
callee.hasSignature(SEND_AS_IMAGE_TO) -> {
// RECEIVER.sendAsImageTo
holder.registerResourceNotClosedProblem(
parent.receiverExpression,
Fix("sendAsImageTo"),
)
}
callee.hasSignature(UPLOAD_AS_IMAGE) -> {
holder.registerResourceNotClosedProblem(
parent.receiverExpression,
Fix("uploadAsImage"),
)
}
}
}
override fun visitPsiExpr(holder: ProblemsHolder, isOnTheFly: Boolean, expr: PsiCallExpression) {
}
}
object FirstArgumentProcessor : Processor {
val CONTACT_UPLOAD_IMAGE = FunctionSignature {
name("uploadImage")
dispatchReceiver(CONTACT_FQ_NAME)
parameters("net.mamoe.mirai.utils.ExternalResource")
}
val CONTACT_UPLOAD_IMAGE_STATIC = FunctionSignature {
name("uploadImage")
extensionReceiver(CONTACT_FQ_NAME)
dispatchReceiver(CONTACT_COMPANION_FQ_NAME)
parameters("net.mamoe.mirai.utils.ExternalResource")
}
val CONTACT_COMPANION_UPLOAD_IMAGE = FunctionSignature {
name("uploadImage")
extensionReceiver(CONTACT_FQ_NAME)
parameters("net.mamoe.mirai.utils.ExternalResource")
}
val CONTACT_COMPANION_SEND_IMAGE = FunctionSignature {
name("sendImage")
extensionReceiver(CONTACT_FQ_NAME)
parameters("net.mamoe.mirai.utils.ExternalResource")
}
private val signatures = arrayOf(
CONTACT_UPLOAD_IMAGE,
CONTACT_COMPANION_UPLOAD_IMAGE,
CONTACT_COMPANION_SEND_IMAGE
)
override fun visitKtExpr(holder: ProblemsHolder, isOnTheFly: Boolean, callExpr: KtCallExpression) {
val callee = callExpr.resolveCalleeFunction() ?: return
if (signatures.none { callee.hasSignature(it) }) return
val firstArgument = callExpr.valueArguments.firstOrNull() ?: return
val firstArgumentExpr = firstArgument.getArgumentExpression()
if (firstArgumentExpr?.isCallingExternalResourceCreators() != true) return
holder.registerResourceNotClosedProblem(
firstArgument,
LocalQuickFix("修复", firstArgumentExpr) {
fun tryAddImport() {
if (file !is KtFile) return
val companion = callee.descriptor?.containingDeclaration?.companionObjectDescriptor() ?: return
val toImport = companion.findMemberFunction(callee.nameAsName ?: return) ?: return
// net.mamoe.mirai.contact.Contact.Companion
ImportInsertHelper.getInstance(project).importDescriptor(file, toImport)
}
val newArgumentText = element.dotReceiverExpression()?.text ?: return@LocalQuickFix
callExpr.replace(KtPsiFactory(project).createExpression(buildString {
append(callee.name)
append('(')
append(newArgumentText)
append(')')
}))
tryAddImport()
}
)
}
override fun visitPsiExpr(holder: ProblemsHolder, isOnTheFly: Boolean, expr: PsiCallExpression) {
if (expr !is PsiMethodCallExpression) return
val callee = expr.resolveMethod() ?: return
val arguments = expr.argumentList.expressions
when {
callee.hasSignature(CONTACT_UPLOAD_IMAGE) -> {
createFixImpl(
expr = expr,
holder = holder,
argument = arguments.firstOrNull() ?: return,
fileTypeArgument = arguments.getOrNull(1)
) { it.methodExpression.qualifierExpression?.text ?: "this" }
}
callee.hasSignature(CONTACT_UPLOAD_IMAGE_STATIC) -> {
createFixImpl(
expr = expr,
holder = holder,
argument = arguments.getOrNull(1) ?: return,
fileTypeArgument = arguments.getOrNull(2)
) { arguments.getOrNull(0)?.text ?: "this" }
}
}
}
private fun createFixImpl(
expr: PsiMethodCallExpression,
holder: ProblemsHolder,
argument: PsiExpression,
fileTypeArgument: PsiExpression?,
replaceForFirstArgument: (expr: PsiMethodCallExpression) -> String,
) {
if (!argument.isCallingExternalResourceCreators()) return
holder.registerResourceNotClosedProblem(
argument,
LocalQuickFix("修复", argument) {
/*
useImage(Contact.uploadImage(contact, ExternalResource.create(file))); // before
useImage(Contact.uploadImage(contact, file)); // after
*/
val factory = project.psiElementFactory ?: return@LocalQuickFix
val reference = factory.createExpressionFromText(
if (fileTypeArgument == null) {
"$CONTACT_FQ_NAME.uploadImage(${replaceForFirstArgument(expr)}, ${argument.argumentList?.expressions?.firstOrNull()?.text ?: ""})"
} else {
"$CONTACT_FQ_NAME.uploadImage(${replaceForFirstArgument(expr)}, ${argument.argumentList?.expressions?.firstOrNull()?.text ?: ""}, ${fileTypeArgument.text})"
},
expr.context
)
expr.replace(reference)
}
)
}
}
private fun ProblemsHolder.registerResourceNotClosedProblem(target: PsiElement, vararg fixes: LocalQuickFix) {
registerProblem(
target,
@Suppress("DialogTitleCapitalization") "资源未关闭",
ProblemHighlightType.WARNING,
*fixes
)
}
}
private val EXTERNAL_RESOURCE_CREATE = FunctionSignature {
name("create")
dispatchReceiver("net.mamoe.mirai.utils.ExternalResource.Companion")
}
private val TO_EXTERNAL_RESOURCE = FunctionSignature {
name("toExternalResource")
dispatchReceiver("net.mamoe.mirai.utils.ExternalResource.Companion")
}
fun KtExpression.isCallingExternalResourceCreators(): Boolean {
val callExpr = resolveToCall(BodyResolveMode.PARTIAL)?.resultingDescriptor ?: return false
return callExpr.hasSignature(EXTERNAL_RESOURCE_CREATE) || callExpr.hasSignature(TO_EXTERNAL_RESOURCE)
}
fun PsiExpression.isCallingExternalResourceCreators(): Boolean {
contract { returns() implies (this@isCallingExternalResourceCreators is PsiCallExpression) }
if (this !is PsiCallExpression) return false
val callee = resolveMethod() ?: return false
return callee.hasSignature(EXTERNAL_RESOURCE_CREATE)
}
private const val FAMILY_NAME = "Mirai console"

View File

@ -11,19 +11,24 @@ package net.mamoe.mirai.console.intellij.diagnostics
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementFactory
import com.intellij.psi.PsiElementVisitor
import net.mamoe.mirai.console.compiler.common.castOrNull
import net.mamoe.mirai.console.intellij.diagnostics.fix.ConvertToPlainTextFix
import net.mamoe.mirai.console.intellij.resolve.findChild
import net.mamoe.mirai.console.intellij.resolve.hasSuperType
import com.intellij.psi.PsiFile
import net.mamoe.mirai.console.intellij.resolve.*
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
import org.jetbrains.kotlin.idea.search.declarationsSearch.findDeepestSuperMethodsKotlinAware
import org.jetbrains.kotlin.idea.inspections.KotlinUniversalQuickFix
import org.jetbrains.kotlin.idea.quickfix.KotlinCrossLanguageQuickFixAction
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.nj2k.postProcessing.resolve
import org.jetbrains.kotlin.nj2k.postProcessing.type
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForReceiver
import org.jetbrains.kotlin.psi.psiUtil.referenceExpression
import java.util.*
import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny
/*
private val bundle by lazy {
@ -31,44 +36,119 @@ private val bundle by lazy {
}*/
class UsingStringPlusMessageInspection : AbstractKotlinInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return referenceExpressionVisitor visitor@{ expression ->
val originalCallee = expression.resolve() ?: return@visitor
if (originalCallee !is KtNamedFunction) return@visitor
companion object {
const val DESCRIPTION = "使用 String + Message 会导致 Message 被转换为 String 再相加"
private const val MESSAGE_FQ_NAME_STR = "net.mamoe.mirai.message.data.Message"
private const val CONVERT_TO_PLAIN_TEXT = "将 String 转换为 PlainText"
val callee = findDeepestSuperMethodsKotlinAware(originalCallee).lastOrNull() as? KtNamedFunction ?: originalCallee
fun KtReferenceExpression.isCallingStringPlus(): Boolean {
val callee = this.referenceExpression()?.resolve() ?: return false
if (callee !is KtNamedFunction) return false
val className = callee.containingClassOrObject?.fqName?.asString()
if (className != "kotlin.String") return@visitor
if (callee.name != "plus") return@visitor
val inspectionTarget = when (val parent = expression.parent) {
is KtBinaryExpression -> {
val right = parent.right?.referenceExpression()?.resolve() as? KtDeclaration ?: return@visitor
val rightType = right.type() ?: return@visitor
if (!rightType.hasSuperType("net.mamoe.mirai.message.data.Message")) return@visitor
parent.left
}
is KtCallExpression -> {
val argumentType = parent
.valueArguments.singleOrNull()
?.findChild<KtReferenceExpression>()
?.resolve()?.castOrNull<KtDeclaration>()?.type()
?: return@visitor
if (!argumentType.hasSuperType("net.mamoe.mirai.message.data.Message")) return@visitor
parent.parent?.castOrNull<KtDotQualifiedExpression>()?.receiverExpression // explicit receiver, inspection on it.
?: parent.findChild<KtNameReferenceExpression>() // implicit receiver, inspection on 'plus'
}
else -> null
} ?: return@visitor
holder.registerProblem(
inspectionTarget,
"使用 String + Message 会导致 Message 被转换为 String 再相加",
ProblemHighlightType.WARNING,
ConvertToPlainTextFix(inspectionTarget)
)
if (className != "kotlin.String") return false
if (callee.name != "plus") return false
return true
}
}
private class Visitor(
val holder: ProblemsHolder
) : KtVisitorVoid() {
class BinaryExprFix(left: KtExpression) : ConvertToPlainTextFix<KtExpression>(left) {
override fun invokeImpl(project: Project, editor: Editor?, file: PsiFile) {
if (editor == null || file !is KtFile) return
val element = element ?: return
val referenceExpr = element.referenceExpression()
if (referenceExpr == null || element.parent is KtBinaryExpression) {
// `+ operator`, e.g. `str + msg`
// or
// complex expressions, e.g. `str.toString().plus(msg)`, `"".also { }.plus(msg)`
val replaced =
element.replace(KtPsiFactory(project).createExpression("net.mamoe.mirai.message.data.PlainText(${element.text})"))
as? KtElement ?: return
ShortenReferences.DEFAULT.process(replaced)
return
}
if (element is KtNameReferenceExpression) {
val receiver = element.getQualifiedExpressionForReceiver() ?: return
val replaced = receiver
.replace(KtPsiFactory(project).createExpression("net.mamoe.mirai.message.data.PlainText(${receiver.text})"))
as? KtElement ?: return
ShortenReferences.DEFAULT.process(replaced)
}
}
}
override fun visitBinaryExpression(binaryExpression: KtBinaryExpression) {
if (binaryExpression.operationToken != KtTokens.PLUS) return
if (binaryExpression.left?.getCalleeExpressionIfAny()?.typeFqName()?.toString() != "kotlin.String") return
val rightType = binaryExpression.right?.type() ?: return
if (!rightType.hasSuperType(MESSAGE_FQ_NAME_STR)) return
val left = binaryExpression.left ?: return
holder.registerProblem(
left,
DESCRIPTION,
ProblemHighlightType.WARNING,
BinaryExprFix(left)
)
}
override fun visitCallExpression(expression: KtCallExpression) {
if (!expression.isCallingStringPlus()) return
val argumentType = expression.valueArguments.singleOrNull()?.type() ?: return
if (!argumentType.hasSuperType(MESSAGE_FQ_NAME_STR)) return
val explicitReceiverExpr = expression.siblingDotReceiverExpression()
if (explicitReceiverExpr != null) {
holder.registerProblem(
explicitReceiverExpr,
DESCRIPTION,
ProblemHighlightType.WARNING,
LocalQuickFix(CONVERT_TO_PLAIN_TEXT, explicitReceiverExpr) {
element.replaceExpressionAndShortenReferences("net.mamoe.mirai.message.data.PlainText(${element.text})")
}
)
} else {
val nameReferenceExpression = expression.findChild<KtNameReferenceExpression>() ?: expression.calleeExpression ?: expression
holder.registerProblem(
nameReferenceExpression,
DESCRIPTION,
ProblemHighlightType.WARNING,
LocalQuickFix(CONVERT_TO_PLAIN_TEXT, expression) {
val callExpression = this.element.calleeExpression ?: return@LocalQuickFix
val implicitReceiverText = this.element.implicitExpressionText() ?: return@LocalQuickFix
this.element.replaceExpressionAndShortenReferences(
"net.mamoe.mirai.message.data.PlainText(${implicitReceiverText}).${callExpression.text}"
)
}
)
}
}
}
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor = Visitor(holder)
abstract class ConvertToPlainTextFix<T : PsiElement>(element: T) : KotlinCrossLanguageQuickFixAction<T>(element), KotlinUniversalQuickFix {
@Suppress("DialogTitleCapitalization")
override fun getFamilyName(): String = "Mirai Console"
@Suppress("DialogTitleCapitalization")
override fun getText(): String = "将 String 转换为 PlainText"
}
}
fun KtElement.replaceExpressionAndShortenReferences(expression: String) {
val replaced = replace(KtPsiFactory(this.project).createExpression(expression)) as? KtElement ?: return
ShortenReferences.DEFAULT.process(replaced)
}

View File

@ -9,6 +9,7 @@
package net.mamoe.mirai.console.intellij.diagnostics
import com.intellij.psi.PsiElement
import net.mamoe.mirai.console.compiler.common.castOrNull
import net.mamoe.mirai.console.compiler.common.resolve.READ_ONLY_PLUGIN_DATA_FQ_NAME
import net.mamoe.mirai.console.intellij.resolve.getResolvedCall
@ -18,6 +19,7 @@ import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtTypeParameter
import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.psi.KtUserType
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
@ -37,7 +39,7 @@ fun DeclarationCheckerContext.report(diagnostic: Diagnostic) {
val DeclarationCheckerContext.bindingContext get() = this.trace.bindingContext
fun KtElement?.getResolvedCall(
fun KtElement.getResolvedCall(
context: DeclarationCheckerContext,
): ResolvedCall<out CallableDescriptor>? {
return this.getResolvedCall(context.bindingContext)
@ -49,4 +51,12 @@ fun KtTypeReference.isReferencing(fqName: FqName): Boolean {
val KtTypeReference.referencedUserType: KtUserType? get() = this.typeElement.castOrNull()
fun KtTypeReference.resolveReferencedType() = referencedUserType?.referenceExpression?.mainReference?.resolve()
fun KtTypeReference.resolveReferencedType(): PsiElement? {
val resolved = referencedUserType?.referenceExpression?.mainReference?.resolve()
if (resolved is KtTypeParameter) {
val bound = resolved.extendsBound ?: return resolved
if (bound.name == resolved.name) return null // <C: C> bad type, avoid infinite run
return bound.resolveReferencedType()
}
return resolved
}

View File

@ -1,92 +0,0 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.intellij.diagnostics.fix
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.inspections.KotlinUniversalQuickFix
import org.jetbrains.kotlin.idea.quickfix.KotlinCrossLanguageQuickFixAction
import org.jetbrains.kotlin.idea.util.getFactoryForImplicitReceiverWithSubtypeOf
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.nj2k.postProcessing.resolve
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.referenceExpression
class ConvertToPlainTextFix(
/**
* Maybe:
*
* - [KtNameReferenceExpression]: if implicit receiver
* - [KtExpression]
*/
element: KtExpression,
) : KotlinCrossLanguageQuickFixAction<KtExpression>(element), KotlinUniversalQuickFix {
override fun getFamilyName(): String = "Mirai Console"
override fun getText(): String = "将 String 转换为 PlainText"
override fun invokeImpl(project: Project, editor: Editor?, file: PsiFile) {
if (editor == null) return
if (file !is KtFile) return
val element = element ?: return
val psiFactory = KtPsiFactory(project)
val referenceExpr = element.referenceExpression()
if (referenceExpr == null || element.parent is KtBinaryExpression) {
// + operator, e.g. 'str + msg'
// or
// complex expressions, e.g. 'str.toString().plus(msg)', '"".also { }.plus(msg)'
val replaced = element.replace(psiFactory.createExpression("net.mamoe.mirai.message.data.PlainText(${element.text})"))
as? KtElement ?: return
ShortenReferences.DEFAULT.process(replaced)
return
}
val resolved = referenceExpr.resolve()
if (resolved !is KtDeclaration) return
// 'plus' function
// perform fix on receiver
val dotQualifiedExpr = element.parent
if (dotQualifiedExpr is KtDotQualifiedExpression) {
// got explicit receiver
val replaced = dotQualifiedExpr.receiverExpression
.replace(psiFactory.createExpression("net.mamoe.mirai.message.data.PlainText(${dotQualifiedExpr.receiverExpression.text})"))
as? KtElement ?: return
ShortenReferences.DEFAULT.process(replaced)
} else {
// implicit receiver
val context = element.analyze()
val scope = element.getResolutionScope(context) ?: return
val descriptor = element.resolveToCall()?.resultingDescriptor ?: return
val receiverDescriptor = descriptor.extensionReceiverParameter
?: descriptor.dispatchReceiverParameter
?: return
val receiverType = receiverDescriptor.type
val expressionFactory = scope.getFactoryForImplicitReceiverWithSubtypeOf(receiverType) ?: return
val receiverText = if (expressionFactory.isImmediate) "this" else expressionFactory.expressionText
// element.parent is 'plus(msg)'
// replace it with a dot qualified expr
val replaced =
element.parent.replace(psiFactory.createExpression("net.mamoe.mirai.message.data.PlainText($receiverText).${element.parent.text}"))
as? KtElement ?: return
ShortenReferences.DEFAULT.process(replaced)
}
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.intellij.diagnostics.fix
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.projectRoots.JavaSdkVersion
import com.intellij.openapi.projectRoots.JavaVersionService
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethodCallExpression
import net.mamoe.mirai.console.intellij.diagnostics.ResourceNotClosedInspectionProcessors.KtExtensionProcessor.SEND_AS_IMAGE_TO
import net.mamoe.mirai.console.intellij.diagnostics.ResourceNotClosedInspectionProcessors.KtExtensionProcessor.UPLOAD_AS_IMAGE
import net.mamoe.mirai.console.intellij.diagnostics.replaceExpressionAndShortenReferences
import net.mamoe.mirai.console.intellij.diagnostics.resolveCalleeFunction
import net.mamoe.mirai.console.intellij.resolve.hasSignature
import org.jetbrains.kotlin.idea.intentions.SelfTargetingIntention
import org.jetbrains.kotlin.idea.util.module
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.psiUtil.referenceExpression
/**
* @since 2.4
*/
class WrapWithResourceUseCallIntention : SelfTargetingIntention<KtDotQualifiedExpression>(KtDotQualifiedExpression::class.java, { "转换为 .use" }) {
override fun applyTo(element: KtDotQualifiedExpression, editor: Editor?) {
val selectorExpression = element.selectorExpression ?: return
selectorExpression.replaceExpressionAndShortenReferences("use { it.${selectorExpression.text} }")
}
override fun isApplicableTo(element: KtDotQualifiedExpression, caretOffset: Int): Boolean {
val callee = element.selectorExpression?.referenceExpression()?.resolveCalleeFunction() ?: return false
if (!callee.hasSignature(UPLOAD_AS_IMAGE) && !callee.hasSignature(SEND_AS_IMAGE_TO)) return false
val receiver = element.receiverExpression
return receiver is KtSimpleNameExpression
}
}
// https://github.com/mamoe/mirai-console/issues/284
/**
*
* to be supported by 2.5
* @since 2.4
*/
class WrapWithResourceUseCallJavaIntention : SelfTargetingIntention<PsiMethodCallExpression>(PsiMethodCallExpression::class.java, { "转换为 .use" }) {
override fun applyTo(element: PsiMethodCallExpression, editor: Editor?) {
// val selectorExpression = element.methodExpression
}
override fun isApplicableTo(element: PsiMethodCallExpression, caretOffset: Int): Boolean {
return false
// if (!element.isJavaVersionAtLeast(JavaSdkVersion.JDK_1_9)) return false
}
}
fun PsiElement.isJavaVersionAtLeast(version: JavaSdkVersion): Boolean {
return this.module?.getService(JavaVersionService::class.java)?.isAtLeast(this, JavaSdkVersion.JDK_1_9) == true
}

View File

@ -0,0 +1,153 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.intellij.resolve
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiModifier
import net.mamoe.mirai.console.intellij.diagnostics.resolveReferencedType
import org.jetbrains.kotlin.asJava.elements.KtLightMethod
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getReturnTypeReference
import org.jetbrains.kotlin.idea.refactoring.fqName.fqName
import org.jetbrains.kotlin.idea.search.getKotlinFqName
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.nj2k.postProcessing.type
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
inline fun FunctionSignature(builderAction: FunctionSignatureBuilder.() -> Unit): FunctionSignature {
return FunctionSignatureBuilder().apply(builderAction).build()
}
data class FunctionSignature(
val name: String? = null,
val dispatchReceiver: FqName? = null,
val extensionReceiver: FqName? = null,
val parameters: List<FqName>? = null,
val returnType: FqName? = null,
)
class FunctionSignatureBuilder {
private var name: String? = null
private var dispatchReceiver: FqName? = null
private var extensionReceiver: FqName? = null
private var parameters: List<FqName>? = null
private var returnType: FqName? = null
fun name(name: String) {
this.name = name
}
fun dispatchReceiver(dispatchReceiver: String) {
this.dispatchReceiver = FqName(dispatchReceiver)
}
fun extensionReceiver(extensionReceiver: String) {
this.extensionReceiver = FqName(extensionReceiver)
}
fun parameters(vararg parameters: String) {
this.parameters = parameters.map { FqName(it) }
}
fun returnType(returnType: String) {
this.returnType = FqName(returnType)
}
fun build(): FunctionSignature = FunctionSignature(name, dispatchReceiver, extensionReceiver, parameters, returnType)
}
fun FunctionSignatureBuilder.dispatchReceiver(dispatchReceiver: FqName) {
dispatchReceiver(dispatchReceiver.toString())
}
fun FunctionSignatureBuilder.extensionReceiver(extensionReceiver: FqName) {
extensionReceiver(extensionReceiver.toString())
}
fun KtFunction.hasSignature(functionSignature: FunctionSignature): Boolean {
if (functionSignature.name != null) {
if (this.name != functionSignature.name) return false
}
if (functionSignature.dispatchReceiver != null) {
if (this.containingClassOrObject?.fqName != functionSignature.dispatchReceiver) return false
}
if (functionSignature.extensionReceiver != null) {
if (this.receiverTypeReference?.resolveReferencedType()?.getKotlinFqName() != functionSignature.extensionReceiver) return false
}
if (functionSignature.parameters != null) {
if (this.valueParameters.zip(functionSignature.parameters).any { it.first.type()?.fqName != it.second }) return false
}
if (functionSignature.returnType != null) {
if (this.getReturnTypeReference()?.resolveReferencedType()?.getKotlinFqName() != functionSignature.returnType) return false
}
return true
}
fun KtLightMethod.hasSignature(functionSignature: FunctionSignature): Boolean {
if (functionSignature.name != null) {
if (this.name != functionSignature.name) return false
}
val parameters = parameterList.parameters.toMutableList()
if (functionSignature.dispatchReceiver != null) {
val kotlinContainingClassFqn =
if (this.modifierList.hasExplicitModifier(PsiModifier.STATIC)) {
this.containingClass.kotlinOrigin?.companionObjects?.firstOrNull()?.fqName
} else this.containingClass.getKotlinFqName()
if (kotlinContainingClassFqn != functionSignature.dispatchReceiver) return false
}
if (functionSignature.extensionReceiver != null) {
val receiver = parameters.removeFirstOrNull() ?: return false
if (receiver.type.canonicalText != functionSignature.extensionReceiver.toString()) return false
}
if (functionSignature.parameters != null) {
if (parameters.zip(functionSignature.parameters).any { it.first.type.canonicalText != it.second.toString() }) return false
}
if (functionSignature.returnType != null) {
if (returnType?.canonicalText != functionSignature.returnType.toString()) return false
}
return true
}
fun PsiMethod.hasSignature(functionSignature: FunctionSignature): Boolean {
if (this is KtLightMethod) {
return this.hasSignature(functionSignature)
}
return true
}
fun KtExpression.isCalling(functionSignature: FunctionSignature): Boolean {
val descriptor = resolveToCall(BodyResolveMode.PARTIAL)?.resultingDescriptor ?: return false
return descriptor.hasSignature(functionSignature)
}
fun CallableDescriptor.hasSignature(functionSignature: FunctionSignature): Boolean {
if (functionSignature.name != null) {
if (this.name.toString() != functionSignature.name) return false
}
if (functionSignature.extensionReceiver != null) {
if (this.extensionReceiverParameter?.fqNameUnsafe != functionSignature.extensionReceiver.toUnsafe()) return false
}
if (functionSignature.dispatchReceiver != null) {
if (this.containingDeclaration.fqNameUnsafe != functionSignature.dispatchReceiver.toUnsafe()) return false
}
if (functionSignature.parameters != null) {
if (this.valueParameters.zip(functionSignature.parameters).any { it.first.type.fqName != it.second }) return false
}
if (functionSignature.returnType != null) {
if (this.returnType?.fqName != functionSignature.returnType) return false
}
return true
}

View File

@ -0,0 +1,96 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.intellij.resolve
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.util.getFactoryForImplicitReceiverWithSubtypeOf
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtPsiFactory
sealed class ReceiverExpression {
abstract val receiverExpression: KtExpression
abstract val receiverText: String
operator fun component1(): KtExpression = receiverExpression
operator fun component2(): String = receiverText
class Explicit(
override val receiverExpression: KtExpression
) : ReceiverExpression() {
override val receiverText: String
get() = receiverExpression.text
}
class Implicit(
receiverExpression: Lazy<KtExpression>,
override val receiverText: String,
) : ReceiverExpression() {
override val receiverExpression: KtExpression by receiverExpression
}
}
fun KtCallExpression.siblingDotReceiverExpression(): KtExpression? {
val dotQualifiedExpression = parent
if (dotQualifiedExpression is KtDotQualifiedExpression) {
return dotQualifiedExpression.receiverExpression
}
return null
}
fun KtExpression.dotReceiverExpression(): KtExpression? {
return if (this is KtDotQualifiedExpression) receiverExpression else null
}
/**
* Find:
* - explicit receiver: `a` for `a.foo()`
* - implicit labeled receiver: `this@run` in `a.run { foo() }`
*
* @receiver identifier reference in a call. e.g. `foo` in `a.foo()` and `foo()`
*/
fun KtExpression.receiverExpression(psiFactory: KtPsiFactory): ReceiverExpression? {
val dotQualifiedExpr = parent
if (dotQualifiedExpr is KtDotQualifiedExpression) {
return ReceiverExpression.Explicit(dotQualifiedExpr.receiverExpression)
} else {
val context = analyze()
val scope = getResolutionScope(context) ?: return null
val descriptor = getResolvedCall(context)?.resultingDescriptor ?: return null
val receiverDescriptor = descriptor.extensionReceiverParameter
?: descriptor.dispatchReceiverParameter
?: return null
val expressionFactory = scope.getFactoryForImplicitReceiverWithSubtypeOf(receiverDescriptor.type) ?: return null
val receiverText = if (expressionFactory.isImmediate) "this" else expressionFactory.expressionText
return ReceiverExpression.Implicit(lazy { expressionFactory.createExpression(psiFactory, true) }, receiverText)
}
}
fun KtExpression.implicitExpressionText(): String? {
val dotQualifiedExpr = parent
if (dotQualifiedExpr is KtDotQualifiedExpression) {
return null
} else {
val context = analyze()
val scope = getResolutionScope(context) ?: return null
val descriptor = getResolvedCall(context)?.resultingDescriptor ?: return null
val receiverDescriptor = descriptor.extensionReceiverParameter
?: descriptor.dispatchReceiverParameter
?: return null
val expressionFactory = scope.getFactoryForImplicitReceiverWithSubtypeOf(receiverDescriptor.type) ?: return null
return if (expressionFactory.isImmediate) "this" else expressionFactory.expressionText
}
}

View File

@ -9,23 +9,24 @@
package net.mamoe.mirai.console.intellij.resolve
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiDeclarationStatement
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiModifierListOwner
import com.intellij.openapi.project.Project
import com.intellij.psi.*
import com.intellij.psi.util.parentsWithSelf
import net.mamoe.mirai.console.compiler.common.castOrNull
import net.mamoe.mirai.console.compiler.common.resolve.*
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.refactoring.fqName.fqName
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.nj2k.postProcessing.resolve
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.referenceExpression
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getCall
import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny
@ -36,6 +37,7 @@ import org.jetbrains.kotlin.resolve.constants.ConstantValue
import org.jetbrains.kotlin.resolve.constants.StringValue
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeProjection
import org.jetbrains.kotlin.types.typeUtil.supertypes
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
@ -203,14 +205,23 @@ val PsiElement.allChildrenFlat: Sequence<PsiElement>
inline fun <reified E> PsiElement.findChild(): E? = this.children.find { it is E } as E?
fun KtElement?.getResolvedCall(
context: BindingContext,
fun KtValueArgument.type() = getArgumentExpression()?.referenceExpression()?.type()
fun KtExpression.resultingDescriptor() = resolveToCall(BodyResolveMode.PARTIAL)?.resultingDescriptor
fun KtExpression.type() = resultingDescriptor()?.returnType
fun KtReferenceExpression.typeFqName() = type()?.fqName
fun KtExpression.typeFqName() = referenceExpression()?.typeFqName()
fun KtElement.getResolvedCall(
context: BindingContext = analyze(BodyResolveMode.PARTIAL),
): ResolvedCall<out CallableDescriptor>? {
return this?.getCall(context)?.getResolvedCall(context)
return this.getCall(context)?.getResolvedCall(context)
}
val ResolvedCall<out CallableDescriptor>.valueParameters: List<ValueParameterDescriptor> get() = this.resultingDescriptor.valueParameters
val Project.psiElementFactory: PsiElementFactory?
get() = PsiElementFactory.getInstance(this)
fun ConstantValue<*>.selfOrChildrenConstantStrings(): Sequence<String> {
return when (this) {
is StringValue -> sequenceOf(value)
@ -221,6 +232,17 @@ fun ConstantValue<*>.selfOrChildrenConstantStrings(): Sequence<String> {
}
}
fun ClassDescriptor.findMemberFunction(name: Name, vararg typeProjection: TypeProjection): SimpleFunctionDescriptor? {
return getMemberScope(typeProjection.toList()).getContributedFunctions(name, NoLookupLocation.FROM_IDE).firstOrNull()
}
fun DeclarationDescriptor.companionObjectDescriptor(): ClassDescriptor? {
if (this !is ClassDescriptor) {
return null
}
return this.companionObjectDescriptor
}
fun KtExpression.resolveStringConstantValues(): Sequence<String> {
when (this) {
is KtNameReferenceExpression -> {