diff --git a/CliFx.Analyzers.Tests/CliFx.Analyzers.Tests.csproj b/CliFx.Analyzers.Tests/CliFx.Analyzers.Tests.csproj
index 2bdacc8..ac96222 100644
--- a/CliFx.Analyzers.Tests/CliFx.Analyzers.Tests.csproj
+++ b/CliFx.Analyzers.Tests/CliFx.Analyzers.Tests.csproj
@@ -2,7 +2,6 @@
net6.0
- true
true
opencover
diff --git a/CliFx.Tests/CliFx.Tests.csproj b/CliFx.Tests/CliFx.Tests.csproj
index 97bc38d..b548d34 100644
--- a/CliFx.Tests/CliFx.Tests.csproj
+++ b/CliFx.Tests/CliFx.Tests.csproj
@@ -2,7 +2,6 @@
net6.0
- true
true
opencover
diff --git a/CliFx.Tests/OptionBindingSpecs.cs b/CliFx.Tests/OptionBindingSpecs.cs
index 10155f5..3fbc891 100644
--- a/CliFx.Tests/OptionBindingSpecs.cs
+++ b/CliFx.Tests/OptionBindingSpecs.cs
@@ -496,6 +496,83 @@ public class Command : ICommand
);
}
+ [Fact]
+ public async Task Option_binding_supports_multiple_inheritance_through_default_interface_members()
+ {
+ // Arrange
+ var commandType = DynamicCommandBuilder.Compile(
+ // language=cs
+ @"
+public static class SharedContext
+{
+ public static int Foo { get; set; }
+
+ public static bool Bar { get; set; }
+}
+
+public interface IHasFoo : ICommand
+{
+ [CommandOption(""foo"")]
+ public int Foo
+ {
+ get => SharedContext.Foo;
+ set => SharedContext.Foo = value;
+ }
+}
+
+public interface IHasBar : ICommand
+{
+ [CommandOption(""bar"")]
+ public bool Bar
+ {
+ get => SharedContext.Bar;
+ set => SharedContext.Bar = value;
+ }
+}
+
+public interface IHasBaz : ICommand
+{
+ public string Baz { get; set; }
+}
+
+[Command]
+public class Command : IHasFoo, IHasBar, IHasBaz
+{
+ [CommandOption(""baz"")]
+ public string Baz { get; set; }
+
+ public ValueTask ExecuteAsync(IConsole console)
+ {
+ console.Output.WriteLine(""Foo = "" + SharedContext.Foo);
+ console.Output.WriteLine(""Bar = "" + SharedContext.Bar);
+ console.Output.WriteLine(""Baz = "" + Baz);
+
+ return default;
+ }
+}
+");
+
+ var application = new CliApplicationBuilder()
+ .AddCommand(commandType)
+ .UseConsole(FakeConsole)
+ .Build();
+
+ // Act
+ var exitCode = await application.RunAsync(
+ new[] { "--foo", "42", "--bar", "--baz", "xyz" }
+ );
+
+ var stdOut = FakeConsole.ReadOutputString();
+
+ // Assert
+ exitCode.Should().Be(0);
+ stdOut.Should().ConsistOfLines(
+ "Foo = 42",
+ "Bar = True",
+ "Baz = xyz"
+ );
+ }
+
[Fact]
public async Task Option_binding_does_not_consider_a_negative_number_as_an_option_name_or_short_name()
{
@@ -704,64 +781,4 @@ 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" + Environment.NewLine + "Bar: True");
- }
}
\ No newline at end of file
diff --git a/CliFx/Schema/CommandSchema.cs b/CliFx/Schema/CommandSchema.cs
index f6acb8c..97c87a3 100644
--- a/CliFx/Schema/CommandSchema.cs
+++ b/CliFx/Schema/CommandSchema.cs
@@ -86,12 +86,22 @@ internal partial class CommandSchema
var implicitOptionSchemas = string.IsNullOrWhiteSpace(name)
? new[] {OptionSchema.HelpOption, OptionSchema.VersionOption}
: new[] {OptionSchema.HelpOption};
-
- // 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 properties = type
+ // Get properties directly on command type
+ .GetProperties()
+ // Get non-abstract properties on interfaces (to support default interfaces members)
+ .Union(type
+ .GetInterfaces()
+ // Only interfaces implementing ICommand for explicitness
+ .Where(i => typeof(ICommand).IsAssignableFrom(i) && i != typeof(ICommand))
+ .SelectMany(i => i
+ .GetProperties()
+ .Where(p => !p.GetMethod.IsAbstract && !p.SetMethod.IsAbstract)
+ )
+ )
+ .ToArray();
+
var parameterSchemas = properties
.Select(ParameterSchema.TryResolve)
.WhereNotNull()