From 175e8501f66af472ae68879b51804fad91439e84 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Mon, 23 Jan 2023 13:15:19 +0100 Subject: [PATCH 1/4] extend existing test to cover the discovered bug (it's red) --- src/System.CommandLine.Tests/CompletionTests.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/System.CommandLine.Tests/CompletionTests.cs b/src/System.CommandLine.Tests/CompletionTests.cs index ab238d6b68..bb9691b151 100644 --- a/src/System.CommandLine.Tests/CompletionTests.cs +++ b/src/System.CommandLine.Tests/CompletionTests.cs @@ -57,20 +57,25 @@ public void Command_GetCompletions_returns_available_option_aliases() [Fact] // https://github.com/dotnet/command-line-api/issues/1563 public void Command_GetCompletions_returns_available_option_aliases_for_global_options() { - var subcommand = new Command("command") + var subcommand2 = new Command("command2") { new Option("--one", "option one"), new Option("--two", "option two") }; + var subcommand1 = new Command("command1") + { + subcommand2 + }; + var rootCommand = new RootCommand { - subcommand + subcommand1 }; rootCommand.AddGlobalOption(new Option("--three", "option three")); - var completions = subcommand.GetCompletions(CompletionContext.Empty); + var completions = subcommand2.GetCompletions(CompletionContext.Empty); completions .Select(item => item.Label) From 010ef60712bc3db7662de076e65e5aee8214503e Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Mon, 23 Jan 2023 13:18:06 +0100 Subject: [PATCH 2/4] fix --- src/System.CommandLine/Command.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.CommandLine/Command.cs b/src/System.CommandLine/Command.cs index 2c0a1adbfc..9f3bf8ca58 100644 --- a/src/System.CommandLine/Command.cs +++ b/src/System.CommandLine/Command.cs @@ -205,7 +205,7 @@ public override IEnumerable GetCompletions(CompletionContext con } } - parent = parent.Next; + parent = parent.Symbol.FirstParent; } } From 7e247da247a5449e6df6f800667c7f03d6c4ea6a Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Mon, 23 Jan 2023 14:11:54 +0100 Subject: [PATCH 3/4] add a test based on excellent feedback from @KalleOlaviNiemitalo --- .../ParseResultTests.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/System.CommandLine.Tests/ParseResultTests.cs b/src/System.CommandLine.Tests/ParseResultTests.cs index c8d87b6df4..d0f22af927 100644 --- a/src/System.CommandLine.Tests/ParseResultTests.cs +++ b/src/System.CommandLine.Tests/ParseResultTests.cs @@ -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.Linq; using System.CommandLine.Parsing; using FluentAssertions; using Xunit; @@ -83,5 +84,42 @@ public void Command_will_not_accept_a_command_if_a_sibling_command_has_already_b result2.CommandResult.Command.Name.Should().Be("inner-two"); result2.Errors.Count.Should().Be(1); } + + [Fact] // https://github.com/dotnet/command-line-api/pull/2030#issuecomment-1400275332 + public void ParseResult_GetCompletions_returns_global_options_of_given_command_only() + { + var leafCommand = new Command("leafCommand") + { + new Option("--one", "option one"), + new Option("--two", "option two") + }; + + var midCommand1 = new Command("midCommand1") + { + leafCommand + }; + midCommand1.AddGlobalOption(new Option("--three1", "option three 1")); + + var midCommand2 = new Command("midCommand2") + { + leafCommand + }; + midCommand2.AddGlobalOption(new Option("--three2", "option three 2")); + + var rootCommand = new Command("root") + { + midCommand1, + midCommand2 + }; + + var result = new Parser(rootCommand).Parse("root midCommand2 leafCommand --"); + + var completions = result.GetCompletions(); + + completions + .Select(item => item.Label) + .Should() + .BeEquivalentTo("--one", "--two", "--three2"); + } } } From 42d90a2457cef00d7084befab0ab0dda0a5b1ffa Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Mon, 23 Jan 2023 14:32:33 +0100 Subject: [PATCH 4/4] fix: include only parsed commands --- src/System.CommandLine/Command.cs | 24 ++++++++++++------- .../Completions/CompletionContext.cs | 2 ++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/System.CommandLine/Command.cs b/src/System.CommandLine/Command.cs index 9f3bf8ca58..1bf406913a 100644 --- a/src/System.CommandLine/Command.cs +++ b/src/System.CommandLine/Command.cs @@ -192,20 +192,28 @@ public override IEnumerable GetCompletions(CompletionContext con ParentNode? parent = FirstParent; while (parent is not null) { - if (parent.Symbol is Command parentCommand && parentCommand.HasOptions) + Command parentCommand = (Command)parent.Symbol; + + if (context.IsEmpty || context.ParseResult.FindResultFor(parentCommand) is not null) { - for (var i = 0; i < parentCommand.Options.Count; i++) + if (parentCommand.HasOptions) { - var option = parentCommand.Options[i]; - - if (option.IsGlobal) + for (var i = 0; i < parentCommand.Options.Count; i++) { - AddCompletionsFor(option); + var option = parentCommand.Options[i]; + + if (option.IsGlobal) + { + AddCompletionsFor(option); + } } } + parent = parent.Symbol.FirstParent; + } + else + { + parent = parent.Next; } - - parent = parent.Symbol.FirstParent; } } diff --git a/src/System.CommandLine/Completions/CompletionContext.cs b/src/System.CommandLine/Completions/CompletionContext.cs index 0f8f928e9b..708027b836 100644 --- a/src/System.CommandLine/Completions/CompletionContext.cs +++ b/src/System.CommandLine/Completions/CompletionContext.cs @@ -31,6 +31,8 @@ internal CompletionContext(ParseResult parseResult, string wordToComplete) /// Can be used for testing purposes. public static CompletionContext Empty => _empty ??= new TokenCompletionContext(ParseResult.Empty()); + internal bool IsEmpty => ReferenceEquals(this, _empty); + /// /// Gets the text to be matched for completion, which can be used to filter a list of completions. ///