Skip to content

Commit 6c71bd7

Browse files
authored
Fix #1638 (#1677)
* make Option (non-generic) abstract * make Argument (non-generic) abstract
1 parent d82ab23 commit 6c71bd7

File tree

52 files changed

+594
-931
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+594
-931
lines changed

src/Common/ArgumentBuilder.cs

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
2+
using System;
3+
using System.CommandLine;
4+
using System.Reflection;
5+
6+
internal static class ArgumentBuilder
7+
{
8+
private static readonly ConstructorInfo _ctor;
9+
10+
static ArgumentBuilder()
11+
{
12+
_ctor = typeof(Argument<string>).GetConstructor(new[] { typeof(string), typeof(string) });
13+
}
14+
15+
public static Argument CreateArgument(Type valueType, string name = "value")
16+
{
17+
var argumentType = typeof(Argument<>).MakeGenericType(valueType);
18+
19+
#if NET6_0_OR_GREATER
20+
var ctor = (ConstructorInfo)argumentType.GetMemberWithSameMetadataDefinitionAs(_ctor);
21+
#else
22+
var ctor = argumentType.GetConstructor(new[] { typeof(string), typeof(string) });
23+
#endif
24+
25+
var option = (Argument)ctor.Invoke(new object[] { name, null });
26+
27+
return option;
28+
}
29+
}

src/Common/OptionBuilder.cs

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.Reflection;
5+
6+
namespace System.CommandLine.Utility;
7+
8+
internal static class OptionBuilder
9+
{
10+
private static readonly ConstructorInfo _ctor;
11+
12+
static OptionBuilder()
13+
{
14+
_ctor = typeof(Option<string>).GetConstructor(new[] { typeof(string), typeof(string) });
15+
}
16+
17+
public static Option CreateOption(string name, Type valueType)
18+
{
19+
var optionType = typeof(Option<>).MakeGenericType(valueType);
20+
21+
#if NET6_0_OR_GREATER
22+
var ctor = (ConstructorInfo)optionType.GetMemberWithSameMetadataDefinitionAs(_ctor);
23+
#else
24+
var ctor = optionType.GetConstructor(new[] { typeof(string), typeof(string) });
25+
#endif
26+
27+
var option = (Option)ctor.Invoke(new object[] { name, null });
28+
29+
return option;
30+
}
31+
}

src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_api_is_not_changed.approved.txt

+4-8
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
System.CommandLine
2-
public class Argument : Symbol, System.CommandLine.Binding.IValueDescriptor, System.CommandLine.Completions.ICompletionSource
3-
.ctor()
4-
.ctor(System.String name = null, System.String description = null)
2+
public abstract class Argument : Symbol, System.CommandLine.Binding.IValueDescriptor, System.CommandLine.Completions.ICompletionSource
53
public ArgumentArity Arity { get; set; }
64
public CompletionSourceList Completions { get; }
75
public System.Boolean HasDefaultValue { get; }
86
public System.String HelpName { get; set; }
9-
public System.Type ValueType { get; set; }
7+
public System.Type ValueType { get; }
108
public System.Void AddValidator(System.CommandLine.Parsing.ValidateSymbolResult<System.CommandLine.Parsing.ArgumentResult> validate)
119
public System.Collections.Generic.IEnumerable<System.CommandLine.Completions.CompletionItem> GetCompletions(System.CommandLine.Completions.CompletionContext context)
1210
public System.Object GetDefaultValue()
@@ -21,7 +19,7 @@
2119
.ctor(Func<T> getDefaultValue)
2220
.ctor(System.String name, ParseArgument<T> parse, System.Boolean isDefault = False, System.String description = null)
2321
.ctor(ParseArgument<T> parse, System.Boolean isDefault = False)
24-
public System.Type ValueType { get; set; }
22+
public System.Type ValueType { get; }
2523
public struct ArgumentArity : System.ValueType, System.IEquatable<ArgumentArity>
2624
public static ArgumentArity ExactlyOne { get; }
2725
public static ArgumentArity OneOrMore { get; }
@@ -181,9 +179,7 @@
181179
public System.String UnrecognizedCommandOrArgument(System.String arg)
182180
public System.String VersionOptionCannotBeCombinedWithOtherArguments(System.String optionAlias)
183181
public System.String VersionOptionDescription()
184-
public class Option : IdentifierSymbol, System.CommandLine.Binding.IValueDescriptor, System.CommandLine.Completions.ICompletionSource
185-
.ctor(System.String name, System.String description = null, System.Type argumentType = null, System.Func<System.Object> getDefaultValue = null, ArgumentArity arity = null)
186-
.ctor(System.String[] aliases, System.String description = null, System.Type argumentType = null, System.Func<System.Object> getDefaultValue = null, ArgumentArity arity = null)
182+
public abstract class Option : IdentifierSymbol, System.CommandLine.Binding.IValueDescriptor, System.CommandLine.Completions.ICompletionSource
187183
public System.Boolean AllowMultipleArgumentsPerToken { get; set; }
188184
public System.String ArgumentHelpName { get; set; }
189185
public ArgumentArity Arity { get; set; }

src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_Options_Bare.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ public class Perf_Parser_Options_Bare
2222
private IEnumerable<Option> GenerateTestOptions(int count, ArgumentArity arity)
2323
=> Enumerable.Range(0, count)
2424
.Select(i =>
25-
new Option($"-option{i}", arity: arity)
25+
new Option<string>($"-option{i}")
2626
{
27+
Arity = arity,
2728
Description = $"Description for -option {i} ...."
2829
}
2930
);
@@ -50,7 +51,7 @@ public void SetupTestOptions()
5051
[Benchmark]
5152
public Parser ParserFromOptions_Ctor()
5253
{
53-
return Utils.CreateParser(_testSymbols);
54+
return _testSymbols.CreateParser();
5455
}
5556

5657
[GlobalSetup(Target = nameof(ParserFromOptions_Parse))]

src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_Options_With_Arguments.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ public class Perf_Parser_Options_With_Arguments
2020

2121
private IEnumerable<Option> GenerateTestOptions(int count, ArgumentArity arity)
2222
=> Enumerable.Range(0, count)
23-
.Select(i => new Option($"-option{i}", arity: arity)
23+
.Select(i => new Option<string>($"-option{i}")
2424
{
25+
Arity = arity,
2526
Description = $"Description for -option {i} ...."
2627
}
2728
);

src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_ParseResult.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public class Perf_Parser_ParseResult
2020

2121
public Perf_Parser_ParseResult()
2222
{
23-
var option = new Option("-opt");
23+
var option = new Option<bool>("-opt");
2424

2525
_testParser =
2626
new CommandLineBuilder(new RootCommand { option })

src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_TypoCorrection.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public class Perf_Parser_TypoCorrection
2222

2323
public Perf_Parser_TypoCorrection()
2424
{
25-
var option = new Option("--0123456789");
25+
var option = new Option<bool>("--0123456789");
2626

2727
_testParser = new CommandLineBuilder(new RootCommand { option })
2828
.UseTypoCorrections()

src/System.CommandLine.Benchmarks/CommandLine/Perf_Suggestions.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,15 @@ private string[] GenerateSuggestionsArray(int count)
2929

3030
private IEnumerable<Option> GenerateOptionsArray(int count)
3131
=> Enumerable.Range(0, count)
32-
.Select(i => new Option($"suggestion{i}"));
32+
.Select(i => new Option<string>($"suggestion{i}"));
3333

3434
[Params(1, 5, 20, 100)]
3535
public int TestSuggestionsCount;
3636

3737
[GlobalSetup(Target = nameof(SuggestionsFromSymbol))]
3838
public void Setup_FromSymbol()
3939
{
40-
_testSymbol = new Option("--hello", arity: ArgumentArity.ExactlyOne)
40+
_testSymbol = new Option<string>("--hello")
4141
.AddCompletions(GenerateSuggestionsArray(TestSuggestionsCount));
4242
}
4343

src/System.CommandLine.DragonFruit/CommandLine.cs

+15-13
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.CommandLine.NamingConventionBinder;
99
using System.CommandLine.Parsing;
1010
using System.CommandLine.Rendering;
11+
using System.CommandLine.Utility;
1112
using System.IO;
1213
using System.Linq;
1314
using System.Reflection;
@@ -163,18 +164,13 @@ public static void ConfigureFromMethod(
163164
command.AddOption(option);
164165
}
165166

166-
if (method.GetParameters()
167-
.FirstOrDefault(p => _argumentParameterNames.Contains(p.Name)) is ParameterInfo argsParam)
167+
if (method.GetParameters().FirstOrDefault(p => _argumentParameterNames.Contains(p.Name)) is { } argsParam)
168168
{
169-
var argument = new Argument
170-
{
171-
ValueType = argsParam.ParameterType,
172-
Name = argsParam.Name
173-
};
169+
var argument = ArgumentBuilder.CreateArgument(argsParam.ParameterType, argsParam.Name);
174170

175171
if (argsParam.HasDefaultValue)
176172
{
177-
if (argsParam.DefaultValue != null)
173+
if (argsParam.DefaultValue is not null)
178174
{
179175
argument.SetDefaultValue(argsParam.DefaultValue);
180176
}
@@ -296,11 +292,17 @@ public static Option BuildOption(this ParameterDescriptor parameter)
296292
{
297293
getDefaultValue = parameter.GetDefaultValue;
298294
}
299-
return new Option(
300-
parameter.BuildAlias(),
301-
parameter.ValueName,
302-
parameter.ValueType,
303-
getDefaultValue);
295+
296+
var option = OptionBuilder.CreateOption(parameter.BuildAlias(), parameter.ValueType);
297+
298+
option.Description = parameter.ValueName;
299+
300+
if (getDefaultValue is not null)
301+
{
302+
option.SetDefaultValueFactory(getDefaultValue);
303+
}
304+
305+
return option;
304306
}
305307

306308
private static string GetDefaultXmlDocsFileLocation(Assembly assembly)

src/System.CommandLine.DragonFruit/System.CommandLine.DragonFruit.csproj

+5
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,9 @@
1212
<ProjectReference Include="..\System.CommandLine.NamingConventionBinder\System.CommandLine.NamingConventionBinder.csproj" />
1313
<Content Include="targets/*" PackagePath="build/$(TargetFramework)/" />
1414
</ItemGroup>
15+
16+
<ItemGroup>
17+
<Compile Include="..\Common\ArgumentBuilder.cs" Link="Utility\ArgumentBuilder.cs" />
18+
<Compile Include="..\Common\OptionBuilder.cs" Link="Utility\OptionBuilder.cs" />
19+
</ItemGroup>
1520
</Project>

src/System.CommandLine.NamingConventionBinder.Tests/ModelBinderTests.cs

+6-17
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.CommandLine.Invocation;
77
using System.CommandLine.Parsing;
88
using System.CommandLine.Tests.Binding;
9+
using System.CommandLine.Utility;
910
using System.IO;
1011
using System.Threading.Tasks;
1112
using FluentAssertions;
@@ -30,7 +31,7 @@ public void Option_arguments_are_bound_by_name_to_constructor_parameters(
3031

3132
var command = new Command("the-command")
3233
{
33-
new Option("--value", argumentType: type)
34+
OptionBuilder.CreateOption("--value", type)
3435
};
3536

3637
var bindingContext = new InvocationContext(command.Parse(commandLine)).BindingContext;
@@ -57,11 +58,7 @@ public void Command_arguments_are_bound_by_name_to_constructor_parameters(
5758

5859
var command = new Command("the-command")
5960
{
60-
new Argument
61-
{
62-
Name = "value",
63-
ValueType = type
64-
}
61+
ArgumentBuilder.CreateArgument(type)
6562
};
6663

6764
var bindingContext = new InvocationContext(command.Parse(commandLine)).BindingContext;
@@ -84,11 +81,7 @@ public void Command_arguments_are_bound_by_name_to_complex_constructor_parameter
8481

8582
var command = new Command("the-command")
8683
{
87-
new Argument
88-
{
89-
Name = "value",
90-
ValueType = type
91-
}
84+
ArgumentBuilder.CreateArgument(type)
9285
};
9386

9487
var bindingContext = new InvocationContext(command.Parse(commandLine)).BindingContext;
@@ -135,7 +128,7 @@ public void Option_arguments_are_bound_by_name_to_property_setters(
135128

136129
var command = new Command("the-command")
137130
{
138-
new Option("--value", argumentType: type)
131+
OptionBuilder.CreateOption("--value", type)
139132
};
140133
var parser = new Parser(command);
141134

@@ -163,11 +156,7 @@ public void Command_arguments_are_bound_by_name_to_property_setters(
163156

164157
var command = new Command("the-command")
165158
{
166-
new Argument
167-
{
168-
Name = "value",
169-
ValueType = type
170-
}
159+
ArgumentBuilder.CreateArgument(type)
171160
};
172161
var parser = new Parser(command);
173162

src/System.CommandLine.NamingConventionBinder.Tests/ModelBindingCommandHandlerTests.BindingByName.cs

+5-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
// // Copyright (c) .NET Foundation and contributors. All rights reserved.
22
// // Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4-
using System.CommandLine.Binding;
54
using System.CommandLine.Invocation;
65
using System.CommandLine.IO;
76
using System.CommandLine.Tests.Binding;
7+
using System.CommandLine.Utility;
88
using System.IO;
99
using System.Threading.Tasks;
1010
using FluentAssertions;
@@ -35,7 +35,7 @@ public async Task Option_arguments_are_bound_by_name_to_method_parameters(
3535

3636
var command = new Command("the-command")
3737
{
38-
new Option("--value", argumentType: type)
38+
OptionBuilder.CreateOption("--value", type)
3939
};
4040

4141
var console = new TestConsole();
@@ -67,7 +67,7 @@ public async Task Option_arguments_are_bound_by_name_to_the_properties_of_method
6767

6868
var command = new Command("the-command")
6969
{
70-
new Option("--value", argumentType: type)
70+
OptionBuilder.CreateOption("--value", type)
7171
};
7272

7373
var console = new TestConsole();
@@ -99,7 +99,7 @@ public async Task Option_arguments_are_bound_by_name_to_the_constructor_paramete
9999

100100
var command = new Command("the-command")
101101
{
102-
new Option("--value", argumentType: type)
102+
OptionBuilder.CreateOption("--value", type)
103103
};
104104

105105
var console = new TestConsole();
@@ -127,11 +127,7 @@ public async Task Command_arguments_are_bound_by_name_to_handler_method_paramete
127127

128128
var command = new Command("the-command")
129129
{
130-
new Argument
131-
{
132-
Name = "value",
133-
ValueType = type
134-
}
130+
ArgumentBuilder.CreateArgument(type)
135131
};
136132

137133
var console = new TestConsole();

0 commit comments

Comments
 (0)