Improve inspection performance

This commit is contained in:
Him188 2021-01-29 10:23:18 +08:00
parent 445cfd07c3
commit 2d69c6ae10
14 changed files with 392 additions and 175 deletions

View File

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

View File

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

View File

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

View File

@ -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 = "示例指令"
) {}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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