diff --git a/mirai-console/backend/mirai-console/compatibility-validation/jvm/api/jvm.api b/mirai-console/backend/mirai-console/compatibility-validation/jvm/api/jvm.api index 7ab5b28051e..3057a63f06e 100644 --- a/mirai-console/backend/mirai-console/compatibility-validation/jvm/api/jvm.api +++ b/mirai-console/backend/mirai-console/compatibility-validation/jvm/api/jvm.api @@ -88,6 +88,25 @@ public abstract class net/mamoe/mirai/console/command/AbstractPluginCustomComman protected abstract fun sendMessageImpl (Ljava/lang/String;)V } +public abstract class net/mamoe/mirai/console/command/AbstractSubCommandGroup : net/mamoe/mirai/console/command/SubCommandGroup, net/mamoe/mirai/console/command/descriptor/CommandArgumentContextAware { + public fun ()V + public fun (Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext;)V + public synthetic fun (Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun getContext ()Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext; + public final fun getProvideOverloads ()Ljava/util/List; +} + +protected abstract interface annotation class net/mamoe/mirai/console/command/AbstractSubCommandGroup$AnotherCombinedCommand : java/lang/annotation/Annotation { +} + +protected abstract interface annotation class net/mamoe/mirai/console/command/AbstractSubCommandGroup$AnotherDescription : java/lang/annotation/Annotation { + public abstract fun value ()Ljava/lang/String; +} + +protected abstract interface annotation class net/mamoe/mirai/console/command/AbstractSubCommandGroup$AnotherSubCommand : java/lang/annotation/Annotation { + public abstract fun value ()[Ljava/lang/String; +} + public abstract class net/mamoe/mirai/console/command/AbstractUserCommandSender : net/mamoe/mirai/console/command/AbstractCommandSender, net/mamoe/mirai/console/command/UserCommandSender { public fun getBot ()Lnet/mamoe/mirai/Bot; public final fun getName ()Ljava/lang/String; @@ -389,7 +408,7 @@ public abstract class net/mamoe/mirai/console/command/CompositeCommand : net/mam public fun getUsage ()Ljava/lang/String; } -protected abstract interface annotation class net/mamoe/mirai/console/command/CompositeCommand$ChildCommand : java/lang/annotation/Annotation { +protected abstract interface annotation class net/mamoe/mirai/console/command/CompositeCommand$CombinedCommand : java/lang/annotation/Annotation { } protected abstract interface annotation class net/mamoe/mirai/console/command/CompositeCommand$Description : java/lang/annotation/Annotation { @@ -613,6 +632,10 @@ public final class net/mamoe/mirai/console/command/StrangerCommandSenderOnMessag public fun getFromEvent ()Lnet/mamoe/mirai/event/events/StrangerMessageEvent; } +public abstract interface class net/mamoe/mirai/console/command/SubCommandGroup { + public abstract fun getProvideOverloads ()Ljava/util/List; +} + public abstract interface class net/mamoe/mirai/console/command/SystemCommandSender : net/mamoe/mirai/console/command/CommandSender { public abstract fun isAnsiSupported ()Z } @@ -1097,6 +1120,12 @@ public final class net/mamoe/mirai/console/command/descriptor/StringValueArgumen public fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/String; } +public class net/mamoe/mirai/console/command/descriptor/SubcommandDeclarationClashException : net/mamoe/mirai/console/command/descriptor/CommandDeclarationException { + public fun (Ljava/lang/Object;Ljava/util/List;)V + public final fun getOwner ()Ljava/lang/Object; + public final fun getSignatures ()Ljava/util/List; +} + public abstract interface class net/mamoe/mirai/console/command/descriptor/TypeVariant { public static final field Companion Lnet/mamoe/mirai/console/command/descriptor/TypeVariant$Companion; public abstract fun getOutType ()Lkotlin/reflect/KType; diff --git a/mirai-console/backend/mirai-console/src/command/AbstractSubCommandGroup.kt b/mirai-console/backend/mirai-console/src/command/AbstractSubCommandGroup.kt new file mode 100644 index 00000000000..bffbf99084f --- /dev/null +++ b/mirai-console/backend/mirai-console/src/command/AbstractSubCommandGroup.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2019-2022 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/dev/LICENSE + */ + +package net.mamoe.mirai.console.command + +import net.mamoe.mirai.console.command.descriptor.* +import net.mamoe.mirai.console.compiler.common.ResolveContext +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER +import net.mamoe.mirai.console.internal.command.GroupedCommandSubCommandAnnotationResolver +import net.mamoe.mirai.console.internal.command.SubCommandReflector +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME +import net.mamoe.mirai.console.util.ConsoleExperimentalApi +import kotlin.annotation.AnnotationRetention.RUNTIME +import kotlin.annotation.AnnotationTarget.FUNCTION +import kotlin.annotation.AnnotationTarget.PROPERTY + +public abstract class AbstractSubCommandGroup( + overrideContext: CommandArgumentContext = EmptyCommandArgumentContext, +) : CommandArgumentContextAware, SubCommandGroup { + + private val reflector by lazy { SubCommandReflector(this, GroupedCommandSubCommandAnnotationResolver) } + + @ExperimentalCommandDescriptors + public final override val provideOverloads: List by lazy { + reflector.findSubCommands().also { + reflector.validate(it) + } + } + + /** + * 标记一个属性为子指令集合 + */ + @Retention(RUNTIME) + @Target(PROPERTY) + protected annotation class AnotherCombinedCommand( + ) + + /** + * 标记一个函数为子指令, 当 [value] 为空时使用函数名. + * @param value 子指令名 + */ + @Retention(RUNTIME) + @Target(FUNCTION) + protected annotation class AnotherSubCommand( + @ResolveContext(COMMAND_NAME) vararg val value: String = [], + ) + + /** 指令描述 */ + @Retention(RUNTIME) + @Target(FUNCTION) + protected annotation class AnotherDescription(val value: String) + + /** 参数名, 由具体Command决定用途 */ + @ConsoleExperimentalApi("Classname might change") + @Retention(RUNTIME) + @Target(AnnotationTarget.VALUE_PARAMETER) + protected annotation class AnotherName(val value: String) + + /** + * 智能参数解析环境 + */ // open since 2.12 + public override val context: CommandArgumentContext = CommandArgumentContext.Builtins + overrideContext + +} + + diff --git a/mirai-console/backend/mirai-console/src/command/CompositeCommand.kt b/mirai-console/backend/mirai-console/src/command/CompositeCommand.kt index c7b350f80da..5743d44e5e4 100644 --- a/mirai-console/backend/mirai-console/src/command/CompositeCommand.kt +++ b/mirai-console/backend/mirai-console/src/command/CompositeCommand.kt @@ -22,7 +22,6 @@ import kotlin.annotation.AnnotationRetention.RUNTIME import kotlin.annotation.AnnotationTarget.FUNCTION import kotlin.annotation.AnnotationTarget.PROPERTY - /** * 复合指令. 指令注册时候会通过反射构造指令解析器. * @@ -118,21 +117,21 @@ public abstract class CompositeCommand( public override val context: CommandArgumentContext = CommandArgumentContext.Builtins + overrideContext /** - * 标记一个函数为子指令, 当 [value] 为空时使用函数名. - * @param value 子指令名 + * 标记一个属性为子指令集合 */ @Retention(RUNTIME) - @Target(FUNCTION) - protected annotation class SubCommand( - @ResolveContext(COMMAND_NAME) vararg val value: String = [], + @Target(PROPERTY) + protected annotation class CombinedCommand( ) /** - * 标记一个属性为子指令集合 + * 标记一个函数为子指令, 当 [value] 为空时使用函数名. + * @param value 子指令名 */ @Retention(RUNTIME) - @Target(PROPERTY) - protected annotation class ChildCommand( + @Target(FUNCTION) + protected annotation class SubCommand( + @ResolveContext(COMMAND_NAME) vararg val value: String = [], ) /** 指令描述 */ diff --git a/mirai-console/backend/mirai-console/src/command/SimpleCommand.kt b/mirai-console/backend/mirai-console/src/command/SimpleCommand.kt index 7647143e775..10c0c172462 100644 --- a/mirai-console/backend/mirai-console/src/command/SimpleCommand.kt +++ b/mirai-console/backend/mirai-console/src/command/SimpleCommand.kt @@ -72,6 +72,7 @@ public abstract class SimpleCommand( } } + /** * 自动根据带有 [Handler] 注解的函数签名生成 [usage]. 也可以被覆盖. */ diff --git a/mirai-console/backend/mirai-console/src/command/SubCommandGroup.kt b/mirai-console/backend/mirai-console/src/command/SubCommandGroup.kt new file mode 100644 index 00000000000..793bf94834e --- /dev/null +++ b/mirai-console/backend/mirai-console/src/command/SubCommandGroup.kt @@ -0,0 +1,16 @@ +package net.mamoe.mirai.console.command + +import net.mamoe.mirai.console.command.descriptor.CommandSignatureFromKFunction +import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors +import net.mamoe.mirai.console.util.ConsoleExperimentalApi + +public interface SubCommandGroup { + + /** + * 被聚合时提供的子指令 + */ + @ConsoleExperimentalApi("Property name is experimental") + @ExperimentalCommandDescriptors + public val provideOverloads: List<@JvmWildcard CommandSignatureFromKFunction> + +} \ No newline at end of file diff --git a/mirai-console/backend/mirai-console/src/command/descriptor/Exceptions.kt b/mirai-console/backend/mirai-console/src/command/descriptor/Exceptions.kt index bd9319a7e7c..1d113a758bd 100644 --- a/mirai-console/backend/mirai-console/src/command/descriptor/Exceptions.kt +++ b/mirai-console/backend/mirai-console/src/command/descriptor/Exceptions.kt @@ -42,6 +42,13 @@ public open class CommandDeclarationClashException( public val signatures: List, ) : CommandDeclarationException("Declaration clash for command '${command.primaryName}': \n${signatures.joinToString("\n")}") +@ExperimentalCommandDescriptors +public open class SubcommandDeclarationClashException( + public val owner: Any, + public val signatures: List, +) : CommandDeclarationException("Declaration clash for owner '${owner::class.qualifiedName}': \n${signatures.joinToString("\n")}") + + public open class CommandDeclarationException : RuntimeException { public constructor() : super() public constructor(message: String?) : super(message) diff --git a/mirai-console/backend/mirai-console/src/internal/command/CommandReflector.kt b/mirai-console/backend/mirai-console/src/internal/command/CommandReflector.kt index 5a95c3df6cd..3fa2d14dfc2 100644 --- a/mirai-console/backend/mirai-console/src/internal/command/CommandReflector.kt +++ b/mirai-console/backend/mirai-console/src/internal/command/CommandReflector.kt @@ -64,7 +64,7 @@ internal fun Any.flattenCommandComponents(): MessageChain = buildMessageChain { @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") internal object CompositeCommandSubCommandAnnotationResolver : - SubCommandAnnotationResolver { + SubCommandAnnotationResolver { override fun hasAnnotation(ownerCommand: Command, function: KFunction<*>) = function.hasAnnotation() @@ -80,14 +80,42 @@ internal object CompositeCommandSubCommandAnnotationResolver : override fun getDescription(ownerCommand: Command, function: KFunction<*>): String? = function.findAnnotation()?.value - override fun hasPropertyAnnotation(command: Command, property: KProperty<*>): Boolean = - property.hasAnnotation() + override fun hasPropertyAnnotation(command: Command, kProperty: KProperty<*>): Boolean = + kProperty.hasAnnotation() + +} + +/* + * - 不看Function上的Annotation + * - 不从Function获取SubCommandNames + * - 看Property上的Annotation + */ +@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") +internal object GroupedCommandSubCommandAnnotationResolver : + SubCommandAnnotationResolver { + override fun hasAnnotation(ownerCommand: Any, function: KFunction<*>) = + function.hasAnnotation() + + override fun getSubCommandNames(ownerCommand: Any, function: KFunction<*>): Array { + val annotated = function.findAnnotation()!!.value + return if (annotated.isEmpty()) arrayOf(function.name) + else annotated + } + + override fun getAnnotatedName(ownerCommand: Any, parameter: KParameter): String? = + parameter.findAnnotation()?.value + + override fun getDescription(ownerCommand: Any, function: KFunction<*>): String? = + function.findAnnotation()?.value + + override fun hasPropertyAnnotation(command: Any, kProperty: KProperty<*>): Boolean = + kProperty.hasAnnotation() } @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") internal object SimpleCommandSubCommandAnnotationResolver : - SubCommandAnnotationResolver { + SubCommandAnnotationResolver { override fun hasAnnotation(ownerCommand: Command, function: KFunction<*>) = function.hasAnnotation() @@ -103,12 +131,12 @@ internal object SimpleCommandSubCommandAnnotationResolver : override fun hasPropertyAnnotation(command: Command, kProperty: KProperty<*>): Boolean = false } -internal interface SubCommandAnnotationResolver { - fun hasAnnotation(ownerCommand: Command, function: KFunction<*>): Boolean - fun getSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array - fun getAnnotatedName(ownerCommand: Command, parameter: KParameter): String? - fun getDescription(ownerCommand: Command, function: KFunction<*>): String? - fun hasPropertyAnnotation(command: Command, kProperty: KProperty<*>): Boolean +internal interface SubCommandAnnotationResolver { + fun hasAnnotation(ownerCommand: T, function: KFunction<*>): Boolean + fun getSubCommandNames(ownerCommand: T, function: KFunction<*>): Array + fun getAnnotatedName(ownerCommand: T, parameter: KParameter): String? + fun getDescription(ownerCommand: T, function: KFunction<*>): String? + fun hasPropertyAnnotation(command: T, kProperty: KProperty<*>): Boolean } @ConsoleExperimentalApi @@ -116,10 +144,10 @@ public class IllegalCommandDeclarationException : Exception { public override val message: String? public constructor( - ownerCommand: Command, + owner: Any, correspondingFunction: KFunction<*>, message: String?, - ) : super("Illegal command declaration: ${correspondingFunction.name} declared in ${ownerCommand::class.qualifiedName}") { + ) : super("Illegal command declaration: ${correspondingFunction.name} declared in ${owner::class.qualifiedName}") { this.message = message } @@ -134,47 +162,8 @@ public class IllegalCommandDeclarationException : Exception { @OptIn(ExperimentalCommandDescriptors::class) internal class CommandReflector( val command: Command, - private val annotationResolver: SubCommandAnnotationResolver, -) { - - @Suppress("NOTHING_TO_INLINE") - private inline fun KFunction<*>.illegalDeclaration( - message: String, - ): Nothing { - throw IllegalCommandDeclarationException(command, this, message) - } - - private fun KProperty<*>.isSubCommandProperty(): Boolean = annotationResolver.hasPropertyAnnotation(command, this) - private fun KFunction<*>.isSubCommandFunction(): Boolean = annotationResolver.hasAnnotation(command, this) - private fun KFunction<*>.checkExtensionReceiver() { - this.extensionReceiverParameter?.let { receiver -> - val classifier = receiver.type.classifierAsKClassOrNull() - if (classifier != null) { - if (!classifier.isSubclassOf(CommandSender::class) && !classifier.isSubclassOf(CommandContext::class)) { - illegalDeclaration("Extension receiver parameter type is not subclass of CommandSender nor CommandContext.") - } - } - } - } - - private fun KFunction<*>.checkNames() { - val names = annotationResolver.getSubCommandNames(command, this) - for (name in names) { - ILLEGAL_SUB_NAME_CHARS.find { it in name }?.let { - illegalDeclaration("'$it' is forbidden in command name.") - } - } - } - - private fun KFunction<*>.checkModifiers() { - if (isInline) illegalDeclaration("Command function cannot be inline") - if (visibility == KVisibility.PRIVATE) illegalDeclaration("Command function must be accessible from Mirai Console, that is, effectively public.") - if (this.hasAnnotation()) illegalDeclaration("Command function must not be static.") - - // should we allow abstract? - - // if (isAbstract) illegalDeclaration("Command function cannot be abstract") - } + private val annotationResolver: SubCommandAnnotationResolver, +) : SubCommandReflectible by SubCommandReflector(command, annotationResolver) { fun generateUsage(overloads: Iterable): String { return generateUsage(command, annotationResolver, overloads) @@ -183,7 +172,7 @@ internal class CommandReflector( companion object { fun generateUsage( command: Command, - annotationResolver: SubCommandAnnotationResolver?, + annotationResolver: SubCommandAnnotationResolver?, overloads: Iterable ): String { return overloads.joinToString("\n") { subcommand -> @@ -224,8 +213,61 @@ internal class CommandReflector( } } } +} + +@OptIn(ExperimentalCommandDescriptors::class) +internal interface SubCommandReflectible { + @Throws(IllegalCommandDeclarationException::class) + fun findSubCommands(): List + fun validate(signatures: List) +} + +@OptIn(ExperimentalCommandDescriptors::class) +internal class SubCommandReflector( + val owner: T, + private val annotationResolver: SubCommandAnnotationResolver, +) : SubCommandReflectible { + + @Suppress("NOTHING_TO_INLINE") + private inline fun KFunction<*>.illegalDeclaration( + message: String, + ): Nothing { + throw IllegalCommandDeclarationException(owner, this, message) + } + + private fun KProperty<*>.isSubCommandProviderProperty(): Boolean = annotationResolver.hasPropertyAnnotation(owner, this) + private fun KFunction<*>.isSubCommandFunction(): Boolean = annotationResolver.hasAnnotation(owner, this) + private fun KFunction<*>.checkExtensionReceiver() { + this.extensionReceiverParameter?.let { receiver -> + val classifier = receiver.type.classifierAsKClassOrNull() + if (classifier != null) { + if (!classifier.isSubclassOf(CommandSender::class) && !classifier.isSubclassOf(CommandContext::class)) { + illegalDeclaration("Extension receiver parameter type is not subclass of CommandSender nor CommandContext.") + } + } + } + } - fun validate(signatures: List) { + private fun KFunction<*>.checkNames() { + val names = annotationResolver.getSubCommandNames(owner, this) + for (name in names) { + ILLEGAL_SUB_NAME_CHARS.find { it in name }?.let { + illegalDeclaration("'$it' is forbidden in command name.") + } + } + } + + private fun KFunction<*>.checkModifiers() { + if (isInline) illegalDeclaration("Command function cannot be inline") + if (visibility == KVisibility.PRIVATE) illegalDeclaration("Command function must be accessible from Mirai Console, that is, effectively public.") + if (this.hasAnnotation()) illegalDeclaration("Command function must not be static.") + + // should we allow abstract? + + // if (isAbstract) illegalDeclaration("Command function cannot be abstract") + } + + override fun validate(signatures: List) { data class ErasedParameterInfo( val index: Int, @@ -261,19 +303,20 @@ internal class CommandReflector( value.size > 1 } ?: return - throw CommandDeclarationClashException(command, clashes.value.map { it.first }) + + throw SubcommandDeclarationClashException(owner, clashes.value.map { it.first }) } @Throws(IllegalCommandDeclarationException::class) - fun findSubCommands(): List { - val fromMemberFunctions = command::class.functions // exclude static later + override fun findSubCommands(): List { + val fromMemberFunctions = owner::class.functions // exclude static later .asSequence() .filter { it.isSubCommandFunction() } .onEach { it.checkExtensionReceiver() } .onEach { it.checkModifiers() } .onEach { it.checkNames() } .flatMap { function -> - val names = annotationResolver.getSubCommandNames(command, function) + val names = annotationResolver.getSubCommandNames(owner, function) if (names.isEmpty()) sequenceOf(createMapEntry(null, function)) else names.associateWith { function }.asSequence() } @@ -317,11 +360,11 @@ internal class CommandReflector( val instanceParameter = function.instanceParameter if (instanceParameter != null) { - check(instanceParameter.type.classifierAsKClass().isInstance(command)) { + check(instanceParameter.type.classifierAsKClass().isInstance(owner)) { "Bad command call resolved. " + - "Function expects instance parameter ${instanceParameter.type} whereas actual instance is ${command::class}." + "Function expects instance parameter ${instanceParameter.type} whereas actual instance is ${owner::class}." } - args[instanceParameter] = command + args[instanceParameter] = owner } if (receiverParameter != null) { @@ -349,14 +392,14 @@ internal class CommandReflector( } }.toList() - val fromMemberProperties = command::class.declaredMemberProperties + val fromMemberProperties = owner::class.declaredMemberProperties .asSequence() - .filter { it.isSubCommandProperty() } - .map { it.getter.call(command) } - .filter { it is CompositeCommand } + .filter { it.isSubCommandProviderProperty() } + .map { it.getter.call(owner) } + .filter { it is SubCommandGroup } .flatMap { property -> - property as CompositeCommand - property.overloads + property as SubCommandGroup + property.provideOverloads }.toList() val list: MutableList = ArrayList() @@ -407,5 +450,5 @@ internal class CommandReflector( } private fun KParameter.nameForCommandParameter(): String? = - annotationResolver.getAnnotatedName(command, this) ?: this.name + annotationResolver.getAnnotatedName(owner, this) ?: this.name } \ No newline at end of file diff --git a/mirai-console/backend/mirai-console/test/command/CommandContextTest.kt b/mirai-console/backend/mirai-console/test/command/CommandContextTest.kt index 2da0fb6da5e..1a59e80b605 100644 --- a/mirai-console/backend/mirai-console/test/command/CommandContextTest.kt +++ b/mirai-console/backend/mirai-console/test/command/CommandContextTest.kt @@ -36,39 +36,13 @@ internal class CommandContextTest : AbstractCommandTest() { companion object Key : AbstractMessageKey({ it.safeCast() }) } + /////////////////////////////////////////////////////////////////////////// // RawCommand /////////////////////////////////////////////////////////////////////////// @TestFactory fun `can execute with sender`(): List { - val containerCompositeCommand = object : CompositeCommand(owner, "test") { - @ChildCommand - val childFoo = object : CompositeCommand(owner, "useless") { - @SubCommand - fun CommandContext.childSub(arg: MessageChain) { - Testing.ok(arg) - } - } - @SubCommand - fun CommandContext.containerSub(arg: MessageChain) { - Testing.ok(arg) - } - } - val jContainerCompositeCommand = object : JCompositeCommand(owner, "test") { - @ChildCommand - val childFoo = object : JCompositeCommand(owner, "useless") { - @SubCommand - fun childSub(context: CommandContext, arg: MessageChain) { - Testing.ok(arg) - } - } - @SubCommand - fun containerSub(context: CommandContext, arg: MessageChain) { - Testing.ok(arg) - } - } - return listOf( object : RawCommand(owner, "test") { override suspend fun CommandContext.onCommand(args: MessageChain) { @@ -87,8 +61,6 @@ internal class CommandContextTest : AbstractCommandTest() { Testing.ok(arg) } } to "/test sub foo", - containerCompositeCommand to "/test childSub foo", - containerCompositeCommand to "/test containerSub foo", object : JRawCommand(owner, "test") { override fun onCommand(context: CommandContext, args: MessageChain) { @@ -107,8 +79,6 @@ internal class CommandContextTest : AbstractCommandTest() { Testing.ok(arg) } } to "/test sub foo", - jContainerCompositeCommand to "/test childSub foo", - jContainerCompositeCommand to "/test containerSub foo", ).map { (instance, cmd) -> DynamicTest.dynamicTest(instance::class.supertypes.first().classifierAsKClass().simpleName) { runBlocking { @@ -127,33 +97,6 @@ internal class CommandContextTest : AbstractCommandTest() { @TestFactory fun `RawCommand can execute and get original chain`(): List { - val containerCompositeCommand = object : CompositeCommand(owner, "test") { - @ChildCommand - val childFoo = object : CompositeCommand(owner, "useless") { - @SubCommand - fun CommandContext.childSub(arg: MessageChain) { - Testing.ok(originalMessage) - } - } - @SubCommand - fun CommandContext.containerSub(arg: MessageChain) { - Testing.ok(originalMessage) - } - } - val jContainerCompositeCommand = object : JCompositeCommand(owner, "test") { - @ChildCommand - val childFoo = object : JCompositeCommand(owner, "useless") { - @SubCommand - fun childSub(context: CommandContext, arg: MessageChain) { - Testing.ok(context.originalMessage) - } - } - @SubCommand - fun containerSub(context: CommandContext, arg: MessageChain) { - Testing.ok(context.originalMessage) - } - } - return listOf( object : RawCommand(owner, "test") { override suspend fun CommandContext.onCommand(args: MessageChain) { @@ -172,8 +115,6 @@ internal class CommandContextTest : AbstractCommandTest() { Testing.ok(originalMessage) } } to "/test sub foo", - containerCompositeCommand to "/test childSub foo", - containerCompositeCommand to "/test containerSub foo", object : JRawCommand(owner, "test") { override fun onCommand(context: CommandContext, args: MessageChain) { @@ -192,8 +133,6 @@ internal class CommandContextTest : AbstractCommandTest() { Testing.ok(context.originalMessage) } } to "/test sub foo", - jContainerCompositeCommand to "/test childSub foo", - jContainerCompositeCommand to "/test containerSub foo", ).map { (instance, cmd) -> DynamicTest.dynamicTest(instance::class.supertypes.first().classifierAsKClass().simpleName) { runBlocking { @@ -212,33 +151,6 @@ internal class CommandContextTest : AbstractCommandTest() { @TestFactory fun `can execute and get metadata`(): List { - val containerCompositeCommand = object : CompositeCommand(owner, "test") { - @ChildCommand - val childFoo = object : CompositeCommand(owner, "useless") { - @SubCommand - fun CommandContext.childSub(arg: MessageChain) { - Testing.ok(originalMessage[MyMetadata]) - } - } - @SubCommand - fun CommandContext.containerSub(arg: MessageChain) { - Testing.ok(originalMessage[MyMetadata]) - } - } - val jContainerCompositeCommand = object : JCompositeCommand(owner, "test") { - @ChildCommand - val childFoo = object : JCompositeCommand(owner, "useless") { - @SubCommand - fun childSub(context: CommandContext, arg: MessageChain) { - Testing.ok(context.originalMessage[MyMetadata]) - } - } - @SubCommand - fun containerSub(context: CommandContext, arg: MessageChain) { - Testing.ok(context.originalMessage[MyMetadata]) - } - } - val metadata = MyMetadata() return listOf( object : RawCommand(owner, "test") { @@ -258,8 +170,7 @@ internal class CommandContextTest : AbstractCommandTest() { Testing.ok(originalMessage[MyMetadata]) } } to messageChainOf(PlainText("/test"), PlainText("sub"), metadata, PlainText("foo")), - containerCompositeCommand to messageChainOf(PlainText("/test"), PlainText("childSub"), metadata, PlainText("foo")), - containerCompositeCommand to messageChainOf(PlainText("/test"), PlainText("containerSub"), metadata, PlainText("foo")), + object : JRawCommand(owner, "test") { override fun onCommand(context: CommandContext, args: MessageChain) { @@ -278,8 +189,6 @@ internal class CommandContextTest : AbstractCommandTest() { Testing.ok(context.originalMessage[MyMetadata]) } } to messageChainOf(PlainText("/test"), PlainText("sub"), metadata, PlainText("foo")), - jContainerCompositeCommand to messageChainOf(PlainText("/test"), PlainText("childSub"), metadata, PlainText("foo")), - jContainerCompositeCommand to messageChainOf(PlainText("/test"), PlainText("containerSub"), metadata, PlainText("foo")), ).map { (instance, cmd) -> DynamicTest.dynamicTest(instance::class.supertypes.first().classifierAsKClass().simpleName) { runBlocking { @@ -298,33 +207,6 @@ internal class CommandContextTest : AbstractCommandTest() { @TestFactory fun `RawCommand can execute and get chain including metadata`(): List { - val containerCompositeCommand = object : CompositeCommand(owner, "test") { - @ChildCommand - val childFoo = object : CompositeCommand(owner, "useless") { - @SubCommand - fun CommandContext.childSub(arg: MessageChain) { - Testing.ok(originalMessage) - } - } - @SubCommand - fun CommandContext.containerSub(arg: MessageChain) { - Testing.ok(originalMessage) - } - } - val jContainerCompositeCommand = object : JCompositeCommand(owner, "test") { - @ChildCommand - val childFoo = object : JCompositeCommand(owner, "useless") { - @SubCommand - fun childSub(context: CommandContext, arg: MessageChain) { - Testing.ok(context.originalMessage) - } - } - @SubCommand - fun containerSub(context: CommandContext, arg: MessageChain) { - Testing.ok(context.originalMessage) - } - } - val metadata = MyMetadata() return listOf( object : RawCommand(owner, "test") { @@ -344,8 +226,6 @@ internal class CommandContextTest : AbstractCommandTest() { Testing.ok(originalMessage) } } to messageChainOf(PlainText("/test"), PlainText("sub"), metadata, PlainText("foo")), - containerCompositeCommand to messageChainOf(PlainText("/test"), PlainText("childSub"), metadata, PlainText("foo")), - containerCompositeCommand to messageChainOf(PlainText("/test"), PlainText("containerSub"), metadata, PlainText("foo")), object : JRawCommand(owner, "test") { @@ -365,8 +245,6 @@ internal class CommandContextTest : AbstractCommandTest() { Testing.ok(context.originalMessage) } } to messageChainOf(PlainText("/test"), PlainText("sub"), metadata, PlainText("foo")), - jContainerCompositeCommand to messageChainOf(PlainText("/test"), PlainText("childSub"), metadata, PlainText("foo")), - jContainerCompositeCommand to messageChainOf(PlainText("/test"), PlainText("containerSub"), metadata, PlainText("foo")), ).map { (instance, cmd) -> DynamicTest.dynamicTest(instance::class.supertypes.first().classifierAsKClass().simpleName) { runBlocking { diff --git a/mirai-console/backend/mirai-console/test/command/InstanceTestCommand.kt b/mirai-console/backend/mirai-console/test/command/InstanceTestCommand.kt index 25dc00dbac2..c1b29f59cd0 100644 --- a/mirai-console/backend/mirai-console/test/command/InstanceTestCommand.kt +++ b/mirai-console/backend/mirai-console/test/command/InstanceTestCommand.kt @@ -36,33 +36,37 @@ import java.time.temporal.TemporalAccessor import kotlin.reflect.KClass import kotlin.test.* + + + class TestContainerCompositeCommand : CompositeCommand( owner, "testContainerComposite", "tsPC" ) { - class ChildCompositeCommand1 : CompositeCommand(owner, "useless") { - @SubCommand - fun foo(seconds: Int) { - Testing.ok(seconds) + class TopGroup : AbstractSubCommandGroup() { + + class NestGroup : AbstractSubCommandGroup() { + @AnotherSubCommand + fun foo2(seconds: Int) { + Testing.ok(seconds) + } } - } - class ChildCompositeCommand2 : CompositeCommand(owner, "useless") { - @SubCommand - fun bar(seconds: Int) { + @AnotherCombinedCommand + val provider: SubCommandGroup = NestGroup() + + @AnotherSubCommand + fun foo1(seconds: Int) { Testing.ok(seconds) } } - @ChildCommand - val child1: ChildCompositeCommand1 = ChildCompositeCommand1() - - @ChildCommand - val child2: ChildCompositeCommand2 = ChildCompositeCommand2() + @CombinedCommand + val provider: SubCommandGroup = TopGroup() @SubCommand - fun containerFoo(seconds: Int) { + fun foo0(seconds: Int) { Testing.ok(seconds) } @@ -542,17 +546,14 @@ internal class InstanceTestCommand : AbstractConsoleInstanceTest() { @Test fun `container composite command executing`() = runBlocking { containerCompositeCommand.withRegistration { - assertEquals(1, withTesting { - assertSuccess(containerCompositeCommand.execute(sender, "containerFoo 1")) + assertEquals(0, withTesting { + assertSuccess(containerCompositeCommand.execute(sender, "foo0 0")) }) assertEquals(1, withTesting { - assertSuccess(containerCompositeCommand.execute(sender, "containerBar 1")) - }) - assertEquals(2, withTesting { - assertSuccess(containerCompositeCommand.execute(sender, "foo 2")) + assertSuccess(containerCompositeCommand.execute(sender, "foo1 1")) }) assertEquals(2, withTesting { - assertSuccess(containerCompositeCommand.execute(sender, "bar 2")) + assertSuccess(containerCompositeCommand.execute(sender, "foo2 2")) }) } }