Skip to content

Commit 296a021

Browse files
authored
Correct arguments passed to the subcommand in presence of -- (#40120)
1 parent acce423 commit 296a021

File tree

5 files changed

+94
-22
lines changed

5 files changed

+94
-22
lines changed

src/BuiltInTools/dotnet-watch/CommandLineOptions.cs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ internal sealed class CommandLineOptions
6363
// dotnet watch specific options:
6464

6565
var quietOption = new CliOption<bool>("--quiet", "-q") { Description = Resources.Help_Quiet };
66-
var verboseOption = new CliOption<bool>("--verbose", "-v") { Description = Resources.Help_Verbose };
66+
var verboseOption = new CliOption<bool>("--verbose") { Description = Resources.Help_Verbose };
6767
var listOption = new CliOption<bool>("--list") { Description = Resources.Help_List };
6868
var noHotReloadOption = new CliOption<bool>("--no-hot-reload") { Description = Resources.Help_NoHotReload };
6969
var nonInteractiveOption = new CliOption<bool>("--non-interactive") { Description = Resources.Help_NonInteractive };
@@ -190,8 +190,7 @@ where name is not []
190190

191191
private static IReadOnlyList<string> GetLaunchProcessArguments(ParseResult parseResult, IReadOnlyList<CliOption> watchOptions, out string? explicitCommand)
192192
{
193-
explicitCommand = null;
194-
var launchProcessArguments = new List<string>();
193+
var launchArgumentsBuilder = new List<string>();
195194

196195
foreach (var child in parseResult.CommandResult.Children)
197196
{
@@ -204,14 +203,14 @@ private static IReadOnlyList<string> GetLaunchProcessArguments(ParseResult parse
204203

205204
if (optionResult.Tokens.Count == 0)
206205
{
207-
launchProcessArguments.Add(optionResult.IdentifierToken.Value);
206+
launchArgumentsBuilder.Add(optionResult.IdentifierToken.Value);
208207
}
209208
else
210209
{
211210
foreach (var token in optionResult.Tokens)
212211
{
213-
launchProcessArguments.Add(optionResult.IdentifierToken.Value);
214-
launchProcessArguments.Add(token.Value);
212+
launchArgumentsBuilder.Add(optionResult.IdentifierToken.Value);
213+
launchArgumentsBuilder.Add(token.Value);
215214
}
216215
}
217216
}
@@ -223,6 +222,9 @@ private static IReadOnlyList<string> GetLaunchProcessArguments(ParseResult parse
223222
var dashDashIndex = IndexOf(parseResult.Tokens, t => t.Value == "--");
224223
var unmatchedTokensBeforeDashDash = parseResult.UnmatchedTokens.Count - (dashDashIndex >= 0 ? parseResult.Tokens.Count - dashDashIndex - 1 : 0);
225224

225+
explicitCommand = null;
226+
var dashDashInserted = false;
227+
226228
for (int i = 0; i < parseResult.UnmatchedTokens.Count; i++)
227229
{
228230
var token = parseResult.UnmatchedTokens[i];
@@ -234,12 +236,17 @@ private static IReadOnlyList<string> GetLaunchProcessArguments(ParseResult parse
234236
}
235237
else
236238
{
237-
launchProcessArguments.Add(token);
239+
if (!dashDashInserted && i >= unmatchedTokensBeforeDashDash)
240+
{
241+
launchArgumentsBuilder.Add("--");
242+
dashDashInserted = true;
243+
}
244+
245+
launchArgumentsBuilder.Add(token);
238246
}
239247
}
240248

241-
launchProcessArguments.Insert(0, explicitCommand ?? DefaultCommand);
242-
return launchProcessArguments;
249+
return [explicitCommand ?? DefaultCommand, ..launchArgumentsBuilder];
243250
}
244251

245252
private static int IndexOf<T>(IReadOnlyList<T> list, Func<T, bool> predicate)

test/dotnet-watch.Tests/CommandLineOptionsTests.cs

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ public void RemainingOptionsDashDash()
195195
var options = VerifyOptions(["-watchArg", "--", "--verbose", "run", "-runArg"]);
196196

197197
Assert.False(options.Verbose);
198-
Assert.Equal(["run", "-watchArg", "--verbose", "run", "-runArg"], options.LaunchProcessArguments);
198+
Assert.Equal(["run", "-watchArg", "--", "--verbose", "run", "-runArg"], options.LaunchProcessArguments);
199199
}
200200

201201
[Fact]
@@ -204,7 +204,66 @@ public void RemainingOptionsDashDashRun()
204204
var options = VerifyOptions(["--", "run"]);
205205

206206
Assert.False(options.Verbose);
207-
Assert.Equal(["run", "run"], options.LaunchProcessArguments);
207+
Assert.Equal(["run", "--", "run"], options.LaunchProcessArguments);
208+
}
209+
210+
[Fact]
211+
public void NoOptionsAfterDashDash()
212+
{
213+
var options = VerifyOptions(["--"]);
214+
Assert.Equal(["run"], options.LaunchProcessArguments);
215+
}
216+
217+
/// <summary>
218+
/// dotnet watch needs to understand some options that are passed to the subcommands.
219+
/// For example, `-f TFM`
220+
/// When `dotnet watch run -- -f TFM` is parsed `-f TFM` is ignored.
221+
/// Therfore, it has to also be ignored by `dotnet run`,
222+
/// otherwise the TFMs would be inconsistent between `dotnet watch` and `dotnet run`.
223+
/// </summary>
224+
[Fact]
225+
public void ParsedNonWatchOptionsAfterDashDash_Framework()
226+
{
227+
var options = VerifyOptions(["--", "-f", "TFM"]);
228+
229+
Assert.Null(options.TargetFramework);
230+
Assert.Equal(["run", "--", "-f", "TFM"], options.LaunchProcessArguments);
231+
}
232+
233+
[Fact]
234+
public void ParsedNonWatchOptionsAfterDashDash_Project()
235+
{
236+
var options = VerifyOptions(["--", "--project", "proj"]);
237+
238+
Assert.Null(options.Project);
239+
Assert.Equal(["run", "--", "--project", "proj"], options.LaunchProcessArguments);
240+
}
241+
242+
[Fact]
243+
public void ParsedNonWatchOptionsAfterDashDash_NoLaunchProfile()
244+
{
245+
var options = VerifyOptions(["--", "--no-launch-profile"]);
246+
247+
Assert.False(options.NoLaunchProfile);
248+
Assert.Equal(["run", "--", "--no-launch-profile"], options.LaunchProcessArguments);
249+
}
250+
251+
[Fact]
252+
public void ParsedNonWatchOptionsAfterDashDash_LaunchProfile()
253+
{
254+
var options = VerifyOptions(["--", "--launch-profile", "p"]);
255+
256+
Assert.False(options.NoLaunchProfile);
257+
Assert.Equal(["run", "--", "--launch-profile", "p"], options.LaunchProcessArguments);
258+
}
259+
260+
[Fact]
261+
public void ParsedNonWatchOptionsAfterDashDash_Property()
262+
{
263+
var options = VerifyOptions(["--", "--property", "x=1"]);
264+
265+
Assert.False(options.NoLaunchProfile);
266+
Assert.Equal(["run", "--", "--property", "x=1"], options.LaunchProcessArguments);
208267
}
209268

210269
[Theory]
@@ -287,12 +346,12 @@ public void OptionDuplicates_NotAllowed(string option)
287346
[Theory]
288347
[InlineData(new[] { "--unrecognized-arg" }, new[] { "run", "--unrecognized-arg" })]
289348
[InlineData(new[] { "run" }, new[] { "run" })]
290-
[InlineData(new[] { "run", "--", "runarg" }, new[] { "run", "runarg" })]
349+
[InlineData(new[] { "run", "--", "runarg" }, new[] { "run", "--", "runarg" })]
291350
[InlineData(new[] { "--verbose", "run", "runarg1", "-runarg2" }, new[] { "run", "runarg1", "-runarg2" })]
292351
// run is after -- and therefore not parsed as a command:
293-
[InlineData(new[] { "--verbose", "--", "run", "--", "runarg" }, new[] { "run", "run", "--", "runarg" })]
352+
[InlineData(new[] { "--verbose", "--", "run", "--", "runarg" }, new[] { "run", "--", "run", "--", "runarg" })]
294353
// run is before -- and therefore parsed as a command:
295-
[InlineData(new[] { "--verbose", "run", "--", "--", "runarg" }, new[] { "run", "--", "runarg" })]
354+
[InlineData(new[] { "--verbose", "run", "--", "--", "runarg" }, new[] { "run", "--", "--", "runarg" })]
296355
public void ParsesRemainingArgs(string[] args, string[] expected)
297356
{
298357
var options = VerifyOptions(args);

test/dotnet-watch.Tests/Watch/DotNetWatcherTests.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ public async Task RunsWithDotnetLaunchProfileEnvVariableWhenExplicitlySpecified(
4646
var testAsset = TestAssets.CopyTestAsset(AppName)
4747
.WithSource();
4848

49-
await App.StartWatcherAsync(testAsset, applicationArguments: [ "--launch-profile", "Second"]);
49+
App.DotnetWatchArgs.Add("--launch-profile");
50+
App.DotnetWatchArgs.Add("Second");
51+
52+
await App.StartWatcherAsync(testAsset);
5053
Assert.Equal("<<<Second>>>", await App.AssertOutputLineStartsWith("DOTNET_LAUNCH_PROFILE = "));
5154
}
5255

@@ -58,7 +61,10 @@ public async Task RunsWithDotnetLaunchProfileEnvVariableWhenExplicitlySpecifiedB
5861
var testAsset = TestAssets.CopyTestAsset(AppName)
5962
.WithSource();
6063

61-
await App.StartWatcherAsync(testAsset, applicationArguments: ["--launch-profile", "Third"]);
64+
App.DotnetWatchArgs.Add("--launch-profile");
65+
App.DotnetWatchArgs.Add("Third");
66+
67+
await App.StartWatcherAsync(testAsset);
6268
Assert.Equal("<<<First>>>", await App.AssertOutputLineStartsWith("DOTNET_LAUNCH_PROFILE = "));
6369
}
6470

test/dotnet-watch.Tests/Watch/NoDepsAppTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ public async Task RestartProcessOnFileChange()
1818
var testAsset = TestAssets.CopyTestAsset(AppName)
1919
.WithSource();
2020

21-
await App.StartWatcherAsync(testAsset, applicationArguments: ["--no-hot-reload", "--no-exit"]);
21+
App.DotnetWatchArgs.Add("--no-hot-reload");
22+
await App.StartWatcherAsync(testAsset, applicationArguments: ["--no-exit"]);
2223
var processIdentifier = await App.AssertOutputLineStartsWith("Process identifier =");
2324

2425
// Then wait for it to restart when we change a file

test/dotnet-watch.Tests/Watch/ProgramTests.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,16 +87,15 @@ public async Task RunArguments_NoHotReload()
8787
[
8888
"--no-hot-reload",
8989
"run",
90-
"-f", // dotnet watch does not recognize this arg -> dotnet run arg
90+
"-f",
9191
"net6.0",
9292
"--property:AssemblyVersion=1.2.3.4",
9393
"--property",
9494
"AssemblyTitle= | A=B'\tC | ",
95-
"--", // the following args are not dotnet watch args
96-
"-v", // dotnet run arg
95+
"-v",
9796
"minimal",
98-
"--", // the following args are not dotnet run args
99-
"-v", // application arg
97+
"--", // the following args are application args
98+
"-v",
10099
]);
101100

102101
Assert.Equal("-v", await App.AssertOutputLineStartsWith("Arguments = "));

0 commit comments

Comments
 (0)