Skip to content

Fix some help and GetRequiredValue issues #2601

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
Contributing
============

Please read [.NET Guidelines](https://github.com/dotnet/runtime/blob/master/CONTRIBUTING.md) for more general information about coding styles, source structure, making pull requests, and more.
Please read [.NET Guidelines](https://github.com/dotnet/runtime/blob/main/CONTRIBUTING.md) for more general information about coding styles, source structure, making pull requests, and more.

## Developer guide

This project can be developed on any platform. To get started, follow instructions for your OS.

### Prerequisites

This project depends on .NET 7. Before working on the project, check that the [.NET SDK](https://dotnet.microsoft.com/en-us/download) is installed.
This project depends on the .NET 9 SDK. Before working on the project, check that the [.NET SDK](https://dotnet.microsoft.com/en-us/download) is installed.

### Visual Studio

Expand Down
5 changes: 4 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
<ItemGroup>
<!-- Roslyn dependencies -->
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.0.1" />
<!-- Runtime dependencies -->
<PackageVersion Include="Microsoft.Bcl.Memory" Version="9.0.6" />
<!-- external dependencies -->
<PackageVersion Include="ApprovalTests" Version="7.0.0-beta.3" />
<PackageVersion Include="BenchmarkDotNet" Version="0.13.1" />
Expand All @@ -20,9 +22,10 @@
</ItemGroup>

<ItemGroup Condition="'$(DisableArcade)' == '1'">
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<!-- The xunit version should be kept in sync with the one that Arcade promotes -->
<PackageVersion Include="xunit" Version="2.9.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.2" />
</ItemGroup>

</Project>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,11 @@
public System.Collections.Generic.IEnumerable<Symbol> Parents { get; }
public System.Collections.Generic.IEnumerable<System.CommandLine.Completions.CompletionItem> GetCompletions(System.CommandLine.Completions.CompletionContext context)
public System.String ToString()
public class VersionOption : Option<System.Boolean>
public class VersionOption : Option
.ctor()
.ctor(System.String name, System.String[] aliases)
public System.CommandLine.Invocation.CommandLineAction Action { get; set; }
public System.Type ValueType { get; }
System.CommandLine.Completions
public class CompletionContext
public static CompletionContext Empty { get; }
Expand Down Expand Up @@ -185,10 +186,11 @@ System.CommandLine.Help
public class HelpAction : System.CommandLine.Invocation.SynchronousCommandLineAction
.ctor()
public System.Int32 Invoke(System.CommandLine.ParseResult parseResult)
public class HelpOption : System.CommandLine.Option<System.Boolean>
public class HelpOption : System.CommandLine.Option
.ctor()
.ctor(System.String name, System.String[] aliases)
public System.CommandLine.Invocation.CommandLineAction Action { get; set; }
public System.Type ValueType { get; }
System.CommandLine.Invocation
public abstract class AsynchronousCommandLineAction : CommandLineAction
public System.Threading.Tasks.Task<System.Int32> InvokeAsync(System.CommandLine.ParseResult parseResult, System.Threading.CancellationToken cancellationToken = null)
Expand Down
48 changes: 48 additions & 0 deletions src/System.CommandLine.Tests/ArgumentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using FluentAssertions;
using System.Linq;
using FluentAssertions.Execution;
using Xunit;

namespace System.CommandLine.Tests;
Expand Down Expand Up @@ -41,6 +42,53 @@ public void When_there_is_no_default_value_then_GetDefaultValue_throws()
.Be("Argument \"the-arg\" does not have a default value");
}

[Fact]
public void GetRequiredValue_does_not_throw_when_help_is_requested_and_DefaultValueFactory_is_set()
{
var argument = new Argument<string>("the-arg")
{
DefaultValueFactory = _ => "default"
};

var result = new RootCommand { argument }.Parse("-h");

using var _ = new AssertionScope();

result.Invoking(r => r.GetRequiredValue(argument)).Should().NotThrow();
result.GetRequiredValue(argument).Should().Be("default");

result.Invoking(r => r.GetRequiredValue<string>("the-arg")).Should().NotThrow();
result.GetRequiredValue<string>("the-arg").Should().Be("default");

result.Errors.Should().BeEmpty();
}

[Fact]
public void When_there_is_no_default_value_then_GetDefaultValue_does_not_throw_for_bool()
{
var argument = new Argument<bool>("the-arg");

argument.GetDefaultValue().Should().Be(false);
}

[Fact]
public void When_there_is_no_default_value_then_GetRequiredValue_does_not_throw_for_bool()
{
var argument = new Argument<bool>("the-arg");

var result = new RootCommand { argument }.Parse("");

using var _ = new AssertionScope();

result.Invoking(r => r.GetRequiredValue(argument)).Should().NotThrow();
result.GetRequiredValue(argument).Should().BeFalse();

result.Invoking(r => r.GetRequiredValue<bool>("the-arg")).Should().NotThrow();
result.GetRequiredValue<bool>("the-arg").Should().BeFalse();

result.Errors.Should().BeEmpty();
}

[Fact]
public void Argument_of_enum_can_limit_enum_members_as_valid_values()
{
Expand Down
2 changes: 2 additions & 0 deletions src/System.CommandLine.Tests/Binding/TypeConversionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.IO;
using System.Linq;
using System.Net;
using FluentAssertions.Execution;
using Xunit;

namespace System.CommandLine.Tests.Binding
Expand Down Expand Up @@ -585,6 +586,7 @@ public void Values_can_be_correctly_converted_to_Uri_when_custom_parser_is_provi
[Fact]
public void Options_with_arguments_specified_can_be_correctly_converted_to_bool_without_the_parser_specifying_a_custom_converter()
{
using var _ = new AssertionScope();
GetValue(new Option<bool>("-x"), "-x false").Should().BeFalse();
GetValue(new Option<bool>("-x"), "-x true").Should().BeTrue();
}
Expand Down
5 changes: 3 additions & 2 deletions src/System.CommandLine.Tests/DirectiveTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.CommandLine.Parsing;
using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
Expand All @@ -22,14 +23,14 @@ public void Directives_should_be_considered_as_unmatched_tokens_when_they_are_no
}

[Fact]
public void Raw_tokens_still_hold_directives()
public void Tokens_still_hold_directives()
{
Directive directive = new ("parse");

ParseResult result = Parse(new Option<bool>("-y"), directive, "[parse] -y");

result.GetResult(directive).Should().NotBeNull();
result.Tokens.Should().Contain(t => t.Value == "[parse]");
result.Tokens.Should().Contain(t => t.Value == "[parse]" && t.Type == TokenType.Directive);
}

[Fact]
Expand Down
26 changes: 26 additions & 0 deletions src/System.CommandLine.Tests/GetValueByNameParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -368,4 +368,30 @@ public void Recursive_option_on_parent_command_can_be_looked_up_when_subcommand_

result.GetValue<string>("--opt").Should().Be("hello");
}

[Fact]
public void When_argument_type_is_unknown_then_named_lookup_can_be_used_to_get_value_as_supertype()
{
var command = new RootCommand
{
new Argument<string>("arg")
};

var result = command.Parse("value");

result.GetValue<object>("arg").Should().Be("value");
}

[Fact]
public void When_option_type_is_unknown_then_named_lookup_can_be_used_to_get_value_as_supertype()
{
var command = new RootCommand
{
new Option<string>("-x")
};

var result = command.Parse("-x value");

result.GetValue<object>("-x").Should().Be("value");
}
}
6 changes: 3 additions & 3 deletions src/System.CommandLine.Tests/GlobalOptionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ public void When_a_required_global_option_is_omitted_it_results_in_an_error()
{
var command = new Command("child");
var rootCommand = new RootCommand { command };
command.SetAction((_) => { });
var requiredOption = new Option<bool>("--i-must-be-set")
command.SetAction(_ => { });
var requiredOption = new Option<string>("--i-must-be-set")
{
Required = true,
Recursive = true
Expand All @@ -45,7 +45,7 @@ public void When_a_required_global_option_is_omitted_it_results_in_an_error()
public void When_a_required_global_option_has_multiple_aliases_the_error_message_uses_the_name()
{
var rootCommand = new RootCommand();
var requiredOption = new Option<bool>("-i", "--i-must-be-set")
var requiredOption = new Option<string>("-i", "--i-must-be-set")
{
Required = true,
Recursive = true
Expand Down
9 changes: 6 additions & 3 deletions src/System.CommandLine.Tests/Help/ApprovalTests.Config.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using ApprovalTests.Reporters;
using ApprovalTests.Reporters.TestFrameworks;
// Alias workaround for https://github.com/approvals/ApprovalTests.Net/issues/768
extern alias ApprovalTests;

using ApprovalTests.ApprovalTests.Reporters;
using ApprovalTests.ApprovalTests.Reporters.TestFrameworks;

// Use globally defined Reporter for ApprovalTests. Please see
// https://github.com/approvals/ApprovalTests.Net/blob/master/docs/ApprovalTests/Reporters.md

[assembly: UseReporter(typeof(FrameworkAssertReporter))]

[assembly: ApprovalTests.Namers.UseApprovalSubdirectory("Approvals")]
[assembly: ApprovalTests.ApprovalTests.Namers.UseApprovalSubdirectory("Approvals")]
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

// Alias workaround for https://github.com/approvals/ApprovalTests.Net/issues/768
extern alias ApprovalTests;

using Xunit;
using System.IO;
using ApprovalTests;
using ApprovalTests.Reporters;
using ApprovalTests.ApprovalTests;
using ApprovalTests.ApprovalTests.Reporters;

namespace System.CommandLine.Tests.Help
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,6 @@ public void Argument_can_fallback_to_default_when_customizing(
config.Output.ToString().Should().MatchRegex(expected);
}


[Fact]
public void Individual_symbols_can_be_customized()
{
Expand Down
35 changes: 31 additions & 4 deletions src/System.CommandLine.Tests/Help/HelpBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -778,13 +778,15 @@ public void Help_describes_default_value_for_argument()
help.Should().Contain("[default: the-arg-value]");
}

[Fact]
public void Help_does_not_show_default_value_for_argument_when_default_value_is_empty()
[Theory]
[InlineData("")]
[InlineData(null)]
public void Help_does_not_show_default_value_for_argument_when_default_value_is_null_or_empty(string defaultValue)
{
var argument = new Argument<string>("the-arg")
{
Description = "The argument description",
DefaultValueFactory = (_) => ""
DefaultValueFactory = _ => defaultValue
};

var command = new Command("the-command", "The command description")
Expand All @@ -798,7 +800,32 @@ public void Help_does_not_show_default_value_for_argument_when_default_value_is_

var help = _console.ToString();

help.Should().NotContain("[default");
help.Should().NotContain("[]");
}

[Theory]
[InlineData("")]
[InlineData(null)]
public void Help_does_not_show_default_value_for_option_when_default_value_is_null_or_empty(string defaultValue)
{
var argument = new Option<string>("--opt")
{
Description = "The option description",
DefaultValueFactory = _ => defaultValue
};

var command = new Command("the-command", "The command description")
{
argument
};

var helpBuilder = GetHelpBuilder(SmallMaxWidth);

helpBuilder.Write(command, _console);

var help = _console.ToString();

help.Should().NotContain("[]");
}

[Fact]
Expand Down
Loading