Diagnostics and resolve utilities

This commit is contained in:
Him188 2020-09-18 00:21:05 +08:00
parent d97e79d6a4
commit 3fc2d7f5c1
12 changed files with 322 additions and 204 deletions

View File

@ -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

View File

@ -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())
}

View File

@ -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)

View File

@ -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
}

View File

@ -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")

View File

@ -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() {
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() // 取消注册指令
} }
} }
// 定义插件数据 class PM : KotlinPlugin(
// 插件
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}")
}
}

View File

@ -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)
}
}
}
}
}
} }

View File

@ -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)
}

View File

@ -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

View File

@ -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

View File

@ -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 }

View File

@ -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
}