mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-25 15:40:28 +08:00
Improve inspection performance
This commit is contained in:
parent
445cfd07c3
commit
2d69c6ae10
@ -1,10 +1,10 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
* Copyright 2019-2021 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.
|
||||
* 此源代码的使用受 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
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
package net.mamoe.mirai.console.compiler.common.diagnostics
|
||||
|
||||
@ -24,18 +24,41 @@ import org.jetbrains.kotlin.psi.*
|
||||
* 2. 在 [MiraiConsoleErrorsRendering] 添加对应的 render
|
||||
*/
|
||||
object MiraiConsoleErrors {
|
||||
// plugin desc
|
||||
|
||||
@JvmField
|
||||
val ILLEGAL_PLUGIN_DESCRIPTION = create<PsiElement, String>(ERROR)
|
||||
|
||||
@JvmField
|
||||
val ILLEGAL_VERSION_REQUIREMENT = create<PsiElement, String, String>(ERROR)
|
||||
|
||||
// plugin data
|
||||
|
||||
@JvmField
|
||||
val NOT_CONSTRUCTABLE_TYPE = create<KtTypeProjection, KtCallExpression, String>(ERROR)
|
||||
|
||||
@JvmField
|
||||
val UNSERIALIZABLE_TYPE = create<PsiElement, ClassDescriptor>(ERROR)
|
||||
|
||||
@JvmField
|
||||
val READ_ONLY_VALUE_CANNOT_BE_VAR = create<PsiElement>(ERROR)
|
||||
|
||||
// command
|
||||
|
||||
@JvmField
|
||||
val ILLEGAL_COMMAND_NAME = create<PsiElement, String, String>(ERROR)
|
||||
|
||||
@JvmField
|
||||
val ILLEGAL_COMMAND_REGISTER_USE = create<PsiElement, KtNamedDeclaration, String>(ERROR)
|
||||
|
||||
@JvmField
|
||||
val RESTRICTED_CONSOLE_COMMAND_OWNER = create<KtElement>(WARNING)
|
||||
|
||||
@JvmField
|
||||
val ILLEGAL_COMMAND_DECLARATION_RECEIVER = create<KtTypeReference>(ERROR)
|
||||
|
||||
// permission
|
||||
|
||||
@JvmField
|
||||
val ILLEGAL_PERMISSION_NAME = create<PsiElement, String, String>(ERROR)
|
||||
|
||||
@ -45,27 +68,12 @@ object MiraiConsoleErrors {
|
||||
@JvmField
|
||||
val ILLEGAL_PERMISSION_NAMESPACE = create<PsiElement, String, String>(ERROR)
|
||||
|
||||
@JvmField
|
||||
val ILLEGAL_COMMAND_REGISTER_USE = create<PsiElement, KtNamedDeclaration, String>(ERROR)
|
||||
|
||||
@JvmField
|
||||
val ILLEGAL_PERMISSION_REGISTER_USE = create<PsiElement, KtNamedDeclaration, String>(ERROR)
|
||||
|
||||
@JvmField
|
||||
val ILLEGAL_VERSION_REQUIREMENT = create<PsiElement, String, String>(ERROR)
|
||||
|
||||
// @JvmField
|
||||
// val INAPPLICABLE_COMMAND_ANNOTATION = create<PsiElement, String>(ERROR)
|
||||
|
||||
@JvmField
|
||||
val RESTRICTED_CONSOLE_COMMAND_OWNER = create<KtElement>(WARNING)
|
||||
|
||||
@JvmField
|
||||
val ILLEGAL_COMMAND_DECLARATION_RECEIVER = create<KtTypeReference>(ERROR)
|
||||
|
||||
@JvmField
|
||||
val READ_ONLY_VALUE_CANNOT_BE_VAR = create<PsiElement>(ERROR)
|
||||
|
||||
@Suppress("ObjectPropertyName", "unused")
|
||||
@JvmField
|
||||
@Deprecated("", level = DeprecationLevel.ERROR)
|
||||
|
@ -1,10 +1,10 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
* Copyright 2019-2021 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.
|
||||
* 此源代码的使用受 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
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.compiler.common.resolve
|
||||
@ -21,15 +21,19 @@ fun Annotated.hasAnnotation(fqName: FqName) = this.annotations.hasAnnotation(fqN
|
||||
fun Annotated.findAnnotation(fqName: FqName) = this.annotations.findAnnotation(fqName)
|
||||
|
||||
|
||||
val PsiElement.allChildrenWithSelf: Sequence<PsiElement>
|
||||
val PsiElement.allChildrenWithSelfSequence: Sequence<PsiElement>
|
||||
get() = sequence {
|
||||
yield(this@allChildrenWithSelf)
|
||||
yield(this@allChildrenWithSelfSequence)
|
||||
for (child in children) {
|
||||
yieldAll(child.allChildrenWithSelf)
|
||||
yieldAll(child.allChildrenWithSelfSequence)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val PsiElement.childrenWithSelf: List<PsiElement>
|
||||
get() = listOf(this, *children)
|
||||
|
||||
|
||||
inline fun <reified E> PsiElement.findParent(): E? = this.parents.filterIsInstance<E>().firstOrNull()
|
||||
|
||||
|
||||
|
@ -68,11 +68,12 @@ fun ResolveContext.Kind.Companion.valueOfOrNull(string: String) = ResolveContext
|
||||
val Annotated.resolveContextKinds: List<ResolveContextKind>?
|
||||
get() {
|
||||
val ann = this.findAnnotation(RESOLVE_CONTEXT_FQ_NAME) ?: return null
|
||||
val kinds =
|
||||
ann.allValueArguments.firstValue().castOrNull<ArrayValue>()?.value?.mapNotNull { it.castOrNull<EnumValue>()?.value }
|
||||
?: return null // undetermined kind
|
||||
|
||||
return kinds.map { (_, enumEntryName) ->
|
||||
ResolveContextKind.valueOf(enumEntryName.asString())
|
||||
}
|
||||
return ann.allValueArguments
|
||||
.firstValue()
|
||||
.castOrNull<ArrayValue>()?.value
|
||||
?.mapNotNull { value ->
|
||||
val (_, enumEntryName) = value.castOrNull<EnumValue>()?.value ?: return@mapNotNull null
|
||||
ResolveContextKind.valueOf(enumEntryName.asString())
|
||||
}
|
||||
?: return null // undetermined kind
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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 org.example.myplugin
|
||||
|
||||
import net.mamoe.mirai.console.command.ConsoleCommandOwner
|
||||
import net.mamoe.mirai.console.command.SimpleCommand
|
||||
|
||||
|
||||
object MySimpleCommand0002 : SimpleCommand(
|
||||
ConsoleCommandOwner, "foo",
|
||||
description = "示例指令"
|
||||
) {}
|
@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 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 org.example.myplugin
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
@ -21,6 +30,11 @@ object MySimpleCommand000 : SimpleCommand(
|
||||
suspend fun CommandSender.handle(int: Int, str: String) {
|
||||
|
||||
}
|
||||
|
||||
@Handler
|
||||
suspend fun String.bad(int: Int, str: String) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
object DataTest : AutoSavePluginConfig("data") {
|
||||
@ -28,7 +42,8 @@ object DataTest : AutoSavePluginConfig("data") {
|
||||
}
|
||||
|
||||
object DataTest1 : ReadOnlyPluginConfig("data") {
|
||||
val pp by value<String>()
|
||||
var pp by value<String>()
|
||||
// var should be reported
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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 org.example.myplugin
|
||||
|
||||
import net.mamoe.mirai.console.data.ReadOnlyPluginConfig
|
||||
import net.mamoe.mirai.console.data.value
|
||||
import org.example.myplugin.DataTest1.provideDelegate
|
||||
|
||||
|
||||
object DataTest2 : ReadOnlyPluginConfig("data") {
|
||||
var pp by value<String>()
|
||||
// var should be reported
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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 org.example.myplugin
|
||||
|
||||
import net.mamoe.mirai.message.data.PlainText
|
||||
import net.mamoe.mirai.message.data.toPlainText
|
||||
|
||||
fun main() {
|
||||
val x: String = ""
|
||||
val plain: PlainText = PlainText("")
|
||||
|
||||
PlainText(x) + plain
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
* Copyright 2019-2021 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.
|
||||
* 此源代码的使用受 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
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.intellij
|
||||
@ -12,10 +12,16 @@ package net.mamoe.mirai.console.intellij
|
||||
import net.mamoe.mirai.console.intellij.diagnostics.CommandDeclarationChecker
|
||||
import net.mamoe.mirai.console.intellij.diagnostics.ContextualParametersChecker
|
||||
import net.mamoe.mirai.console.intellij.diagnostics.PluginDataValuesChecker
|
||||
import net.mamoe.mirai.console.intellij.util.DEBUG_ENABLED
|
||||
import net.mamoe.mirai.console.intellij.util.runIgnoringErrors
|
||||
import org.jetbrains.kotlin.container.StorageComponentContainer
|
||||
import org.jetbrains.kotlin.container.useInstance
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
|
||||
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
|
||||
|
||||
class IDEContainerContributor : StorageComponentContainerContributor {
|
||||
override fun registerModuleComponents(
|
||||
@ -23,8 +29,24 @@ class IDEContainerContributor : StorageComponentContainerContributor {
|
||||
platform: org.jetbrains.kotlin.platform.TargetPlatform,
|
||||
moduleDescriptor: ModuleDescriptor,
|
||||
) {
|
||||
container.useInstance(ContextualParametersChecker())
|
||||
container.useInstance(PluginDataValuesChecker())
|
||||
container.useInstance(CommandDeclarationChecker())
|
||||
container.useInstance(ContextualParametersChecker().wrapIgnoringExceptionIfNotDebug())
|
||||
container.useInstance(PluginDataValuesChecker().wrapIgnoringExceptionIfNotDebug())
|
||||
container.useInstance(CommandDeclarationChecker().wrapIgnoringExceptionIfNotDebug())
|
||||
}
|
||||
|
||||
private fun DeclarationChecker.wrapIgnoringExceptionIfNotDebug(): DeclarationChecker {
|
||||
if (DEBUG_ENABLED) {
|
||||
return this
|
||||
}
|
||||
return DeclarationCheckerIgnoringExceptions(this)
|
||||
}
|
||||
|
||||
class DeclarationCheckerIgnoringExceptions(
|
||||
private val delegate: DeclarationChecker
|
||||
) : DeclarationChecker {
|
||||
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
|
||||
runIgnoringErrors { delegate.check(declaration, descriptor, context) }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,10 +1,17 @@
|
||||
/*
|
||||
* 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.diagnostics
|
||||
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_COMMAND_DECLARATION_RECEIVER
|
||||
import net.mamoe.mirai.console.compiler.common.resolve.COMMAND_SENDER_FQ_NAME
|
||||
import net.mamoe.mirai.console.intellij.resolve.hasSuperType
|
||||
import net.mamoe.mirai.console.intellij.resolve.isCompositeCommandSubCommand
|
||||
import net.mamoe.mirai.console.intellij.resolve.isSimpleCommandHandler
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
@ -17,14 +24,15 @@ class CommandDeclarationChecker : DeclarationChecker {
|
||||
if (declaration !is KtNamedFunction) return
|
||||
|
||||
// exclusive checks
|
||||
when {
|
||||
declaration.isSimpleCommandHandler() -> {
|
||||
}
|
||||
|
||||
declaration.isCompositeCommandSubCommand() -> {
|
||||
}
|
||||
else -> return
|
||||
}
|
||||
// currently no checks
|
||||
// when {
|
||||
// declaration.isSimpleCommandHandler() -> {
|
||||
// }
|
||||
//
|
||||
// declaration.isCompositeCommandSubCommand() -> {
|
||||
// }
|
||||
// else -> return
|
||||
// }
|
||||
|
||||
// common checks
|
||||
checkCommandReceiverParameter(declaration)?.let { context.report(it) }
|
||||
|
@ -1,10 +1,10 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
* Copyright 2019-2021 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.
|
||||
* 此源代码的使用受 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
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.intellij.diagnostics
|
||||
@ -19,17 +19,19 @@ import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.RE
|
||||
import net.mamoe.mirai.console.compiler.common.resolve.CONSOLE_COMMAND_OWNER_FQ_NAME
|
||||
import net.mamoe.mirai.console.compiler.common.resolve.ResolveContextKind
|
||||
import net.mamoe.mirai.console.compiler.common.resolve.resolveContextKinds
|
||||
import net.mamoe.mirai.console.intellij.resolve.findChild
|
||||
import net.mamoe.mirai.console.intellij.resolve.resolveAllCalls
|
||||
import net.mamoe.mirai.console.intellij.resolve.bodyCalls
|
||||
import net.mamoe.mirai.console.intellij.resolve.resolveStringConstantValues
|
||||
import net.mamoe.mirai.console.intellij.resolve.valueParametersWithArguments
|
||||
import net.mamoe.mirai.console.intellij.util.RequirementHelper
|
||||
import net.mamoe.mirai.console.intellij.util.RequirementParser
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.idea.inspections.collections.isCalling
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.KtReferenceExpression
|
||||
import org.jetbrains.kotlin.psi.ValueArgument
|
||||
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
|
||||
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
|
||||
import java.util.*
|
||||
@ -39,6 +41,40 @@ import kotlin.reflect.KFunction2
|
||||
* Checks parameters with [ResolveContextKind]
|
||||
*/
|
||||
class ContextualParametersChecker : DeclarationChecker {
|
||||
override fun check(
|
||||
declaration: KtDeclaration,
|
||||
descriptor: DeclarationDescriptor,
|
||||
context: DeclarationCheckerContext,
|
||||
) {
|
||||
val calls = declaration.bodyCalls(context.bindingContext) ?: return
|
||||
|
||||
for ((call, _) in calls) {
|
||||
for ((parameter, argument) in call.valueParametersWithArguments()) {
|
||||
checkArgument(parameter, argument, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkArgument(
|
||||
parameter: ValueParameterDescriptor,
|
||||
argument: ValueArgument,
|
||||
context: DeclarationCheckerContext,
|
||||
) {
|
||||
val elementCheckers = parameter.resolveContextKinds?.mapNotNull(checkersMap::get) ?: return
|
||||
|
||||
val resolvedConstants = argument.resolveStringConstantValues()?.toList().orEmpty()
|
||||
|
||||
for (elementChecker in elementCheckers) {
|
||||
if (resolvedConstants.isEmpty()) {
|
||||
elementChecker(context, argument.asElement(), argument, null)?.let { context.report(it) }
|
||||
} else {
|
||||
for (resolvedConstant in resolvedConstants) {
|
||||
elementChecker(context, argument.asElement(), argument, resolvedConstant)?.let { context.report(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val ID_REGEX: Regex = Regex("""([a-zA-Z][a-zA-Z0-9]*(?:\.[a-zA-Z][a-zA-Z0-9]*)*)\.([a-zA-Z][a-zA-Z0-9]*(?:-[a-zA-Z0-9]+)*)""")
|
||||
private val FORBIDDEN_ID_NAMES: Array<String> = arrayOf("main", "console", "plugin", "config", "data")
|
||||
@ -150,6 +186,7 @@ class ContextualParametersChecker : DeclarationChecker {
|
||||
operator fun invoke(context: DeclarationCheckerContext, declaration: KtElement, valueArgument: ValueArgument, value: String?): Diagnostic?
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
private val checkersMap: EnumMap<ResolveContextKind, ElementChecker> =
|
||||
EnumMap<ResolveContextKind, ElementChecker>(ResolveContextKind::class.java).apply {
|
||||
|
||||
@ -183,47 +220,4 @@ class ContextualParametersChecker : DeclarationChecker {
|
||||
put(ResolveContextKind.RESTRICTED_CONSOLE_COMMAND_OWNER, ::checkConsoleCommandOwner)
|
||||
}
|
||||
|
||||
override fun check(
|
||||
declaration: KtDeclaration,
|
||||
descriptor: DeclarationDescriptor,
|
||||
context: DeclarationCheckerContext,
|
||||
) {
|
||||
val calls: Sequence<ResolvedCall<*>> = when (declaration) {
|
||||
is KtClassOrObject -> {
|
||||
declaration.findChild<KtSuperTypeList>()?.resolveAllCalls(context.bindingContext)
|
||||
// ignore class body, which will be [check]ed
|
||||
}
|
||||
|
||||
// is KtNamedFunction -> {
|
||||
//
|
||||
// }
|
||||
else -> declaration.resolveAllCalls(context.bindingContext)
|
||||
|
||||
} ?: return
|
||||
|
||||
calls
|
||||
.flatMap { call ->
|
||||
call.valueParametersWithArguments().asSequence()
|
||||
}
|
||||
.mapNotNull { (p, a) ->
|
||||
p.resolveContextKinds
|
||||
?.map(checkersMap::get)
|
||||
?.mapNotNull {
|
||||
if (it == null) null else it to a
|
||||
}
|
||||
}
|
||||
.flatMap { it.asSequence() }
|
||||
.mapNotNull { (kind, argument) ->
|
||||
Triple(kind, argument, argument.resolveStringConstantValues())
|
||||
}
|
||||
.forEach { (fn, argument, resolvedConstantsSequence) ->
|
||||
val resolvedConstants = resolvedConstantsSequence?.toList().orEmpty()
|
||||
if (resolvedConstants.isEmpty()) {
|
||||
fn(context, argument.asElement(), argument, null)?.let { context.report(it) }
|
||||
} else for (resolvedConstant in resolvedConstants) {
|
||||
fn(context, argument.asElement(), argument, resolvedConstant)?.let { context.report(it) }
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
@ -1,96 +1,125 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
* Copyright 2019-2021 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.
|
||||
* 此源代码的使用受 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
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
package net.mamoe.mirai.console.intellij.diagnostics
|
||||
|
||||
import net.mamoe.mirai.console.compiler.common.SERIALIZABLE_FQ_NAME
|
||||
import net.mamoe.mirai.console.compiler.common.castOrNull
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors
|
||||
import net.mamoe.mirai.console.compiler.common.resolve.*
|
||||
import net.mamoe.mirai.console.intellij.resolve.resolveAllCallsWithElement
|
||||
import net.mamoe.mirai.console.intellij.resolve.bodyCalls
|
||||
import net.mamoe.mirai.console.intellij.resolve.hasSuperType
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.receiverType
|
||||
import org.jetbrains.kotlin.idea.inspections.collections.isCalling
|
||||
import org.jetbrains.kotlin.idea.refactoring.fqName.fqName
|
||||
import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName
|
||||
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
|
||||
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperClassifiers
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.SimpleType
|
||||
import org.jetbrains.kotlinx.serialization.compiler.resolve.*
|
||||
|
||||
|
||||
class PluginDataValuesChecker : DeclarationChecker {
|
||||
/**
|
||||
* [KtObjectDeclaration], [KtParameter], [KtPrimaryConstructor], [KtClass], [KtNamedFunction], [KtProperty]
|
||||
*/
|
||||
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
|
||||
val bindingContext = context.bindingContext
|
||||
declaration.resolveAllCallsWithElement(bindingContext)
|
||||
.filter { (call) -> call.isCalling(PLUGIN_DATA_VALUE_FUNCTIONS_FQ_FQ_NAME) }
|
||||
.filter { (call) ->
|
||||
call.resultingDescriptor.resolveContextKinds?.contains(ResolveContextKind.RESTRICTED_NO_ARG_CONSTRUCTOR) == true
|
||||
}.flatMap { (call, element) ->
|
||||
call.typeArguments.entries.associateWith { element }.asSequence()
|
||||
}.filter { (e, _) ->
|
||||
val (p, t) = e
|
||||
(p.isReified || p.resolveContextKinds?.contains(ResolveContextKind.RESTRICTED_NO_ARG_CONSTRUCTOR) == true)
|
||||
&& t is SimpleType
|
||||
}.forEach { (e, callExpr) ->
|
||||
val (_, type) = e
|
||||
checkCallExpression(type, callExpr, context)
|
||||
}
|
||||
|
||||
declaration.resolveAllCallsWithElement(bindingContext)
|
||||
.filter { (call) -> call.isCalling(PLUGIN_DATA_VALUE_FUNCTIONS_FQ_FQ_NAME) }
|
||||
.forEach { (_, callExpr) ->
|
||||
checkReadOnly(callExpr, context)
|
||||
}
|
||||
//println(declaration::class.qualifiedName + "\t:" + declaration.text.take(10))
|
||||
|
||||
if (declaration is KtProperty) {
|
||||
if (checkReadOnly(declaration, context)) return // it reports an error on property so no need to check further
|
||||
}
|
||||
|
||||
val calls = declaration.bodyCalls(bindingContext) ?: return
|
||||
|
||||
for ((call, expr) in calls) {
|
||||
check(call, expr, context)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun checkReadOnly(callExpr: KtCallExpression, context: DeclarationCheckerContext) {
|
||||
// first parent is KtPropertyDelegate, next is KtProperty
|
||||
val property = callExpr.parent.parent.castOrNull<KtProperty>() ?: return
|
||||
if (property.isVar &&
|
||||
callExpr.receiverType()?.toClassDescriptor?.getAllSuperClassifiers()?.any { it.fqNameOrNull() == READ_ONLY_PLUGIN_DATA_FQ_NAME } == true
|
||||
/**
|
||||
* Check `PluginData.value` calls
|
||||
*/
|
||||
fun check(call: ResolvedCall<out CallableDescriptor>, expr: KtExpression, context: DeclarationCheckerContext) {
|
||||
if (!call.isCalling(PLUGIN_DATA_VALUE_FUNCTIONS_FQ_FQ_NAME)) return
|
||||
|
||||
if (expr is KtCallExpression)
|
||||
checkConstructableAndSerializable(call, expr, context)
|
||||
}
|
||||
|
||||
private fun KtProperty.isInsideOrExtensionOfReadOnlyPluginData(): Boolean {
|
||||
return containingClassOrObject?.hasSuperType(READ_ONLY_PLUGIN_DATA_FQ_NAME) == true // inside
|
||||
|| receiverTypeReference?.hasSuperType(READ_ONLY_PLUGIN_DATA_FQ_NAME) == true // extension
|
||||
}
|
||||
|
||||
private fun checkReadOnly(property: KtProperty, context: DeclarationCheckerContext): Boolean {
|
||||
// first parent is KtPropertyDelegate, next is KtProperty
|
||||
|
||||
if (property.isVar // var
|
||||
&& property.delegateExpression?.getResolvedCall(context)?.isCalling(PLUGIN_DATA_VALUE_FUNCTIONS_FQ_FQ_NAME) == true // by value()
|
||||
&& property.isInsideOrExtensionOfReadOnlyPluginData() // extensionReceiver is ReadOnlyPluginData or null
|
||||
) {
|
||||
context.report(MiraiConsoleErrors.READ_ONLY_VALUE_CANNOT_BE_VAR.on(property.valOrVarKeyword))
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun checkConstructableAndSerializable(call: ResolvedCall<out CallableDescriptor>, expr: KtCallExpression, context: DeclarationCheckerContext) {
|
||||
if (call.resultingDescriptor.resolveContextKinds?.contains(ResolveContextKind.RESTRICTED_NO_ARG_CONSTRUCTOR) != true) return
|
||||
|
||||
for ((typeParameterDescriptor, kotlinType) in call.typeArguments.entries) {
|
||||
if ((typeParameterDescriptor.isReified || typeParameterDescriptor.resolveContextKinds?.contains(ResolveContextKind.RESTRICTED_NO_ARG_CONSTRUCTOR) == true)
|
||||
&& kotlinType is SimpleType
|
||||
) {
|
||||
context.report(MiraiConsoleErrors.READ_ONLY_VALUE_CANNOT_BE_VAR.on(property.valOrVarKeyword))
|
||||
|
||||
checkConstructableAndSerializable(kotlinType, expr, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkCallExpression(type: KotlinType, callExpr: KtCallExpression, context: DeclarationCheckerContext) {
|
||||
val classDescriptor = type.constructor.declarationDescriptor?.castOrNull<ClassDescriptor>() ?: return
|
||||
private fun checkConstructableAndSerializable(type: KotlinType, callExpr: KtCallExpression, context: DeclarationCheckerContext) {
|
||||
val classDescriptor = type.constructor.declarationDescriptor?.castOrNull<ClassDescriptor>() ?: return
|
||||
|
||||
if (canBeSerializedInternally(classDescriptor)) return
|
||||
if (canBeSerializedInternally(classDescriptor)) return
|
||||
|
||||
val inspectionTarget = kotlin.run {
|
||||
val fqName = type.fqName ?: return@run null
|
||||
callExpr.typeArguments.find { it.typeReference?.isReferencing(fqName) == true }
|
||||
} ?: return
|
||||
val inspectionTarget = kotlin.run {
|
||||
val fqName = type.fqName ?: return@run null
|
||||
callExpr.typeArguments.find { it.typeReference?.isReferencing(fqName) == true }
|
||||
} ?: return
|
||||
|
||||
if (!classDescriptor.hasNoArgConstructor())
|
||||
return context.report(MiraiConsoleErrors.NOT_CONSTRUCTABLE_TYPE.on(
|
||||
if (!classDescriptor.hasNoArgConstructor())
|
||||
return context.report(
|
||||
MiraiConsoleErrors.NOT_CONSTRUCTABLE_TYPE.on(
|
||||
inspectionTarget,
|
||||
callExpr,
|
||||
type.fqName?.asString().toString())
|
||||
type.fqName?.asString().toString()
|
||||
)
|
||||
)
|
||||
|
||||
if (!classDescriptor.hasAnnotation(SERIALIZABLE_FQ_NAME))
|
||||
return context.report(MiraiConsoleErrors.UNSERIALIZABLE_TYPE.on(
|
||||
if (!classDescriptor.hasAnnotation(SERIALIZABLE_FQ_NAME))
|
||||
return context.report(
|
||||
MiraiConsoleErrors.UNSERIALIZABLE_TYPE.on(
|
||||
inspectionTarget,
|
||||
classDescriptor
|
||||
))
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,15 +1,16 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
* Copyright 2019-2021 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.
|
||||
* 此源代码的使用受 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
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.intellij.diagnostics
|
||||
|
||||
import net.mamoe.mirai.console.compiler.common.castOrNull
|
||||
import net.mamoe.mirai.console.compiler.common.resolve.READ_ONLY_PLUGIN_DATA_FQ_NAME
|
||||
import net.mamoe.mirai.console.intellij.resolve.getResolvedCall
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
@ -21,6 +22,14 @@ import org.jetbrains.kotlin.psi.KtTypeReference
|
||||
import org.jetbrains.kotlin.psi.KtUserType
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperClassifiers
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlinx.serialization.compiler.resolve.toClassDescriptor
|
||||
|
||||
fun KotlinType.isSubtypeOfReadOnlyPluginData(): Boolean {
|
||||
return this.toClassDescriptor?.getAllSuperClassifiers()?.any { it.fqNameOrNull() == READ_ONLY_PLUGIN_DATA_FQ_NAME } == true
|
||||
}
|
||||
|
||||
fun DeclarationCheckerContext.report(diagnostic: Diagnostic) {
|
||||
return this.trace.report(diagnostic)
|
||||
|
@ -1,10 +1,10 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
* Copyright 2019-2021 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.
|
||||
* 此源代码的使用受 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
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.intellij.resolve
|
||||
@ -14,10 +14,7 @@ import com.intellij.psi.PsiDeclarationStatement
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.parentsWithSelf
|
||||
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.compiler.common.resolve.allChildrenWithSelf
|
||||
import net.mamoe.mirai.console.compiler.common.resolve.findParent
|
||||
import net.mamoe.mirai.console.compiler.common.resolve.*
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.VariableDescriptor
|
||||
@ -30,6 +27,7 @@ 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.getCalleeExpressionIfAny
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.constants.ArrayValue
|
||||
@ -59,8 +57,10 @@ val KtPureClassOrObject.allSuperTypes: Sequence<KtSuperTypeListEntry>
|
||||
get() = sequence {
|
||||
yieldAll(superTypeListEntries)
|
||||
for (list in superTypeListEntries.asSequence()) {
|
||||
yieldAll((list.typeAsUserType?.referenceExpression?.resolve()?.parentsWithSelf?.filterIsInstance<KtClass>()
|
||||
?.firstOrNull())?.allSuperTypes.orEmpty())
|
||||
yieldAll(
|
||||
(list.typeAsUserType?.referenceExpression?.resolve()?.parentsWithSelf?.filterIsInstance<KtClass>()
|
||||
?.firstOrNull())?.allSuperTypes.orEmpty()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -130,19 +130,48 @@ fun KtAnnotated.hasAnnotation(fqName: FqName): Boolean =
|
||||
this.annotationEntries.any { it.annotationClass?.getKotlinFqName() == fqName }
|
||||
|
||||
fun KtElement.resolveAllCalls(bindingContext: BindingContext): Sequence<ResolvedCall<*>> {
|
||||
return allChildrenWithSelf
|
||||
return allChildrenWithSelfSequence
|
||||
.filterIsInstance<KtElement>()
|
||||
.mapNotNull { it.getResolvedCall(bindingContext) }
|
||||
}
|
||||
|
||||
fun KtDeclaration.resolveAllCallsWithElement(bindingContext: BindingContext): Sequence<Pair<ResolvedCall<out CallableDescriptor>, KtCallExpression>> {
|
||||
return allChildrenWithSelf
|
||||
.filterIsInstance<KtCallExpression>()
|
||||
.mapNotNull {
|
||||
val callee = it.calleeExpression ?: return@mapNotNull null
|
||||
data class ResolvedCallWithExpr<C : CallableDescriptor, E : KtExpression>(
|
||||
val call: ResolvedCall<C>,
|
||||
val expr: E
|
||||
)
|
||||
|
||||
/**
|
||||
* 只解决一层
|
||||
*/
|
||||
fun KtDeclaration.bodyCalls(bindingContext: BindingContext): Sequence<ResolvedCallWithExpr<out CallableDescriptor, KtExpression>>? {
|
||||
return when (val declaration = this) {
|
||||
is KtClassOrObject -> {
|
||||
declaration.superTypeListEntries.asSequence().flatMap {
|
||||
it.resolveAllCallsWithElement(bindingContext, true)
|
||||
}
|
||||
}
|
||||
is KtDeclarationWithBody -> {
|
||||
declaration.bodyExpression?.resolveAllCallsWithElement(bindingContext, false) ?: return null
|
||||
}
|
||||
is KtCallExpression -> {
|
||||
val call = declaration.getResolvedCall(bindingContext) ?: return null
|
||||
sequenceOf(ResolvedCallWithExpr(call, declaration))
|
||||
}
|
||||
else -> return null
|
||||
}
|
||||
}
|
||||
|
||||
fun KtElement.resolveAllCallsWithElement(
|
||||
bindingContext: BindingContext,
|
||||
recursive: Boolean = true
|
||||
): Sequence<ResolvedCallWithExpr<out CallableDescriptor, KtExpression>> {
|
||||
return (if (recursive) allChildrenWithSelfSequence else childrenWithSelf.asSequence())
|
||||
.filterIsInstance<KtExpression>()
|
||||
.mapNotNull { expr ->
|
||||
val callee = expr.getCalleeExpressionIfAny() ?: return@mapNotNull null
|
||||
val resolved = callee.getResolvedCall(bindingContext) ?: return@mapNotNull null
|
||||
|
||||
resolved to it
|
||||
ResolvedCallWithExpr(resolved, expr)
|
||||
}
|
||||
}
|
||||
|
||||
|
39
tools/intellij-plugin/src/util/RunIgnoringErrors.kt
Normal file
39
tools/intellij-plugin/src/util/RunIgnoringErrors.kt
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
|
||||
internal val DEBUG_ENABLED = System.getenv("mirai.console.intellij.debug") == "true"
|
||||
|
||||
internal inline fun runIgnoringErrors(
|
||||
block: () -> Unit
|
||||
) {
|
||||
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
|
||||
try {
|
||||
block()
|
||||
} catch (e: Error) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
@kotlin.internal.LowPriorityInOverloadResolution
|
||||
internal inline fun <R> R.runIgnoringErrors(
|
||||
block: R.() -> Unit
|
||||
) {
|
||||
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
|
||||
try {
|
||||
block()
|
||||
} catch (e: Error) {
|
||||
// ignored
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user