diff --git a/src/CommandLine/Core/ValueMapper.cs b/src/CommandLine/Core/ValueMapper.cs index ab01e006..9be7263b 100644 --- a/src/CommandLine/Core/ValueMapper.cs +++ b/src/CommandLine/Core/ValueMapper.cs @@ -62,8 +62,19 @@ private static IEnumerable>> MapValues converted => Tuple.Create(pt.WithValue(Maybe.Just(converted)), Maybe.Nothing()), Tuple.Create>( pt, Maybe.Just(new BadFormatConversionError(NameInfo.EmptyName)))); + + var remainingSpecs = specProps.Skip(1); + var remainingValues = values.Skip(taken.Count()); + + if (remainingValues.Any() && remainingSpecs.Empty()) + { + foreach ( var value in remainingValues ) + { + yield return Tuple.Create>(pt, Maybe.Just(new UnknownValueError(value))); + } + } - foreach (var value in MapValuesImpl(specProps.Skip(1), values.Skip(taken.Count()), converter)) + foreach (var value in MapValuesImpl(remainingSpecs, remainingValues, converter)) { yield return value; } @@ -92,4 +103,4 @@ private static Maybe MakeErrorInCaseOfMinConstraint(this Specification sp : Maybe.Nothing(); } } -} \ No newline at end of file +} diff --git a/src/CommandLine/Error.cs b/src/CommandLine/Error.cs index 21c2fdcd..42a14733 100644 --- a/src/CommandLine/Error.cs +++ b/src/CommandLine/Error.cs @@ -24,6 +24,10 @@ public enum ErrorType /// UnknownOptionError, /// + /// Value of type. + /// + UnknownValueError, + /// /// Value of type. /// MissingRequiredOptionError, @@ -344,6 +348,17 @@ internal UnknownOptionError(string token) } } + /// + /// Models an error generated when an unknown value is detected. + /// + public sealed class UnknownValueError : TokenError + { + internal UnknownValueError(string token) + : base(ErrorType.UnknownValueError, token) + { + } + } + /// /// Models an error generated when a required option is required. /// diff --git a/src/CommandLine/Parser.cs b/src/CommandLine/Parser.cs index 4301aa52..20689baa 100644 --- a/src/CommandLine/Parser.cs +++ b/src/CommandLine/Parser.cs @@ -219,7 +219,7 @@ private static ParserResult DisplayHelp(ParserResult parserResult, Text private static IEnumerable HandleUnknownArguments(bool ignoreUnknownArguments) { return ignoreUnknownArguments - ? Enumerable.Empty().Concat(ErrorType.UnknownOptionError) + ? Enumerable.Empty().Concat(ErrorType.UnknownOptionError).Concat(ErrorType.UnknownValueError) : Enumerable.Empty(); } diff --git a/src/CommandLine/Text/SentenceBuilder.cs b/src/CommandLine/Text/SentenceBuilder.cs index 842ae675..dfd820f8 100644 --- a/src/CommandLine/Text/SentenceBuilder.cs +++ b/src/CommandLine/Text/SentenceBuilder.cs @@ -125,6 +125,8 @@ public override Func FormatError "' has no value."); case ErrorType.UnknownOptionError: return "Option '".JoinTo(((UnknownOptionError)error).Token, "' is unknown."); + case ErrorType.UnknownValueError: + return "Value '".JoinTo(((UnknownValueError)error).Token, "' is unknown."); case ErrorType.MissingRequiredOptionError: var errMisssing = ((MissingRequiredOptionError)error); return errMisssing.NameInfo.Equals(NameInfo.EmptyName) diff --git a/tests/CommandLine.Tests/Unit/Issue847Tests.cs b/tests/CommandLine.Tests/Unit/Issue847Tests.cs new file mode 100644 index 00000000..465d0e3d --- /dev/null +++ b/tests/CommandLine.Tests/Unit/Issue847Tests.cs @@ -0,0 +1,56 @@ +using FluentAssertions; +using System.Linq; +using Xunit; + +// Issue #847 +// no parsing error if additional positional argument is present + +namespace CommandLine.Tests.Unit +{ + public class Issue847Tests + { + [Fact] + public void IgnoreUnknownArguments_should_work_for_values() + { + var arguments = new[] { "foo", "bar", "too_much" }; + var result = new Parser(with => { with.IgnoreUnknownArguments = true; }) + .ParseArguments(arguments); + + Assert.Empty(result.Errors); + Assert.Equal(ParserResultType.Parsed, result.Tag); + + result.WithParsed(options => + { + options.Foo.Should().Be("foo"); + options.Bar.Should().Be("bar"); + }); + } + + [Fact] + public void Additional_positional_arguments_should_raise_errors() + { + var arguments = new[] { "foo", "bar", "too_much" }; + var result = new Parser(with => { with.IgnoreUnknownArguments = false; }) + .ParseArguments(arguments); + + Assert.NotEmpty(result.Errors); + Assert.Equal(ParserResultType.NotParsed, result.Tag); + + result.WithNotParsed(errors => + { + Assert.NotEmpty(errors); + Assert.IsType(errors.Single()); + }); + } + + private class Options + { + [Value(0, Required = true)] + public string Foo { get; set; } + + + [Value(1, Required = false)] + public string Bar { get; set; } + } + } +}