Support ReadOnlyPluginData:

Add ReadOnlyPluginData, ReadOnlyPluginConfig,
New inspection: READ_ONLY_VALUE_CANNOT_BE_VAR,
This commit is contained in:
Him188 2020-11-30 14:25:40 +08:00
parent 64e0d545f4
commit a6d5656161
11 changed files with 134 additions and 25 deletions

View File

@ -48,7 +48,7 @@ public abstract class AbstractPluginData : PluginData, PluginDataImpl() {
* 供手动实现时值跟踪使用 ( Java 用户). 一般 Kotlin 用户需使用 [provideDelegate] * 供手动实现时值跟踪使用 ( Java 用户). 一般 Kotlin 用户需使用 [provideDelegate]
*/ */
@ConsoleExperimentalApi @ConsoleExperimentalApi
public fun <T : SerializerAwareValue<*>> track( public open fun <T : SerializerAwareValue<*>> track(
value: T, value: T,
/** /**
* 值名称. * 值名称.

View File

@ -0,0 +1,21 @@
/*
* Copyright 2019-2020 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.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console.data
/**
* 只读的 [PluginConfig]. 插件只能读取其值, 但值可能在后台被前端 (用户) 修改.
*
* @see PluginConfig
* @see AutoSavePluginData
* @since 1.1.0
*/
public open class ReadOnlyPluginConfig public constructor(saveName: String) : ReadOnlyPluginData(saveName), PluginConfig

View File

@ -0,0 +1,45 @@
/*
* Copyright 2019-2020 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.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console.data
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.utils.error
/**
* 只读的 [PluginData]. 插件只能读取其值, 但值可能在后台被前端 (用户) 修改.
*
* @see PluginData
* @see AutoSavePluginData
* @since 1.1.0
*/
public open class ReadOnlyPluginData private constructor(
// KEEP THIS PRIMARY CONSTRUCTOR FOR FUTURE USE: WE'LL SUPPORT SERIALIZERS_MODULE FOR POLYMORPHISM
@Suppress("UNUSED_PARAMETER") primaryConstructorMark: Any?,
) : AbstractPluginData() {
public final override val saveName: String
get() = _saveName
private lateinit var _saveName: String
public constructor(saveName: String) : this(null) {
_saveName = saveName
}
@ConsoleExperimentalApi
override fun onInit(owner: PluginDataHolder, storage: PluginDataStorage) {
}
@ConsoleExperimentalApi
public final override fun onValueChanged(value: Value<*>) {
debuggingLogger1.error { "onValueChanged: $value" }
}
}

View File

@ -11,7 +11,7 @@
object Versions { object Versions {
const val core = "1.3.3" const val core = "1.3.3"
const val console = "1.1.0-dev-32" const val console = "1.1.0-dev-33"
const val consoleGraphical = "0.0.7" const val consoleGraphical = "0.0.7"
const val consoleTerminal = console const val consoleTerminal = console

View File

@ -63,6 +63,9 @@ object MiraiConsoleErrors {
@JvmField @JvmField
val ILLEGAL_COMMAND_DECLARATION_RECEIVER = create<KtTypeReference>(ERROR) val ILLEGAL_COMMAND_DECLARATION_RECEIVER = create<KtTypeReference>(ERROR)
@JvmField
val READ_ONLY_VALUE_CANNOT_BE_VAR = create<PsiElement>(ERROR)
@Suppress("ObjectPropertyName", "unused") @Suppress("ObjectPropertyName", "unused")
@JvmField @JvmField
@Deprecated("", level = DeprecationLevel.ERROR) @Deprecated("", level = DeprecationLevel.ERROR)

View File

@ -19,6 +19,7 @@ import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.IL
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_VERSION_REQUIREMENT import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_VERSION_REQUIREMENT
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.NOT_CONSTRUCTABLE_TYPE import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.NOT_CONSTRUCTABLE_TYPE
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.READ_ONLY_VALUE_CANNOT_BE_VAR
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.RESTRICTED_CONSOLE_COMMAND_OWNER import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.RESTRICTED_CONSOLE_COMMAND_OWNER
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.UNSERIALIZABLE_TYPE import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.UNSERIALIZABLE_TYPE
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
@ -108,6 +109,11 @@ object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension {
"插件不允许使用 ConsoleCommandOwner 构造指令, 请使用插件主类作为 CommandOwner", "插件不允许使用 ConsoleCommandOwner 构造指令, 请使用插件主类作为 CommandOwner",
) )
put(
READ_ONLY_VALUE_CANNOT_BE_VAR,
"在 ReadOnlyPluginData 中不可定义 'var' by value",
)
// put( // put(
// INAPPLICABLE_COMMAND_ANNOTATION, // INAPPLICABLE_COMMAND_ANNOTATION,
// "''{0}'' 无法在顶层函数使用.", // "''{0}'' 无法在顶层函数使用.",

View File

@ -49,6 +49,7 @@ val SIMPLE_JVM_PLUGIN_DESCRIPTION_FQ_NAME = FqName("net.mamoe.mirai.console.plug
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
val PLUGIN_DATA_VALUE_FUNCTIONS_FQ_FQ_NAME = FqName("net.mamoe.mirai.console.data.value") val PLUGIN_DATA_VALUE_FUNCTIONS_FQ_FQ_NAME = FqName("net.mamoe.mirai.console.data.value")
val READ_ONLY_PLUGIN_DATA_FQ_NAME = FqName("net.mamoe.mirai.console.data.ReadOnlyPluginData")
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// Resolve // Resolve

View File

@ -10,6 +10,6 @@
package net.mamoe.mirai.console.gradle package net.mamoe.mirai.console.gradle
internal object VersionConstants { internal object VersionConstants {
const val CONSOLE_VERSION = "1.1.0-dev-32" // value is written here automatically during build const val CONSOLE_VERSION = "1.1.0-dev-33" // value is written here automatically during build
const val CORE_VERSION = "1.3.3" // value is written here automatically during build const val CORE_VERSION = "1.3.3" // value is written here automatically during build
} }

View File

@ -1,7 +1,7 @@
plugins { plugins {
kotlin("jvm") version "1.4.20" kotlin("jvm") version "1.4.20"
kotlin("plugin.serialization") version "1.4.20" kotlin("plugin.serialization") version "1.4.20"
id("net.mamoe.mirai-console") version "1.1.0-dev-32" id("net.mamoe.mirai-console") version "1.1.0-dev-33"
} }
group = "org.example" group = "org.example"

View File

@ -5,6 +5,7 @@ import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.ConsoleCommandOwner import net.mamoe.mirai.console.command.ConsoleCommandOwner
import net.mamoe.mirai.console.command.SimpleCommand import net.mamoe.mirai.console.command.SimpleCommand
import net.mamoe.mirai.console.data.AutoSavePluginConfig import net.mamoe.mirai.console.data.AutoSavePluginConfig
import net.mamoe.mirai.console.data.ReadOnlyPluginConfig
import net.mamoe.mirai.console.data.value import net.mamoe.mirai.console.data.value
object MySimpleCommand0001 : SimpleCommand( object MySimpleCommand0001 : SimpleCommand(
@ -26,6 +27,10 @@ object DataTest : AutoSavePluginConfig("data") {
val pp by value(NoDefaultValue(1)) val pp by value(NoDefaultValue(1))
} }
object DataTest1 : ReadOnlyPluginConfig("data") {
var pp by value<String>()
}
@Serializable @Serializable
data class HasDefaultValue( data class HasDefaultValue(
val x: Int = 0, val x: Int = 0,

View File

@ -16,12 +16,18 @@ import net.mamoe.mirai.console.compiler.common.resolve.*
import net.mamoe.mirai.console.intellij.resolve.resolveAllCallsWithElement import net.mamoe.mirai.console.intellij.resolve.resolveAllCallsWithElement
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor 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.inspections.collections.isCalling
import org.jetbrains.kotlin.idea.refactoring.fqName.fqName import org.jetbrains.kotlin.idea.refactoring.fqName.fqName
import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtProperty
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 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.kotlin.types.SimpleType
import org.jetbrains.kotlinx.serialization.compiler.resolve.* import org.jetbrains.kotlinx.serialization.compiler.resolve.*
@ -41,28 +47,50 @@ class PluginDataValuesChecker : DeclarationChecker {
&& t is SimpleType && t is SimpleType
}.forEach { (e, callExpr) -> }.forEach { (e, callExpr) ->
val (_, type) = e val (_, type) = e
val classDescriptor = type.constructor.declarationDescriptor?.castOrNull<ClassDescriptor>() ?: return@forEach checkCallExpression(type, callExpr, context)
if (canBeSerializedInternally(classDescriptor)) return@forEach
val inspectionTarget = kotlin.run {
val fqName = type.fqName ?: return@run null
callExpr.typeArguments.find { it.typeReference?.isReferencing(fqName) == true }
} ?: return@forEach
if (!classDescriptor.hasNoArgConstructor())
return@forEach context.report(MiraiConsoleErrors.NOT_CONSTRUCTABLE_TYPE.on(
inspectionTarget,
callExpr,
type.fqName?.asString().toString())
)
if (!classDescriptor.hasAnnotation(SERIALIZABLE_FQ_NAME))
return@forEach context.report(MiraiConsoleErrors.UNSERIALIZABLE_TYPE.on(
inspectionTarget,
classDescriptor
))
} }
declaration.resolveAllCallsWithElement(bindingContext)
.filter { (call) -> call.isCalling(PLUGIN_DATA_VALUE_FUNCTIONS_FQ_FQ_NAME) }
.forEach { (_, callExpr) ->
checkReadOnly(callExpr, 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
) {
context.report(MiraiConsoleErrors.READ_ONLY_VALUE_CANNOT_BE_VAR.on(property.valOrVarKeyword))
}
}
private fun checkCallExpression(type: KotlinType, callExpr: KtCallExpression, context: DeclarationCheckerContext) {
val classDescriptor = type.constructor.declarationDescriptor?.castOrNull<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
if (!classDescriptor.hasNoArgConstructor())
return context.report(MiraiConsoleErrors.NOT_CONSTRUCTABLE_TYPE.on(
inspectionTarget,
callExpr,
type.fqName?.asString().toString())
)
if (!classDescriptor.hasAnnotation(SERIALIZABLE_FQ_NAME))
return context.report(MiraiConsoleErrors.UNSERIALIZABLE_TYPE.on(
inspectionTarget,
classDescriptor
))
}
} }
} }