mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-10 18:40:15 +08:00
Diagnostics and resolve utilities
This commit is contained in:
parent
d97e79d6a4
commit
3fc2d7f5c1
@ -9,7 +9,7 @@
|
||||
|
||||
object Versions {
|
||||
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 consoleTerminal = "0.1.0"
|
||||
const val consolePure = console
|
||||
|
@ -9,7 +9,10 @@
|
||||
|
||||
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.resolve.constants.EnumValue
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// 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 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 {
|
||||
compileOnly(kotlin("stdlib-jdk8"))
|
||||
|
||||
val core = "1.2.3"
|
||||
val console = "1.0-M4"
|
||||
val core = "1.3.0"
|
||||
val console = "1.0-RC-dev-1"
|
||||
|
||||
compileOnly("net.mamoe:mirai-console:$console")
|
||||
compileOnly("net.mamoe:mirai-core:$core")
|
||||
|
@ -1,163 +1,19 @@
|
||||
@file:Suppress("unused")
|
||||
|
||||
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.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(
|
||||
JvmPluginDescription(
|
||||
"org.example.example-plugin",
|
||||
"0.1.0"
|
||||
)
|
||||
) {
|
||||
fun test() {
|
||||
|
||||
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)` 这种定义方式.
|
||||
class PM : KotlinPlugin(
|
||||
|
||||
|
||||
// 带默认值的非空 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
|
||||
|
||||
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.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.DeclarationCheckerContext
|
||||
import java.util.*
|
||||
import kotlin.contracts.contract
|
||||
|
||||
/**
|
||||
* Checks:
|
||||
@ -20,11 +31,95 @@ import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
|
||||
* - plugin name
|
||||
*/
|
||||
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(
|
||||
declaration: KtDeclaration,
|
||||
descriptor: DeclarationDescriptor,
|
||||
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.psi.PsiElement
|
||||
import net.mamoe.mirai.console.intellij.Icons
|
||||
import net.mamoe.mirai.console.intellij.resolve.getElementForLineMark
|
||||
import net.mamoe.mirai.console.intellij.resolve.isSimpleCommandHandlerOrCompositeCommandSubCommand
|
||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||
|
||||
|
@ -18,6 +18,9 @@ import com.intellij.psi.PsiElement
|
||||
import com.intellij.util.castSafelyTo
|
||||
import net.mamoe.mirai.console.compiler.common.resolve.PLUGIN_FQ_NAME
|
||||
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.psi.KtClass
|
||||
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
|
||||
|
||||
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.SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME
|
||||
import net.mamoe.mirai.console.intellij.line.marker.hasAnnotation
|
||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
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 =
|
||||
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