Skip to content

Commit 89211a6

Browse files
authored
Fix #1540 (#1545)
* refactor BindingContext to enforce single source of truth on child objects; remove ConfigureConsole and IConsoleFactory * fix #1540 * split CompletionContext classes into separate files * fix warning * update API approvals
1 parent b805d09 commit 89211a6

21 files changed

+219
-201
lines changed

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -226,9 +226,8 @@ System.CommandLine.Binding
226226
public abstract class BinderBase<T>, IValueDescriptor<T>, IValueDescriptor, IValueSource
227227
protected T GetBoundValue(BindingContext bindingContext)
228228
public class BindingContext, System.IServiceProvider
229-
.ctor(System.CommandLine.Parsing.ParseResult parseResult, System.CommandLine.IConsole console = null)
230229
public System.CommandLine.IConsole Console { get; }
231-
public System.CommandLine.Parsing.ParseResult ParseResult { get; set; }
230+
public System.CommandLine.Parsing.ParseResult ParseResult { get; }
232231
public System.Void AddService(System.Type serviceType, System.Func<System.IServiceProvider,System.Object> factory)
233232
public System.Void AddService<T>(Func<System.IServiceProvider,T> factory)
234233
public System.Object GetService(System.Type serviceType)
@@ -259,7 +258,6 @@ System.CommandLine.Builder
259258
public static CommandLineBuilder AddMiddleware(this CommandLineBuilder builder, System.CommandLine.Invocation.InvocationMiddleware middleware, System.CommandLine.Invocation.MiddlewareOrder order = Default)
260259
public static CommandLineBuilder AddMiddleware(this CommandLineBuilder builder, System.Action<System.CommandLine.Invocation.InvocationContext> onInvoke, System.CommandLine.Invocation.MiddlewareOrder order = Default)
261260
public static CommandLineBuilder CancelOnProcessTermination(this CommandLineBuilder builder)
262-
public static CommandLineBuilder ConfigureConsole(this CommandLineBuilder builder, System.Func<System.CommandLine.Binding.BindingContext,System.CommandLine.IConsole> createConsole)
263261
public static CommandLineBuilder EnableDirectives(this CommandLineBuilder builder, System.Boolean value = True)
264262
public static CommandLineBuilder EnableLegacyDoubleDashBehavior(this CommandLineBuilder builder, System.Boolean value = True)
265263
public static CommandLineBuilder EnablePosixBundling(this CommandLineBuilder builder, System.Boolean value = True)
@@ -368,7 +366,7 @@ System.CommandLine.Invocation
368366
public class InvocationContext, System.IDisposable
369367
.ctor(System.CommandLine.Parsing.ParseResult parseResult, System.CommandLine.IConsole console = null)
370368
public System.CommandLine.Binding.BindingContext BindingContext { get; }
371-
public System.CommandLine.IConsole Console { get; }
369+
public System.CommandLine.IConsole Console { get; set; }
372370
public System.Int32 ExitCode { get; set; }
373371
public System.CommandLine.Help.HelpBuilder HelpBuilder { get; }
374372
public IInvocationResult InvocationResult { get; set; }

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

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
using System.Collections.Generic;
5-
using System.CommandLine.Binding;
65
using System.CommandLine.Builder;
6+
using System.CommandLine.Invocation;
77
using System.CommandLine.Parsing;
88
using System.CommandLine.Tests.Binding;
99
using System.IO;
@@ -33,7 +33,7 @@ public void Option_arguments_are_bound_by_name_to_constructor_parameters(
3333
new Option("--value", argumentType: type)
3434
};
3535

36-
var bindingContext = new BindingContext(command.Parse(commandLine));
36+
var bindingContext = new InvocationContext(command.Parse(commandLine)).BindingContext;
3737

3838
var instance = binder.CreateInstance(bindingContext);
3939

@@ -64,7 +64,7 @@ public void Command_arguments_are_bound_by_name_to_constructor_parameters(
6464
}
6565
};
6666

67-
var bindingContext = new BindingContext(command.Parse(commandLine));
67+
var bindingContext = new InvocationContext(command.Parse(commandLine)).BindingContext;
6868

6969
var instance = binder.CreateInstance(bindingContext);
7070

@@ -91,7 +91,7 @@ public void Command_arguments_are_bound_by_name_to_complex_constructor_parameter
9191
}
9292
};
9393

94-
var bindingContext = new BindingContext(command.Parse(commandLine));
94+
var bindingContext = new InvocationContext(command.Parse(commandLine)).BindingContext;
9595

9696
var instance = binder.CreateInstance(bindingContext);
9797

@@ -113,7 +113,7 @@ public void Explicitly_configured_default_values_can_be_bound_by_name_to_constru
113113
var binder = new ModelBinder(typeof(ClassWithMultiLetterCtorParameters));
114114

115115
var parser = new Parser(command);
116-
var bindingContext = new BindingContext(parser.Parse(""));
116+
var bindingContext = new InvocationContext(parser.Parse("")).BindingContext;
117117

118118
var instance = (ClassWithMultiLetterCtorParameters)binder.CreateInstance(bindingContext);
119119

@@ -139,7 +139,7 @@ public void Option_arguments_are_bound_by_name_to_property_setters(
139139
};
140140
var parser = new Parser(command);
141141

142-
var bindingContext = new BindingContext(parser.Parse(commandLine));
142+
var bindingContext = new InvocationContext(parser.Parse(commandLine)).BindingContext;
143143

144144
var instance = binder.CreateInstance(bindingContext);
145145

@@ -171,7 +171,7 @@ public void Command_arguments_are_bound_by_name_to_property_setters(
171171
};
172172
var parser = new Parser(command);
173173

174-
var bindingContext = new BindingContext(parser.Parse(commandLine));
174+
var bindingContext = new InvocationContext(parser.Parse(commandLine)).BindingContext;
175175

176176
var instance = binder.CreateInstance(bindingContext);
177177

@@ -190,7 +190,7 @@ public void Types_having_constructors_accepting_a_single_string_are_bound_using_
190190
var command = new Command("the-command");
191191
command.AddOption(option);
192192
var binder = new ModelBinder(typeof(ClassWithCtorParameter<DirectoryInfo>));
193-
var bindingContext = new BindingContext(command.Parse($"--value \"{tempPath}\""));
193+
var bindingContext = new InvocationContext(command.Parse($"--value \"{tempPath}\"")).BindingContext;
194194

195195
var instance = (ClassWithCtorParameter<DirectoryInfo>)binder.CreateInstance(bindingContext);
196196

@@ -207,7 +207,7 @@ public void Explicitly_configured_default_values_can_be_bound_by_name_to_propert
207207
var binder = new ModelBinder(typeof(ClassWithSetter<string>));
208208

209209
var parser = new Parser(command);
210-
var bindingContext = new BindingContext(parser.Parse(""));
210+
var bindingContext = new InvocationContext(parser.Parse("")).BindingContext;
211211

212212
var instance = (ClassWithSetter<string>)binder.CreateInstance(bindingContext);
213213

@@ -225,8 +225,8 @@ public void Property_setters_with_no_default_value_and_no_matching_option_are_no
225225
var binder = new ModelBinder(typeof(ClassWithSettersAndCtorParametersWithDifferentNames));
226226

227227
var parser = new Parser(command);
228-
var bindingContext = new BindingContext(
229-
parser.Parse(""));
228+
var bindingContext = new InvocationContext(
229+
parser.Parse("")).BindingContext;
230230

231231
var instance = (ClassWithSettersAndCtorParametersWithDifferentNames)binder.CreateInstance(bindingContext);
232232

@@ -240,7 +240,7 @@ public void Parse_result_can_be_used_to_create_an_instance_without_doing_handler
240240
{
241241
new Option<int>("--int-option")
242242
});
243-
var bindingContext = new BindingContext(parser.Parse("the-command --int-option 123"));
243+
var bindingContext = new InvocationContext(parser.Parse("the-command --int-option 123")).BindingContext;
244244
var binder = new ModelBinder(typeof(ClassWithMultiLetterSetters));
245245

246246
var instance = (ClassWithMultiLetterSetters)binder.CreateInstance(bindingContext);
@@ -256,7 +256,7 @@ public void Parse_result_can_be_used_to_modify_an_existing_instance_without_doin
256256
new Option<int>("--int-option")
257257
});
258258
var instance = new ClassWithMultiLetterSetters();
259-
var bindingContext = new BindingContext(parser.Parse("the-command --int-option 123"));
259+
var bindingContext = new InvocationContext(parser.Parse("the-command --int-option 123")).BindingContext;
260260
var binder = new ModelBinder(typeof(ClassWithMultiLetterSetters));
261261

262262
binder.UpdateInstance(instance, bindingContext);
@@ -270,7 +270,7 @@ public void Modify_an_existing_instance_should_keep_all_default_values_if_no_arg
270270
var parser = new Parser(new Command("the-command"));
271271

272272
var instance = new ClassWithComplexTypes();
273-
var bindingContext = new BindingContext(parser.Parse("the-command"));
273+
var bindingContext = new InvocationContext(parser.Parse("the-command")).BindingContext;
274274
var binder = new ModelBinder(typeof(ClassWithComplexTypes));
275275

276276
binder.UpdateInstance(instance, bindingContext);
@@ -291,7 +291,7 @@ public void Values_from_options_on_parent_commands_are_bound_by_name_by_default(
291291

292292
var parseResult = parentCommand.Parse("parent-command --int-option 123 child-command");
293293

294-
var bindingContext = new BindingContext(parseResult);
294+
var bindingContext = new InvocationContext(parseResult).BindingContext;
295295

296296
var instance = (ClassWithMultiLetterSetters)binder.CreateInstance(bindingContext);
297297

@@ -311,7 +311,7 @@ public void Default_values_from_options_on_parent_commands_are_bound_by_name_by_
311311

312312
var parseResult = parentCommand.Parse("parent-command child-command");
313313

314-
var bindingContext = new BindingContext(parseResult);
314+
var bindingContext = new InvocationContext(parseResult).BindingContext;
315315

316316
var instance = (ClassWithMultiLetterSetters)binder.CreateInstance(bindingContext);
317317

@@ -334,7 +334,7 @@ public void Values_from_parent_command_arguments_are_bound_by_name_by_default()
334334

335335
var parseResult = parentCommand.Parse("parent-command 123 child-command");
336336

337-
var bindingContext = new BindingContext(parseResult);
337+
var bindingContext = new InvocationContext(parseResult).BindingContext;
338338

339339
var instance = (ClassWithMultiLetterSetters)binder.CreateInstance(bindingContext);
340340

@@ -357,7 +357,7 @@ public void Default_values_from_parent_command_arguments_are_bound_by_name_by_de
357357

358358
var parseResult = parentCommand.Parse("parent-command child-command");
359359

360-
var bindingContext = new BindingContext(parseResult);
360+
var bindingContext = new InvocationContext(parseResult).BindingContext;
361361

362362
var instance = (ClassWithMultiLetterSetters)binder.CreateInstance(bindingContext);
363363

@@ -381,7 +381,7 @@ public void Values_from_options_on_parent_commands_can_be_bound_regardless_of_na
381381
c => c.IntOption,
382382
option);
383383

384-
var bindingContext = new BindingContext(parentCommand.Parse("parent-command -x 123 child-command"));
384+
var bindingContext = new InvocationContext(parentCommand.Parse("parent-command -x 123 child-command")).BindingContext;
385385

386386
var instance = (ClassWithMultiLetterSetters)binder.CreateInstance(bindingContext);
387387

@@ -399,7 +399,7 @@ public void Arbitrary_values_can_be_bound()
399399
c => c.IntOption,
400400
_ => 123);
401401

402-
var bindingContext = new BindingContext(command.Parse("the-command"));
402+
var bindingContext = new InvocationContext(command.Parse("the-command")).BindingContext;
403403

404404
var instance = (ClassWithMultiLetterSetters)binder.CreateInstance(bindingContext);
405405

@@ -421,7 +421,7 @@ public void PropertyInfo_can_be_bound_to_option()
421421
propertyInfo,
422422
option);
423423

424-
var bindingContext = new BindingContext(command.Parse("the-command --fred 42"));
424+
var bindingContext = new InvocationContext(command.Parse("the-command --fred 42")).BindingContext;
425425

426426
var instance = (ClassWithMultiLetterSetters)binder.CreateInstance(bindingContext);
427427

@@ -441,7 +441,7 @@ public void PropertyInfo_can_be_bound_to_argument()
441441

442442
binder.BindMemberFromValue(propertyInfo, argument);
443443

444-
var bindingContext = new BindingContext(command.Parse("the-command 42"));
444+
var bindingContext = new InvocationContext(command.Parse("the-command 42")).BindingContext;
445445

446446
var instance = (ClassWithMultiLetterSetters)binder.CreateInstance(bindingContext);
447447

@@ -461,7 +461,7 @@ public void PropertyExpression_can_be_bound_to_option()
461461
i => i.IntOption,
462462
option);
463463

464-
var bindingContext = new BindingContext(command.Parse("the-command --fred 42"));
464+
var bindingContext = new InvocationContext(command.Parse("the-command --fred 42")).BindingContext;
465465

466466
var instance = (ClassWithMultiLetterSetters)binder.CreateInstance(bindingContext);
467467

@@ -481,7 +481,7 @@ public void PropertyExpression_can_be_bound_to_argument()
481481
i => i.IntOption,
482482
argument);
483483

484-
var bindingContext = new BindingContext(command.Parse("the-command 42"));
484+
var bindingContext = new InvocationContext(command.Parse("the-command 42")).BindingContext;
485485

486486
var instance = (ClassWithMultiLetterSetters)binder.CreateInstance(bindingContext);
487487

@@ -493,7 +493,7 @@ public void Option_argument_is_bound_to_longest_constructor()
493493
{
494494
var option = new Option<int>("--int-property");
495495

496-
var bindingContext = new BindingContext(option.Parse("--int-property 42"));
496+
var bindingContext = new InvocationContext(option.Parse("--int-property 42")).BindingContext;
497497
var binder = new ModelBinder<ClassWithMultipleCtor>();
498498
var instance = binder.CreateInstance(bindingContext) as ClassWithMultipleCtor;
499499

@@ -508,7 +508,7 @@ public void Command_argument_is_bound_to_longest_constructor()
508508
rootCommand.AddArgument(new Argument<int> { Name = nameof(ClassWithMultipleCtor.IntProperty) });
509509
var parser = new Parser(rootCommand);
510510

511-
var bindingContext = new BindingContext(parser.Parse("42"));
511+
var bindingContext = new InvocationContext(parser.Parse("42")).BindingContext;
512512
var binder = new ModelBinder<ClassWithMultipleCtor>();
513513
var instance = binder.CreateInstance(bindingContext) as ClassWithMultipleCtor;
514514

@@ -523,7 +523,7 @@ public void Explicit_model_binder_binds_only_to_configured_properties()
523523
var stringOption = new Option<string>("--string-property");
524524
var parser = new Parser(new RootCommand { intOption, stringOption });
525525

526-
var bindingContext = new BindingContext(parser.Parse("--int-property 42 --string-property Hello"));
526+
var bindingContext = new InvocationContext(parser.Parse("--int-property 42 --string-property Hello")).BindingContext;
527527
var binder = new ModelBinder<ClassWithMultiLetterSetters>
528528
{
529529
EnforceExplicitBinding = true

src/System.CommandLine.Rendering.Tests/TerminalModeTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.CommandLine.Invocation;
66
using System.CommandLine.IO;
77
using System.CommandLine.Parsing;
8-
using System.CommandLine.Tests;
98
using System.CommandLine.Tests.Utility;
109
using System.Threading.Tasks;
1110
using FluentAssertions;

src/System.CommandLine.Rendering/CommandLineBuilderExtensions.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using System.CommandLine.Binding;
55
using System.CommandLine.Builder;
6-
using System.CommandLine.Invocation;
76
using System.Linq;
87

98
namespace System.CommandLine.Rendering
@@ -13,15 +12,15 @@ public static class CommandLineBuilderExtensions
1312
public static CommandLineBuilder UseAnsiTerminalWhenAvailable(
1413
this CommandLineBuilder builder)
1514
{
16-
builder.ConfigureConsole(context =>
15+
builder.AddMiddleware(context =>
1716
{
1817
var console = context.Console;
1918

2019
var terminal = console.GetTerminal(
21-
PreferVirtualTerminal(context),
22-
OutputMode(context));
20+
PreferVirtualTerminal(context.BindingContext),
21+
OutputMode(context.BindingContext));
2322

24-
return terminal ?? console;
23+
context.Console = terminal ?? console;
2524
});
2625

2726
return builder;

src/System.CommandLine.Tests/CommandTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ public void Command_argument_of_ICollection_of_T_defaults_to_empty_when_not_spec
419419
}
420420

421421
[Fact]
422-
public void AddGlobalOption_updates_Options_and_GlobalOptions_property()
422+
public void AddGlobalOption_updates_Options_property()
423423
{
424424
var option = new Option<string>("-x");
425425
var command = new Command("mycommand");

src/System.CommandLine.Tests/GlobalOptionTests.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// Copyright (c) .NET Foundation and contributors. All rights reserved.
55
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
66

7+
using System.Threading.Tasks;
78
using FluentAssertions;
89
using Xunit;
910

@@ -41,6 +42,43 @@ public void Global_options_appear_in_options_list()
4142
root.Options.Should().Contain(option);
4243
}
4344

45+
[Fact] // https://github.com/dotnet/command-line-api/issues/1540
46+
public void When_a_required_global_option_is_omitted_it_results_in_an_error()
47+
{
48+
var command = new Command("child");
49+
var rootCommand = new RootCommand { command };
50+
command.SetHandler(() => { });
51+
var requiredOption = new Option<bool>("--i-must-be-set")
52+
{
53+
IsRequired = true
54+
};
55+
rootCommand.AddGlobalOption(requiredOption);
56+
57+
var result = rootCommand.Parse("child");
58+
59+
result.Errors
60+
.Should()
61+
.ContainSingle()
62+
.Which.Message.Should().Be("Option '--i-must-be-set' is required.");
63+
}
64+
65+
[Fact]
66+
public void When_a_required_global_option_is_present_on_child_of_command_it_was_added_to_it_does_not_result_in_an_error()
67+
{
68+
var command = new Command("child");
69+
var rootCommand = new RootCommand { command };
70+
command.SetHandler(() => { });
71+
var requiredOption = new Option<bool>("--i-must-be-set")
72+
{
73+
IsRequired = true
74+
};
75+
rootCommand.AddGlobalOption(requiredOption);
76+
77+
var result = rootCommand.Parse("child --i-must-be-set");
78+
79+
result.Errors.Should().BeEmpty();
80+
}
81+
4482
[Fact]
4583
public void Subcommands_added_after_a_global_option_is_added_to_parent_will_recognize_the_global_option()
4684
{

src/System.CommandLine.Tests/ParserTests.MultiplePositions.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
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.Invocation;
5-
using System.CommandLine.IO;
6-
using System.CommandLine.Parsing;
74
using System.Linq;
85
using FluentAssertions;
96
using Xunit;

src/System.CommandLine.Tests/ParsingValidationTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
using System.Collections.Generic;
5-
using System.CommandLine.Invocation;
65
using System.CommandLine.Parsing;
76
using System.IO;
87
using FluentAssertions;

0 commit comments

Comments
 (0)