Support ILLEGAL_COMMAND_NAME, ILLEGAL_PERMISSION_NAME, ILLEGAL_PERMISSION_ID, ILLEGAL_PERMISSION_NAMESPACE

This commit is contained in:
Him188 2020-09-19 00:22:38 +08:00
parent 78ebdf038d
commit 3fa7c9e128
9 changed files with 138 additions and 20 deletions

View File

@ -104,6 +104,7 @@ public interface Command {
/**
* 检查指令名的合法性. 在非法时抛出 [IllegalArgumentException]
*/
@JvmStatic
@Throws(IllegalArgumentException::class)
public fun checkCommandName(@ResolveContext(COMMAND_NAME) name: String) {
when {

View File

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

View File

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

View File

@ -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<PsiElement, String> ILLEGAL_PLUGIN_DESCRIPTION = DiagnosticFactory1.create(ERROR);
DiagnosticFactory1<PsiElement, String> NOT_CONSTRUCTABLE_TYPE = DiagnosticFactory1.create(ERROR);
DiagnosticFactory1<PsiElement, String> UNSERIALIZABLE_TYPE = DiagnosticFactory1.create(ERROR);
DiagnosticFactory2<PsiElement, String, String> ILLEGAL_COMMAND_NAME = DiagnosticFactory2.create(ERROR);
DiagnosticFactory2<PsiElement, String, String> ILLEGAL_PERMISSION_NAME = DiagnosticFactory2.create(ERROR);
DiagnosticFactory2<PsiElement, String, String> ILLEGAL_PERMISSION_ID = DiagnosticFactory2.create(ERROR);
DiagnosticFactory2<PsiElement, String, String> ILLEGAL_PERMISSION_NAMESPACE = DiagnosticFactory2.create(ERROR);
@Deprecated
Object _init = new Object() {

View File

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

View File

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

View File

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

View File

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

View File

@ -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<String> = 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<ResolveContextKind, (declaration: PsiElement, value: String) -> 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(