mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-11 02:50:15 +08:00
Resource not closed inspection (#286)
* Initial support of ResourceNotClosedInspection, #283 * Improve UsingStringPlusMessageInspection * Add fix for sendAsImageTo and uploadAsImage * Add Fix for ResourceNotClosedInspection * ResourceNotClosedInspection support for Java * Add WrapWithResourceUseCallIntention for Kotlin, #284
This commit is contained in:
parent
5cfd9d7aa9
commit
fada587297
@ -44,18 +44,34 @@
|
|||||||
groupKey="group.names.plugin.service.issues" enabledByDefault="true" level="WARNING"
|
groupKey="group.names.plugin.service.issues" enabledByDefault="true" level="WARNING"
|
||||||
implementationClass="net.mamoe.mirai.console.intellij.diagnostics.PluginMainServiceNotConfiguredInspection"/>
|
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"
|
<localInspection groupPath="Mirai console" language="kotlin" shortName="UsingStringPlusMessage"
|
||||||
bundle="messages.InspectionGadgetsBundle"
|
bundle="messages.InspectionGadgetsBundle"
|
||||||
key="using.string.plus.message.display.name" groupBundle="messages.InspectionsBundle"
|
key="using.string.plus.message.display.name" groupBundle="messages.InspectionsBundle"
|
||||||
groupKey="group.names.message.issues" enabledByDefault="true" level="WARNING"
|
groupKey="group.names.message.issues" enabledByDefault="true" level="WARNING"
|
||||||
implementationClass="net.mamoe.mirai.console.intellij.diagnostics.UsingStringPlusMessageInspection"/>
|
implementationClass="net.mamoe.mirai.console.intellij.diagnostics.UsingStringPlusMessageInspection"/>
|
||||||
|
|
||||||
<!--
|
|
||||||
<intentionAction>
|
<intentionAction>
|
||||||
<className>net.mamoe.mirai.console.intellij.diagnostics.fix.AbuseYellowIntention</className>
|
<className>net.mamoe.mirai.console.intellij.diagnostics.fix.WrapWithResourceUseCallIntention</className>
|
||||||
<category>Mirai</category>
|
<category>Mirai console</category>
|
||||||
</intentionAction>
|
</intentionAction>
|
||||||
-->
|
|
||||||
|
<intentionAction>
|
||||||
|
<className>net.mamoe.mirai.console.intellij.diagnostics.fix.WrapWithResourceUseCallJavaIntention</className>
|
||||||
|
<category>Mirai console</category>
|
||||||
|
</intentionAction>
|
||||||
|
|
||||||
</extensions>
|
</extensions>
|
||||||
|
|
||||||
<extensions defaultExtensionNs="org.jetbrains.kotlin">
|
<extensions defaultExtensionNs="org.jetbrains.kotlin">
|
||||||
|
@ -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>
|
@ -0,0 +1 @@
|
|||||||
|
resource.use { it.uploadAsImage(contact) }
|
@ -0,0 +1 @@
|
|||||||
|
resource.uploadAsImage(contact)
|
@ -0,0 +1,8 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<p>将资源直接使用转换为 `.use { ... }`
|
||||||
|
</p>
|
||||||
|
<!-- tooltip end -->
|
||||||
|
<p>将 `resource.sendAsImageTo(contact)` 转换为 `resource.use { it.sendAsImageTo(contact) }` </p>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,4 @@
|
|||||||
|
ExternalResource resource;
|
||||||
|
Contact contact;
|
||||||
|
|
||||||
|
resource.use { it.uploadAsImage(contact) }
|
@ -0,0 +1,4 @@
|
|||||||
|
ExternalResource resource;
|
||||||
|
Contact contact;
|
||||||
|
|
||||||
|
resource.uploadAsImage(contact)
|
@ -0,0 +1,8 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<p>将资源直接使用转换为 `.use { ... }`
|
||||||
|
</p>
|
||||||
|
<!-- tooltip end -->
|
||||||
|
<p>将 `resource.sendAsImageTo(contact)` 转换为 `resource.use { it.sendAsImageTo(contact) }` </p>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -8,3 +8,4 @@
|
|||||||
#
|
#
|
||||||
plugin.service.not.configured.display.name=Plugin main not configured
|
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
|
@ -8,3 +8,4 @@
|
|||||||
#
|
#
|
||||||
group.names.plugin.service.issues=Plugin service issues
|
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
|
@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "1.4.20"
|
kotlin("jvm") version "1.4.20"
|
||||||
kotlin("plugin.serialization") 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
|
java
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -9,7 +9,6 @@ group = "org.example"
|
|||||||
version = "1.0-SNAPSHOT"
|
version = "1.0-SNAPSHOT"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
|
||||||
jcenter()
|
jcenter()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
@ -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) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -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!!
|
@ -16,5 +16,16 @@ fun main() {
|
|||||||
val x: String = ""
|
val x: String = ""
|
||||||
val plain: PlainText = PlainText("")
|
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)
|
||||||
}
|
}
|
@ -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) }
|
||||||
|
}
|
||||||
|
}
|
37
tools/intellij-plugin/src/diagnostics/QuickFixUtils.kt
Normal file
37
tools/intellij-plugin/src/diagnostics/QuickFixUtils.kt
Normal 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,
|
||||||
|
)
|
@ -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"
|
@ -11,19 +11,24 @@ package net.mamoe.mirai.console.intellij.diagnostics
|
|||||||
|
|
||||||
import com.intellij.codeInspection.ProblemHighlightType
|
import com.intellij.codeInspection.ProblemHighlightType
|
||||||
import com.intellij.codeInspection.ProblemsHolder
|
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 com.intellij.psi.PsiElementVisitor
|
||||||
import net.mamoe.mirai.console.compiler.common.castOrNull
|
import com.intellij.psi.PsiFile
|
||||||
import net.mamoe.mirai.console.intellij.diagnostics.fix.ConvertToPlainTextFix
|
import net.mamoe.mirai.console.intellij.resolve.*
|
||||||
import net.mamoe.mirai.console.intellij.resolve.findChild
|
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
||||||
import net.mamoe.mirai.console.intellij.resolve.hasSuperType
|
|
||||||
import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
|
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.resolve
|
||||||
import org.jetbrains.kotlin.nj2k.postProcessing.type
|
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForReceiver
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.referenceExpression
|
import org.jetbrains.kotlin.psi.psiUtil.referenceExpression
|
||||||
import java.util.*
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny
|
||||||
|
|
||||||
/*
|
/*
|
||||||
private val bundle by lazy {
|
private val bundle by lazy {
|
||||||
@ -31,44 +36,119 @@ private val bundle by lazy {
|
|||||||
}*/
|
}*/
|
||||||
|
|
||||||
class UsingStringPlusMessageInspection : AbstractKotlinInspection() {
|
class UsingStringPlusMessageInspection : AbstractKotlinInspection() {
|
||||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
companion object {
|
||||||
return referenceExpressionVisitor visitor@{ expression ->
|
const val DESCRIPTION = "使用 String + Message 会导致 Message 被转换为 String 再相加"
|
||||||
val originalCallee = expression.resolve() ?: return@visitor
|
private const val MESSAGE_FQ_NAME_STR = "net.mamoe.mirai.message.data.Message"
|
||||||
if (originalCallee !is KtNamedFunction) return@visitor
|
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()
|
val className = callee.containingClassOrObject?.fqName?.asString()
|
||||||
if (className != "kotlin.String") return@visitor
|
if (className != "kotlin.String") return false
|
||||||
if (callee.name != "plus") return@visitor
|
if (callee.name != "plus") return false
|
||||||
|
return true
|
||||||
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)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
}
|
}
|
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.console.intellij.diagnostics
|
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.castOrNull
|
||||||
import net.mamoe.mirai.console.compiler.common.resolve.READ_ONLY_PLUGIN_DATA_FQ_NAME
|
import net.mamoe.mirai.console.compiler.common.resolve.READ_ONLY_PLUGIN_DATA_FQ_NAME
|
||||||
import net.mamoe.mirai.console.intellij.resolve.getResolvedCall
|
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.idea.references.mainReference
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.psi.KtElement
|
import org.jetbrains.kotlin.psi.KtElement
|
||||||
|
import org.jetbrains.kotlin.psi.KtTypeParameter
|
||||||
import org.jetbrains.kotlin.psi.KtTypeReference
|
import org.jetbrains.kotlin.psi.KtTypeReference
|
||||||
import org.jetbrains.kotlin.psi.KtUserType
|
import org.jetbrains.kotlin.psi.KtUserType
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||||
@ -37,7 +39,7 @@ fun DeclarationCheckerContext.report(diagnostic: Diagnostic) {
|
|||||||
|
|
||||||
val DeclarationCheckerContext.bindingContext get() = this.trace.bindingContext
|
val DeclarationCheckerContext.bindingContext get() = this.trace.bindingContext
|
||||||
|
|
||||||
fun KtElement?.getResolvedCall(
|
fun KtElement.getResolvedCall(
|
||||||
context: DeclarationCheckerContext,
|
context: DeclarationCheckerContext,
|
||||||
): ResolvedCall<out CallableDescriptor>? {
|
): ResolvedCall<out CallableDescriptor>? {
|
||||||
return this.getResolvedCall(context.bindingContext)
|
return this.getResolvedCall(context.bindingContext)
|
||||||
@ -49,4 +51,12 @@ fun KtTypeReference.isReferencing(fqName: FqName): Boolean {
|
|||||||
|
|
||||||
val KtTypeReference.referencedUserType: KtUserType? get() = this.typeElement.castOrNull()
|
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
|
||||||
|
}
|
@ -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)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
|
}
|
153
tools/intellij-plugin/src/resolve/FunctionSignature.kt
Normal file
153
tools/intellij-plugin/src/resolve/FunctionSignature.kt
Normal 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
|
||||||
|
}
|
96
tools/intellij-plugin/src/resolve/ReceiverExpression.kt
Normal file
96
tools/intellij-plugin/src/resolve/ReceiverExpression.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -9,23 +9,24 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.console.intellij.resolve
|
package net.mamoe.mirai.console.intellij.resolve
|
||||||
|
|
||||||
import com.intellij.psi.PsiClass
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.psi.PsiDeclarationStatement
|
import com.intellij.psi.*
|
||||||
import com.intellij.psi.PsiElement
|
|
||||||
import com.intellij.psi.PsiModifierListOwner
|
|
||||||
import com.intellij.psi.util.parentsWithSelf
|
import com.intellij.psi.util.parentsWithSelf
|
||||||
import net.mamoe.mirai.console.compiler.common.castOrNull
|
import net.mamoe.mirai.console.compiler.common.castOrNull
|
||||||
import net.mamoe.mirai.console.compiler.common.resolve.*
|
import net.mamoe.mirai.console.compiler.common.resolve.*
|
||||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
import org.jetbrains.kotlin.descriptors.VariableDescriptor
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||||
import org.jetbrains.kotlin.idea.refactoring.fqName.fqName
|
import org.jetbrains.kotlin.idea.refactoring.fqName.fqName
|
||||||
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
|
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
|
||||||
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
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.FqName
|
||||||
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.nj2k.postProcessing.resolve
|
import org.jetbrains.kotlin.nj2k.postProcessing.resolve
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.referenceExpression
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getCall
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getCall
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny
|
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.constants.StringValue
|
||||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
|
import org.jetbrains.kotlin.types.TypeProjection
|
||||||
import org.jetbrains.kotlin.types.typeUtil.supertypes
|
import org.jetbrains.kotlin.types.typeUtil.supertypes
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
|
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?
|
inline fun <reified E> PsiElement.findChild(): E? = this.children.find { it is E } as E?
|
||||||
|
|
||||||
fun KtElement?.getResolvedCall(
|
fun KtValueArgument.type() = getArgumentExpression()?.referenceExpression()?.type()
|
||||||
context: BindingContext,
|
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>? {
|
): 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 ResolvedCall<out CallableDescriptor>.valueParameters: List<ValueParameterDescriptor> get() = this.resultingDescriptor.valueParameters
|
||||||
|
|
||||||
|
val Project.psiElementFactory: PsiElementFactory?
|
||||||
|
get() = PsiElementFactory.getInstance(this)
|
||||||
|
|
||||||
fun ConstantValue<*>.selfOrChildrenConstantStrings(): Sequence<String> {
|
fun ConstantValue<*>.selfOrChildrenConstantStrings(): Sequence<String> {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
is StringValue -> sequenceOf(value)
|
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> {
|
fun KtExpression.resolveStringConstantValues(): Sequence<String> {
|
||||||
when (this) {
|
when (this) {
|
||||||
is KtNameReferenceExpression -> {
|
is KtNameReferenceExpression -> {
|
||||||
|
Loading…
Reference in New Issue
Block a user