Skip to content

Commit dfdf5ce

Browse files
authored
fix #1238 (#1461)
* fix #1238 * enable legacy double dash behavior in dotnet-suggest
1 parent e098c0d commit dfdf5ce

File tree

11 files changed

+156
-54
lines changed

11 files changed

+156
-54
lines changed

src/System.CommandLine.Hosting.Tests/HostingTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ void Execute(IHost host)
110110
{
111111
Handler = CommandHandler.Create<IHost>(Execute),
112112
})
113+
.EnableLegacyDoubleDashBehavior()
113114
.UseHost(host =>
114115
{
115116
var invocation = (InvocationContext)host.Properties[typeof(InvocationContext)];
@@ -147,6 +148,7 @@ void Execute(IHost host)
147148
{
148149
Handler = CommandHandler.Create<IHost>(Execute),
149150
})
151+
.EnableLegacyDoubleDashBehavior()
150152
.UseHost(args =>
151153
{
152154
var host = new HostBuilder();

src/System.CommandLine.Suggest.Tests/SuggestionDispatcherTests.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ public class SuggestionDispatcherTests
3030
? @"C:\Windows\System32\net.exe"
3131
: "/bin/net";
3232

33-
private static Registration CurrentExeRegistrationPair()
34-
=> new Registration(CurrentExeFullPath());
33+
private static Registration CurrentExeRegistrationPair() => new(CurrentExeFullPath());
3534

3635
private static string CurrentExeFullPath() => Path.GetFullPath(_currentExeName);
3736

src/System.CommandLine.Suggest/SuggestionDispatcher.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public SuggestionDispatcher(ISuggestionRegistration suggestionRegistration, ISug
6363
};
6464

6565
Parser = new CommandLineBuilder(root)
66+
.EnableLegacyDoubleDashBehavior()
6667
.UseVersionOption()
6768
.UseHelp()
6869
.UseParseDirective()
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.CommandLine.Builder;
5+
using System.CommandLine.Parsing;
6+
using System.CommandLine.Tests.Utility;
7+
using FluentAssertions;
8+
using Xunit;
9+
10+
namespace System.CommandLine.Tests
11+
{
12+
public partial class ParserTests
13+
{
14+
public class DoubleDash
15+
{
16+
[Fact]
17+
public void When_legacy_behavior_is_enabled_then_the_portion_of_the_command_line_following_a_double_dasare_treated_as_unparsed_tokens()
18+
{
19+
var result = new CommandLineBuilder(new RootCommand { new Option("-o") })
20+
.EnableLegacyDoubleDashBehavior()
21+
.Build()
22+
.Parse("-o \"some stuff\" -- x y z");
23+
24+
result.UnparsedTokens
25+
.Should()
26+
.BeEquivalentSequenceTo("x", "y", "z");
27+
}
28+
29+
[Fact]
30+
public void When_legacy_behavior_is_enabled_then_a_double_dash_specifies_that_tokens_matching_options_will_be_treated_as_unparsed_tokens()
31+
{
32+
var optionO = new Option(new[] { "-o" });
33+
var optionX = new Option(new[] { "-x" });
34+
var optionY = new Option(new[] { "-y" });
35+
var optionZ = new Option(new[] { "-z" });
36+
var rootCommand = new RootCommand
37+
{
38+
optionO,
39+
optionX,
40+
optionY,
41+
optionZ
42+
};
43+
var result = new CommandLineBuilder(rootCommand)
44+
.EnableLegacyDoubleDashBehavior()
45+
.Build()
46+
.Parse("-o \"some stuff\" -- -x -y -z -o:foo");
47+
48+
result.HasOption(optionO).Should().BeTrue();
49+
result.HasOption(optionX).Should().BeFalse();
50+
result.HasOption(optionY).Should().BeFalse();
51+
result.HasOption(optionZ).Should().BeFalse();
52+
53+
result.UnparsedTokens
54+
.Should()
55+
.BeEquivalentSequenceTo("-x",
56+
"-y",
57+
"-z",
58+
"-o:foo");
59+
}
60+
61+
[Fact]
62+
public void When_legacy_behavior_is_disabled_then_a_double_dash_specifies_that_further_command_line_args_will_be_treated_as_arguments()
63+
{
64+
var option = new Option<string[]>(new[] { "-o", "--one" });
65+
var argument = new Argument<string[]>();
66+
var rootCommand = new RootCommand
67+
{
68+
option,
69+
argument
70+
};
71+
72+
var result = new CommandLineBuilder(rootCommand)
73+
.EnableLegacyDoubleDashBehavior(false)
74+
.Build()
75+
.Parse("-o \"some stuff\" -- -o --one -x -y -z -o:foo");
76+
77+
result.HasOption(option).Should().BeTrue();
78+
79+
result.GetValueForOption(option).Should().BeEquivalentTo("some stuff");
80+
81+
result.GetValueForArgument(argument).Should().BeEquivalentSequenceTo("-o", "--one", "-x", "-y", "-z", "-o:foo");
82+
83+
result.UnparsedTokens.Should().BeEmpty();
84+
}
85+
86+
[Fact]
87+
public void When_legacy_behavior_is_disabled_then_a_second_double_dash_is_parsed_as_an_argument()
88+
{
89+
var argument = new Argument<string[]>();
90+
var rootCommand = new RootCommand
91+
{
92+
argument
93+
};
94+
95+
var result = new CommandLineBuilder(rootCommand)
96+
.EnableLegacyDoubleDashBehavior(false)
97+
.Build()
98+
.Parse("a b c -- -- d");
99+
100+
var strings = result.GetValueForArgument(argument);
101+
102+
strings.Should().BeEquivalentSequenceTo("a", "b", "c", "--", "d");
103+
}
104+
}
105+
}
106+
}

src/System.CommandLine.Tests/ParserTests.MultipleArguments.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
using System.Collections.Generic;
5-
using System.CommandLine.Parsing;
65
using System.CommandLine.Tests.Utility;
76
using FluentAssertions;
87
using FluentAssertions.Execution;

src/System.CommandLine.Tests/ParserTests.cs

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -93,36 +93,6 @@ public void Two_options_cannot_have_conflicting_aliases()
9393
.Be("Alias '--one' is already in use.");
9494
}
9595

96-
[Fact]
97-
public void A_double_dash_delimiter_specifies_that_no_further_command_line_args_will_be_treated_as_options()
98-
{
99-
var option = new Option(new[] { "-o", "--one" });
100-
var result = new Parser(option)
101-
.Parse("-o \"some stuff\" -- -x -y -z -o:foo");
102-
103-
result.HasOption(option)
104-
.Should()
105-
.BeTrue();
106-
107-
result.UnparsedTokens
108-
.Should()
109-
.BeEquivalentSequenceTo("-x",
110-
"-y",
111-
"-z",
112-
"-o:foo");
113-
}
114-
115-
[Fact]
116-
public void The_portion_of_the_command_line_following_a_double_dash_is_accessible_as_UnparsedTokens()
117-
{
118-
var result = new Parser(new Option("-o"))
119-
.Parse("-o \"some stuff\" -- x y z");
120-
121-
result.UnparsedTokens
122-
.Should()
123-
.BeEquivalentSequenceTo("x", "y", "z");
124-
}
125-
12696
[Fact]
12797
public void Short_form_options_can_be_specified_using_equals_delimiter()
12898
{

src/System.CommandLine/Builder/CommandLineBuilder.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ public CommandLineBuilder(Command? rootCommand = null)
3535
/// </summary>
3636
public bool EnablePosixBundling { get; set; } = true;
3737

38+
/// <summary>
39+
/// Determines the behavior when parsing a double dash (<c>--</c>) in a command line.
40+
/// </summary>
41+
/// <remarks>When set to <see langword="true"/>, all tokens following <c>--</c> will be placed into the <see cref="ParseResult.UnparsedTokens"/> collection. When set to <see langword="false"/>, all tokens following <c>--</c> will be treated as command arguments, even if they match an existing option.</remarks>
42+
public bool EnableLegacyDoubleDashBehavior { get; set; }
43+
3844
/// <summary>
3945
/// Configures the parser's handling of response files. When enabled, a command line token beginning with <c>@</c> that is a valid file path will be expanded as though inserted into the command line.
4046
/// </summary>
@@ -64,6 +70,7 @@ public Parser Build()
6470
Command,
6571
enablePosixBundling: EnablePosixBundling,
6672
enableDirectives: EnableDirectives,
73+
enableLegacyDoubleDashBehavior: EnableLegacyDoubleDashBehavior,
6774
resources: LocalizationResources,
6875
responseFileHandling: ResponseFileHandling,
6976
middlewarePipeline: _middlewareList.OrderBy(m => m.order)

src/System.CommandLine/Builder/CommandLineBuilderExtensions.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,18 @@ public static CommandLineBuilder EnableDirectives(
129129
return builder;
130130
}
131131

132+
/// <summary>
133+
/// Determines the behavior when parsing a double dash (<c>--</c>) in a command line.
134+
/// </summary>
135+
/// <remarks>When set to <see langword="true"/>, all tokens following <c>--</c> will be placed into the <see cref="ParseResult.UnparsedTokens"/> collection. When set to <see langword="false"/>, all tokens following <c>--</c> will be treated as command arguments, even if they match an existing option.</remarks>
136+
public static CommandLineBuilder EnableLegacyDoubleDashBehavior(
137+
this CommandLineBuilder builder,
138+
bool value = true)
139+
{
140+
builder.EnableLegacyDoubleDashBehavior = value;
141+
return builder;
142+
}
143+
132144
/// <summary>
133145
/// Enables the parser to recognize and expand POSIX-style bundled options.
134146
/// </summary>

src/System.CommandLine/Parsing/ParseOperation.cs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ internal class ParseOperation
1111
private readonly TokenizeResult _tokenizeResult;
1212
private readonly CommandLineConfiguration _configuration;
1313
private int _index;
14-
private readonly Dictionary<IArgument, int> _argumentCounts = new Dictionary<IArgument, int>();
14+
private readonly Dictionary<IArgument, int> _argumentCounts = new();
1515

1616
public ParseOperation(
1717
TokenizeResult tokenizeResult,
@@ -23,18 +23,15 @@ public ParseOperation(
2323

2424
private Token CurrentToken => _tokenizeResult.Tokens[_index];
2525

26-
public List<ParseError> Errors { get; } = new List<ParseError>();
26+
public List<ParseError> Errors { get; } = new();
2727

2828
public RootCommandNode? RootCommandNode { get; private set; }
2929

30-
public List<Token> UnmatchedTokens { get; } = new List<Token>();
30+
public List<Token> UnmatchedTokens { get; } = new();
3131

32-
public List<Token> UnparsedTokens { get; } = new List<Token>();
32+
public List<Token> UnparsedTokens { get; } = new();
3333

34-
private void Advance()
35-
{
36-
_index++;
37-
}
34+
private void Advance() => _index++;
3835

3936
private void IncrementCount(IArgument argument)
4037
{
@@ -108,7 +105,8 @@ private void ParseCommandChildren(CommandNode parent)
108105
{
109106
while (More())
110107
{
111-
if (CurrentToken.Type == TokenType.EndOfArguments)
108+
if (_configuration.EnableLegacyDoubleDashBehavior &&
109+
CurrentToken.Type == TokenType.DoubleDash)
112110
{
113111
return;
114112
}
@@ -276,7 +274,7 @@ private void ParseRemainingTokens()
276274

277275
while (More())
278276
{
279-
if (CurrentToken.Type == TokenType.EndOfArguments)
277+
if (CurrentToken.Type == TokenType.DoubleDash)
280278
{
281279
foundEndOfArguments = true;
282280
}

src/System.CommandLine/Parsing/StringExtensions.cs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ internal static TokenizeResult Tokenize(
6464
var errorList = new List<TokenizeError>();
6565

6666
ICommand? currentCommand = null;
67-
var foundEndOfArguments = false;
67+
var foundDoubleDash = false;
6868
var foundEndOfDirectives = !configuration.EnableDirectives;
6969
var argList = NormalizeRootCommand(configuration, args);
7070

@@ -74,16 +74,24 @@ internal static TokenizeResult Tokenize(
7474
{
7575
var arg = argList[i];
7676

77-
if (foundEndOfArguments)
77+
if (foundDoubleDash)
7878
{
79-
tokenList.Add(Operand(arg));
79+
if (configuration.EnableLegacyDoubleDashBehavior)
80+
{
81+
tokenList.Add(Unparsed(arg));
82+
}
83+
else
84+
{
85+
tokenList.Add(Argument(arg));
86+
}
8087
continue;
8188
}
8289

83-
if (arg == "--")
90+
if (!foundDoubleDash &&
91+
arg == "--")
8492
{
85-
tokenList.Add(EndOfArguments());
86-
foundEndOfArguments = true;
93+
tokenList.Add(DoubleDash());
94+
foundDoubleDash = true;
8795
continue;
8896
}
8997

@@ -199,9 +207,9 @@ internal static TokenizeResult Tokenize(
199207

200208
Token UnbundledOption(string value) => new(value, i, wasBundled: true);
201209

202-
Token EndOfArguments() => new("--", TokenType.EndOfArguments, i);
210+
Token DoubleDash() => new("--", TokenType.DoubleDash, i);
203211

204-
Token Operand(string value) => new(value, TokenType.Operand, i);
212+
Token Unparsed(string value) => new(value, TokenType.Unparsed, i);
205213

206214
Token Directive(string value) => new(value, TokenType.Directive, i);
207215
}

src/System.CommandLine/Parsing/TokenType.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ public enum TokenType
3030
/// A double dash (<c>--</c>) token, which changes the meaning of subsequent tokens.
3131
/// </summary>
3232
/// <see cref="CommandLineConfiguration.EnableLegacyDoubleDashBehavior"/>
33-
EndOfArguments,
33+
DoubleDash,
3434

3535
/// <summary>
36-
/// A token following <see cref="EndOfArguments"/>.
36+
/// A token following <see cref="DoubleDash"/> when <see cref="CommandLineConfiguration.EnableLegacyDoubleDashBehavior"/> is set to <see langword="true"/>.
3737
/// </summary>
3838
/// <see cref="CommandLineConfiguration.EnableLegacyDoubleDashBehavior"/>
39-
Operand,
39+
Unparsed,
4040

4141
/// <summary>
4242
/// A directive token.

0 commit comments

Comments
 (0)