diff --git a/CliFx.Tests/OptionBindingSpecs.cs b/CliFx.Tests/OptionBindingSpecs.cs index 0a9f301..726bb31 100644 --- a/CliFx.Tests/OptionBindingSpecs.cs +++ b/CliFx.Tests/OptionBindingSpecs.cs @@ -704,4 +704,64 @@ public class Command : ICommand exitCode.Should().NotBe(0); stdErr.Should().Contain("expects a single argument, but provided with multiple"); } + + + [Fact] + public async Task Option_bound_using_interfaces_for_multiple_inheritance_should_work() + { + // Arrange + var commandType = DynamicCommandBuilder.Compile( + // language=cs + @" +public static class FooBarLogger +{ + public static bool Foo { get; set; } = false; + public static bool Bar { get; set; } = false; +} +public interface IOptionBar : ICommand +{ + [CommandOption(""bar"")] + public bool Bar + { + get => FooBarLogger.Bar; + set => FooBarLogger.Bar = value; + } +} +public interface IOptionFoo : ICommand +{ + [CommandOption(""foo"")] + public bool Foo + { + get => FooBarLogger.Foo; + set => FooBarLogger.Foo = value; + } +} + +[Command] +public class Command : IOptionFoo, IOptionBar +{ + public ValueTask ExecuteAsync(IConsole console) + { + console.Output.WriteLine($""Foo: { FooBarLogger.Foo }""); + console.Output.WriteLine($""Bar: { FooBarLogger.Bar }""); + return default; + } +} +"); + + var application = new CliApplicationBuilder() + .AddCommand(commandType) + .UseConsole(FakeConsole) + .Build(); + + // Act + var exitCode = await application.RunAsync( + new[] {"--foo" , "--bar"}); + + var stdOut = FakeConsole.ReadOutputString(); + + // Assert + exitCode.Should().Be(0); + stdOut.Trim().Should().Be("Foo: True\r\nBar: True"); + } } \ No newline at end of file diff --git a/CliFx/Schema/CommandSchema.cs b/CliFx/Schema/CommandSchema.cs index cc75f7d..f6acb8c 100644 --- a/CliFx/Schema/CommandSchema.cs +++ b/CliFx/Schema/CommandSchema.cs @@ -86,13 +86,18 @@ internal partial class CommandSchema var implicitOptionSchemas = string.IsNullOrWhiteSpace(name) ? new[] {OptionSchema.HelpOption, OptionSchema.VersionOption} : new[] {OptionSchema.HelpOption}; - - var parameterSchemas = type.GetProperties() + + // Include interface members for multiple inheritance + // If interface inherits from ICommand, it will be included + var interfaces = type.GetInterfaces().Where(i => i != typeof(ICommand) && typeof(ICommand).IsAssignableFrom(i) ); + var properties = type.GetProperties().Concat(interfaces.SelectMany(i => i.GetProperties())).ToArray(); + + var parameterSchemas = properties .Select(ParameterSchema.TryResolve) .WhereNotNull() .ToArray(); - var optionSchemas = type.GetProperties() + var optionSchemas = properties .Select(OptionSchema.TryResolve) .WhereNotNull() .Concat(implicitOptionSchemas)