From 98219f17f5d584aed8115764c396312e79607d56 Mon Sep 17 00:00:00 2001 From: Jon Sequeira Date: Wed, 30 Jul 2025 18:32:44 -0700 Subject: [PATCH 1/2] fix #2640 --- .../HelpOptionTests.cs | 36 +++++++++++++++++++ src/System.CommandLine/Argument.cs | 6 +++- .../Parsing/SymbolResult.cs | 2 +- .../Parsing/SymbolResultTree.cs | 10 +++--- 4 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/System.CommandLine.Tests/HelpOptionTests.cs b/src/System.CommandLine.Tests/HelpOptionTests.cs index 27552bf8ac..fa9ca7df1f 100644 --- a/src/System.CommandLine.Tests/HelpOptionTests.cs +++ b/src/System.CommandLine.Tests/HelpOptionTests.cs @@ -6,6 +6,7 @@ using System.CommandLine.Invocation; using System.CommandLine.Tests.Utility; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Xunit; @@ -255,6 +256,41 @@ 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}\"."); } From 0858f5000c55c737d9bfed3392d8c0cff7f8139e Mon Sep 17 00:00:00 2001 From: Jon Sequeira Date: Thu, 31 Jul 2025 07:24:16 -0700 Subject: [PATCH 2/2] cleanup --- src/System.CommandLine.Tests/HelpOptionTests.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/System.CommandLine.Tests/HelpOptionTests.cs b/src/System.CommandLine.Tests/HelpOptionTests.cs index fa9ca7df1f..922fc159e2 100644 --- a/src/System.CommandLine.Tests/HelpOptionTests.cs +++ b/src/System.CommandLine.Tests/HelpOptionTests.cs @@ -6,7 +6,6 @@ using System.CommandLine.Invocation; using System.CommandLine.Tests.Utility; using System.IO; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Xunit; @@ -273,13 +272,10 @@ public void DefaultValueFactory_does_not_throw_when_help_is_invoked() { result.AddError("Oops!"); return null; - } } }; - subcommand.SetAction(_ => - { - }); + subcommand.SetAction(_ => { }); RootCommand rootCommand = new() { subcommand