From a6d5656161fffd76c4e48a9390d0a472ff6c4b45 Mon Sep 17 00:00:00 2001 From: Him188 Date: Mon, 30 Nov 2020 14:25:40 +0800 Subject: [PATCH] Support ReadOnlyPluginData: Add ReadOnlyPluginData, ReadOnlyPluginConfig, New inspection: READ_ONLY_VALUE_CANNOT_BE_VAR, --- .../src/data/AbstractPluginData.kt | 2 +- .../src/data/ReadOnlyPluginConfig.kt | 21 ++++++ .../src/data/ReadOnlyPluginData.kt | 45 ++++++++++++ buildSrc/src/main/kotlin/Versions.kt | 2 +- .../src/diagnostics/MiraiConsoleErrors.kt | 3 + .../MiraiConsoleErrorsRendering.kt | 6 ++ .../src/resolve/resolveTypes.kt | 1 + tools/gradle-plugin/src/VersionConstants.kt | 2 +- .../projects/test-project/build.gradle.kts | 2 +- .../org/example/myplugin/MySimpleCommand.kt | 5 ++ .../diagnostics/PluginDataValuesChecker.kt | 70 +++++++++++++------ 11 files changed, 134 insertions(+), 25 deletions(-) create mode 100644 backend/mirai-console/src/data/ReadOnlyPluginConfig.kt create mode 100644 backend/mirai-console/src/data/ReadOnlyPluginData.kt diff --git a/backend/mirai-console/src/data/AbstractPluginData.kt b/backend/mirai-console/src/data/AbstractPluginData.kt index a6f3ed4d1..5ee9ea43f 100644 --- a/backend/mirai-console/src/data/AbstractPluginData.kt +++ b/backend/mirai-console/src/data/AbstractPluginData.kt @@ -48,7 +48,7 @@ public abstract class AbstractPluginData : PluginData, PluginDataImpl() { * 供手动实现时值跟踪使用 (如 Java 用户). 一般 Kotlin 用户需使用 [provideDelegate] */ @ConsoleExperimentalApi - public fun > track( + public open fun > track( value: T, /** * 值名称. diff --git a/backend/mirai-console/src/data/ReadOnlyPluginConfig.kt b/backend/mirai-console/src/data/ReadOnlyPluginConfig.kt new file mode 100644 index 000000000..e2d5e0ba2 --- /dev/null +++ b/backend/mirai-console/src/data/ReadOnlyPluginConfig.kt @@ -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 \ No newline at end of file diff --git a/backend/mirai-console/src/data/ReadOnlyPluginData.kt b/backend/mirai-console/src/data/ReadOnlyPluginData.kt new file mode 100644 index 000000000..402879d2b --- /dev/null +++ b/backend/mirai-console/src/data/ReadOnlyPluginData.kt @@ -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" } + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 19f1befac..d60449256 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -11,7 +11,7 @@ object Versions { 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 consoleTerminal = console diff --git a/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt b/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt index 95acfc18b..1b0eaf1cb 100644 --- a/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt +++ b/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt @@ -63,6 +63,9 @@ object MiraiConsoleErrors { @JvmField val ILLEGAL_COMMAND_DECLARATION_RECEIVER = create(ERROR) + @JvmField + val READ_ONLY_VALUE_CANNOT_BE_VAR = create(ERROR) + @Suppress("ObjectPropertyName", "unused") @JvmField @Deprecated("", level = DeprecationLevel.ERROR) diff --git a/tools/compiler-common/src/diagnostics/MiraiConsoleErrorsRendering.kt b/tools/compiler-common/src/diagnostics/MiraiConsoleErrorsRendering.kt index b1974df60..fac10869c 100644 --- a/tools/compiler-common/src/diagnostics/MiraiConsoleErrorsRendering.kt +++ b/tools/compiler-common/src/diagnostics/MiraiConsoleErrorsRendering.kt @@ -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_VERSION_REQUIREMENT 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.UNSERIALIZABLE_TYPE import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages @@ -108,6 +109,11 @@ object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension { "插件不允许使用 ConsoleCommandOwner 构造指令, 请使用插件主类作为 CommandOwner", ) + put( + READ_ONLY_VALUE_CANNOT_BE_VAR, + "在 ReadOnlyPluginData 中不可定义 'var' by value", + ) + // put( // INAPPLICABLE_COMMAND_ANNOTATION, // "''{0}'' 无法在顶层函数使用.", diff --git a/tools/compiler-common/src/resolve/resolveTypes.kt b/tools/compiler-common/src/resolve/resolveTypes.kt index 9f3ce2a51..1fd39b20e 100644 --- a/tools/compiler-common/src/resolve/resolveTypes.kt +++ b/tools/compiler-common/src/resolve/resolveTypes.kt @@ -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 READ_ONLY_PLUGIN_DATA_FQ_NAME = FqName("net.mamoe.mirai.console.data.ReadOnlyPluginData") /////////////////////////////////////////////////////////////////////////// // Resolve diff --git a/tools/gradle-plugin/src/VersionConstants.kt b/tools/gradle-plugin/src/VersionConstants.kt index a7c77b185..810c58f66 100644 --- a/tools/gradle-plugin/src/VersionConstants.kt +++ b/tools/gradle-plugin/src/VersionConstants.kt @@ -10,6 +10,6 @@ package net.mamoe.mirai.console.gradle 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 } \ No newline at end of file diff --git a/tools/intellij-plugin/run/projects/test-project/build.gradle.kts b/tools/intellij-plugin/run/projects/test-project/build.gradle.kts index dacfba69d..6b4e39278 100644 --- a/tools/intellij-plugin/run/projects/test-project/build.gradle.kts +++ b/tools/intellij-plugin/run/projects/test-project/build.gradle.kts @@ -1,7 +1,7 @@ plugins { kotlin("jvm") 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" diff --git a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt index 9ac1aaf83..7ebe7b6a5 100644 --- a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt +++ b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt @@ -5,6 +5,7 @@ import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.ConsoleCommandOwner import net.mamoe.mirai.console.command.SimpleCommand import net.mamoe.mirai.console.data.AutoSavePluginConfig +import net.mamoe.mirai.console.data.ReadOnlyPluginConfig import net.mamoe.mirai.console.data.value object MySimpleCommand0001 : SimpleCommand( @@ -26,6 +27,10 @@ object DataTest : AutoSavePluginConfig("data") { val pp by value(NoDefaultValue(1)) } +object DataTest1 : ReadOnlyPluginConfig("data") { + var pp by value() +} + @Serializable data class HasDefaultValue( val x: Int = 0, diff --git a/tools/intellij-plugin/src/diagnostics/PluginDataValuesChecker.kt b/tools/intellij-plugin/src/diagnostics/PluginDataValuesChecker.kt index e05db2e83..86a0c86ff 100644 --- a/tools/intellij-plugin/src/diagnostics/PluginDataValuesChecker.kt +++ b/tools/intellij-plugin/src/diagnostics/PluginDataValuesChecker.kt @@ -16,12 +16,18 @@ import net.mamoe.mirai.console.compiler.common.resolve.* import net.mamoe.mirai.console.intellij.resolve.resolveAllCallsWithElement 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.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.* @@ -41,28 +47,50 @@ class PluginDataValuesChecker : DeclarationChecker { && t is SimpleType }.forEach { (e, callExpr) -> val (_, type) = e - val classDescriptor = type.constructor.declarationDescriptor?.castOrNull() ?: return@forEach - - 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 - )) + checkCallExpression(type, callExpr, context) } + + 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() ?: 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() ?: 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 + )) + } } }