Skip to content

Commit 86183c0

Browse files
committed
Fix field ordering for subcommands deriving from a non-Command base type.
Base type fields now come first, and the fields from the command come last.
1 parent 2d5fb18 commit 86183c0

File tree

2 files changed

+37
-11
lines changed

2 files changed

+37
-11
lines changed

Src/CommandLine.cs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -338,18 +338,13 @@ private static FieldInfo[] getEligibleFields(Type type)
338338
{
339339
var bindingFlags = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
340340

341-
// Get all fields from the type
342-
var fields = type.GetFields(bindingFlags).ToList();
341+
// Include this type, plus all base types in the chain until we find one with CommandNameAttribute or CommandLineAttribute
342+
var types = type.SelectChain(t => t.BaseType)
343+
.TakeWhile(t => t == type || (t != typeof(object) && !t.IsDefined<CommandNameAttribute>() && !t.IsDefined<CommandLineAttribute>()));
344+
// Starting with the deepest base type, get all fields from all the above types
345+
var fields = types.Reverse().SelectMany(t => t.GetFields(bindingFlags)).ToArray();
343346

344-
// Keep adding fields from the base type until we find one with CommandNameAttribute or CommandLineAttribute
345-
var testType = type.BaseType;
346-
while (testType != typeof(object) && !testType.IsDefined<CommandNameAttribute>() && !testType.IsDefined<CommandLineAttribute>())
347-
{
348-
fields.AddRange(testType.GetFields(bindingFlags));
349-
testType = testType.BaseType;
350-
}
351-
352-
return fields.ToArray();
347+
return fields;
353348
}
354349

355350
private static Type[] getDirectSubcommands(Type type)

Tests/CommandLineTests.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,26 @@ public ConsoleColoredString Validate()
6464
return null;
6565
}
6666
}
67+
68+
[CommandLine]
69+
abstract class CmdBase3
70+
{
71+
[IsPositional, IsMandatory]
72+
public string Base;
73+
}
74+
75+
abstract class CmdSubBase3 : CmdBase3
76+
{
77+
[IsPositional, IsMandatory]
78+
public string SubBase;
79+
}
80+
81+
[CommandName("sub")]
82+
sealed class CmdSubcmd3 : CmdSubBase3
83+
{
84+
[IsPositional, IsMandatory]
85+
public string ItemName;
86+
}
6787
#pragma warning restore 0649 // Field is never assigned to, and will always have its default value null
6888

6989
[Fact]
@@ -158,4 +178,15 @@ public static void TestSubcommandValidation()
158178
Assert.Equal("item", cs2.ItemName);
159179
Assert.Equal(1, CmdSubcmd2.ValidateCalled);
160180
}
181+
182+
[Fact]
183+
public static void TestSubcommandIntermediate()
184+
{
185+
var c3 = CommandLineParser.Parse<CmdBase3>(["base", "sub", "subbase", "item"]);
186+
Assert.IsType<CmdSubcmd3>(c3);
187+
var cs3 = (CmdSubcmd3) c3;
188+
Assert.Equal("base", cs3.Base);
189+
Assert.Equal("subbase", cs3.SubBase);
190+
Assert.Equal("item", cs3.ItemName);
191+
}
161192
}

0 commit comments

Comments
 (0)