mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-11 02:50:15 +08:00
Diagnostics and resolve utilities
This commit is contained in:
parent
d97e79d6a4
commit
3fc2d7f5c1
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
object Versions {
|
object Versions {
|
||||||
const val core = "1.3.0"
|
const val core = "1.3.0"
|
||||||
const val console = "1.0-M4"
|
const val console = "1.0-RC-dev-1"
|
||||||
const val consoleGraphical = "0.0.7"
|
const val consoleGraphical = "0.0.7"
|
||||||
const val consoleTerminal = "0.1.0"
|
const val consoleTerminal = "0.1.0"
|
||||||
const val consolePure = console
|
const val consolePure = console
|
||||||
|
@ -9,7 +9,10 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.console.compiler.common.resolve
|
package net.mamoe.mirai.console.compiler.common.resolve
|
||||||
|
|
||||||
|
import net.mamoe.mirai.console.compiler.common.castOrNull
|
||||||
|
import org.jetbrains.kotlin.descriptors.annotations.Annotated
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.resolve.constants.EnumValue
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
// Command
|
// Command
|
||||||
@ -25,3 +28,33 @@ val SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME = FqName("net.mamoe.mirai.console.com
|
|||||||
val PLUGIN_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.Plugin")
|
val PLUGIN_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.Plugin")
|
||||||
val JVM_PLUGIN_DESCRIPTION_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription")
|
val JVM_PLUGIN_DESCRIPTION_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription")
|
||||||
val SIMPLE_JVM_PLUGIN_DESCRIPTION_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription")
|
val SIMPLE_JVM_PLUGIN_DESCRIPTION_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription")
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Resolve
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
val RESOLVE_CONTEXT_FQ_NAME = FqName("net.mamoe.mirai.console.compiler.common.ResolveContext")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* net.mamoe.mirai.console.compiler.common.ResolveContext.Kind
|
||||||
|
*/
|
||||||
|
enum class ResolveContextKind {
|
||||||
|
PLUGIN_ID,
|
||||||
|
PLUGIN_NAME,
|
||||||
|
PLUGIN_VERSION,
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun valueOfOrNull(string: String): ResolveContextKind? = ResolveContextKind.values().find { it.name == string }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Annotated.isResolveContext(kind: ResolveContextKind) = this.resolveContextKind == kind
|
||||||
|
|
||||||
|
val Annotated.resolveContextKind: ResolveContextKind?
|
||||||
|
get() {
|
||||||
|
val ann = this.findAnnotation(RESOLVE_CONTEXT_FQ_NAME) ?: return null
|
||||||
|
val (_, enumEntryName) = ann.allValueArguments.castOrNull<EnumValue>()?.value ?: return null // undetermined kind
|
||||||
|
return ResolveContextKind.valueOf(enumEntryName.asString())
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* 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.compiler.common.resolve
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.descriptors.annotations.Annotated
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
|
||||||
|
fun Annotated.hasAnnotation(fqName: FqName) = this.annotations.hasAnnotation(fqName)
|
||||||
|
fun Annotated.findAnnotation(fqName: FqName) = this.annotations.findAnnotation(fqName)
|
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* 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.compiler.common
|
||||||
|
|
||||||
|
import kotlin.contracts.contract
|
||||||
|
|
||||||
|
|
||||||
|
fun <K, V> Map<K, V>.firstValue(): V = this.entries.first().value
|
||||||
|
fun <K, V> Map<K, V>.firstKey(): K = this.entries.first().key
|
||||||
|
|
||||||
|
|
||||||
|
inline fun <reified T : Any> Any?.castOrNull(): T? {
|
||||||
|
contract {
|
||||||
|
returnsNotNull() implies (this@castOrNull is T)
|
||||||
|
}
|
||||||
|
return this as? T
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : Any> Any?.cast(): T {
|
||||||
|
contract {
|
||||||
|
returns() implies (this@cast is T)
|
||||||
|
}
|
||||||
|
return this as T
|
||||||
|
}
|
@ -21,8 +21,8 @@ kotlin.sourceSets.all {
|
|||||||
dependencies {
|
dependencies {
|
||||||
compileOnly(kotlin("stdlib-jdk8"))
|
compileOnly(kotlin("stdlib-jdk8"))
|
||||||
|
|
||||||
val core = "1.2.3"
|
val core = "1.3.0"
|
||||||
val console = "1.0-M4"
|
val console = "1.0-RC-dev-1"
|
||||||
|
|
||||||
compileOnly("net.mamoe:mirai-console:$console")
|
compileOnly("net.mamoe:mirai-console:$console")
|
||||||
compileOnly("net.mamoe:mirai-core:$core")
|
compileOnly("net.mamoe:mirai-core:$core")
|
||||||
|
@ -1,163 +1,19 @@
|
|||||||
@file:Suppress("unused")
|
|
||||||
|
|
||||||
package org.example.myplugin
|
package org.example.myplugin
|
||||||
|
|
||||||
import com.google.auto.service.AutoService
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import net.mamoe.mirai.Bot
|
|
||||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
|
|
||||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregister
|
|
||||||
import net.mamoe.mirai.console.command.CommandSender
|
|
||||||
import net.mamoe.mirai.console.command.CompositeCommand
|
|
||||||
import net.mamoe.mirai.console.command.ConsoleCommandSender
|
|
||||||
import net.mamoe.mirai.console.command.SimpleCommand
|
|
||||||
import net.mamoe.mirai.console.data.AutoSavePluginConfig
|
|
||||||
import net.mamoe.mirai.console.data.AutoSavePluginData
|
|
||||||
import net.mamoe.mirai.console.data.PluginDataExtensions.mapKeys
|
|
||||||
import net.mamoe.mirai.console.data.PluginDataExtensions.withEmptyDefault
|
|
||||||
import net.mamoe.mirai.console.data.value
|
|
||||||
import net.mamoe.mirai.console.permission.PermissionService
|
|
||||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.hasPermission
|
|
||||||
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
|
||||||
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
|
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
|
||||||
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
|
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
|
||||||
import net.mamoe.mirai.console.util.scopeWith
|
|
||||||
import net.mamoe.mirai.contact.Member
|
|
||||||
import net.mamoe.mirai.message.data.Image
|
|
||||||
import net.mamoe.mirai.utils.info
|
|
||||||
|
|
||||||
@AutoService(JvmPlugin::class)
|
|
||||||
object MyPluginMain : KotlinPlugin(
|
object MyPluginMain : KotlinPlugin(
|
||||||
JvmPluginDescription(
|
JvmPluginDescription(
|
||||||
"org.example.example-plugin",
|
"org.example.example-plugin",
|
||||||
"0.1.0"
|
"0.1.0"
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
fun test() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PM : KotlinPlugin(
|
||||||
|
|
||||||
val PERMISSION_EXECUTE_1 = PermissionService.INSTANCE.register(
|
|
||||||
permissionId("execute1"),
|
|
||||||
"注册权限的示例"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
override fun onEnable() {
|
|
||||||
MySetting.reload() // 从数据库自动读取配置实例
|
|
||||||
MyPluginData.reload()
|
|
||||||
|
|
||||||
logger.info { "Hi: ${MySetting.name}" } // 输出一条日志.
|
|
||||||
logger.info("Hi: ${MySetting.name}") // 输出一条日志. 与上面一条相同, 但更推荐上面一条.
|
|
||||||
logger.verbose("Hi: ${MySetting.name}") // 多种日志级别可选
|
|
||||||
|
|
||||||
// 请不要使用 println, System.out.println 等标准输出方式. 请总是使用 logger.
|
|
||||||
|
|
||||||
|
|
||||||
MySetting.count++ // 对 Setting 的改动会自动在合适的时间保存
|
|
||||||
|
|
||||||
MySimpleCommand.register() // 注册指令
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDisable() {
|
|
||||||
MySimpleCommand.unregister() // 取消注册指令
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 定义插件数据
|
|
||||||
// 插件
|
|
||||||
object MyPluginData : AutoSavePluginData() {
|
|
||||||
var list: MutableList<String> by value(mutableListOf("a", "b")) // mutableListOf("a", "b") 是初始值, 可以省略
|
|
||||||
var long: Long by value(0L) // 允许 var
|
|
||||||
var int by value(0) // 可以使用类型推断, 但更推荐使用 `var long: Long by value(0)` 这种定义方式.
|
|
||||||
|
|
||||||
|
|
||||||
// 带默认值的非空 map.
|
|
||||||
// notnullMap[1] 的返回值总是非 null 的 MutableMap<Int, String>
|
|
||||||
var notnullMap
|
|
||||||
by value<MutableMap<Int, MutableMap<Int, String>>>().withEmptyDefault()
|
|
||||||
|
|
||||||
// 可将 MutableMap<Long, Long> 映射到 MutableMap<Bot, Long>.
|
|
||||||
val botToLongMap: MutableMap<Bot, Long> by value<MutableMap<Long, Long>>().mapKeys(Bot::getInstance, Bot::id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 定义一个配置. 所有属性都会被追踪修改, 并自动保存.
|
|
||||||
// 配置是插件与用户交互的接口, 但不能用来保存插件的数据.
|
|
||||||
object MySetting : AutoSavePluginConfig() {
|
|
||||||
val name by value("test")
|
|
||||||
|
|
||||||
var count by value(0)
|
|
||||||
|
|
||||||
val nested by value<MyNestedData>() // 嵌套类型是支持的
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class MyNestedData(
|
|
||||||
val list: List<String> = listOf(),
|
|
||||||
)
|
|
||||||
|
|
||||||
// 简单指令
|
|
||||||
object MySimpleCommand : SimpleCommand(
|
|
||||||
MyPluginMain, "foo",
|
|
||||||
description = "示例指令"
|
|
||||||
) {
|
|
||||||
// 会自动创建一个 ID 为 "org.example.example-plugin:command.foo" 的权限.
|
|
||||||
|
|
||||||
|
|
||||||
// 通过 /foo 调用, 参数自动解析
|
|
||||||
@Handler
|
|
||||||
suspend fun CommandSender.handle(int: Int, str: String) { // 函数名随意, 但参数需要按顺序放置.
|
|
||||||
|
|
||||||
if (this.hasPermission(MyPluginMain.PERMISSION_EXECUTE_1)) {
|
|
||||||
sendMessage("你有 ${MyPluginMain.PERMISSION_EXECUTE_1.id} 权限.")
|
|
||||||
} else {
|
|
||||||
sendMessage(
|
|
||||||
"""
|
|
||||||
你没有 ${MyPluginMain.PERMISSION_EXECUTE_1.id} 权限.
|
|
||||||
可以在控制台使用 /permission 管理权限.
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
sendMessage("/foo 的第一个参数是 $int, 第二个是 $str")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 复合指令
|
|
||||||
object MyCompositeCommand : CompositeCommand(
|
|
||||||
MyPluginMain, "manage",
|
|
||||||
description = "示例指令",
|
|
||||||
// prefixOptional = true // 还有更多参数可填, 此处忽略
|
|
||||||
) {
|
|
||||||
// 会自动创建一个 ID 为 "org.example.example-plugin:command.manage" 的权限.
|
|
||||||
|
|
||||||
//
|
|
||||||
// 在控制台执行 "/manage <群号>.<群员> <持续时间>",
|
|
||||||
// 或在聊天群内发送 "/manage <@一个群员> <持续时间>",
|
|
||||||
// 或在聊天群内发送 "/manage <目标群员的群名> <持续时间>",
|
|
||||||
// 或在聊天群内发送 "/manage <目标群员的账号> <持续时间>"
|
|
||||||
@SubCommand
|
|
||||||
suspend fun CommandSender.mute(target: Member, duration: Int) { // 通过 /manage mute <target> <duration> 调用
|
|
||||||
sendMessage("/manage mute 被调用了, 参数为: $target, $duration")
|
|
||||||
|
|
||||||
val result = kotlin.runCatching {
|
|
||||||
target.mute(duration).toString()
|
|
||||||
}.getOrElse {
|
|
||||||
it.stackTraceToString()
|
|
||||||
} // 失败时返回堆栈信息
|
|
||||||
|
|
||||||
|
|
||||||
// 表示对 this 和 ConsoleCommandSender 一起操作
|
|
||||||
this.scopeWith(ConsoleCommandSender) {
|
|
||||||
sendMessage("结果: $result") // 同时发送给 this@CommandSender 和 ConsoleCommandSender
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubCommand
|
|
||||||
suspend fun CommandSender.list() { // 执行 "/manage list" 时调用这个函数
|
|
||||||
sendMessage("/manage list 被调用了")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 支持 Image 类型, 需在聊天中执行此指令.
|
|
||||||
@SubCommand
|
|
||||||
suspend fun CommandSender.test(image: Image) { // 执行 "/manage test <一张图片>" 时调用这个函数
|
|
||||||
sendMessage("/manage image 被调用了, 图片是 ${image.imageId}")
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,10 +9,21 @@
|
|||||||
|
|
||||||
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.resolve.ResolveContextKind
|
||||||
|
import net.mamoe.mirai.console.compiler.common.resolve.resolveContextKind
|
||||||
|
import net.mamoe.mirai.console.intellij.resolve.findChildren
|
||||||
|
import net.mamoe.mirai.console.intellij.resolve.resolveStringConstantValue
|
||||||
|
import net.mamoe.mirai.console.intellij.resolve.valueParameters
|
||||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
|
||||||
|
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
|
||||||
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
|
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
|
||||||
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
|
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.contracts.contract
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks:
|
* Checks:
|
||||||
@ -20,11 +31,95 @@ import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
|
|||||||
* - plugin name
|
* - plugin name
|
||||||
*/
|
*/
|
||||||
class PluginDescriptionChecker : DeclarationChecker {
|
class PluginDescriptionChecker : DeclarationChecker {
|
||||||
|
companion object {
|
||||||
|
fun checkPluginName(declaration: KtDeclaration, value: String): Diagnostic? {
|
||||||
|
return null // TODO: 2020/9/18 checkPluginName
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkPluginId(declaration: KtDeclaration, value: String): Diagnostic? {
|
||||||
|
return null // TODO: 2020/9/18 checkPluginId
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkPluginVersion(declaration: KtDeclaration, value: String): Diagnostic? {
|
||||||
|
return null // TODO: 2020/9/18 checkPluginVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun PsiElement.shouldPerformCheck(): Boolean {
|
||||||
|
contract {
|
||||||
|
returns(true) implies (this@shouldPerformCheck is KtCallExpression)
|
||||||
|
}
|
||||||
|
return when (this) {
|
||||||
|
is KtCallExpression,
|
||||||
|
-> true
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val checkersMap: EnumMap<ResolveContextKind, (declaration: KtDeclaration, value: String) -> Diagnostic?> =
|
||||||
|
EnumMap<ResolveContextKind, (declaration: KtDeclaration, value: String) -> Diagnostic?>(ResolveContextKind::class.java).apply {
|
||||||
|
put(ResolveContextKind.PLUGIN_NAME, ::checkPluginName)
|
||||||
|
put(ResolveContextKind.PLUGIN_ID, ::checkPluginId)
|
||||||
|
put(ResolveContextKind.PLUGIN_VERSION, ::checkPluginVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun check(
|
||||||
|
declaration: KtDeclaration,
|
||||||
|
expression: KtCallExpression,
|
||||||
|
context: DeclarationCheckerContext,
|
||||||
|
) {
|
||||||
|
val call = expression.calleeExpression.getResolvedCallOrResolveToCall(context) ?: return // unresolved
|
||||||
|
call.valueArgumentsByIndex?.forEach { resolvedValueArgument ->
|
||||||
|
for ((parameter, argument) in call.valueParameters.zip(resolvedValueArgument.arguments)) {
|
||||||
|
val parameterContextKind = parameter.resolveContextKind
|
||||||
|
if (checkersMap.containsKey(parameterContextKind)) {
|
||||||
|
val value = argument.getArgumentExpression()
|
||||||
|
?.resolveStringConstantValue(context.bindingContext) ?: continue
|
||||||
|
for ((kind, fn) in checkersMap) {
|
||||||
|
if (parameterContextKind == kind) fn(declaration, value)?.let { context.report(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun check(
|
override fun check(
|
||||||
declaration: KtDeclaration,
|
declaration: KtDeclaration,
|
||||||
descriptor: DeclarationDescriptor,
|
descriptor: DeclarationDescriptor,
|
||||||
context: DeclarationCheckerContext,
|
context: DeclarationCheckerContext,
|
||||||
) {
|
) {
|
||||||
|
println("${declaration::class.qualifiedName} $declaration")
|
||||||
|
when (declaration) {
|
||||||
|
is KtObjectDeclaration -> {
|
||||||
|
// check super type constructor
|
||||||
|
val superTypeCallEntry = declaration.findChildren<KtSuperTypeList>()?.findChildren<KtSuperTypeCallEntry>() ?: return
|
||||||
|
val constructorCall = superTypeCallEntry.findChildren<KtConstructorCalleeExpression>()?.resolveToCall() ?: return
|
||||||
|
val valueArgumentList = superTypeCallEntry.findChildren<KtValueArgumentList>() ?: return
|
||||||
|
valueArgumentList.arguments.asSequence().mapNotNull(KtValueArgument::getArgumentExpression).forEach {
|
||||||
|
if (it.shouldPerformCheck()) {
|
||||||
|
check(declaration, it as KtCallExpression, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
is KtClassOrObject -> {
|
||||||
|
// check constructor
|
||||||
|
|
||||||
|
val superTypeCallEntry = declaration.findChildren<KtSuperTypeList>()?.findChildren<KtSuperTypeCallEntry>() ?: return
|
||||||
|
|
||||||
|
val constructorCall = superTypeCallEntry.findChildren<KtConstructorCalleeExpression>()?.resolveToCall() ?: return
|
||||||
|
val valueArgumentList = superTypeCallEntry.findChildren<KtValueArgumentList>() ?: return
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
declaration.children.filter { it.shouldPerformCheck() }.forEach { element ->
|
||||||
|
if (element is KtDeclaration) {
|
||||||
|
val desc = element.descriptor ?: return@forEach
|
||||||
|
check(element, desc, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* 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 net.mamoe.mirai.console.intellij.resolve.getResolvedCallOrResolveToCall
|
||||||
|
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||||
|
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||||
|
import org.jetbrains.kotlin.psi.KtElement
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||||
|
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
|
||||||
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
|
|
||||||
|
fun DeclarationCheckerContext.report(diagnostic: Diagnostic) {
|
||||||
|
return this.trace.report(diagnostic)
|
||||||
|
}
|
||||||
|
|
||||||
|
val DeclarationCheckerContext.bindingContext get() = this.trace.bindingContext
|
||||||
|
|
||||||
|
fun KtElement?.getResolvedCallOrResolveToCall(
|
||||||
|
context: DeclarationCheckerContext,
|
||||||
|
bodyResolveMode: BodyResolveMode = BodyResolveMode.PARTIAL,
|
||||||
|
): ResolvedCall<out CallableDescriptor>? {
|
||||||
|
return this.getResolvedCallOrResolveToCall(context.bindingContext, bodyResolveMode)
|
||||||
|
}
|
@ -16,6 +16,7 @@ import com.intellij.openapi.actionSystem.AnAction
|
|||||||
import com.intellij.openapi.editor.markup.GutterIconRenderer
|
import com.intellij.openapi.editor.markup.GutterIconRenderer
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import net.mamoe.mirai.console.intellij.Icons
|
import net.mamoe.mirai.console.intellij.Icons
|
||||||
|
import net.mamoe.mirai.console.intellij.resolve.getElementForLineMark
|
||||||
import net.mamoe.mirai.console.intellij.resolve.isSimpleCommandHandlerOrCompositeCommandSubCommand
|
import net.mamoe.mirai.console.intellij.resolve.isSimpleCommandHandlerOrCompositeCommandSubCommand
|
||||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||||
|
|
||||||
|
@ -18,6 +18,9 @@ import com.intellij.psi.PsiElement
|
|||||||
import com.intellij.util.castSafelyTo
|
import com.intellij.util.castSafelyTo
|
||||||
import net.mamoe.mirai.console.compiler.common.resolve.PLUGIN_FQ_NAME
|
import net.mamoe.mirai.console.compiler.common.resolve.PLUGIN_FQ_NAME
|
||||||
import net.mamoe.mirai.console.intellij.Icons
|
import net.mamoe.mirai.console.intellij.Icons
|
||||||
|
import net.mamoe.mirai.console.intellij.resolve.allSuperNames
|
||||||
|
import net.mamoe.mirai.console.intellij.resolve.getElementForLineMark
|
||||||
|
import net.mamoe.mirai.console.intellij.resolve.parents
|
||||||
import org.jetbrains.kotlin.nj2k.postProcessing.resolve
|
import org.jetbrains.kotlin.nj2k.postProcessing.resolve
|
||||||
import org.jetbrains.kotlin.psi.KtClass
|
import org.jetbrains.kotlin.psi.KtClass
|
||||||
import org.jetbrains.kotlin.psi.KtConstructor
|
import org.jetbrains.kotlin.psi.KtConstructor
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.line.marker
|
|
||||||
|
|
||||||
import com.intellij.psi.PsiElement
|
|
||||||
import com.intellij.psi.PsiFile
|
|
||||||
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
|
|
||||||
import org.jetbrains.kotlin.name.FqName
|
|
||||||
import org.jetbrains.kotlin.nj2k.postProcessing.resolve
|
|
||||||
import org.jetbrains.kotlin.psi.*
|
|
||||||
|
|
||||||
|
|
||||||
internal val KtPureClassOrObject.allSuperTypes: Sequence<KtSuperTypeListEntry>
|
|
||||||
get() = sequence {
|
|
||||||
yieldAll(superTypeListEntries)
|
|
||||||
for (list in superTypeListEntries.asSequence()) {
|
|
||||||
yieldAll((list.typeAsUserType?.referenceExpression?.resolve() as? KtClass)?.allSuperTypes.orEmpty())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal inline fun <reified E> PsiElement.findParent(): E? = this.parents.filterIsInstance<E>().firstOrNull()
|
|
||||||
|
|
||||||
internal val KtClassOrObject.allSuperNames: Sequence<FqName> get() = allSuperTypes.mapNotNull { it.getKotlinFqName() }
|
|
||||||
|
|
||||||
val PsiElement.parents: Sequence<PsiElement>
|
|
||||||
get() {
|
|
||||||
val seed = if (this is PsiFile) null else parent
|
|
||||||
return generateSequence(seed) { if (it is PsiFile) null else it.parent }
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun getElementForLineMark(callElement: PsiElement): PsiElement =
|
|
||||||
when (callElement) {
|
|
||||||
is KtSimpleNameExpression -> callElement.getReferencedNameElement()
|
|
||||||
else ->
|
|
||||||
// a fallback,
|
|
||||||
//but who knows what to reference in KtArrayAccessExpression ?
|
|
||||||
generateSequence(callElement, { it.firstChild }).last()
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val KtAnnotationEntry.annotationClass: KtClass?
|
|
||||||
get() = calleeExpression?.constructorReferenceExpression?.resolve()?.findParent<KtClass>()
|
|
||||||
|
|
||||||
internal fun KtAnnotated.hasAnnotation(fqName: FqName): Boolean =
|
|
||||||
this.annotationEntries.any { it.annotationClass?.getKotlinFqName() == fqName }
|
|
@ -9,10 +9,25 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.console.intellij.resolve
|
package net.mamoe.mirai.console.intellij.resolve
|
||||||
|
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.psi.PsiFile
|
||||||
|
import net.mamoe.mirai.console.compiler.common.castOrNull
|
||||||
import net.mamoe.mirai.console.compiler.common.resolve.COMPOSITE_COMMAND_SUB_COMMAND_FQ_NAME
|
import net.mamoe.mirai.console.compiler.common.resolve.COMPOSITE_COMMAND_SUB_COMMAND_FQ_NAME
|
||||||
import net.mamoe.mirai.console.compiler.common.resolve.SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME
|
import net.mamoe.mirai.console.compiler.common.resolve.SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME
|
||||||
import net.mamoe.mirai.console.intellij.line.marker.hasAnnotation
|
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.VariableDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.nj2k.postProcessing.resolve
|
||||||
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getCall
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||||
|
import org.jetbrains.kotlin.resolve.constants.StringValue
|
||||||
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,3 +42,91 @@ fun KtNamedFunction.isSimpleCommandHandler(): Boolean = this.hasAnnotation(SIMPL
|
|||||||
|
|
||||||
fun KtNamedFunction.isSimpleCommandHandlerOrCompositeCommandSubCommand(): Boolean =
|
fun KtNamedFunction.isSimpleCommandHandlerOrCompositeCommandSubCommand(): Boolean =
|
||||||
this.isSimpleCommandHandler() || this.isCompositeCommandSubCommand()
|
this.isSimpleCommandHandler() || this.isCompositeCommandSubCommand()
|
||||||
|
|
||||||
|
|
||||||
|
val KtPureClassOrObject.allSuperTypes: Sequence<KtSuperTypeListEntry>
|
||||||
|
get() = sequence {
|
||||||
|
yieldAll(superTypeListEntries)
|
||||||
|
for (list in superTypeListEntries.asSequence()) {
|
||||||
|
yieldAll((list.typeAsUserType?.referenceExpression?.resolve() as? KtClass)?.allSuperTypes.orEmpty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun KtConstructorCalleeExpression.getTypeAsUserType(): KtUserType? {
|
||||||
|
val reference = typeReference
|
||||||
|
if (reference != null) {
|
||||||
|
val element = reference.typeElement
|
||||||
|
if (element is KtUserType) {
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified E> PsiElement.findParent(): E? = this.parents.filterIsInstance<E>().firstOrNull()
|
||||||
|
|
||||||
|
val KtClassOrObject.allSuperNames: Sequence<FqName> get() = allSuperTypes.mapNotNull { it.getKotlinFqName() }
|
||||||
|
|
||||||
|
val PsiElement.parents: Sequence<PsiElement>
|
||||||
|
get() {
|
||||||
|
val seed = if (this is PsiFile) null else parent
|
||||||
|
return generateSequence(seed) { if (it is PsiFile) null else it.parent }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getElementForLineMark(callElement: PsiElement): PsiElement =
|
||||||
|
when (callElement) {
|
||||||
|
is KtSimpleNameExpression -> callElement.getReferencedNameElement()
|
||||||
|
else ->
|
||||||
|
// a fallback,
|
||||||
|
//but who knows what to reference in KtArrayAccessExpression ?
|
||||||
|
generateSequence(callElement, { it.firstChild }).last()
|
||||||
|
}
|
||||||
|
|
||||||
|
val KtAnnotationEntry.annotationClass: KtClass?
|
||||||
|
get() = calleeExpression?.constructorReferenceExpression?.resolve()?.findParent<KtClass>()
|
||||||
|
|
||||||
|
fun KtAnnotated.hasAnnotation(fqName: FqName): Boolean =
|
||||||
|
this.annotationEntries.any { it.annotationClass?.getKotlinFqName() == fqName }
|
||||||
|
|
||||||
|
val PsiElement.allChildrenFlat: Sequence<PsiElement>
|
||||||
|
get() {
|
||||||
|
return sequence {
|
||||||
|
for (child in children) {
|
||||||
|
yield(child)
|
||||||
|
yieldAll(child.allChildrenFlat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified E : PsiElement> PsiElement.findChildren(): E? = this.children.find { it is E } as E?
|
||||||
|
|
||||||
|
fun KtElement?.getResolvedCallOrResolveToCall(
|
||||||
|
context: BindingContext,
|
||||||
|
bodyResolveMode: BodyResolveMode = BodyResolveMode.PARTIAL,
|
||||||
|
): ResolvedCall<out CallableDescriptor>? {
|
||||||
|
return this?.getCall(context)?.getResolvedCall(context) ?: this?.resolveToCall(bodyResolveMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
val ResolvedCall<out CallableDescriptor>.valueParameters: List<ValueParameterDescriptor> get() = this.resultingDescriptor.valueParameters
|
||||||
|
|
||||||
|
fun KtExpression.resolveStringConstantValue(bindingContext: BindingContext): String? {
|
||||||
|
when (this) {
|
||||||
|
is KtStringTemplateExpression -> {
|
||||||
|
if (hasInterpolation()) return null
|
||||||
|
return entries.joinToString("") { it.text }
|
||||||
|
}
|
||||||
|
is KtCallExpression -> {
|
||||||
|
val callee = this.calleeExpression?.getResolvedCallOrResolveToCall(bindingContext)?.resultingDescriptor
|
||||||
|
if (callee is VariableDescriptor) {
|
||||||
|
val compileTimeConstant = callee.compileTimeInitializer ?: return null
|
||||||
|
return compileTimeConstant.castOrNull<StringValue>()?.value
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
is KtConstantExpression -> {
|
||||||
|
// TODO: 2020/9/18 KtExpression.resolveStringConstantValue: KtConstantExpression
|
||||||
|
}
|
||||||
|
else -> return null
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user