diff --git a/src/System.CommandLine.Tests/ParsingValidationTests.cs b/src/System.CommandLine.Tests/ParsingValidationTests.cs index bb922c9c19..b7a20a2329 100644 --- a/src/System.CommandLine.Tests/ParsingValidationTests.cs +++ b/src/System.CommandLine.Tests/ParsingValidationTests.cs @@ -119,6 +119,34 @@ public void When_FromAmong_is_used_for_multiple_arguments_and_invalid_input_is_p .Be(LocalizationResources.Instance.UnrecognizedArgument("not-key1", new[] { "key1", "key2" })); } + [Fact] + public void When_FromAmong_is_used_multiple_times_only_the_most_recently_provided_values_are_taken_into_account() + { + Argument argument = new("key"); + argument.AcceptOnlyFromAmong("key1"); + + var command = new Command("set") + { + argument + }; + + var result = command.Parse("set key2"); + + result.Errors + .Should() + .ContainSingle() + .Which + .Message + .Should() + .Be(LocalizationResources.Instance.UnrecognizedArgument("key2", new[] { "key1" })); + + argument.AcceptOnlyFromAmong("key2"); + + result = command.Parse("set key2"); + + result.Errors.Should().BeEmpty(); + } + [Fact] public void When_FromAmong_is_used_for_multiple_arguments_and_invalid_input_is_provided_for_the_second_one_then_the_error_is_informative() { diff --git a/src/System.CommandLine/Argument.cs b/src/System.CommandLine/Argument.cs index 2ebd5e1c44..67784e8bd3 100644 --- a/src/System.CommandLine/Argument.cs +++ b/src/System.CommandLine/Argument.cs @@ -37,8 +37,6 @@ protected Argument(string? name = null, string? description = null) Description = description; } - internal HashSet? AllowedValues { get; private set; } - /// /// Gets or sets the arity of the argument. /// @@ -127,16 +125,6 @@ private protected override string DefaultName internal virtual bool HasCustomParser => false; - internal void AddAllowedValues(IReadOnlyList values) - { - if (AllowedValues is null) - { - AllowedValues = new HashSet(); - } - - AllowedValues.UnionWith(values); - } - /// public override IEnumerable GetCompletions(CompletionContext context) { diff --git a/src/System.CommandLine/Argument{T}.cs b/src/System.CommandLine/Argument{T}.cs index dea4e2fffc..85467134f2 100644 --- a/src/System.CommandLine/Argument{T}.cs +++ b/src/System.CommandLine/Argument{T}.cs @@ -1,9 +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.Collections.Generic; using System.CommandLine.Binding; -using System.CommandLine.Completions; using System.CommandLine.Parsing; using System.IO; @@ -179,12 +177,31 @@ public void SetDefaultValueFactory(Func defaultValueFactory) /// The configured argument. public Argument AcceptOnlyFromAmong(params string[] values) { - AllowedValues?.Clear(); - AddAllowedValues(values); - CompletionSources.Clear(); - CompletionSources.Add(values); + if (values is not null && values.Length > 0) + { + Validators.Clear(); + Validators.Add(UnrecognizedArgumentError); + CompletionSources.Clear(); + CompletionSources.Add(values); + } return this; + + void UnrecognizedArgumentError(ArgumentResult argumentResult) + { + for (var i = 0; i < argumentResult.Tokens.Count; i++) + { + var token = argumentResult.Tokens[i]; + + if (token.Symbol is null || token.Symbol == this) + { + if (Array.IndexOf(values, token.Value) < 0) + { + argumentResult.ErrorMessage = argumentResult.LocalizationResources.UnrecognizedArgument(token.Value, values); + } + } + } + } } /// diff --git a/src/System.CommandLine/Parsing/ArgumentResult.cs b/src/System.CommandLine/Parsing/ArgumentResult.cs index 7323e44e5e..de1c16baf1 100644 --- a/src/System.CommandLine/Parsing/ArgumentResult.cs +++ b/src/System.CommandLine/Parsing/ArgumentResult.cs @@ -83,7 +83,7 @@ public void OnlyTake(int numberOfTokens) if (!string.IsNullOrWhiteSpace(ErrorMessage)) { - return new ParseError(ErrorMessage!, this); + return new ParseError(ErrorMessage!, Parent is OptionResult option ? option : this); } } diff --git a/src/System.CommandLine/Parsing/ParseResultVisitor.cs b/src/System.CommandLine/Parsing/ParseResultVisitor.cs index 31edfb3db5..7cb598cbad 100644 --- a/src/System.CommandLine/Parsing/ParseResultVisitor.cs +++ b/src/System.CommandLine/Parsing/ParseResultVisitor.cs @@ -506,9 +506,7 @@ private void ValidateAndConvertArgumentResult(ArgumentResult argumentResult) { var argument = argumentResult.Argument; - var parseError = - argumentResult.Parent?.UnrecognizedArgumentError(argument) ?? - argumentResult.CustomError(argument); + var parseError = argumentResult.CustomError(argument); if (parseError is { }) { diff --git a/src/System.CommandLine/Parsing/SymbolResult.cs b/src/System.CommandLine/Parsing/SymbolResult.cs index ff72eaf48b..0f495cd7cf 100644 --- a/src/System.CommandLine/Parsing/SymbolResult.cs +++ b/src/System.CommandLine/Parsing/SymbolResult.cs @@ -185,29 +185,5 @@ internal ArgumentResult GetOrCreateDefaultArgumentResult(Argument argument) => /// public override string ToString() => $"{GetType().Name}: {this.Token()} {string.Join(" ", Tokens.Select(t => t.Value))}"; - - internal ParseError? UnrecognizedArgumentError(Argument argument) - { - if (argument.AllowedValues?.Count > 0 && - Tokens.Count > 0) - { - for (var i = 0; i < Tokens.Count; i++) - { - var token = Tokens[i]; - - if (token.Symbol is null || token.Symbol == argument) - { - if (!argument.AllowedValues.Contains(token.Value)) - { - return new ParseError( - LocalizationResources.UnrecognizedArgument(token.Value, argument.AllowedValues), - this); - } - } - } - } - - return null; - } } }