-
Notifications
You must be signed in to change notification settings - Fork 417
Open
Labels
Description
I am currently porting a CLI tool to system.commandLine and have run into a scenario where we only expect to take a argument on root but not on its commands. If we put the argument after the commands everything works but if we put it before the argument is silently ignored. Furthermore the argument appears in the help text for the commands where it is not relevant.
Here is an example of this scenario:
using System.CommandLine;
using System.CommandLine.Help;
// Minimal repro for two related issues with System.CommandLine 2.0.3:
//
// 1. Root command arguments appear in subcommand help text (/?).
// 2. Root command argument preceding a subcommand is silently ignored with no error.
var sampleArgument = new Argument<string>("sampleArgument")
{
Description = "An optional argument on the root command.",
Arity = ArgumentArity.ZeroOrOne,
};
var feedbackCommand = new Command("/subcommand")
{
Description = "A subcommand with no arguments of its own.",
};
feedbackCommand.SetAction(_ => Console.WriteLine("[subcommand] Subcommand ran."));
var rootCommand = new RootCommand("A sample app. Example: app hello");
var builtInHelp = rootCommand.Options.OfType<HelpOption>().First();
builtInHelp.Aliases.Clear();
builtInHelp.Hidden = true;
rootCommand.Options.Add(new HelpOption("/help", "/?"));
rootCommand.Add(sampleArgument);
rootCommand.Add(feedbackCommand);
rootCommand.TreatUnmatchedTokensAsErrors = true;
rootCommand.SetAction(parseResult =>
{
var value = parseResult.GetValue(sampleArgument);
if (value is not null) Console.WriteLine($"[root] sampleArgument = {value}");
else Console.WriteLine("[root] No argument specified.");
});
// Issue 1: /subcommand /?
// Expected: help shows only /subcommand's own arguments/options.
// Actual: <sampleArgument> (root-only) appears in the usage line and
// Arguments section, implying it is an input to /subcommand.
Console.WriteLine("=== Issue 1: app /subcommand /? ===");
rootCommand.Parse(["/subcommand", "/?"]).Invoke();
Console.WriteLine();
// Issue 2: hello /subcommand
// Expected: error because "hello" is not valid for /subcommand,
// or the root action runs with the argument.
// Actual: 0 errors, "hello" is parsed into <sampleArgument> but never
// used — /subcommand runs and the root action is silently skipped.
Console.WriteLine("=== Issue 2: app hello /subcommand ===");
var result = rootCommand.Parse(["hello", "/subcommand"]);
Console.WriteLine($"Errors: {result.Errors.Count}");
Console.WriteLine($"Value of <sampleArgument> in parse result: \"{result.GetValue(sampleArgument) ?? "(null)"}\"");
result.Invoke();
Reactions are currently unavailable