From 0f6fdd0edc6f7908c0115d23313d18869a34eb59 Mon Sep 17 00:00:00 2001 From: Jon Sequeira Date: Tue, 15 Jul 2025 11:25:10 -0700 Subject: [PATCH] fix #2628 --- .../VersionOptionTests.cs | 17 ++++++++++++++++- src/System.CommandLine/ParserConfiguration.cs | 1 - src/System.CommandLine/Parsing/CommandResult.cs | 15 ++++----------- .../Parsing/ParseOperation.cs | 14 +++++++++++--- 4 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/System.CommandLine.Tests/VersionOptionTests.cs b/src/System.CommandLine.Tests/VersionOptionTests.cs index 5e7f528ab2..135f0a8b14 100644 --- a/src/System.CommandLine.Tests/VersionOptionTests.cs +++ b/src/System.CommandLine.Tests/VersionOptionTests.cs @@ -37,7 +37,7 @@ public async Task When_the_version_option_is_specified_then_invocation_is_short_ { var wasCalled = false; var rootCommand = new RootCommand(); - rootCommand.SetAction((_) => wasCalled = true); + rootCommand.SetAction(_ => wasCalled = true); var output = new StringWriter(); @@ -46,6 +46,21 @@ public async Task When_the_version_option_is_specified_then_invocation_is_short_ wasCalled.Should().BeFalse(); } + [Fact] // https://github.com/dotnet/command-line-api/issues/2628 + public void When_the_version_option_is_specified_then_there_are_no_parse_errors_due_to_unspecified_subcommand() + { + Command subcommand = new("subcommand"); + RootCommand root = new() + { + subcommand + }; + subcommand.SetAction(_ => 0); + + var parseResult = root.Parse("--version"); + + parseResult.Errors.Should().BeEmpty(); + } + [Fact] public async Task Version_option_appears_in_help() { diff --git a/src/System.CommandLine/ParserConfiguration.cs b/src/System.CommandLine/ParserConfiguration.cs index f239c3c4b4..c3a8b06145 100644 --- a/src/System.CommandLine/ParserConfiguration.cs +++ b/src/System.CommandLine/ParserConfiguration.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.CommandLine.Parsing; -using System.Linq; namespace System.CommandLine { diff --git a/src/System.CommandLine/Parsing/CommandResult.cs b/src/System.CommandLine/Parsing/CommandResult.cs index 6561f5a386..ae4bd2922f 100644 --- a/src/System.CommandLine/Parsing/CommandResult.cs +++ b/src/System.CommandLine/Parsing/CommandResult.cs @@ -43,17 +43,10 @@ internal CommandResult( internal override bool UseDefaultValueFor(ArgumentResult argumentResult) => argumentResult.Argument.HasDefaultValue && argumentResult.Tokens.Count == 0; - /// Only the inner most command goes through complete validation. - internal void Validate(bool completeValidation) + internal void Validate(bool isInnermostCommand) { - if (completeValidation) + if (isInnermostCommand) { - if (Command.Action is null && Command.HasSubcommands) - { - SymbolResultTree.InsertFirstError( - new ParseError(LocalizationResources.RequiredCommandWasNotProvided(), this)); - } - if (Command.HasValidators) { int errorCountBefore = SymbolResultTree.ErrorCount; @@ -71,12 +64,12 @@ internal void Validate(bool completeValidation) if (Command.HasOptions) { - ValidateOptionsAndAddDefaultResults(completeValidation); + ValidateOptionsAndAddDefaultResults(isInnermostCommand); } if (Command.HasArguments) { - ValidateArgumentsAndAddDefaultResults(completeValidation); + ValidateArgumentsAndAddDefaultResults(isInnermostCommand); } } diff --git a/src/System.CommandLine/Parsing/ParseOperation.cs b/src/System.CommandLine/Parsing/ParseOperation.cs index 8ac410566c..c5c7b9fdf8 100644 --- a/src/System.CommandLine/Parsing/ParseOperation.cs +++ b/src/System.CommandLine/Parsing/ParseOperation.cs @@ -63,6 +63,7 @@ internal ParseResult Parse() ValidateAndAddDefaultResults(); + if (_isHelpRequested) { _symbolResultTree.Errors?.Clear(); @@ -373,17 +374,24 @@ private void AddCurrentTokenToUnmatched() private void ValidateAndAddDefaultResults() { - // Only the inner most command goes through complete validation, + // Only the innermost command goes through complete validation, // for other commands only a subset of options is checked. - _innermostCommandResult.Validate(completeValidation: true); + _innermostCommandResult.Validate(isInnermostCommand: true); CommandResult? currentResult = _innermostCommandResult.Parent as CommandResult; while (currentResult is not null) { - currentResult.Validate(completeValidation: false); + currentResult.Validate(isInnermostCommand: false); currentResult = currentResult.Parent as CommandResult; } + + if (_primaryAction is null && + _innermostCommandResult is { Command: { Action: null, HasSubcommands: true } }) + { + _symbolResultTree.InsertFirstError( + new ParseError(LocalizationResources.RequiredCommandWasNotProvided(), _innermostCommandResult)); + } } } } \ No newline at end of file