diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt index 37e4f3f69..43346dcb9 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt @@ -104,6 +104,7 @@ public interface Command { /** * 检查指令名的合法性. 在非法时抛出 [IllegalArgumentException] */ + @JvmStatic @Throws(IllegalArgumentException::class) public fun checkCommandName(@ResolveContext(COMMAND_NAME) name: String) { when { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt index 714c40f9e..8a3702b69 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt @@ -32,6 +32,10 @@ public annotation class ResolveContext( * 元素数量可能在任意时间被改动 */ public enum class Kind { + /////////////////////////////////////////////////////////////////////////// + // ConstantKind + /////////////////////////////////////////////////////////////////////////// + PLUGIN_ID, PLUGIN_NAME, PLUGIN_VERSION, @@ -40,10 +44,11 @@ public annotation class ResolveContext( PERMISSION_NAMESPACE, PERMISSION_NAME, + PERMISSION_ID, // for parseFromString /** * Custom serializers allowed */ - RESTRICTED_NO_ARG_CONSTRUCTOR + RESTRICTED_NO_ARG_CONSTRUCTOR, } } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionId.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionId.kt index abac4bf12..86fb7e3c3 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionId.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionId.kt @@ -13,8 +13,7 @@ import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.serializer import net.mamoe.mirai.console.compiler.common.ResolveContext -import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PERMISSION_NAME -import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PERMISSION_NAMESPACE +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.* import net.mamoe.mirai.console.internal.data.map @@ -57,13 +56,39 @@ public data class PermissionId( * @throws IllegalArgumentException 在解析失败时抛出. */ @JvmStatic - public fun parseFromString(string: String): PermissionId { + public fun parseFromString(@ResolveContext(PERMISSION_ID) string: String): PermissionId { return kotlin.runCatching { string.split(':').let { (namespace, id) -> PermissionId(namespace, id) } }.getOrElse { throw IllegalArgumentException("Could not parse PermissionId from '$string'", it) } } + + /** + * 检查 [PermissionId.name] 的合法性. 在非法时抛出 [IllegalArgumentException] + */ + @JvmStatic + @Throws(IllegalArgumentException::class) + public fun checkPermissionIdName(@ResolveContext(PERMISSION_NAME) value: String) { + when { + value.isBlank() -> throw IllegalArgumentException("PermissionId.name should not be blank.") + value.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces is not yet allowed in PermissionId.name.") + value.contains(':') -> throw IllegalArgumentException("':' is forbidden in PermissionId.name.") + } + } + + /** + * 检查 [PermissionId.namespace] 的合法性. 在非法时抛出 [IllegalArgumentException] + */ + @JvmStatic + @Throws(IllegalArgumentException::class) + public fun checkPermissionIdNamespace(@ResolveContext(PERMISSION_NAME) value: String) { + when { + value.isBlank() -> throw IllegalArgumentException("PermissionId.namespace should not be blank.") + value.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces is not yet allowed in PermissionId.namespace.") + value.contains(':') -> throw IllegalArgumentException("':' is forbidden in PermissionId.namespace.") + } + } } } diff --git a/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java b/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java index fcf17ee32..325bd8263 100644 --- a/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java +++ b/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java @@ -11,6 +11,7 @@ package net.mamoe.mirai.console.compiler.common.diagnostics; import com.intellij.psi.PsiElement; import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1; +import org.jetbrains.kotlin.diagnostics.DiagnosticFactory2; import org.jetbrains.kotlin.diagnostics.Errors; import static org.jetbrains.kotlin.diagnostics.Severity.ERROR; @@ -19,6 +20,10 @@ public interface MiraiConsoleErrors { DiagnosticFactory1 ILLEGAL_PLUGIN_DESCRIPTION = DiagnosticFactory1.create(ERROR); DiagnosticFactory1 NOT_CONSTRUCTABLE_TYPE = DiagnosticFactory1.create(ERROR); DiagnosticFactory1 UNSERIALIZABLE_TYPE = DiagnosticFactory1.create(ERROR); + DiagnosticFactory2 ILLEGAL_COMMAND_NAME = DiagnosticFactory2.create(ERROR); + DiagnosticFactory2 ILLEGAL_PERMISSION_NAME = DiagnosticFactory2.create(ERROR); + DiagnosticFactory2 ILLEGAL_PERMISSION_ID = DiagnosticFactory2.create(ERROR); + DiagnosticFactory2 ILLEGAL_PERMISSION_NAMESPACE = DiagnosticFactory2.create(ERROR); @Deprecated Object _init = new Object() { diff --git a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt index ddab8060e..6140aed62 100644 --- a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt +++ b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt @@ -19,19 +19,47 @@ object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension { put( ILLEGAL_PLUGIN_DESCRIPTION, "{0}", - Renderers.STRING + Renderers.STRING, ) put( NOT_CONSTRUCTABLE_TYPE, - "类型 {0} 无法通过反射直接构造, 需要提供默认值.", - Renderers.STRING + "类型 ''{0}'' 无法通过反射直接构造, 需要提供默认值.", + Renderers.STRING, ) put( UNSERIALIZABLE_TYPE, - "类型 {0} 无法被自动序列化, 需要添加序列化器", - Renderers.STRING + "类型 ''{0}'' 无法被自动序列化, 需要添加序列化器", + Renderers.STRING, + ) + + put( + ILLEGAL_COMMAND_NAME, + "指令名 ''{0}'' 无效: {1}", + Renderers.STRING, + Renderers.STRING, + ) + + put( + ILLEGAL_PERMISSION_NAME, + "权限名 ''{0}'' 无效: {1}", + Renderers.STRING, + Renderers.STRING, + ) + + put( + ILLEGAL_PERMISSION_ID, + "权限 Id ''{0}'' 无效: {1}", + Renderers.STRING, + Renderers.STRING, + ) + + put( + ILLEGAL_PERMISSION_NAMESPACE, + "权限命名空间 ''{0}'' 无效: {1}", + Renderers.STRING, + Renderers.STRING, ) } diff --git a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt index 18ae85598..478656c70 100644 --- a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt +++ b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt @@ -50,11 +50,17 @@ enum class ResolveContextKind { PLUGIN_NAME, PLUGIN_VERSION, + COMMAND_NAME, + + PERMISSION_NAMESPACE, + PERMISSION_NAME, + PERMISSION_ID, + RESTRICTED_NO_ARG_CONSTRUCTOR ; companion object { - fun valueOfOrNull(string: String): ResolveContextKind? = ResolveContextKind.values().find { it.name == string } + fun valueOfOrNull(string: String): ResolveContextKind? = values().find { it.name == string } } } 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 4e4358b16..72f695b95 100644 --- a/tools/intellij-plugin/run/projects/test-project/build.gradle.kts +++ b/tools/intellij-plugin/run/projects/test-project/build.gradle.kts @@ -22,7 +22,7 @@ dependencies { compileOnly(kotlin("stdlib-jdk8")) val core = "1.3.0" - val console = "1.0-RC-dev-3" + val console = "1.0-RC-dev-4" compileOnly("net.mamoe:mirai-console:$console") compileOnly("net.mamoe:mirai-core:$core") diff --git a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt index 7dd80641a..a8f64c0fe 100644 --- a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt +++ b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt @@ -3,6 +3,8 @@ package org.example.myplugin import kotlinx.serialization.Serializable import net.mamoe.mirai.console.data.AutoSavePluginConfig import net.mamoe.mirai.console.data.value +import net.mamoe.mirai.console.permission.PermissionId +import net.mamoe.mirai.console.permission.PermissionService import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin @@ -17,6 +19,11 @@ object MyPluginMain : KotlinPlugin( id("") } ) { + override fun onEnable() { + super.onEnable() + PermissionService.INSTANCE.register(permissionId("dvs"), "ok") + } + fun test() { } diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/ContextualParametersChecker.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/ContextualParametersChecker.kt index fc5330cc5..790ba9596 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/ContextualParametersChecker.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/ContextualParametersChecker.kt @@ -10,7 +10,7 @@ package net.mamoe.mirai.console.intellij.diagnostics import com.intellij.psi.PsiElement -import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors +import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.* import net.mamoe.mirai.console.compiler.common.resolve.ResolveContextKind import net.mamoe.mirai.console.compiler.common.resolve.resolveContextKind import net.mamoe.mirai.console.intellij.resolve.resolveAllCalls @@ -31,7 +31,7 @@ class ContextualParametersChecker : DeclarationChecker { private val ID_REGEX: Regex = Regex("""([a-zA-Z]+(?:\.[a-zA-Z0-9]+)*)\.([a-zA-Z]+(?:-[a-zA-Z0-9]+)*)""") private val FORBIDDEN_ID_NAMES: Array = arrayOf("main", "console", "plugin", "config", "data") - private const val syntax = """类似于 "net.mamoe.mirai.example-plugin", 其中 "net.mamoe.mirai" 为 groupId, "example-plugin" 为插件名. """ + private const val syntax = """类似于 "net.mamoe.mirai.example-plugin", 其中 "net.mamoe.mirai" 为 groupId, "example-plugin" 为插件名""" /** * https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string @@ -40,37 +40,74 @@ class ContextualParametersChecker : DeclarationChecker { Regex("""^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?${'$'}""") fun checkPluginId(inspectionTarget: PsiElement, value: String): Diagnostic? { - if (value.isBlank()) return MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件 Id 不能为空. \n插件 Id$syntax") - if (value.none { it == '.' }) return MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, + if (value.isBlank()) return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件 Id 不能为空. \n插件 Id$syntax") + if (value.none { it == '.' }) return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件 Id '$value' 无效. 插件 Id 必须同时包含 groupId 和插件名称. $syntax") val lowercaseId = value.toLowerCase() if (ID_REGEX.matchEntire(value) == null) { - return MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件 Id 无效. 正确的插件 Id 应该满足正则表达式 '${ID_REGEX.pattern}', \n$syntax") + return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件 Id 无效. 正确的插件 Id 应该满足正则表达式 '${ID_REGEX.pattern}', \n$syntax") } FORBIDDEN_ID_NAMES.firstOrNull { it == lowercaseId }?.let { illegal -> - return MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "'$illegal' 不允许作为插件 Id. 确保插件 Id 不完全是这个名称.") + return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "'$illegal' 不允许作为插件 Id. 确保插件 Id 不完全是这个名称") } return null } fun checkPluginName(inspectionTarget: PsiElement, value: String): Diagnostic? { - if (value.isBlank()) return MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件名不能为空.") + if (value.isBlank()) return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件名不能为空") val lowercaseName = value.toLowerCase() FORBIDDEN_ID_NAMES.firstOrNull { it == lowercaseName }?.let { illegal -> - return MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "'$illegal' 不允许作为插件名. 确保插件名不完全是这个名称.") + return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "'$illegal' 不允许作为插件名. 确保插件名不完全是这个名称") } return null } fun checkPluginVersion(inspectionTarget: PsiElement, value: String): Diagnostic? { if (!SEMANTIC_VERSIONING_REGEX.matches(value)) { - return MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "版本号无效: '$value'. \nhttps://semver.org/lang/zh-CN/") + return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "版本号无效: '$value'. \nhttps://semver.org/lang/zh-CN/") } return null } + + fun checkCommandName(inspectionTarget: PsiElement, value: String): Diagnostic? { + return when { + value.isBlank() -> ILLEGAL_COMMAND_NAME.on(inspectionTarget, value, "指令名不能为空") + value.any { it.isWhitespace() } -> ILLEGAL_COMMAND_NAME.on(inspectionTarget, value, "暂时不允许指令名中存在空格") + value.contains(':') -> ILLEGAL_COMMAND_NAME.on(inspectionTarget, value, "指令名不允许包含 ':'") + value.contains('.') -> ILLEGAL_COMMAND_NAME.on(inspectionTarget, value, "指令名不允许包含 '.'") + else -> null + } + } + + fun checkPermissionNamespace(inspectionTarget: PsiElement, value: String): Diagnostic? { + return when { + value.isBlank() -> ILLEGAL_PERMISSION_NAMESPACE.on(inspectionTarget, value, "权限命名空间不能为空") + value.any { it.isWhitespace() } -> ILLEGAL_PERMISSION_NAMESPACE.on(inspectionTarget, value, "暂时不允许权限命名空间中存在空格") + value.contains(':') -> ILLEGAL_PERMISSION_NAMESPACE.on(inspectionTarget, value, "权限命名空间不允许包含 ':'") + else -> null + } + } + + fun checkPermissionName(inspectionTarget: PsiElement, value: String): Diagnostic? { + return when { + value.isBlank() -> ILLEGAL_PERMISSION_NAME.on(inspectionTarget, value, "权限名称不能为空") + value.any { it.isWhitespace() } -> ILLEGAL_PERMISSION_NAME.on(inspectionTarget, value, "暂时不允许权限名称中存在空格") + value.contains(':') -> ILLEGAL_PERMISSION_NAME.on(inspectionTarget, value, "权限名称不允许包含 ':'") + else -> null + } + } + + fun checkPermissionId(inspectionTarget: PsiElement, value: String): Diagnostic? { + return when { + value.isBlank() -> ILLEGAL_PERMISSION_ID.on(inspectionTarget, value, "权限 Id 不能为空") + value.any { it.isWhitespace() } -> ILLEGAL_PERMISSION_ID.on(inspectionTarget, value, "暂时不允许权限 Id 中存在空格") + value.count { it == ':' } != 1 -> ILLEGAL_PERMISSION_ID.on(inspectionTarget, value, "权限 Id 必须为 \"命名空间:名称\". 且命名空间和名称均不能包含 ':'") + else -> null + } + } } private val checkersMap: EnumMap Diagnostic?> = @@ -78,6 +115,10 @@ class ContextualParametersChecker : DeclarationChecker { put(ResolveContextKind.PLUGIN_NAME, ::checkPluginName) put(ResolveContextKind.PLUGIN_ID, ::checkPluginId) put(ResolveContextKind.PLUGIN_VERSION, ::checkPluginVersion) + put(ResolveContextKind.COMMAND_NAME, ::checkCommandName) + put(ResolveContextKind.PERMISSION_NAME, ::checkPermissionName) + put(ResolveContextKind.PERMISSION_NAMESPACE, ::checkPermissionNamespace) + put(ResolveContextKind.PERMISSION_ID, ::checkPermissionId) } override fun check(