diff --git a/src/System.CommandLine.Tests/HelpOptionTests.cs b/src/System.CommandLine.Tests/HelpOptionTests.cs index 27552bf8ac..922fc159e2 100644 --- a/src/System.CommandLine.Tests/HelpOptionTests.cs +++ b/src/System.CommandLine.Tests/HelpOptionTests.cs @@ -255,6 +255,38 @@ public void The_users_can_set_max_width() output.ToString().Should().Contain($" {secondPart}{Environment.NewLine}"); } + [Fact] // https://github.com/dotnet/command-line-api/issues/2640 + public void DefaultValueFactory_does_not_throw_when_help_is_invoked() + { + var invocationConfiguration = new InvocationConfiguration + { + Output = new StringWriter(), + Error = new StringWriter() + }; + + Command subcommand = new("do") + { + new Option("-x") + { + DefaultValueFactory = result => + { + result.AddError("Oops!"); + return null; + } + } + }; + subcommand.SetAction(_ => { }); + RootCommand rootCommand = new() + { + subcommand + }; + + rootCommand.Parse("do --help").Invoke(invocationConfiguration); + + invocationConfiguration.Error.ToString().Should().Be(""); + } + + private sealed class CustomizedHelpAction : SynchronousCommandLineAction { internal const string CustomUsageText = "This is custom command usage example."; diff --git a/src/System.CommandLine/Argument.cs b/src/System.CommandLine/Argument.cs index 7df55ce93f..1640e883df 100644 --- a/src/System.CommandLine/Argument.cs +++ b/src/System.CommandLine/Argument.cs @@ -112,7 +112,11 @@ public List>> CompletionSour /// Returns the default value for the argument, if defined. Null otherwise. public object? GetDefaultValue() { - return GetDefaultValue(new ArgumentResult(this, null!, null)); + var command = Parents.FlattenBreadthFirst(x => x.Parents) + .OfType() + .FirstOrDefault(); + + return GetDefaultValue(new ArgumentResult(this, new SymbolResultTree(command), null)); } internal abstract object? GetDefaultValue(ArgumentResult argumentResult); diff --git a/src/System.CommandLine/Parsing/SymbolResult.cs b/src/System.CommandLine/Parsing/SymbolResult.cs index aed8a3ac4b..0016375754 100644 --- a/src/System.CommandLine/Parsing/SymbolResult.cs +++ b/src/System.CommandLine/Parsing/SymbolResult.cs @@ -15,7 +15,7 @@ public abstract class SymbolResult private protected SymbolResult(SymbolResultTree symbolResultTree, SymbolResult? parent) { - SymbolResultTree = symbolResultTree; + SymbolResultTree = symbolResultTree ?? throw new ArgumentNullException(nameof(symbolResultTree)); Parent = parent; } diff --git a/src/System.CommandLine/Parsing/SymbolResultTree.cs b/src/System.CommandLine/Parsing/SymbolResultTree.cs index 49abc6be86..22731d82cf 100644 --- a/src/System.CommandLine/Parsing/SymbolResultTree.cs +++ b/src/System.CommandLine/Parsing/SymbolResultTree.cs @@ -7,14 +7,14 @@ namespace System.CommandLine.Parsing { internal sealed class SymbolResultTree : Dictionary { - private readonly Command _rootCommand; + private readonly Command? _rootCommand; internal List? Errors; internal List? UnmatchedTokens; private Dictionary? _symbolsByName; internal SymbolResultTree( - Command rootCommand, - List? tokenizeErrors) + Command? rootCommand = null, + List? tokenizeErrors = null) { _rootCommand = rootCommand; @@ -78,13 +78,13 @@ internal void AddUnmatchedToken(Token token, CommandResult commandResult, Comman public SymbolResult? GetResult(string name) { - if (_symbolsByName is null) + if (_symbolsByName is null && _rootCommand is not null) { _symbolsByName = new(); PopulateSymbolsByName(_rootCommand); } - if (!_symbolsByName.TryGetValue(name, out SymbolNode? node)) + if (!_symbolsByName!.TryGetValue(name, out SymbolNode? node)) { throw new ArgumentException($"No symbol result found with name \"{name}\"."); }