Make toolshed's CommandImplementationAttribute optional (#6218)

This commit is contained in:
Leon Friedrich
2025-09-23 15:47:21 +12:00
committed by GitHub
parent 585e847818
commit ddeb78accd
6 changed files with 38 additions and 8 deletions

View File

@@ -9,7 +9,7 @@ namespace Robust.Shared.Toolshed;
/// Used to mark a class so that <see cref="ToolshedManager"/> automatically discovers and registers it.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
[MeansImplicitUse]
[MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)]
public sealed class ToolshedCommandAttribute : Attribute
{
public string? Name = null;

View File

@@ -30,10 +30,24 @@ public abstract partial class ToolshedCommand
return true;
}
internal IEnumerable<MethodInfo> GetGenericImplementations()
internal MethodInfo[] GetMethods()
{
var methods = GetType().GetMethods(MethodFlags);
// CommandImplementationAttribute is optional if there is only a single method defined by the type,
return methods.Length == 1
? methods
: methods.Where(x => x.HasCustomAttribute<CommandImplementationAttribute>()).ToArray();
}
internal MethodInfo[] GetMethods(string? subCommand)
{
if (subCommand == null)
return GetMethods();
return GetType()
.GetMethods(MethodFlags)
.Where(x => x.HasCustomAttribute<CommandImplementationAttribute>());
.Where(x => x.GetCustomAttribute<CommandImplementationAttribute>()?.SubCommand == subCommand)
.ToArray();
}
}

View File

@@ -105,7 +105,7 @@ public abstract partial class ToolshedCommand
throw new InvalidCommandImplementation($"{nameof(TypeParameterParsers)} element {typeParser} is not {nameof(TypeTypeParser)} or assignable to {typeof(CustomTypeParser<Type>).PrettyName()}");
}
var impls = GetGenericImplementations().ToArray();
var impls = GetMethods();
if (impls.Length == 0)
throw new Exception($"Command has no implementations?");

View File

@@ -51,10 +51,7 @@ internal sealed class ToolshedCommandImplementor
FullName = SubCommand == null ? Owner.Name : $"{Owner.Name}:{SubCommand}";
_toolshed = toolshed;
Methods = Owner.GetType()
.GetMethods(ToolshedCommand.MethodFlags)
.Where(x => x.GetCustomAttribute<CommandImplementationAttribute>() is { } attr &&
attr.SubCommand == SubCommand)
Methods = Owner.GetMethods(SubCommand)
.Select(x => new CommandMethod(x, this))
.ToArray();

View File

@@ -215,3 +215,18 @@ public sealed class TestNestedEnumerableCommand : ToolshedCommand
[CommandImplementation]
public IEnumerable<ProtoId<EntityCategoryPrototype>> Impl() => _arr.OrderByDescending(x => x.Id);
}
[ToolshedCommand]
public sealed class TestImplicitImplCommand : ToolshedCommand
{
public int Impl() => 1;
}
[ToolshedCommand]
public sealed class TestExplicitImplCommand : ToolshedCommand
{
public int Impl() => 1;
[CommandImplementation]
public int Impl2() => 2;
}

View File

@@ -60,6 +60,10 @@ public sealed class ToolshedTests : ToolshedTest
Assert.Throws<AssertionException>(() => ParseError<OutOfInputError>("i 2"));
Assert.That(ExpectedErrors.Count, Is.EqualTo(1));
ExpectedErrors.Clear();
// Check that the CommandImplementationAttibute is optional when the type only defines one method.
AssertResult("testimplicitimpl", 1);
AssertResult("testexplicitimpl", 2);
});
}