diff --git a/src/sdk/.editorconfig b/src/sdk/.editorconfig index bc17bbe6926..e35b5cd19b0 100644 --- a/src/sdk/.editorconfig +++ b/src/sdk/.editorconfig @@ -511,11 +511,11 @@ dotnet_diagnostic.IDE0040.severity = warning insert_final_newline = false # Verify settings -[*.{received,verified}.{txt,xml,json}] +[*.{received,verified}.{txt,xml,json,sh,zsh,nu,fish,ps1}] charset = "utf-8-bom" end_of_line = lf indent_size = unset indent_style = unset insert_final_newline = false tab_width = unset -trim_trailing_whitespace = false \ No newline at end of file +trim_trailing_whitespace = false diff --git a/src/sdk/.gitattributes b/src/sdk/.gitattributes index 2f46e347155..a7c35ea1b75 100644 --- a/src/sdk/.gitattributes +++ b/src/sdk/.gitattributes @@ -67,3 +67,7 @@ *.verified.txt text eol=lf working-tree-encoding=UTF-8 *.verified.xml text eol=lf working-tree-encoding=UTF-8 *.verified.json text eol=lf working-tree-encoding=UTF-8 +*.verified.sh text eol=lf working-tree-encoding=UTF-8 +*.verified.zsh text eol=lf working-tree-encoding=UTF-8 +*.verified.nu text eol=lf working-tree-encoding=UTF-8 +*.verified.fish text eol=lf working-tree-encoding=UTF-8 diff --git a/src/sdk/.vscode/settings.json b/src/sdk/.vscode/settings.json index 1cdcc7c1869..212ebd955c2 100644 --- a/src/sdk/.vscode/settings.json +++ b/src/sdk/.vscode/settings.json @@ -1,4 +1,11 @@ { "dotnet.testWindow.disableAutoDiscovery": true, - "dotnet.defaultSolution": "sdk.slnx" + "dotnet.testWindow.disableBuildOnRun": true, + "dotnet.defaultSolution": "cli.slnf", + "files.associations": { + "*.slnf": "json", + "*.props": "xml", + "*.targets": "xml", + "*.*proj": "xml" + } } diff --git a/src/sdk/cli.slnf b/src/sdk/cli.slnf new file mode 100644 index 00000000000..a300800156b --- /dev/null +++ b/src/sdk/cli.slnf @@ -0,0 +1,15 @@ +{ + "solution": { + "path": "sdk.slnx", + "projects": [ + "src\\BuiltInTools\\dotnet-watch\\dotnet-watch.csproj", + "src\\Cli\\dotnet\\dotnet.csproj", + "src\\Cli\\Microsoft.DotNet.Cli.Utils\\Microsoft.DotNet.Cli.Utils.csproj", + "test\\dotnet-new.IntegrationTests\\dotnet-new.IntegrationTests.csproj", + "test\\dotnet-watch.Tests\\dotnet-watch.Tests.csproj", + "test\\dotnet.Tests\\dotnet.Tests.csproj", + "test\\Microsoft.DotNet.Cli.Utils.Tests\\Microsoft.DotNet.Cli.Utils.Tests.csproj", + "test\\Microsoft.NET.TestFramework\\Microsoft.NET.TestFramework.csproj" + ] + } +} diff --git a/src/sdk/sdk.slnx b/src/sdk/sdk.slnx index 23f5c801d7d..c8592eeb1f7 100644 --- a/src/sdk/sdk.slnx +++ b/src/sdk/sdk.slnx @@ -62,6 +62,7 @@ + diff --git a/src/sdk/src/BuiltInTools/BrowserRefresh/Microsoft.AspNetCore.Watch.BrowserRefresh.csproj b/src/sdk/src/BuiltInTools/BrowserRefresh/Microsoft.AspNetCore.Watch.BrowserRefresh.csproj index cb7cec42e39..4aeac077f07 100644 --- a/src/sdk/src/BuiltInTools/BrowserRefresh/Microsoft.AspNetCore.Watch.BrowserRefresh.csproj +++ b/src/sdk/src/BuiltInTools/BrowserRefresh/Microsoft.AspNetCore.Watch.BrowserRefresh.csproj @@ -25,4 +25,10 @@ + + + + %(FileName)%(Extension) + + diff --git a/src/sdk/src/BuiltInTools/Web.Middleware/BrowserScriptMiddleware.cs b/src/sdk/src/BuiltInTools/Web.Middleware/BrowserScriptMiddleware.cs index fcc10510e5e..bade75e4fed 100644 --- a/src/sdk/src/BuiltInTools/Web.Middleware/BrowserScriptMiddleware.cs +++ b/src/sdk/src/BuiltInTools/Web.Middleware/BrowserScriptMiddleware.cs @@ -48,7 +48,7 @@ public async Task InvokeAsync(HttpContext context) // for backwards compat only internal static ReadOnlyMemory GetBlazorHotReloadJS() { - var jsFileName = "Microsoft.AspNetCore.Watch.BrowserRefresh.BlazorHotReload.js"; + var jsFileName = "BlazorHotReload.js"; using var stream = new MemoryStream(); var manifestStream = typeof(BrowserScriptMiddleware).Assembly.GetManifestResourceStream(jsFileName)!; manifestStream.CopyTo(stream); @@ -66,7 +66,7 @@ internal static ReadOnlyMemory GetBrowserRefreshJS() internal static ReadOnlyMemory GetWebSocketClientJavaScript(string hostString, string serverKey) { - var jsFileName = "Microsoft.AspNetCore.Watch.BrowserRefresh.WebSocketScriptInjection.js"; + var jsFileName = "WebSocketScriptInjection.js"; using var reader = new StreamReader(typeof(BrowserScriptMiddleware).Assembly.GetManifestResourceStream(jsFileName)!); var script = reader.ReadToEnd() .Replace("{{hostString}}", hostString) diff --git a/src/sdk/src/BuiltInTools/Web.Middleware/Microsoft.DotNet.HotReload.Web.Middleware.Package.csproj b/src/sdk/src/BuiltInTools/Web.Middleware/Microsoft.DotNet.HotReload.Web.Middleware.Package.csproj index f89cbbb3407..5bdde90926b 100644 --- a/src/sdk/src/BuiltInTools/Web.Middleware/Microsoft.DotNet.HotReload.Web.Middleware.Package.csproj +++ b/src/sdk/src/BuiltInTools/Web.Middleware/Microsoft.DotNet.HotReload.Web.Middleware.Package.csproj @@ -38,4 +38,19 @@ + + + $(TargetsForTfmSpecificContentInPackage);_AddJSFilesToSourcePackage + + + + + + + <_File Include="$(MSBuildProjectDirectory)\**\*.js" TargetDir="contentFiles/cs/$(TargetFramework)" BuildAction="EmbeddedResource" /> + + + diff --git a/src/sdk/src/BuiltInTools/dotnet-watch/CommandLine/CommandLineOptions.cs b/src/sdk/src/BuiltInTools/dotnet-watch/CommandLine/CommandLineOptions.cs index 95e98a584af..0679d27d689 100644 --- a/src/sdk/src/BuiltInTools/dotnet-watch/CommandLine/CommandLineOptions.cs +++ b/src/sdk/src/BuiltInTools/dotnet-watch/CommandLine/CommandLineOptions.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using Microsoft.Build.Logging; using Microsoft.DotNet.Cli; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Run; using Microsoft.DotNet.Cli.Extensions; using Microsoft.Extensions.Logging; @@ -119,7 +120,7 @@ internal sealed class CommandLineOptions // determine subcommand: var explicitCommand = TryGetSubcommand(parseResult); var command = explicitCommand ?? RunCommandParser.GetCommand(); - var buildOptions = command.Options.Where(o => o is IForwardedOption); + var buildOptions = command.Options.Where(o => o.ForwardingFunction is not null); foreach (var buildOption in buildOptions) { @@ -161,7 +162,7 @@ internal sealed class CommandLineOptions var commandArguments = GetCommandArguments(parseResult, watchOptions, explicitCommand, out var binLogToken, out var binLogPath); // We assume that forwarded options, if any, are intended for dotnet build. - var buildArguments = buildOptions.Select(option => ((IForwardedOption)option).GetForwardingFunction()(parseResult)).SelectMany(args => args).ToList(); + var buildArguments = buildOptions.Select(option => option.ForwardingFunction!(parseResult)).SelectMany(args => args).ToList(); if (binLogToken != null) { diff --git a/src/sdk/src/Cli/Microsoft.DotNet.Cli.CommandLine/ArgumentBuilderExtensions.cs b/src/sdk/src/Cli/Microsoft.DotNet.Cli.CommandLine/ArgumentBuilderExtensions.cs new file mode 100644 index 00000000000..a88efd6259b --- /dev/null +++ b/src/sdk/src/Cli/Microsoft.DotNet.Cli.CommandLine/ArgumentBuilderExtensions.cs @@ -0,0 +1,19 @@ +using System.CommandLine; +using System.CommandLine.Completions; + +namespace Microsoft.DotNet.Cli.CommandLine; + +/// +/// Extension methods that make it easier to chain argument configuration methods when building arguments. +/// +public static class ArgumentBuilderExtensions +{ + extension(Argument argument) + { + public Argument AddCompletions(Func> completionSource) + { + argument.CompletionSources.Add(completionSource); + return argument; + } + } +} diff --git a/src/sdk/src/Cli/Microsoft.DotNet.Cli.CommandLine/ForwardedOptionExtensions.cs b/src/sdk/src/Cli/Microsoft.DotNet.Cli.CommandLine/ForwardedOptionExtensions.cs new file mode 100644 index 00000000000..040f4595f7b --- /dev/null +++ b/src/sdk/src/Cli/Microsoft.DotNet.Cli.CommandLine/ForwardedOptionExtensions.cs @@ -0,0 +1,219 @@ +using System.CommandLine; +using System.CommandLine.Parsing; + +namespace Microsoft.DotNet.Cli.CommandLine; + +/// +/// Extensions for tracking and invoking forwarding functions on options and arguments. +/// Forwarding functions are used to translate the parsed value of an option or argument +/// into a set of zero or more string values that will be passed to an inner command. +/// +public static class ForwardedOptionExtensions +{ + private static readonly Dictionary>> s_forwardingFunctions = []; + private static readonly Lock s_lock = new(); + + extension(Option option) + { + /// + /// If this option has a forwarding function, this property will return it; otherwise, it will be null. + /// + /// + /// This getter is on the untyped Option because much of the _processing_ of option forwarding + /// is done at the ParseResult level, where we don't have the generic type parameter. + /// + public Func>? ForwardingFunction => s_forwardingFunctions.GetValueOrDefault(option); + } + + extension(Option option) + { + /// + /// Internal-only helper function that ensures the provided forwarding function is only called + /// if the option actually has a value. + /// + private Func> GetForwardingFunction(Func> func) + { + return (ParseResult parseResult) => + { + if (parseResult.GetResult(option) is OptionResult r) + { + if (r.GetValueOrDefault() is TValue value) + { + return func(value); + } + else + { + return []; + } + } + return []; + }; + } + + /// + /// Internal-only helper function that ensures the provided forwarding function is only called + /// if the option actually has a value. + /// + private Func> GetForwardingFunction(Func> func) + { + return (ParseResult parseResult) => + { + if (parseResult.GetResult(option) is OptionResult r) + { + if (r.GetValueOrDefault() is TValue value) + { + return func(value, parseResult); + } + else + { + return []; + } + } + return []; + }; + } + + /// + /// Forwards the option using the provided function to convert the option's value to zero or more string values. + /// The function will only be called if the option has a value. + /// + public Option SetForwardingFunction(Func> func) + { + lock (s_lock) + { + s_forwardingFunctions[option] = option.GetForwardingFunction(func); + } + return option; + } + + /// + /// Forward the option using the provided function to convert the option's value to a single string value. + /// The function will only be called if the option has a value. + /// + public Option SetForwardingFunction(Func format) + { + lock (s_lock) + { + s_forwardingFunctions[option] = option.GetForwardingFunction(o => [format(o)]); + } + return option; + } + + /// + /// Forward the option using the provided function to convert the option's value to a single string value. + /// The function will only be called if the option has a value. + /// + public Option SetForwardingFunction(Func> func) + { + lock (s_lock) + { + s_forwardingFunctions[option] = option.GetForwardingFunction(func); + } + return option; + } + + /// + /// Forward the option as multiple calculated string values from whatever the option's value is. + /// + /// + /// + public Option ForwardAsMany(Func> format) => option.SetForwardingFunction(format); + + /// + /// Forward the option as its own name. + /// + /// + public Option Forward() => option.SetForwardingFunction((TValue? o) => [option.Name]); + + /// + /// Forward the option as a string value. This value will be forwarded as long as the option has a OptionResult - which means that + /// any implicit value calculation will cause the string value to be forwarded. + /// + public Option ForwardAs(string value) => option.SetForwardingFunction((TValue? o) => [value]); + + /// + /// Forward the option as a singular calculated string value. + /// + public Option ForwardAsSingle(Func format) => option.SetForwardingFunction(format); + } + + extension(Option option) + { + /// + /// Forward the boolean option as a string value. This value will be forwarded as long as the option has a OptionResult - which means that + /// any implicit value calculation will cause the string value to be forwarded. For boolean options specifically, if the option is zero arity + /// and has no default value factory, S.CL will synthesize a true or false value based on whether the option was provided or not, so we need to + /// add an additional implicit 'value is true' check to prevent accidentally forwarding the option for flags that are absent.. + /// + public Option ForwardIfEnabled(string value) => option.SetForwardingFunction((bool o) => o ? [value] : []); + /// + /// Forward the boolean option as a string value. This value will be forwarded as long as the option has a OptionResult - which means that + /// any implicit value calculation will cause the string value to be forwarded. For boolean options specifically, if the option is zero arity + /// and has no default value factory, S.CL will synthesize a true or false value based on whether the option was provided or not, so we need to + /// add an additional implicit 'value is true' check to prevent accidentally forwarding the option for flags that are absent.. + /// + public Option ForwardIfEnabled(string[] value) => option.SetForwardingFunction((bool o) => o ? value : []); + + /// + /// Forward the boolean option as a string value. This value will be forwarded as long as the option has a OptionResult - which means that + /// any implicit value calculation will cause the string value to be forwarded. For boolean options specifically, if the option is zero arity + /// and has no default value factory, S.CL will synthesize a true or false value based on whether the option was provided or not, so we need to + /// add an additional implicit 'value is true' check to prevent accidentally forwarding the option for flags that are absent.. + /// + public Option ForwardAs(string value) => option.ForwardIfEnabled(value); + } + + extension(Option> option) + { + /// + /// Foreach argument in the option's value, yield the followed by the argument. + /// + public Option> ForwardAsManyArgumentsEachPrefixedByOption(string alias) => + option.ForwardAsMany(o => ForwardedArguments(alias, o)); + } + + extension(ParseResult parseResult) + { + /// + /// Calls the forwarding functions for all options that have declared a forwarding function (via 's extension members) in the provided . + /// + /// + /// If not provided, uses the 's . + /// + public IEnumerable OptionValuesToBeForwarded(Command? command = null) => + (command ?? parseResult.CommandResult.Command) + .Options + .Select(o => o.ForwardingFunction) + .SelectMany(f => f is not null ? f(parseResult) : []); + + /// + /// Tries to find the first option named in , and if found, + /// invokes its forwarding function (if any) and returns the result. If no option with that name is found, or if the option + /// has no forwarding function, returns an empty enumeration. + /// + /// + /// + /// + public IEnumerable ForwardedOptionValues(Command command, string alias) + { + var func = command.Options? + .Where(o => + (o.Name.Equals(alias) || o.Aliases.Contains(alias)) + && o.ForwardingFunction is not null) + .FirstOrDefault()?.ForwardingFunction; + return func?.Invoke(parseResult) ?? []; + } + } + + /// + /// For each argument in , yield the followed by the argument. + /// + private static IEnumerable ForwardedArguments(string alias, IEnumerable? arguments) + { + foreach (string arg in arguments ?? []) + { + yield return alias; + yield return arg; + } + } +} diff --git a/src/sdk/src/Cli/Microsoft.DotNet.Cli.CommandLine/Microsoft.DotNet.Cli.CommandLine.csproj b/src/sdk/src/Cli/Microsoft.DotNet.Cli.CommandLine/Microsoft.DotNet.Cli.CommandLine.csproj new file mode 100644 index 00000000000..4e2669b28ba --- /dev/null +++ b/src/sdk/src/Cli/Microsoft.DotNet.Cli.CommandLine/Microsoft.DotNet.Cli.CommandLine.csproj @@ -0,0 +1,19 @@ + + + + $(SdkTargetFramework) + enable + enable + MicrosoftAspNetCore + true + true + + + + + + + + + + diff --git a/src/sdk/src/Cli/Microsoft.DotNet.Cli.CommandLine/OptionBuilderExtensions.cs b/src/sdk/src/Cli/Microsoft.DotNet.Cli.CommandLine/OptionBuilderExtensions.cs new file mode 100644 index 00000000000..19b502a4b70 --- /dev/null +++ b/src/sdk/src/Cli/Microsoft.DotNet.Cli.CommandLine/OptionBuilderExtensions.cs @@ -0,0 +1,46 @@ +using System.CommandLine; +using System.CommandLine.Completions; + +namespace Microsoft.DotNet.Cli.CommandLine; + +/// +/// Extension methods that make it easier to chain option configuration methods when building options. +/// +public static class OptionBuilderExtensions +{ + extension(T option) where T : Option + { + /// + /// Forces an option that represents a collection-type to only allow a single + /// argument per instance of the option. This means that you'd have to + /// use the option multiple times to pass multiple values. + /// This prevents ambiguity in parsing when argument tokens may appear after the option. + /// + /// + /// + /// + public T AllowSingleArgPerToken() + { + option.AllowMultipleArgumentsPerToken = false; + return option; + } + + + public T AggregateRepeatedTokens() + { + option.AllowMultipleArgumentsPerToken = true; + return option; + } + + public T Hide() + { + option.Hidden = true; + return option; + } + public T AddCompletions(Func> completionSource) + { + option.CompletionSources.Add(completionSource); + return option; + } + } +} diff --git a/src/sdk/src/Cli/Microsoft.DotNet.Cli.CommandLine/README.md b/src/sdk/src/Cli/Microsoft.DotNet.Cli.CommandLine/README.md new file mode 100644 index 00000000000..82e8afb6e8d --- /dev/null +++ b/src/sdk/src/Cli/Microsoft.DotNet.Cli.CommandLine/README.md @@ -0,0 +1,12 @@ +# Microsoft.Dotnet.Cli.CommandLine + +This project contains extensions and utilities for building command line applications. + +These extensions are layered on top of core System.CommandLine concepts and types, and +do not directly reference concepts that are specific to the `dotnet` CLI. We hope that +these would be published separately as a NuGet package for use by other command line +applications in the future. + +From a layering perspective, everything that is specific to the `dotnet` CLI should +be in the `src/Cli/dotnet` or `src/Cli/Microsoft.DotNet.Cli.Utils` projects, which +reference this project. Keep this one generally-speaking clean. diff --git a/src/sdk/src/Cli/Microsoft.DotNet.Cli.CommandLine/ResultNavigationExtensions.cs b/src/sdk/src/Cli/Microsoft.DotNet.Cli.CommandLine/ResultNavigationExtensions.cs new file mode 100644 index 00000000000..b4bd612cc6c --- /dev/null +++ b/src/sdk/src/Cli/Microsoft.DotNet.Cli.CommandLine/ResultNavigationExtensions.cs @@ -0,0 +1,94 @@ +using System.CommandLine; +using System.CommandLine.Parsing; + +namespace Microsoft.DotNet.Cli.CommandLine; + +/// +/// Extension methods for safely navigating ParseResult and SymbolResult to get option values. +/// +public static class ResultNavigationExtensions +{ + /// + /// Only returns the value for this option if the option is present and there are no parse errors for that option. + /// This allows cross-cutting code like the telemetry filters to safely get the value without throwing on null-ref errors. + /// If you are inside a command handler or 'normal' System.CommandLine code then you don't need this - the parse error handling + /// will have covered these cases. + /// + public static T? SafelyGetValueForOption(this ParseResult parseResult, Option optionToGet) + { + if (parseResult.GetResult(optionToGet) is OptionResult optionResult // only return a value if there _is_ a value - default or otherwise + && !parseResult.Errors.Any(e => e.SymbolResult == optionResult) // only return a value if this isn't a parsing error + && optionResult.Option.ValueType.IsAssignableTo(typeof(T))) // only return a value if coercing the type won't error + { + // shouldn't happen because of the above checks, but we can be safe since this is only used in telemetry, and should + // be resistant to errors + try + { + return optionResult.GetValue(optionToGet); + } + catch + { + return default; + } + } + else + { + return default; + } + } + + /// + /// Only returns the value for this option if the option is present and there are no parse errors for that option. + /// This allows cross-cutting code like the telemetry filters to safely get the value without throwing on null-ref errors. + /// If you are inside a command handler or 'normal' System.CommandLine code then you don't need this - the parse error handling + /// will have covered these cases. + /// + public static T? SafelyGetValueForOption(this ParseResult parseResult, string name) + { + if (parseResult.GetResult(name) is OptionResult optionResult // only return a value if there _is_ a value - default or otherwise + && !parseResult.Errors.Any(e => e.SymbolResult == optionResult) // only return a value if this isn't a parsing error + && optionResult.Option.ValueType.IsAssignableTo(typeof(T))) // only return a value if coercing the type won't error + { + // shouldn't happen because of the above checks, but we can be safe since this is only used in telemetry, and should + // be resistant to errors + try + { + return optionResult.GetValue(name); + } + catch + { + return default; + } + } + else + { + return default; + } + } + + /// + /// Checks if the option is present and not implicit (i.e. not set by default). + /// This is useful for checking if the user has explicitly set an option, as opposed to it being set by default. + /// + public static bool HasOption(this ParseResult parseResult, Option option) => parseResult.GetResult(option) is OptionResult or && !or.Implicit; + + /// + /// Checks if the option with given name is present and not implicit (i.e. not set by default). + /// This is useful for checking if the user has explicitly set an option, as opposed to it being set by default. + /// + public static bool HasOption(this ParseResult parseResult, string name) + => parseResult.GetResult(name) is OptionResult or && !or.Implicit; + + /// + /// Checks if the option is present and not implicit (i.e. not set by default). + /// This is useful for checking if the user has explicitly set an option, as opposed to it being set by default. + /// + public static bool HasOption(this SymbolResult symbolResult, Option option) => symbolResult.GetResult(option) is OptionResult or && !or.Implicit; + + /// + /// Checks if the option with given name is present and not implicit (i.e. not set by default). + /// This is useful for checking if the user has explicitly set an option, as opposed to it being set by default. + /// + public static bool HasOption(this SymbolResult symbolResult, string name) + => symbolResult.GetResult(name) is OptionResult or && !or.Implicit; +} diff --git a/src/sdk/src/Cli/Microsoft.DotNet.Cli.CommandLine/SpanParsableExtensions.cs b/src/sdk/src/Cli/Microsoft.DotNet.Cli.CommandLine/SpanParsableExtensions.cs new file mode 100644 index 00000000000..9419a038ccd --- /dev/null +++ b/src/sdk/src/Cli/Microsoft.DotNet.Cli.CommandLine/SpanParsableExtensions.cs @@ -0,0 +1,153 @@ +using System.Collections.Immutable; +using System.CommandLine; +using System.CommandLine.Parsing; + +namespace Microsoft.DotNet.Cli.CommandLine; + +public static class SpanParserExtensions +{ + extension(Option o) where T : ISpanParsable + { + /// + /// Configures the option with a custom parser that uses the implementation to parse the tokens provided. + /// Will parse a single token with , and if the option allows multiple tokens will take the 'last one wins' approach. + /// + /// + /// Without this, Options will fall-back to using potentially-reflection-based parsing in S.CL, or + /// if the type doesn't have built-in S.CL parsing support will fail to parse at runtime. + /// + public Option AsSpanParsable() + { + o.CustomParser = StaticSingleItemParser; + return o; + } + } + + extension(Option> o) where T : ISpanParsable + { + /// + /// Configures the option with a custom parser that uses the implementation to parse the tokens provided. + /// This parser handles multiple tokens, using for each token. + /// + /// + /// Without this, Options will fall-back to using potentially-reflection-based parsing in S.CL, or + /// if the type doesn't have built-in S.CL parsing support will fail to parse at runtime. + /// + public Option> AsSpanParsable() + { + o.CustomParser = StaticMultiItemItemParser; + return o; + } + } + + extension(Argument a) where T : ISpanParsable + { + /// + /// Configures the argument with a custom parser that uses the implementation to parse the value. + /// Will parse a single token with , and if the argument allows multiple tokens will take the 'last one wins' approach. + /// + /// + /// Without this, Arguments will fall-back to using potentially-reflection-based parsing in S.CL, or + /// if the type doesn't have built-in S.CL parsing support will fail to parse at runtime. + /// + public Argument AsSpanParsable() + { + a.CustomParser = StaticSingleItemParser; + return a; + } + } + + extension(Argument> a) where T : ISpanParsable + { + /// + /// Configures the argument with a custom parser that uses the implementation to parse the value. + /// This parser handles multiple tokens, using for each token. + /// + /// + /// Without this, Arguments will fall-back to using potentially-reflection-based parsing in S.CL, or + /// if the type doesn't have built-in S.CL parsing support will fail to parse at runtime. + /// + public Argument> AsSpanParsable() + { + a.CustomParser = StaticMultiItemItemParser; + return a; + } + } + + internal static IReadOnlyCollection? StaticMultiItemItemParser(ArgumentResult tokenizationResult) + where T : ISpanParsable + { + if (tokenizationResult.Tokens.Count == 0) + { + return default; + } + + var parentName = + tokenizationResult.Parent switch + { + OptionResult optionResult => optionResult.Option.Name, + ArgumentResult argumentResult => argumentResult.Argument.Name, + CommandResult or null => tokenizationResult.Argument.Name, + _ => "" + }; + var coll = ImmutableArray.CreateBuilder(tokenizationResult.Tokens.Count); + + foreach (var token in tokenizationResult.Tokens) + { + var tokenToParse = token.Value; + + if (string.IsNullOrEmpty(tokenToParse)) + { + tokenizationResult.AddError($"Cannot parse null or empty value for symbol '{parentName}'"); + continue; + } + + if (!T.TryParse(tokenToParse, null, out var result)) + { + tokenizationResult.AddError($"Cannot parse value '{tokenToParse}' for symbol '{parentName}' as a {typeof(T).Name}"); + continue; + } + + coll.Add(result); + } + + return coll.ToImmutableArray(); + } + + internal static T? StaticSingleItemParser(ArgumentResult tokenizationResult) + where T : ISpanParsable + { + if (tokenizationResult.Tokens.Count == 0) + { + return default; + } + + var parentName = + tokenizationResult.Parent switch + { + OptionResult optionResult => optionResult.Option.Name, + ArgumentResult argumentResult => argumentResult.Argument.Name, + CommandResult or null => tokenizationResult.Argument.Name, + _ => "" + }; + // we explicitly only support parsing one token, so let's do a last-one-wins approach here + var tokenToParse = + tokenizationResult.Tokens switch + { + [var onlyToken] => onlyToken.Value, + _ => tokenizationResult.Tokens[^1].Value + }; + + if (string.IsNullOrEmpty(tokenToParse)) + { + tokenizationResult.AddError($"Cannot parse null or empty value for symbol '{parentName}'"); + } + + if (!T.TryParse(tokenToParse, null, out var result)) + { + tokenizationResult.AddError($"Cannot parse value '{tokenToParse}' for symbol '{parentName}' as a {typeof(T).Name}"); + } + + return result; + } +} diff --git a/src/sdk/src/Cli/Microsoft.DotNet.Cli.CommandLine/SymbolDocumentationExtensions.cs b/src/sdk/src/Cli/Microsoft.DotNet.Cli.CommandLine/SymbolDocumentationExtensions.cs new file mode 100644 index 00000000000..908a8911b4c --- /dev/null +++ b/src/sdk/src/Cli/Microsoft.DotNet.Cli.CommandLine/SymbolDocumentationExtensions.cs @@ -0,0 +1,40 @@ +using System.CommandLine; + +namespace Microsoft.DotNet.Cli.CommandLine; + +/// +/// Extension methods for adding documentation links to commands, options and arguments. +/// +public static class SymbolDocumentationExtensions +{ + private static readonly Dictionary s_documentationLinks = new(); + private static readonly Lock s_lock = new(); + + extension(Symbol symbol) + { + /// + /// Gets or sets the documentation link for this command, option or argument. + /// This link is intended to be shown in help or error messages to point users to more information. + /// It is not used by the command line parser itself. + /// + public string? DocsLink + { + get => s_documentationLinks.TryGetValue(symbol, out var link) ? link : null; + set + { + lock (s_lock) + { + if (string.IsNullOrEmpty(value)) + { + s_documentationLinks.Remove(symbol); + } + else + { + s_documentationLinks[symbol] = value; + } + } + } + } + } + +} diff --git a/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/Env.cs b/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/Env.cs index dbedb133bfc..c85eaa1d0bf 100644 --- a/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/Env.cs +++ b/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/Env.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; + namespace Microsoft.DotNet.Cli.Utils; public static class Env @@ -21,6 +23,9 @@ public static class Env public static bool GetEnvironmentVariableAsBool(string name, bool defaultValue = false) => s_environment.GetEnvironmentVariableAsBool(name, defaultValue); + public static bool TryGetEnvironmentVariableAsBool(string name, [NotNullWhen(true)] out bool value) => + s_environment.TryGetEnvironmentVariableAsBool(name, out value); + public static int? GetEnvironmentVariableAsNullableInt(string name) => s_environment.GetEnvironmentVariableAsNullableInt(name); diff --git a/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/EnvironmentProvider.cs b/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/EnvironmentProvider.cs index 50984a692d1..06c64b67110 100644 --- a/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/EnvironmentProvider.cs +++ b/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/EnvironmentProvider.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; using Microsoft.DotNet.Cli.Utils.Extensions; namespace Microsoft.DotNet.Cli.Utils; @@ -136,6 +137,12 @@ public bool GetEnvironmentVariableAsBool(string name, bool defaultValue) return Environment.GetEnvironmentVariable(variable, target); } + public bool TryGetEnvironmentVariable(string name, [NotNullWhen(true)] out string? value) + { + value = Environment.GetEnvironmentVariable(name); + return value != null; + } + public void SetEnvironmentVariable(string variable, string value, EnvironmentVariableTarget target) { Environment.SetEnvironmentVariable(variable, value, target); @@ -150,4 +157,57 @@ public void SetEnvironmentVariable(string variable, string value, EnvironmentVar return null; } + + public bool TryGetEnvironmentVariableAsBool(string name, [NotNullWhen(true)] out bool value) + { + if (TryGetEnvironmentVariable(name, out string? strValue) && + (bool.TryParse(strValue, out bool boolValue) + || TryParseNonBoolConstantStringAsBool(strValue, out boolValue))) + { + value = boolValue; + return true; + } + else + { + value = false; + return false; + } + } + + /// + /// Parses non-boolean constant strings like "1", "0", "yes", "no", "on", "off" as boolean values. + /// + private static bool TryParseNonBoolConstantStringAsBool(string? strValue, out bool value) + { + switch (strValue?.ToLowerInvariant()) + { + case "1": + case "yes": + case "on": + value = true; + return true; + case "0": + case "no": + case "off": + value = false; + return true; + default: + value = false; + return false; + } + } + + public bool TryGetEnvironmentVariableAsInt(string name, [NotNullWhen(true)] out int value) + { + if (TryGetEnvironmentVariable(name, out string? strValue) && int.TryParse(strValue, out int intValue)) + { + value = intValue; + return true; + } + else + { + value = -1; + return false; + } + } } diff --git a/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/IEnvironmentProvider.cs b/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/IEnvironmentProvider.cs index 385f989d499..8aaacf30fba 100644 --- a/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/IEnvironmentProvider.cs +++ b/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/IEnvironmentProvider.cs @@ -3,6 +3,9 @@ namespace Microsoft.DotNet.Cli.Utils; +using System.Diagnostics.CodeAnalysis; + + public interface IEnvironmentProvider { IEnumerable ExecutableExtensions { get; } @@ -19,6 +22,10 @@ public interface IEnvironmentProvider string? GetEnvironmentVariable(string name); + bool TryGetEnvironmentVariable(string name, [NotNullWhen(true)] out string? value); + bool TryGetEnvironmentVariableAsBool(string name, [NotNullWhen(true)] out bool value); + bool TryGetEnvironmentVariableAsInt(string name, [NotNullWhen(true)] out int value); + string? GetEnvironmentVariable(string variable, EnvironmentVariableTarget target); void SetEnvironmentVariable(string variable, string value, EnvironmentVariableTarget target); diff --git a/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildArgs.cs b/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildArgs.cs index 12226f42f0d..7fedc17f387 100644 --- a/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildArgs.cs +++ b/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildArgs.cs @@ -21,7 +21,9 @@ private MSBuildArgs( string[]? getTargetResult, string[]? getResultOutputFile, VerbosityOptions? verbosity, - string[]? otherMSBuildArgs) + bool noLogo, + string[]? otherMSBuildArgs + ) { GlobalProperties = properties; RestoreGlobalProperties = restoreProperties; @@ -31,6 +33,7 @@ private MSBuildArgs( GetTargetResult = getTargetResult; GetResultOutputFile = getResultOutputFile; Verbosity = verbosity; + NoLogo = noLogo; OtherMSBuildArgs = otherMSBuildArgs is not null ? [.. otherMSBuildArgs] : new List(); @@ -51,16 +54,33 @@ private MSBuildArgs( /// public string[]? RequestedTargets { get; } + /// + /// If specified, the list of MSBuild Property names to retrieve and report directly for this build of a single project. + /// public string[]? GetProperty { get; } + /// + /// If specified, the list of MSBuild Item names to retrieve and report directly for this build of a single project. + /// public string[]? GetItem { get; } + /// + /// If specified, the list of MSBuild Target Output/Return Items to retrieve and report directly for this build of a single project. + /// public string[]? GetTargetResult { get; } + /// + /// If specified, the list of output files to which to write --getProperty, --getItem, and --getTargetResult outputs. + /// public string[]? GetResultOutputFile { get; } public VerbosityOptions? Verbosity { get; } + /// + /// Whether or not the MSBuild product header text should be emitted at the start of this build + /// + public bool NoLogo { get; } + /// /// All other arguments that aren't already explicitly modeled by this structure. /// The main categories of these today are logger configurations @@ -89,16 +109,15 @@ public static MSBuildArgs AnalyzeMSBuildArguments(IEnumerable forwardedA } var parseResult = fakeCommand.Parse([.. forwardedAndUserFacingArgs], _analysisParsingConfiguration); - var globalProperties = parseResult.GetResult("--property") is OptionResult propResult ? propResult.GetValueOrDefault?>() : null; - var restoreProperties = parseResult.GetResult("--restoreProperty") is OptionResult restoreResult ? restoreResult.GetValueOrDefault?>() : null; - var requestedTargets = parseResult.GetResult("--target") is OptionResult targetResult ? targetResult.GetValueOrDefault() : null; + var globalProperties = TryGetValue?>("--property"); + var restoreProperties = TryGetValue?>("--restoreProperty"); + var requestedTargets = TryGetValue("--target"); var getProperty = TryGetValue("--getProperty"); var getItem = TryGetValue("--getItem"); var getTargetResult = TryGetValue("--getTargetResult"); var getResultOutputFile = TryGetValue("--getResultOutputFile"); - var verbosity = parseResult.GetResult("--verbosity") is OptionResult verbosityResult - ? verbosityResult.GetValueOrDefault() - : null; + var verbosity = TryGetValue("--verbosity"); + var nologo = TryGetValue("--no-logo") ?? true; // Default to nologo if not specified var otherMSBuildArgs = parseResult.UnmatchedTokens.ToArray(); return new MSBuildArgs( properties: globalProperties, @@ -109,8 +128,12 @@ public static MSBuildArgs AnalyzeMSBuildArguments(IEnumerable forwardedA getTargetResult: getTargetResult, getResultOutputFile: getResultOutputFile, otherMSBuildArgs: otherMSBuildArgs, - verbosity: verbosity); + verbosity: verbosity, + noLogo: nologo); + /// We can't use to check if the names of the options we care + /// about were specified, because if they weren't specified it throws. + /// So we first check if the option was specified, and only then get its value. T? TryGetValue(string name) { return options.Any(o => o.Name == name) ? parseResult.GetValue(name) : default; @@ -120,19 +143,19 @@ public static MSBuildArgs AnalyzeMSBuildArguments(IEnumerable forwardedA public static MSBuildArgs FromProperties(ReadOnlyDictionary? properties) { - return new MSBuildArgs(properties, null, null, null, null, null, null, null, null); + return new MSBuildArgs(properties, null, null, null, null, null, null, null, noLogo: false, null); } public static MSBuildArgs FromOtherArgs(params ReadOnlySpan args) { - return new MSBuildArgs(null, null, null, null, null, null, null, null, args.ToArray()); + return new MSBuildArgs(null, null, null, null, null, null, null, null, noLogo: false, args.ToArray()); } public static MSBuildArgs FromVerbosity(VerbosityOptions verbosity) { - return new MSBuildArgs(null, null, null, null, null, null, null, verbosity, null); + return new MSBuildArgs(null, null, null, null, null, null, null, verbosity, noLogo: false, null); } - public static readonly MSBuildArgs ForHelp = new(null, null, null, null, null, null, null, null, ["--help"]); + public static readonly MSBuildArgs ForHelp = new(null, null, null, null, null, null, null, null, noLogo: true, ["--help"]); /// /// Completely replaces the MSBuild arguments with the provided . @@ -148,6 +171,7 @@ public MSBuildArgs CloneWithExplicitArgs(string[] newArgs) getTargetResult: GetTargetResult, getResultOutputFile: GetResultOutputFile, otherMSBuildArgs: newArgs, + noLogo: NoLogo, verbosity: Verbosity); } @@ -168,6 +192,7 @@ public MSBuildArgs CloneWithAdditionalArgs(params string[] additionalArgs) GetTargetResult, GetResultOutputFile, Verbosity, + NoLogo, OtherMSBuildArgs.ToArray()); } @@ -180,6 +205,7 @@ public MSBuildArgs CloneWithAdditionalArgs(params string[] additionalArgs) GetTargetResult, GetResultOutputFile, Verbosity, + NoLogo, [.. OtherMSBuildArgs, .. additionalArgs]); } @@ -197,6 +223,7 @@ public MSBuildArgs CloneWithAdditionalRestoreProperties(ReadOnlyDictionary additi GetTargetResult, GetResultOutputFile, Verbosity, + NoLogo, OtherMSBuildArgs.ToArray()); } @@ -305,6 +338,22 @@ public MSBuildArgs CloneWithVerbosity(VerbosityOptions newVerbosity) GetTargetResult, GetResultOutputFile, newVerbosity, + NoLogo, + OtherMSBuildArgs.ToArray()); + } + + public MSBuildArgs CloneWithNoLogo(bool noLogo) + { + return new MSBuildArgs( + GlobalProperties, + RestoreGlobalProperties, + RequestedTargets, + GetProperty, + GetItem, + GetTargetResult, + GetResultOutputFile, + Verbosity, + noLogo, OtherMSBuildArgs.ToArray()); } diff --git a/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildForwardingAppWithoutLogging.cs b/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildForwardingAppWithoutLogging.cs index e0dca709ac3..87d021eda15 100644 --- a/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildForwardingAppWithoutLogging.cs +++ b/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildForwardingAppWithoutLogging.cs @@ -44,17 +44,11 @@ public static string MSBuildVersion private readonly List _msbuildRequiredParameters = ["-maxcpucount", $"--verbosity:{DefaultVerbosity}"]; - public MSBuildForwardingAppWithoutLogging(MSBuildArgs msbuildArgs, string? msbuildPath = null, bool includeLogo = false, bool isRestoring = true) + public MSBuildForwardingAppWithoutLogging(MSBuildArgs msbuildArgs, string? msbuildPath = null) { string defaultMSBuildPath = GetMSBuildExePath(); _msbuildArgs = msbuildArgs; - if (!includeLogo && !msbuildArgs.OtherMSBuildArgs.Contains("-nologo", StringComparer.OrdinalIgnoreCase)) - { - // If the user didn't explicitly ask for -nologo, we add it to avoid the MSBuild logo. - // This is useful for scenarios like restore where we don't want to print the logo. - // Note that this is different from the default behavior of MSBuild, which prints the logo. - msbuildArgs.OtherMSBuildArgs.Add("-nologo"); - } + string? tlpDefault = TerminalLoggerDefault; if (string.IsNullOrWhiteSpace(tlpDefault)) { @@ -101,6 +95,7 @@ private string[] EmitMSBuildArgs(MSBuildArgs msbuildArgs) => [ .. msbuildArgs.RestoreGlobalProperties?.Select(kvp => EmitProperty(kvp, "restoreProperty")) ?? [], .. msbuildArgs.RequestedTargets?.Select(target => $"--target:{target}") ?? [], .. msbuildArgs.Verbosity is not null ? new string[1] { $"--verbosity:{msbuildArgs.Verbosity}" } : [], + .. msbuildArgs.NoLogo is true ? new string[1] { "--nologo" } : [], .. msbuildArgs.OtherMSBuildArgs ]; diff --git a/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/Polyfills.cs b/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/Polyfills.cs index ab674a2906e..59668dbae39 100644 --- a/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/Polyfills.cs +++ b/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/Polyfills.cs @@ -4,11 +4,24 @@ #if NET472 #pragma warning disable IDE0130 // Namespace does not match folder structure -namespace System.Runtime.CompilerServices; -#pragma warning restore IDE0130 // Namespace does not match folder structure +namespace System.Runtime.CompilerServices { + + internal static class IsExternalInit + { + } + +} -internal static class IsExternalInit +namespace System.Diagnostics.CodeAnalysis { + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class NotNullWhenAttribute : Attribute + { + public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + public bool ReturnValue { get; } + } } +#pragma warning restore IDE0130 // Namespace does not match folder structure #endif diff --git a/src/sdk/src/Cli/Microsoft.TemplateEngine.Cli/Commands/ICommandDocument.cs b/src/sdk/src/Cli/Microsoft.TemplateEngine.Cli/Commands/ICommandDocument.cs deleted file mode 100644 index dc11c8327f3..00000000000 --- a/src/sdk/src/Cli/Microsoft.TemplateEngine.Cli/Commands/ICommandDocument.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.CommandLine; - -namespace Microsoft.TemplateEngine.Cli.Commands; - -/// -/// If a implements this interface, it can open -/// its documentation page online. -/// -public interface ICommandDocument -{ - /// - /// The URL to the documentation page for this command. - /// - string DocsLink { get; } -} diff --git a/src/sdk/src/Cli/Microsoft.TemplateEngine.Cli/Commands/NewCommand.cs b/src/sdk/src/Cli/Microsoft.TemplateEngine.Cli/Commands/NewCommand.cs index 3db21ea63d7..e8883d6a9f6 100644 --- a/src/sdk/src/Cli/Microsoft.TemplateEngine.Cli/Commands/NewCommand.cs +++ b/src/sdk/src/Cli/Microsoft.TemplateEngine.Cli/Commands/NewCommand.cs @@ -3,18 +3,20 @@ using System.CommandLine; using System.CommandLine.Completions; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.TemplateEngine.Abstractions; using Microsoft.TemplateEngine.Edge.Settings; namespace Microsoft.TemplateEngine.Cli.Commands { - internal partial class NewCommand : BaseCommand, ICustomHelp, ICommandDocument + internal partial class NewCommand : BaseCommand, ICustomHelp { internal NewCommand( string commandName, Func hostBuilder) : base(hostBuilder, commandName, SymbolStrings.Command_New_Description) { + this.DocsLink = "https://aka.ms/dotnet-new"; TreatUnmatchedTokensAsErrors = true; //it is important that legacy commands are built before non-legacy, as non legacy commands are building validators that rely on legacy stuff @@ -44,8 +46,6 @@ internal NewCommand( Options.Add(SharedOptions.ProjectPathOption); } - public string DocsLink { get; } = "https://aka.ms/dotnet-new"; - internal static Option DebugCustomSettingsLocationOption { get; } = new("--debug:custom-hive") { Description = SymbolStrings.Option_Debug_CustomSettings, diff --git a/src/sdk/src/Cli/Microsoft.TemplateEngine.Cli/Microsoft.TemplateEngine.Cli.csproj b/src/sdk/src/Cli/Microsoft.TemplateEngine.Cli/Microsoft.TemplateEngine.Cli.csproj index 228c9cd613d..e9140b0e10f 100644 --- a/src/sdk/src/Cli/Microsoft.TemplateEngine.Cli/Microsoft.TemplateEngine.Cli.csproj +++ b/src/sdk/src/Cli/Microsoft.TemplateEngine.Cli/Microsoft.TemplateEngine.Cli.csproj @@ -21,10 +21,11 @@ + - + True @@ -65,6 +66,6 @@ - + diff --git a/src/sdk/src/Cli/dotnet/CommandFactory/CommandResolution/ProjectToolsCommandResolver.cs b/src/sdk/src/Cli/dotnet/CommandFactory/CommandResolution/ProjectToolsCommandResolver.cs index 2f8bb7badd9..aa844850e31 100644 --- a/src/sdk/src/Cli/dotnet/CommandFactory/CommandResolution/ProjectToolsCommandResolver.cs +++ b/src/sdk/src/Cli/dotnet/CommandFactory/CommandResolution/ProjectToolsCommandResolver.cs @@ -385,7 +385,7 @@ internal void GenerateDepsJsonFile( string? stdOut; string? stdErr; - var msbuildArgs = MSBuildArgs.AnalyzeMSBuildArguments([..args], CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, BuildCommandParser.TargetOption, BuildCommandParser.VerbosityOption); + var msbuildArgs = MSBuildArgs.AnalyzeMSBuildArguments([..args], CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, BuildCommandParser.TargetOption, BuildCommandParser.VerbosityOption, BuildCommandParser.NoLogoOption); var forwardingAppWithoutLogging = new MSBuildForwardingAppWithoutLogging(msbuildArgs, msBuildExePath); if (forwardingAppWithoutLogging.ExecuteMSBuildOutOfProc) { diff --git a/src/sdk/src/Cli/dotnet/Commands/Build/BuildCommand.cs b/src/sdk/src/Cli/dotnet/Commands/Build/BuildCommand.cs index 871ead794e8..264abdad7d8 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Build/BuildCommand.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Build/BuildCommand.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.CommandLine; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Restore; using Microsoft.DotNet.Cli.Commands.Run; using Microsoft.DotNet.Cli.Extensions; @@ -42,7 +43,7 @@ public static CommandBase FromParseResult(ParseResult parseResult, string? msbui noRestore: noRestore, msbuildPath: msbuildPath ), - [CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, BuildCommandParser.TargetOption, BuildCommandParser.VerbosityOption], + [CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, BuildCommandParser.TargetOption, BuildCommandParser.VerbosityOption, BuildCommandParser.NoLogoOption], parseResult, msbuildPath ); diff --git a/src/sdk/src/Cli/dotnet/Commands/Build/BuildCommandParser.cs b/src/sdk/src/Cli/dotnet/Commands/Build/BuildCommandParser.cs index 5b87474d754..be2e52c4526 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Build/BuildCommandParser.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Build/BuildCommandParser.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.CommandLine; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Restore; using Microsoft.DotNet.Cli.Extensions; @@ -17,29 +18,25 @@ internal static class BuildCommandParser Arity = ArgumentArity.ZeroOrMore }; - public static readonly Option OutputOption = new ForwardedOption("--output", "-o") + public static readonly Option OutputOption = new Option("--output", "-o") { Description = CliCommandStrings.BuildOutputOptionDescription, HelpName = CliCommandStrings.OutputOptionName }.ForwardAsOutputPath("OutputPath"); - public static readonly Option NoIncrementalOption = new ForwardedOption("--no-incremental") + public static readonly Option NoIncrementalOption = new Option("--no-incremental") { Description = CliCommandStrings.NoIncrementalOptionDescription, Arity = ArgumentArity.Zero }.ForwardAs("--target:Rebuild"); - public static readonly Option NoDependenciesOption = new ForwardedOption("--no-dependencies") + public static readonly Option NoDependenciesOption = new Option("--no-dependencies") { Description = CliCommandStrings.NoDependenciesOptionDescription, Arity = ArgumentArity.Zero }.ForwardAs("--property:BuildProjectReferences=false"); - public static readonly Option NoLogoOption = new ForwardedOption("--nologo") - { - Description = CliCommandStrings.BuildCmdNoLogo, - Arity = ArgumentArity.Zero - }.ForwardAs("-nologo"); + public static readonly Option NoLogoOption = CommonOptions.NoLogoOption(); public static readonly Option NoRestoreOption = CommonOptions.NoRestoreOption; @@ -69,7 +66,10 @@ public static Command GetCommand() private static Command ConstructCommand() { - DocumentedCommand command = new("build", DocsLink, CliCommandStrings.BuildAppFullName); + Command command = new("build", CliCommandStrings.BuildAppFullName) + { + DocsLink = DocsLink + }; command.Arguments.Add(SlnOrProjectOrFileArgument); RestoreCommandParser.AddImplicitRestoreOptions(command, includeRuntimeOption: false, includeNoDependenciesOption: false); diff --git a/src/sdk/src/Cli/dotnet/Commands/BuildServer/BuildServerCommandParser.cs b/src/sdk/src/Cli/dotnet/Commands/BuildServer/BuildServerCommandParser.cs index 60d71d04905..468e60ad4fe 100644 --- a/src/sdk/src/Cli/dotnet/Commands/BuildServer/BuildServerCommandParser.cs +++ b/src/sdk/src/Cli/dotnet/Commands/BuildServer/BuildServerCommandParser.cs @@ -5,6 +5,7 @@ using System.CommandLine; using Microsoft.DotNet.Cli.Commands.BuildServer.Shutdown; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Extensions; namespace Microsoft.DotNet.Cli.Commands.BuildServer; @@ -22,7 +23,10 @@ public static Command GetCommand() private static Command ConstructCommand() { - var command = new DocumentedCommand("build-server", DocsLink, CliCommandStrings.BuildServerCommandDescription); + var command = new Command("build-server", CliCommandStrings.BuildServerCommandDescription) + { + DocsLink = DocsLink + }; command.Subcommands.Add(BuildServerShutdownCommandParser.GetCommand()); diff --git a/src/sdk/src/Cli/dotnet/Commands/Clean/CleanCommand.cs b/src/sdk/src/Cli/dotnet/Commands/Clean/CleanCommand.cs index 1290b8b68cf..c7e48c376ee 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Clean/CleanCommand.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Clean/CleanCommand.cs @@ -33,7 +33,7 @@ public static CommandBase FromParseResult(ParseResult result, string? msbuildPat NoWriteBuildMarkers = true, }, static (msbuildArgs, msbuildPath) => new CleanCommand(msbuildArgs, msbuildPath), - [ CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, CleanCommandParser.TargetOption, CleanCommandParser.VerbosityOption ], + [ CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, CleanCommandParser.TargetOption, CleanCommandParser.VerbosityOption, CleanCommandParser.NoLogoOption], result, msbuildPath ); diff --git a/src/sdk/src/Cli/dotnet/Commands/Clean/CleanCommandParser.cs b/src/sdk/src/Cli/dotnet/Commands/Clean/CleanCommandParser.cs index 19b1854a1e1..117537d7fd1 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Clean/CleanCommandParser.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Clean/CleanCommandParser.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.CommandLine; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Clean.FileBasedAppArtifacts; using Microsoft.DotNet.Cli.Extensions; @@ -17,21 +18,17 @@ internal static class CleanCommandParser Arity = ArgumentArity.ZeroOrMore }; - public static readonly Option OutputOption = new ForwardedOption("--output", "-o") + public static readonly Option OutputOption = new Option("--output", "-o") { Description = CliCommandStrings.CleanCmdOutputDirDescription, HelpName = CliCommandStrings.CleanCmdOutputDir }.ForwardAsOutputPath("OutputPath"); - public static readonly Option NoLogoOption = new ForwardedOption("--nologo") - { - Description = CliCommandStrings.CleanCmdNoLogo, - Arity = ArgumentArity.Zero - }.ForwardAs("-nologo"); + public static readonly Option NoLogoOption = CommonOptions.NoLogoOption(); - public static readonly Option FrameworkOption = CommonOptions.FrameworkOption(CliCommandStrings.CleanFrameworkOptionDescription); + public static readonly Option FrameworkOption = CommonOptions.FrameworkOption(CliCommandStrings.CleanFrameworkOptionDescription); - public static readonly Option ConfigurationOption = CommonOptions.ConfigurationOption(CliCommandStrings.CleanConfigurationOptionDescription); + public static readonly Option ConfigurationOption = CommonOptions.ConfigurationOption(CliCommandStrings.CleanConfigurationOptionDescription); public static readonly Option TargetOption = CommonOptions.RequiredMSBuildTargetOption("Clean"); @@ -47,7 +44,10 @@ public static Command GetCommand() private static Command ConstructCommand() { - DocumentedCommand command = new("clean", DocsLink, CliCommandStrings.CleanAppFullName); + Command command = new("clean", CliCommandStrings.CleanAppFullName) + { + DocsLink = DocsLink + }; command.Arguments.Add(SlnOrProjectOrFileArgument); command.Options.Add(FrameworkOption); diff --git a/src/sdk/src/Cli/dotnet/Commands/CliCommandStrings.resx b/src/sdk/src/Cli/dotnet/Commands/CliCommandStrings.resx index 4fbcfb5e5d2..2c94953811c 100644 --- a/src/sdk/src/Cli/dotnet/Commands/CliCommandStrings.resx +++ b/src/sdk/src/Cli/dotnet/Commands/CliCommandStrings.resx @@ -1,17 +1,17 @@  - @@ -166,7 +166,7 @@ .NET Builder - + Do not display the startup banner or the copyright message. @@ -250,9 +250,6 @@ Paths searched: '{1}', '{2}'. .NET Clean Command - - Do not display the startup banner or the copyright message. - OUTPUT_DIR @@ -1322,9 +1319,6 @@ Tool '{1}' (version '{2}') was successfully installed. Entry is added to the man .NET Core NuGet Package Packer - - Do not display the startup banner or the copyright message. - OUTPUT_DIR @@ -1597,9 +1591,6 @@ Tool '{1}' (version '{2}') was successfully installed. Entry is added to the man Publisher for the .NET Platform - - Do not display the startup banner or the copyright message. - The configuration to publish for. The default is 'Release' for NET 8.0 projects and above, but 'Debug' for older projects. diff --git a/src/sdk/src/Cli/dotnet/Commands/CommandFactory.cs b/src/sdk/src/Cli/dotnet/Commands/CommandFactory.cs index 143de4cd413..40bc500082b 100644 --- a/src/sdk/src/Cli/dotnet/Commands/CommandFactory.cs +++ b/src/sdk/src/Cli/dotnet/Commands/CommandFactory.cs @@ -2,8 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.CommandLine; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Run; -using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; namespace Microsoft.DotNet.Cli.Commands; diff --git a/src/sdk/src/Cli/dotnet/Commands/Format/FormatCommandParser.cs b/src/sdk/src/Cli/dotnet/Commands/Format/FormatCommandParser.cs index 7cc23f3d7ce..dcbeb642e4a 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Format/FormatCommandParser.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Format/FormatCommandParser.cs @@ -4,6 +4,7 @@ #nullable disable using System.CommandLine; +using Microsoft.DotNet.Cli.CommandLine; namespace Microsoft.DotNet.Cli.Commands.Format; @@ -22,9 +23,10 @@ public static Command GetCommand() private static Command ConstructCommand() { - var formatCommand = new DocumentedCommand("format", DocsLink) + var formatCommand = new Command("format") { - Arguments + Arguments = { Arguments }, + DocsLink = DocsLink, }; formatCommand.SetAction((parseResult) => FormatCommand.Run(parseResult.GetValue(Arguments))); return formatCommand; diff --git a/src/sdk/src/Cli/dotnet/Commands/Fsi/FsiCommandParser.cs b/src/sdk/src/Cli/dotnet/Commands/Fsi/FsiCommandParser.cs index 6ca8b23b0ae..c81c34e7956 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Fsi/FsiCommandParser.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Fsi/FsiCommandParser.cs @@ -4,6 +4,7 @@ #nullable disable using System.CommandLine; +using Microsoft.DotNet.Cli.CommandLine; namespace Microsoft.DotNet.Cli.Commands.Fsi; @@ -22,8 +23,10 @@ public static Command GetCommand() private static Command ConstructCommand() { - DocumentedCommand command = new("fsi", DocsLink) { Arguments }; - + Command command = new("fsi") { + Arguments = { Arguments }, + DocsLink = DocsLink, + }; command.SetAction((parseResult) => FsiCommand.Run(parseResult.GetValue(Arguments))); return command; diff --git a/src/sdk/src/Cli/dotnet/Commands/Help/HelpCommand.cs b/src/sdk/src/Cli/dotnet/Commands/Help/HelpCommand.cs index 416516aa9d9..a2646a3764d 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Help/HelpCommand.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Help/HelpCommand.cs @@ -1,14 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#nullable disable - using System.CommandLine; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Cli.Utils.Extensions; -using Microsoft.TemplateEngine.Cli.Commands; using NuGetDocumentedCommand = NuGet.CommandLine.XPlat.Commands.DocumentedCommand; namespace Microsoft.DotNet.Cli.Commands.Help; @@ -103,12 +102,12 @@ public int Execute() } } - private static bool TryGetDocsLink(string[] command, out string docsLink) + private static bool TryGetDocsLink(string[] command, [NotNullWhen(true)] out string? docsLink) { var parsedCommand = Parser.Parse(["dotnet", .. command]); - if (parsedCommand?.CommandResult?.Command is ICommandDocument dc) + if (parsedCommand?.CommandResult?.Command?.DocsLink is { } link) { - docsLink = dc.DocsLink; + docsLink = link; return true; } else if (parsedCommand?.CommandResult?.Command is NuGetDocumentedCommand ndc) diff --git a/src/sdk/src/Cli/dotnet/Commands/Help/HelpCommandParser.cs b/src/sdk/src/Cli/dotnet/Commands/Help/HelpCommandParser.cs index d465ae0a398..8334e262381 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Help/HelpCommandParser.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Help/HelpCommandParser.cs @@ -4,6 +4,7 @@ #nullable disable using System.CommandLine; +using Microsoft.DotNet.Cli.CommandLine; namespace Microsoft.DotNet.Cli.Commands.Help; @@ -26,7 +27,10 @@ public static Command GetCommand() private static Command ConstructCommand() { - DocumentedCommand command = new("help", DocsLink, CliCommandStrings.HelpAppFullName); + Command command = new("help", CliCommandStrings.HelpAppFullName) + { + DocsLink = DocsLink + }; command.Arguments.Add(Argument); diff --git a/src/sdk/src/Cli/dotnet/Commands/Hidden/Add/AddCommandParser.cs b/src/sdk/src/Cli/dotnet/Commands/Hidden/Add/AddCommandParser.cs index 43cb90737cd..c2d21405b1f 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Hidden/Add/AddCommandParser.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Hidden/Add/AddCommandParser.cs @@ -5,6 +5,7 @@ using Microsoft.DotNet.Cli.Commands.Hidden.Add.Package; using Microsoft.DotNet.Cli.Commands.Hidden.Add.Reference; using Microsoft.DotNet.Cli.Commands.Package; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Extensions; namespace Microsoft.DotNet.Cli.Commands.Hidden.Add; @@ -22,9 +23,10 @@ public static Command GetCommand() private static Command ConstructCommand() { - var command = new DocumentedCommand("add", DocsLink, CliCommandStrings.NetAddCommand) + var command = new Command("add", CliCommandStrings.NetAddCommand) { - Hidden = true + Hidden = true, + DocsLink = DocsLink }; command.Arguments.Add(PackageCommandParser.ProjectOrFileArgument); diff --git a/src/sdk/src/Cli/dotnet/Commands/Hidden/List/ListCommandParser.cs b/src/sdk/src/Cli/dotnet/Commands/Hidden/List/ListCommandParser.cs index 5ce44933360..f471c20b62d 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Hidden/List/ListCommandParser.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Hidden/List/ListCommandParser.cs @@ -7,6 +7,7 @@ using Microsoft.DotNet.Cli.Commands.Hidden.List.Package; using Microsoft.DotNet.Cli.Commands.Hidden.List.Reference; using Microsoft.DotNet.Cli.Extensions; +using Microsoft.DotNet.Cli.CommandLine; namespace Microsoft.DotNet.Cli.Commands.Hidden.List; @@ -32,9 +33,10 @@ public static Command GetCommand() private static Command ConstructCommand() { - var command = new DocumentedCommand("list", DocsLink, CliCommandStrings.NetListCommand) + var command = new Command("list", CliCommandStrings.NetListCommand) { - Hidden = true + Hidden = true, + DocsLink = DocsLink }; command.Arguments.Add(SlnOrProjectArgument); diff --git a/src/sdk/src/Cli/dotnet/Commands/Hidden/Parse/ParseCommand.cs b/src/sdk/src/Cli/dotnet/Commands/Hidden/Parse/ParseCommand.cs index 262944805cd..24c5fe6ec3e 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Hidden/Parse/ParseCommand.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Hidden/Parse/ParseCommand.cs @@ -4,6 +4,7 @@ #nullable disable using System.CommandLine; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Extensions; namespace Microsoft.DotNet.Cli.Commands.Hidden.Parse; diff --git a/src/sdk/src/Cli/dotnet/Commands/Hidden/Remove/RemoveCommandParser.cs b/src/sdk/src/Cli/dotnet/Commands/Hidden/Remove/RemoveCommandParser.cs index 0ac1f1834db..a91ed69540f 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Hidden/Remove/RemoveCommandParser.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Hidden/Remove/RemoveCommandParser.cs @@ -5,6 +5,7 @@ using Microsoft.DotNet.Cli.Commands.Hidden.Remove.Package; using Microsoft.DotNet.Cli.Commands.Hidden.Remove.Reference; using Microsoft.DotNet.Cli.Commands.Package; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Extensions; namespace Microsoft.DotNet.Cli.Commands.Hidden.Remove; @@ -22,9 +23,10 @@ public static Command GetCommand() private static Command ConstructCommand() { - var command = new DocumentedCommand("remove", DocsLink, CliCommandStrings.NetRemoveCommand) + var command = new Command("remove", CliCommandStrings.NetRemoveCommand) { - Hidden = true + Hidden = true, + DocsLink = DocsLink }; command.Arguments.Add(PackageCommandParser.ProjectOrFileArgument); diff --git a/src/sdk/src/Cli/dotnet/Commands/MSBuild/MSBuildCommand.cs b/src/sdk/src/Cli/dotnet/Commands/MSBuild/MSBuildCommand.cs index cf0b7e06c66..7e28945067a 100644 --- a/src/sdk/src/Cli/dotnet/Commands/MSBuild/MSBuildCommand.cs +++ b/src/sdk/src/Cli/dotnet/Commands/MSBuild/MSBuildCommand.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.CommandLine; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; @@ -10,7 +11,17 @@ namespace Microsoft.DotNet.Cli.Commands.MSBuild; public class MSBuildCommand( IEnumerable msbuildArgs, string? msbuildPath = null -) : MSBuildForwardingApp(MSBuildArgs.AnalyzeMSBuildArguments([..msbuildArgs], CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, MSBuildCommandParser.TargetOption, CommonOptions.VerbosityOption()), msbuildPath, includeLogo: true) +) : MSBuildForwardingApp(MSBuildArgs.AnalyzeMSBuildArguments( + [.. msbuildArgs], + CommonOptions.PropertiesOption, + CommonOptions.RestorePropertiesOption, + MSBuildCommandParser.TargetOption, + CommonOptions.VerbosityOption(), + // We set the no-logo option to false here to ensure that by default the logo is shown for this command. + // This is different from other commands that default to hiding the logo - but this command is meant to mimic + // the behavior of calling MSBuild directly, which shows the logo by default. + CommonOptions.NoLogoOption(false) + ), msbuildPath) { public static MSBuildCommand FromArgs(string[] args, string? msbuildPath = null) { diff --git a/src/sdk/src/Cli/dotnet/Commands/MSBuild/MSBuildCommandParser.cs b/src/sdk/src/Cli/dotnet/Commands/MSBuild/MSBuildCommandParser.cs index 2638d7fefc7..5893570d7be 100644 --- a/src/sdk/src/Cli/dotnet/Commands/MSBuild/MSBuildCommandParser.cs +++ b/src/sdk/src/Cli/dotnet/Commands/MSBuild/MSBuildCommandParser.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.CommandLine; +using Microsoft.DotNet.Cli.CommandLine; namespace Microsoft.DotNet.Cli.Commands.MSBuild; @@ -21,9 +22,10 @@ public static Command GetCommand() private static Command ConstructCommand() { - var command = new DocumentedCommand("msbuild", DocsLink, CliCommandStrings.BuildAppFullName) + var command = new Command("msbuild", CliCommandStrings.BuildAppFullName) { - Arguments + Arguments = { Arguments }, + DocsLink = DocsLink, }; command.Options.Add(CommonOptions.DisableBuildServersOption); diff --git a/src/sdk/src/Cli/dotnet/Commands/MSBuild/MSBuildForwardingApp.cs b/src/sdk/src/Cli/dotnet/Commands/MSBuild/MSBuildForwardingApp.cs index 3055de0883f..df0eaf4d166 100644 --- a/src/sdk/src/Cli/dotnet/Commands/MSBuild/MSBuildForwardingApp.cs +++ b/src/sdk/src/Cli/dotnet/Commands/MSBuild/MSBuildForwardingApp.cs @@ -42,18 +42,17 @@ private static MSBuildArgs ConcatTelemetryLogger(MSBuildArgs msbuildArgs) /// Mostly intended for quick/one-shot usage - most 'core' SDK commands should do more hands-on parsing. /// public MSBuildForwardingApp(IEnumerable rawMSBuildArgs, string? msbuildPath = null) : this( - MSBuildArgs.AnalyzeMSBuildArguments(rawMSBuildArgs.ToArray(), CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, CommonOptions.MSBuildTargetOption(), CommonOptions.VerbosityOption()), + MSBuildArgs.AnalyzeMSBuildArguments(rawMSBuildArgs.ToArray(), CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, CommonOptions.MSBuildTargetOption(), CommonOptions.VerbosityOption(), CommonOptions.NoLogoOption()), msbuildPath) { } - public MSBuildForwardingApp(MSBuildArgs msBuildArgs, string? msbuildPath = null, bool includeLogo = false) + public MSBuildForwardingApp(MSBuildArgs msBuildArgs, string? msbuildPath = null) { var modifiedMSBuildArgs = CommonRunHelpers.AdjustMSBuildForLLMs(ConcatTelemetryLogger(msBuildArgs)); _forwardingAppWithoutLogging = new MSBuildForwardingAppWithoutLogging( modifiedMSBuildArgs, - msbuildPath: msbuildPath, - includeLogo: includeLogo); + msbuildPath: msbuildPath); // Add the performance log location to the environment of the target process. if (PerformanceLogManager.Instance != null && !string.IsNullOrEmpty(PerformanceLogManager.Instance.CurrentLogDirectory)) diff --git a/src/sdk/src/Cli/dotnet/Commands/New/DotnetCommandCallbacks.cs b/src/sdk/src/Cli/dotnet/Commands/New/DotnetCommandCallbacks.cs index 46ddfe28c4b..4aae2f0f037 100644 --- a/src/sdk/src/Cli/dotnet/Commands/New/DotnetCommandCallbacks.cs +++ b/src/sdk/src/Cli/dotnet/Commands/New/DotnetCommandCallbacks.cs @@ -38,7 +38,7 @@ internal static bool RestoreProject(string pathToRestore) { PathUtility.EnsureAllPathsExist([pathToRestore], CliStrings.CommonFileNotFound, allowDirectories: true); // for the implicit restore we do not want the terminal logger to emit any output unless there are errors - return RestoreCommand.Run([pathToRestore, "-tlp:verbosity=quiet"]) == 0; + return RestoreCommand.Run([pathToRestore, "-tlp:verbosity=quiet", "--no-logo"]) == 0; } internal static bool AddProjectsToSolution(string solutionPath, IReadOnlyList projectsToAdd, string? solutionFolder, bool? inRoot) diff --git a/src/sdk/src/Cli/dotnet/Commands/New/NewCommandParser.cs b/src/sdk/src/Cli/dotnet/Commands/New/NewCommandParser.cs index 5bb9db983a8..45337f19ea7 100644 --- a/src/sdk/src/Cli/dotnet/Commands/New/NewCommandParser.cs +++ b/src/sdk/src/Cli/dotnet/Commands/New/NewCommandParser.cs @@ -3,6 +3,7 @@ using System.CommandLine; using System.CommandLine.Parsing; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.New.MSBuildEvaluation; using Microsoft.DotNet.Cli.Commands.New.PostActions; using Microsoft.DotNet.Cli.Commands.Workload; diff --git a/src/sdk/src/Cli/dotnet/Commands/NuGet/NuGetCommandParser.cs b/src/sdk/src/Cli/dotnet/Commands/NuGet/NuGetCommandParser.cs index 89af3722dd9..814ad45f854 100644 --- a/src/sdk/src/Cli/dotnet/Commands/NuGet/NuGetCommandParser.cs +++ b/src/sdk/src/Cli/dotnet/Commands/NuGet/NuGetCommandParser.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.CommandLine; -using Microsoft.DotNet.Cli.Extensions; +using Microsoft.DotNet.Cli.CommandLine; using NuGetWhyCommand = NuGet.CommandLine.XPlat.Commands.Why.WhyCommand; namespace Microsoft.DotNet.Cli.Commands.NuGet; @@ -22,10 +22,11 @@ public static Command GetCommand() private static Command ConstructCommand() { - var command = new DocumentedCommand("nuget", DocsLink) + var command = new Command("nuget") { // some subcommands are not defined here and just forwarded to NuGet app - TreatUnmatchedTokensAsErrors = false + TreatUnmatchedTokensAsErrors = false, + DocsLink = DocsLink }; command.Options.Add(new Option("--version") @@ -149,7 +150,7 @@ private static Command GetVerifyCommand() { Arity = ArgumentArity.Zero }); - verifyCommand.Options.Add(new ForwardedOption>(fingerprint) + verifyCommand.Options.Add(new Option>(fingerprint) .ForwardAsManyArgumentsEachPrefixedByOption(fingerprint) .AllowSingleArgPerToken()); verifyCommand.Options.Add(CommonOptions.VerbosityOption(Utils.VerbosityOptions.normal)); diff --git a/src/sdk/src/Cli/dotnet/Commands/Pack/PackCommand.cs b/src/sdk/src/Cli/dotnet/Commands/Pack/PackCommand.cs index 1e88f22688f..9f31bfa145f 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Pack/PackCommand.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Pack/PackCommand.cs @@ -4,6 +4,7 @@ using System.Collections.ObjectModel; using System.CommandLine; using System.CommandLine.Parsing; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Build; using Microsoft.DotNet.Cli.Commands.Restore; using Microsoft.DotNet.Cli.Commands.Run; @@ -59,6 +60,7 @@ public static CommandBase FromParseResult(ParseResult parseResult, string? msbui CommonOptions.RestorePropertiesOption, PackCommandParser.TargetOption, PackCommandParser.VerbosityOption, + PackCommandParser.NoLogoOption ], parseResult, msbuildPath, @@ -92,14 +94,14 @@ public static int RunPackCommand(ParseResult parseResult) if (args.Count != 1) { - Console.Error.WriteLine(CliStrings.PackCmd_OneNuspecAllowed); + Console.Error.WriteLine(CliStrings.PackCmd_OneNuspecAllowed); return 1; } var nuspecPath = args[0]; var packArgs = new PackArgs() - { + { Logger = new NuGetConsoleLogger(), Exclude = new List(), OutputDirectory = parseResult.GetValue(PackCommandParser.OutputOption), diff --git a/src/sdk/src/Cli/dotnet/Commands/Pack/PackCommandParser.cs b/src/sdk/src/Cli/dotnet/Commands/Pack/PackCommandParser.cs index c0ee6b1a8b4..23fc1ef58f6 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Pack/PackCommandParser.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Pack/PackCommandParser.cs @@ -2,9 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.CommandLine; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Build; using Microsoft.DotNet.Cli.Commands.Restore; -using Microsoft.DotNet.Cli.Extensions; using NuGet.Versioning; namespace Microsoft.DotNet.Cli.Commands.Pack; @@ -19,41 +19,37 @@ internal static class PackCommandParser Arity = ArgumentArity.ZeroOrMore }; - public static readonly Option OutputOption = new ForwardedOption("--output", "-o") + public static readonly Option OutputOption = new Option("--output", "-o") { Description = CliCommandStrings.PackCmdOutputDirDescription, HelpName = CliCommandStrings.PackCmdOutputDir }.ForwardAsSingle(o => $"-property:PackageOutputPath={CommandDirectoryContext.GetFullPath(o)}"); - public static readonly Option NoBuildOption = new ForwardedOption("--no-build") + public static readonly Option NoBuildOption = new Option("--no-build") { Description = CliCommandStrings.CmdNoBuildOptionDescription, Arity = ArgumentArity.Zero }.ForwardAs("-property:NoBuild=true"); - public static readonly Option IncludeSymbolsOption = new ForwardedOption("--include-symbols") + public static readonly Option IncludeSymbolsOption = new Option("--include-symbols") { Description = CliCommandStrings.CmdIncludeSymbolsDescription, Arity = ArgumentArity.Zero }.ForwardAs("-property:IncludeSymbols=true"); - public static readonly Option IncludeSourceOption = new ForwardedOption("--include-source") + public static readonly Option IncludeSourceOption = new Option("--include-source") { Description = CliCommandStrings.CmdIncludeSourceDescription, Arity = ArgumentArity.Zero }.ForwardAs("-property:IncludeSource=true"); - public static readonly Option ServiceableOption = new ForwardedOption("--serviceable", "-s") + public static readonly Option ServiceableOption = new Option("--serviceable", "-s") { Description = CliCommandStrings.CmdServiceableDescription, Arity = ArgumentArity.Zero }.ForwardAs("-property:Serviceable=true"); - public static readonly Option NoLogoOption = new ForwardedOption("--nologo") - { - Description = CliCommandStrings.PackCmdNoLogo, - Arity = ArgumentArity.Zero - }.ForwardAs("-nologo"); + public static readonly Option NoLogoOption = CommonOptions.NoLogoOption(); public static readonly Option NoRestoreOption = CommonOptions.NoRestoreOption; @@ -63,7 +59,7 @@ internal static class PackCommandParser public static readonly Option VerbosityOption = BuildCommandParser.VerbosityOption; public static Option VersionOption = - new ForwardedOption("--version") + new Option("--version") { Description = CliCommandStrings.PackCmdVersionDescription, HelpName = CliCommandStrings.PackCmdVersion, @@ -90,7 +86,10 @@ public static Command GetCommand() private static Command ConstructCommand() { - var command = new DocumentedCommand("pack", DocsLink, CliCommandStrings.PackAppFullName); + var command = new Command("pack", CliCommandStrings.PackAppFullName) + { + DocsLink = DocsLink + }; command.Arguments.Add(SlnOrProjectOrFileArgument); command.Options.Add(OutputOption); diff --git a/src/sdk/src/Cli/dotnet/Commands/Package/Add/PackageAddCommand.cs b/src/sdk/src/Cli/dotnet/Commands/Package/Add/PackageAddCommand.cs index 93bb43ef8c2..a463165828c 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Package/Add/PackageAddCommand.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Package/Add/PackageAddCommand.cs @@ -9,6 +9,7 @@ using Microsoft.DotNet.Cli.Commands.MSBuild; using Microsoft.DotNet.Cli.Commands.NuGet; using Microsoft.DotNet.Cli.Commands.Run; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.FileBasedPrograms; @@ -90,7 +91,7 @@ private static void GetProjectDependencyGraph(string projectFilePath, string dgF $"-property:RestoreDotnetCliToolReferences=false", // Output should not include MSBuild version header - "-nologo", + "--nologo", // Set verbosity to quiet to avoid cluttering the output for this 'inner' build "-v:quiet" diff --git a/src/sdk/src/Cli/dotnet/Commands/Package/Add/PackageAddCommandParser.cs b/src/sdk/src/Cli/dotnet/Commands/Package/Add/PackageAddCommandParser.cs index 1d24fd9c10d..ad5c1270896 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Package/Add/PackageAddCommandParser.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Package/Add/PackageAddCommandParser.cs @@ -4,7 +4,8 @@ using System.CommandLine; using System.CommandLine.Completions; using System.CommandLine.Parsing; -using Microsoft.DotNet.Cli.Extensions; +using System.CommandLine.StaticCompletions; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.Extensions.EnvironmentAbstractions; using NuGet.Versioning; @@ -12,7 +13,7 @@ namespace Microsoft.DotNet.Cli.Commands.Package.Add; public static class PackageAddCommandParser { - public static readonly Option PrereleaseOption = new ForwardedOption("--prerelease") + public static readonly Option PrereleaseOption = new Option("--prerelease") { Description = CliStrings.CommandPrereleaseOptionDescription, Arity = ArgumentArity.Zero @@ -26,10 +27,11 @@ public static class PackageAddCommandParser return QueryNuGet(context.WordToComplete, allowPrerelease, CancellationToken.None).Result.Select(packageId => new CompletionItem(packageId)); }); - public static readonly Option VersionOption = new DynamicForwardedOption("--version", "-v") + public static readonly Option VersionOption = new Option("--version", "-v") { Description = CliCommandStrings.CmdVersionDescription, - HelpName = CliCommandStrings.CmdVersion + HelpName = CliCommandStrings.CmdVersion, + IsDynamic = true }.ForwardAsSingle(o => $"--version {o}") .AddCompletions((context) => { @@ -48,7 +50,7 @@ public static class PackageAddCommandParser } }); - public static readonly Option FrameworkOption = new ForwardedOption("--framework", "-f") + public static readonly Option FrameworkOption = new Option("--framework", "-f") { Description = CliCommandStrings.PackageAddCmdFrameworkDescription, HelpName = CliCommandStrings.PackageAddCmdFramework @@ -60,13 +62,13 @@ public static class PackageAddCommandParser Arity = ArgumentArity.Zero }; - public static readonly Option SourceOption = new ForwardedOption("--source", "-s") + public static readonly Option SourceOption = new Option("--source", "-s") { Description = CliCommandStrings.PackageAddCmdSourceDescription, HelpName = CliCommandStrings.PackageAddCmdSource }.ForwardAsSingle(o => $"--source {o}"); - public static readonly Option PackageDirOption = new ForwardedOption("--package-directory") + public static readonly Option PackageDirOption = new Option("--package-directory") { Description = CliCommandStrings.CmdPackageDirectoryDescription, HelpName = CliCommandStrings.CmdPackageDirectory diff --git a/src/sdk/src/Cli/dotnet/Commands/Package/List/PackageListCommand.cs b/src/sdk/src/Cli/dotnet/Commands/Package/List/PackageListCommand.cs index 27520377cad..4ff46b804d2 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Package/List/PackageListCommand.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Package/List/PackageListCommand.cs @@ -6,6 +6,7 @@ using System.CommandLine; using Microsoft.DotNet.Cli.Commands.Hidden.List; using Microsoft.DotNet.Cli.Commands.NuGet; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; using System.Globalization; diff --git a/src/sdk/src/Cli/dotnet/Commands/Package/List/PackageListCommandParser.cs b/src/sdk/src/Cli/dotnet/Commands/Package/List/PackageListCommandParser.cs index 6c6970fcb6c..2ce93a3c7e8 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Package/List/PackageListCommandParser.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Package/List/PackageListCommandParser.cs @@ -4,68 +4,68 @@ #nullable disable using System.CommandLine; -using Microsoft.DotNet.Cli.Extensions; +using Microsoft.DotNet.Cli.CommandLine; namespace Microsoft.DotNet.Cli.Commands.Package.List; internal static class PackageListCommandParser { - public static readonly Option OutdatedOption = new ForwardedOption("--outdated") + public static readonly Option OutdatedOption = new Option("--outdated") { Description = CliCommandStrings.CmdOutdatedDescription, Arity = ArgumentArity.Zero }.ForwardAs("--outdated"); - public static readonly Option DeprecatedOption = new ForwardedOption("--deprecated") + public static readonly Option DeprecatedOption = new Option("--deprecated") { Description = CliCommandStrings.CmdDeprecatedDescription, Arity = ArgumentArity.Zero }.ForwardAs("--deprecated"); - public static readonly Option VulnerableOption = new ForwardedOption("--vulnerable") + public static readonly Option VulnerableOption = new Option("--vulnerable") { Description = CliCommandStrings.CmdVulnerableDescription, Arity = ArgumentArity.Zero }.ForwardAs("--vulnerable"); - public static readonly Option FrameworkOption = new ForwardedOption>("--framework", "-f") + public static readonly Option FrameworkOption = new Option>("--framework", "-f") { Description = CliCommandStrings.PackageListCmdFrameworkDescription, HelpName = CliCommandStrings.PackageListCmdFramework }.ForwardAsManyArgumentsEachPrefixedByOption("--framework") .AllowSingleArgPerToken(); - public static readonly Option TransitiveOption = new ForwardedOption("--include-transitive") + public static readonly Option TransitiveOption = new Option("--include-transitive") { Description = CliCommandStrings.CmdTransitiveDescription, Arity = ArgumentArity.Zero }.ForwardAs("--include-transitive"); - public static readonly Option PrereleaseOption = new ForwardedOption("--include-prerelease") + public static readonly Option PrereleaseOption = new Option("--include-prerelease") { Description = CliCommandStrings.CmdPrereleaseDescription, Arity = ArgumentArity.Zero }.ForwardAs("--include-prerelease"); - public static readonly Option HighestPatchOption = new ForwardedOption("--highest-patch") + public static readonly Option HighestPatchOption = new Option("--highest-patch") { Description = CliCommandStrings.CmdHighestPatchDescription, Arity = ArgumentArity.Zero }.ForwardAs("--highest-patch"); - public static readonly Option HighestMinorOption = new ForwardedOption("--highest-minor") + public static readonly Option HighestMinorOption = new Option("--highest-minor") { Description = CliCommandStrings.CmdHighestMinorDescription, Arity = ArgumentArity.Zero }.ForwardAs("--highest-minor"); - public static readonly Option ConfigOption = new ForwardedOption("--config", "--configfile") + public static readonly Option ConfigOption = new Option("--config", "--configfile") { Description = CliCommandStrings.CmdConfigDescription, HelpName = CliCommandStrings.CmdConfig }.ForwardAsMany(o => ["--config", o]); - public static readonly Option SourceOption = new ForwardedOption>("--source", "-s") + public static readonly Option SourceOption = new Option>("--source", "-s") { Description = CliCommandStrings.PackageListCmdSourceDescription, HelpName = CliCommandStrings.PackageListCmdSource @@ -80,18 +80,18 @@ internal static class PackageListCommandParser Arity = ArgumentArity.Zero }; - public static readonly Option VerbosityOption = new ForwardedOption("--verbosity", "-v") + public static readonly Option VerbosityOption = new Option("--verbosity", "-v") { Description = CliStrings.VerbosityOptionDescription, HelpName = CliStrings.LevelArgumentName }.ForwardAsSingle(o => $"--verbosity:{o}"); - public static readonly Option FormatOption = new ForwardedOption("--format") + public static readonly Option FormatOption = new Option("--format") { Description = CliCommandStrings.CmdFormatDescription }.ForwardAsSingle(o => $"--format:{o}"); - public static readonly Option OutputVersionOption = new ForwardedOption("--output-version") + public static readonly Option OutputVersionOption = new Option("--output-version") { Description = CliCommandStrings.CmdOutputVersionDescription }.ForwardAsSingle(o => $"--output-version:{o}"); diff --git a/src/sdk/src/Cli/dotnet/Commands/Package/PackageCommandParser.cs b/src/sdk/src/Cli/dotnet/Commands/Package/PackageCommandParser.cs index a6e44f5bccb..16eb5873794 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Package/PackageCommandParser.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Package/PackageCommandParser.cs @@ -7,6 +7,7 @@ using Microsoft.DotNet.Cli.Commands.Package.Remove; using Microsoft.DotNet.Cli.Commands.Package.Search; using Microsoft.DotNet.Cli.Commands.Run; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; using Command = System.CommandLine.Command; @@ -37,7 +38,10 @@ internal class PackageCommandParser public static Command GetCommand() { - Command command = new DocumentedCommand("package", DocsLink); + Command command = new Command("package") + { + DocsLink = DocsLink + }; command.SetAction((parseResult) => parseResult.HandleMissingCommand()); command.Subcommands.Add(PackageSearchCommandParser.GetCommand()); command.Subcommands.Add(PackageAddCommandParser.GetCommand()); diff --git a/src/sdk/src/Cli/dotnet/Commands/Package/Remove/PackageRemoveCommand.cs b/src/sdk/src/Cli/dotnet/Commands/Package/Remove/PackageRemoveCommand.cs index 6f3ed29dad6..e9009938227 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Package/Remove/PackageRemoveCommand.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Package/Remove/PackageRemoveCommand.cs @@ -5,7 +5,7 @@ using System.Diagnostics; using Microsoft.DotNet.Cli.Commands.NuGet; using Microsoft.DotNet.Cli.Commands.Run; -using Microsoft.DotNet.Cli.Extensions; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.FileBasedPrograms; diff --git a/src/sdk/src/Cli/dotnet/Commands/Package/Remove/PackageRemoveCommandParser.cs b/src/sdk/src/Cli/dotnet/Commands/Package/Remove/PackageRemoveCommandParser.cs index e1b020f7695..20354b4f065 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Package/Remove/PackageRemoveCommandParser.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Package/Remove/PackageRemoveCommandParser.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.CommandLine; -using Microsoft.DotNet.Cli.Extensions; +using Microsoft.DotNet.Cli.CommandLine; namespace Microsoft.DotNet.Cli.Commands.Package.Remove; diff --git a/src/sdk/src/Cli/dotnet/Commands/Package/Search/PackageSearchCommand.cs b/src/sdk/src/Cli/dotnet/Commands/Package/Search/PackageSearchCommand.cs index 4317f96329b..d26a96fab23 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Package/Search/PackageSearchCommand.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Package/Search/PackageSearchCommand.cs @@ -4,7 +4,7 @@ #nullable disable using Microsoft.DotNet.Cli.Commands.NuGet; -using Microsoft.DotNet.Cli.Extensions; +using Microsoft.DotNet.Cli.CommandLine; using System.CommandLine; namespace Microsoft.DotNet.Cli.Commands.Package.Search; diff --git a/src/sdk/src/Cli/dotnet/Commands/Package/Search/PackageSearchCommandParser.cs b/src/sdk/src/Cli/dotnet/Commands/Package/Search/PackageSearchCommandParser.cs index 52e15de7089..54b0749cca3 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Package/Search/PackageSearchCommandParser.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Package/Search/PackageSearchCommandParser.cs @@ -4,6 +4,7 @@ #nullable disable using System.CommandLine; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Extensions; namespace Microsoft.DotNet.Cli.Commands.Package.Search; @@ -17,26 +18,26 @@ internal static class PackageSearchCommandParser Arity = ArgumentArity.ZeroOrOne }; - public static readonly Option Sources = new ForwardedOption>("--source") + public static readonly Option Sources = new Option>("--source") { Description = CliCommandStrings.SourceDescription, HelpName = CliCommandStrings.SourceArgumentName }.ForwardAsManyArgumentsEachPrefixedByOption("--source") .AllowSingleArgPerToken(); - public static readonly Option Take = new ForwardedOption("--take") + public static readonly Option Take = new Option("--take") { Description = CliCommandStrings.PackageSearchTakeDescription, HelpName = CliCommandStrings.PackageSearchTakeArgumentName }.ForwardAsSingle(o => $"--take:{o}"); - public static readonly Option Skip = new ForwardedOption("--skip") + public static readonly Option Skip = new Option("--skip") { Description = CliCommandStrings.PackageSearchSkipDescription, HelpName = CliCommandStrings.PackageSearchSkipArgumentName }.ForwardAsSingle(o => $"--skip:{o}"); - public static readonly Option ExactMatch = new ForwardedOption("--exact-match") + public static readonly Option ExactMatch = new Option("--exact-match") { Description = CliCommandStrings.ExactMatchDescription, Arity = ArgumentArity.Zero @@ -44,25 +45,25 @@ internal static class PackageSearchCommandParser public static readonly Option Interactive = CommonOptions.InteractiveOption().ForwardIfEnabled("--interactive"); - public static readonly Option Prerelease = new ForwardedOption("--prerelease") + public static readonly Option Prerelease = new Option("--prerelease") { Description = CliCommandStrings.PackageSearchPrereleaseDescription, Arity = ArgumentArity.Zero }.ForwardAs("--prerelease"); - public static readonly Option ConfigFile = new ForwardedOption("--configfile") + public static readonly Option ConfigFile = new Option("--configfile") { Description = CliCommandStrings.ConfigFileDescription, HelpName = CliCommandStrings.ConfigFileArgumentName }.ForwardAsSingle(o => $"--configfile:{o}"); - public static readonly Option Format = new ForwardedOption("--format") + public static readonly Option Format = new Option("--format") { Description = CliCommandStrings.FormatDescription, HelpName = CliCommandStrings.FormatArgumentName }.ForwardAsSingle(o => $"--format:{o}"); - public static readonly Option Verbosity = new ForwardedOption("--verbosity") + public static readonly Option Verbosity = new Option("--verbosity") { Description = CliCommandStrings.VerbosityDescription, HelpName = CliCommandStrings.VerbosityArgumentName diff --git a/src/sdk/src/Cli/dotnet/Commands/Publish/PublishCommand.cs b/src/sdk/src/Cli/dotnet/Commands/Publish/PublishCommand.cs index 45dc32c8430..424a2250c25 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Publish/PublishCommand.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Publish/PublishCommand.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.CommandLine; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Restore; using Microsoft.DotNet.Cli.Commands.Run; using Microsoft.DotNet.Cli.Extensions; @@ -57,7 +58,7 @@ public static CommandBase FromParseResult(ParseResult parseResult, string? msbui noRestore: noRestore, msbuildPath: msbuildPath ), - [CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, PublishCommandParser.TargetOption, PublishCommandParser.VerbosityOption], + [CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, PublishCommandParser.TargetOption, PublishCommandParser.VerbosityOption, PublishCommandParser.NoLogoOption], parseResult, msbuildPath, (msbuildArgs) => diff --git a/src/sdk/src/Cli/dotnet/Commands/Publish/PublishCommandParser.cs b/src/sdk/src/Cli/dotnet/Commands/Publish/PublishCommandParser.cs index 7be9784f5a1..2c2b1b20e36 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Publish/PublishCommandParser.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Publish/PublishCommandParser.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.CommandLine; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Build; using Microsoft.DotNet.Cli.Commands.Restore; using Microsoft.DotNet.Cli.Extensions; @@ -18,30 +19,26 @@ internal static class PublishCommandParser Arity = ArgumentArity.ZeroOrMore }; - public static readonly Option OutputOption = new ForwardedOption("--output", "-o") + public static readonly Option OutputOption = new Option("--output", "-o") { Description = CliCommandStrings.PublishOutputOptionDescription, HelpName = CliCommandStrings.PublishOutputOption }.ForwardAsOutputPath("PublishDir"); - public static readonly Option> ManifestOption = new ForwardedOption>("--manifest") + public static readonly Option> ManifestOption = new Option>("--manifest") { Description = CliCommandStrings.ManifestOptionDescription, HelpName = CliCommandStrings.ManifestOption }.ForwardAsSingle(o => $"-property:TargetManifestFiles={string.Join("%3B", o.Select(CommandDirectoryContext.GetFullPath))}") .AllowSingleArgPerToken(); - public static readonly Option NoBuildOption = new ForwardedOption("--no-build") + public static readonly Option NoBuildOption = new Option("--no-build") { Description = CliCommandStrings.NoBuildOptionDescription, Arity = ArgumentArity.Zero }.ForwardAs("-property:NoBuild=true"); - public static readonly Option NoLogoOption = new ForwardedOption("--nologo") - { - Description = CliCommandStrings.PublishCmdNoLogo, - Arity = ArgumentArity.Zero - }.ForwardAs("-nologo"); + public static readonly Option NoLogoOption = CommonOptions.NoLogoOption(); public static readonly Option NoRestoreOption = CommonOptions.NoRestoreOption; @@ -67,7 +64,10 @@ public static Command GetCommand() private static Command ConstructCommand() { - var command = new DocumentedCommand("publish", DocsLink, CliCommandStrings.PublishAppDescription); + var command = new Command("publish", CliCommandStrings.PublishAppDescription) + { + DocsLink = DocsLink + }; command.Arguments.Add(SlnOrProjectOrFileArgument); RestoreCommandParser.AddImplicitRestoreOptions(command, includeRuntimeOption: false, includeNoDependenciesOption: true); diff --git a/src/sdk/src/Cli/dotnet/Commands/Reference/Add/ReferenceAddCommand.cs b/src/sdk/src/Cli/dotnet/Commands/Reference/Add/ReferenceAddCommand.cs index b60050fb12c..10340392c0d 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Reference/Add/ReferenceAddCommand.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Reference/Add/ReferenceAddCommand.cs @@ -5,8 +5,8 @@ using System.CommandLine; using Microsoft.Build.Evaluation; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Package; -using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; using NuGet.Frameworks; diff --git a/src/sdk/src/Cli/dotnet/Commands/Reference/Add/ReferenceAddCommandParser.cs b/src/sdk/src/Cli/dotnet/Commands/Reference/Add/ReferenceAddCommandParser.cs index 5e5690fdd65..777f7458501 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Reference/Add/ReferenceAddCommandParser.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Reference/Add/ReferenceAddCommandParser.cs @@ -4,6 +4,8 @@ #nullable disable using System.CommandLine; +using System.CommandLine.StaticCompletions; +using Microsoft.DotNet.Cli.CommandLine; namespace Microsoft.DotNet.Cli.Commands.Reference.Add; @@ -21,12 +23,13 @@ internal static class ReferenceAddCommandParser } }; - public static readonly Option FrameworkOption = new DynamicOption("--framework", "-f") + public static readonly Option FrameworkOption = new Option("--framework", "-f") { Description = CliCommandStrings.ReferenceAddCmdFrameworkDescription, - HelpName = CliStrings.CommonCmdFramework - - }.AddCompletions(CliCompletion.TargetFrameworksFromProjectFile); + HelpName = CliStrings.CommonCmdFramework, + IsDynamic = true, + } + .AddCompletions(CliCompletion.TargetFrameworksFromProjectFile); public static readonly Option InteractiveOption = CommonOptions.InteractiveOption(); diff --git a/src/sdk/src/Cli/dotnet/Commands/Reference/List/ReferenceListCommand.cs b/src/sdk/src/Cli/dotnet/Commands/Reference/List/ReferenceListCommand.cs index 55d54b3d110..cbd7a84e3a7 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Reference/List/ReferenceListCommand.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Reference/List/ReferenceListCommand.cs @@ -8,8 +8,8 @@ using Microsoft.Build.Evaluation; using Microsoft.Build.Exceptions; using Microsoft.Build.Execution; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Hidden.List; -using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; namespace Microsoft.DotNet.Cli.Commands.Reference.List; diff --git a/src/sdk/src/Cli/dotnet/Commands/Reference/ReferenceCommandParser.cs b/src/sdk/src/Cli/dotnet/Commands/Reference/ReferenceCommandParser.cs index 7756b35edf0..e26cd6cd8c3 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Reference/ReferenceCommandParser.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Reference/ReferenceCommandParser.cs @@ -7,6 +7,7 @@ using Microsoft.DotNet.Cli.Commands.Reference.Add; using Microsoft.DotNet.Cli.Commands.Reference.List; using Microsoft.DotNet.Cli.Commands.Reference.Remove; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Extensions; namespace Microsoft.DotNet.Cli.Commands.Reference; @@ -30,7 +31,10 @@ public static Command GetCommand() private static Command ConstructCommand() { - var command = new DocumentedCommand("reference", DocsLink, CliCommandStrings.NetRemoveCommand); + var command = new Command("reference", CliCommandStrings.NetRemoveCommand) + { + DocsLink = DocsLink + }; command.Subcommands.Add(ReferenceAddCommandParser.GetCommand()); command.Subcommands.Add(ReferenceListCommandParser.GetCommand()); diff --git a/src/sdk/src/Cli/dotnet/Commands/Reference/Remove/ReferenceRemoveCommand.cs b/src/sdk/src/Cli/dotnet/Commands/Reference/Remove/ReferenceRemoveCommand.cs index 64db0bec340..3a71f6b761a 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Reference/Remove/ReferenceRemoveCommand.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Reference/Remove/ReferenceRemoveCommand.cs @@ -5,8 +5,8 @@ using System.CommandLine; using Microsoft.Build.Evaluation; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Package; -using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; namespace Microsoft.DotNet.Cli.Commands.Reference.Remove; diff --git a/src/sdk/src/Cli/dotnet/Commands/Reference/Remove/ReferenceRemoveCommandParser.cs b/src/sdk/src/Cli/dotnet/Commands/Reference/Remove/ReferenceRemoveCommandParser.cs index 9cb48cdfff5..03e67bdbb37 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Reference/Remove/ReferenceRemoveCommandParser.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Reference/Remove/ReferenceRemoveCommandParser.cs @@ -4,15 +4,19 @@ #nullable disable using System.CommandLine; +using System.CommandLine.StaticCompletions; + +using Microsoft.DotNet.Cli.CommandLine; namespace Microsoft.DotNet.Cli.Commands.Reference.Remove; internal static class ReferenceRemoveCommandParser { - public static readonly Argument> ProjectPathArgument = new DynamicArgument>(CliCommandStrings.ReferenceRemoveProjectPathArgumentName) + public static readonly Argument> ProjectPathArgument = new Argument>(CliCommandStrings.ReferenceRemoveProjectPathArgumentName) { Description = CliCommandStrings.ReferenceRemoveProjectPathArgumentDescription, Arity = ArgumentArity.OneOrMore, + IsDynamic = true, }.AddCompletions(CliCompletion.ProjectReferencesFromProjectFile); public static readonly Option FrameworkOption = new("--framework", "-f") diff --git a/src/sdk/src/Cli/dotnet/Commands/Restore/RestoreCommand.cs b/src/sdk/src/Cli/dotnet/Commands/Restore/RestoreCommand.cs index 6eb650b0e26..5a9ff6a3705 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Restore/RestoreCommand.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Restore/RestoreCommand.cs @@ -40,7 +40,7 @@ public static CommandBase FromParseResult(ParseResult result, string? msbuildPat { return CreateForwarding(msbuildArgs, msbuildPath); }, - [CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, RestoreCommandParser.TargetOption, RestoreCommandParser.VerbosityOption], + [CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, RestoreCommandParser.TargetOption, RestoreCommandParser.VerbosityOption, RestoreCommandParser.NoLogoOption], result, msbuildPath ); diff --git a/src/sdk/src/Cli/dotnet/Commands/Restore/RestoreCommandParser.cs b/src/sdk/src/Cli/dotnet/Commands/Restore/RestoreCommandParser.cs index 5477584f573..f32a0b1545b 100644 --- a/src/sdk/src/Cli/dotnet/Commands/Restore/RestoreCommandParser.cs +++ b/src/sdk/src/Cli/dotnet/Commands/Restore/RestoreCommandParser.cs @@ -2,7 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.CommandLine; -using Microsoft.DotNet.Cli.Extensions; +using System.CommandLine.StaticCompletions; +using Microsoft.DotNet.Cli.CommandLine; using NuGet.Packaging; namespace Microsoft.DotNet.Cli.Commands.Restore; @@ -17,7 +18,7 @@ internal static class RestoreCommandParser Arity = ArgumentArity.ZeroOrMore }; - public static readonly Option> SourceOption = new ForwardedOption>("--source", "-s") + public static readonly Option> SourceOption = new Option>("--source", "-s") { Description = CliCommandStrings.CmdSourceOptionDescription, HelpName = CliCommandStrings.CmdSourceOption @@ -26,36 +27,36 @@ internal static class RestoreCommandParser public static readonly Option TargetOption = CommonOptions.RequiredMSBuildTargetOption("Restore"); public static readonly Option VerbosityOption = CommonOptions.VerbosityOption(Utils.VerbosityOptions.minimal); - - private static IEnumerable - public static ForwardedOption?> ForwardAsMSBuildProperty(this ForwardedOption?> option) => option - .SetForwardingFunction(propertyDict => ForwardedMSBuildPropertyValues(propertyDict, option.Name)); + public static Option?> ForwardAsMSBuildProperty(this Option?> option) => + option.SetForwardingFunction(propertyDict => ForwardedMSBuildPropertyValues(propertyDict, option.Name)); private static IEnumerable ForwardedMSBuildPropertyValues(ReadOnlyDictionary? properties, string optionName) { @@ -92,152 +58,4 @@ private static IEnumerable ForwardedMSBuildPropertyValues(ReadOnlyDictio return properties.Select(kv => $"{optionName}:{kv.Key}={kv.Value}"); } - - public static Option ForwardAsMany(this ForwardedOption option, Func> format) => option.SetForwardingFunction(format); - - public static Option> ForwardAsManyArgumentsEachPrefixedByOption(this ForwardedOption> option, string alias) => option.ForwardAsMany(o => ForwardedArguments(alias, o)); - - /// - /// Calls the forwarding functions for all options that implement in the provided . - /// - /// - /// If not provided, uses the 's . - /// - public static IEnumerable OptionValuesToBeForwarded(this ParseResult parseResult, Command? command = null) => - (command ?? parseResult.CommandResult.Command).Options - .OfType() - .Select(o => o.GetForwardingFunction()) - .SelectMany(f => f is not null ? f(parseResult) : []); - - public static IEnumerable ForwardedOptionValues(this ParseResult parseResult, Command command, string alias) - { - var func = command.Options? - .Where(o => o.Name.Equals(alias) || o.Aliases.Contains(alias))? - .OfType()? - .FirstOrDefault()? - .GetForwardingFunction(); - return func?.Invoke(parseResult) ?? []; - } - - /// - /// Forces an option that represents a collection-type to only allow a single - /// argument per instance of the option. This means that you'd have to - /// use the option multiple times to pass multiple values. - /// This prevents ambiguity in parsing when argument tokens may appear after the option. - /// - /// - /// - /// - public static T AllowSingleArgPerToken(this T option) where T : Option - { - option.AllowMultipleArgumentsPerToken = false; - return option; - } - - - public static T AggregateRepeatedTokens(this T option) where T : Option - { - option.AllowMultipleArgumentsPerToken = true; - return option; - } - - public static Option Hide(this Option option) - { - option.Hidden = true; - return option; - } - - private static IEnumerable ForwardedArguments(string alias, IEnumerable? arguments) - { - foreach (string arg in arguments ?? []) - { - yield return alias; - yield return arg; - } - } -} - -public interface IForwardedOption -{ - Func> GetForwardingFunction(); -} - -public class ForwardedOption : Option, IForwardedOption -{ - private Func> ForwardingFunction; - - public ForwardedOption(string name, params string[] aliases) : base(name, aliases) - { - ForwardingFunction = _ => []; - } - - public ForwardedOption(string name, Func parseArgument, string? description = null) - : base(name) - { - CustomParser = parseArgument; - Description = description; - ForwardingFunction = _ => []; - } - - public ForwardedOption SetForwardingFunction(Func> func) - { - ForwardingFunction = GetForwardingFunction(func); - return this; - } - - public ForwardedOption SetForwardingFunction(Func format) - { - ForwardingFunction = GetForwardingFunction((o) => [format(o)]); - return this; - } - - public ForwardedOption SetForwardingFunction(Func> func) - { - ForwardingFunction = (ParseResult parseResult) => - { - if (parseResult.GetResult(this) is OptionResult argresult && argresult.GetValue(this) is T validValue) - { - return func(validValue, parseResult) ?? []; - } - else - { - return []; - } - }; - return this; - } - - public Func> GetForwardingFunction(Func> func) - { - return (ParseResult parseResult) => - { - if (parseResult.GetResult(this) is OptionResult r) - { - if (r.GetValueOrDefault() is T value) - { - return func(value); - } - else - { - return []; - } - } - return []; - }; - } - - public Func> GetForwardingFunction() - { - return ForwardingFunction; - } -} - -public class DynamicForwardedOption : ForwardedOption, IDynamicOption -{ - public DynamicForwardedOption(string name, Func parseArgument, string? description = null) - : base(name, parseArgument, description) - { - } - - public DynamicForwardedOption(string name, params string[] aliases) : base(name, aliases) { } } diff --git a/src/sdk/src/Cli/dotnet/Extensions/ParseResultExtensions.cs b/src/sdk/src/Cli/dotnet/Extensions/ParseResultExtensions.cs index 508a1abd137..6bcf5f84ff8 100644 --- a/src/sdk/src/Cli/dotnet/Extensions/ParseResultExtensions.cs +++ b/src/sdk/src/Cli/dotnet/Extensions/ParseResultExtensions.cs @@ -5,7 +5,7 @@ using System.CommandLine.Parsing; using System.Diagnostics; using System.Text.RegularExpressions; -using Microsoft.DotNet.Cli; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Run; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Cli.Utils.Extensions; @@ -237,71 +237,4 @@ public static void HandleDebugSwitch(this ParseResult parseResult) DebugHelper.WaitForDebugger(); } } - - /// - /// Only returns the value for this option if the option is present and there are no parse errors for that option. - /// This allows cross-cutting code like the telemetry filters to safely get the value without throwing on null-ref errors. - /// If you are inside a command handler or 'normal' System.CommandLine code then you don't need this - the parse error handling - /// will have covered these cases. - /// - public static T? SafelyGetValueForOption(this ParseResult parseResult, Option optionToGet) - { - if (parseResult.GetResult(optionToGet) is OptionResult optionResult && - !parseResult.Errors.Any(e => e.SymbolResult == optionResult)) - { - return optionResult.GetValue(optionToGet); - } - else - { - return default; - } - } - public static T? SafelyGetValueForOption(this ParseResult parseResult, string name) - { - if (parseResult.GetResult(name) is OptionResult optionResult && // only return a value if there _is_ a value - default or otherwise - !parseResult.Errors.Any(e => e.SymbolResult == optionResult) // only return a value if this isn't a parsing error - && optionResult.Option.ValueType.IsAssignableTo(typeof(T))) // only return a value if coercing the type won't error - { - // shouldn't happen because of the above checks, but we can be safe since this is only used in telemetry, and should - // be resistant to errors - try - { - return optionResult.GetValue(name); - } - catch - { - return default; - } - } - else - { - return default; - } - } - - /// - /// Checks if the option is present and not implicit (i.e. not set by default). - /// This is useful for checking if the user has explicitly set an option, as opposed to it being set by default. - /// - public static bool HasOption(this ParseResult parseResult, Option option) => parseResult.GetResult(option) is OptionResult or && !or.Implicit; - - /// - /// Checks if the option with given name is present and not implicit (i.e. not set by default). - /// This is useful for checking if the user has explicitly set an option, as opposed to it being set by default. - /// - public static bool HasOption(this ParseResult parseResult, string name) - => parseResult.GetResult(name) is OptionResult or && !or.Implicit; - - /// - /// Checks if the option is present and not implicit (i.e. not set by default). - /// This is useful for checking if the user has explicitly set an option, as opposed to it being set by default. - /// - public static bool HasOption(this SymbolResult symbolResult, Option option) => symbolResult.GetResult(option) is OptionResult or && !or.Implicit; - - /// - /// Checks if the option with given name is present and not implicit (i.e. not set by default). - /// This is useful for checking if the user has explicitly set an option, as opposed to it being set by default. - /// - public static bool HasOption(this SymbolResult symbolResult, string name) - => symbolResult.GetResult(name) is OptionResult or && !or.Implicit; } diff --git a/src/sdk/src/Cli/dotnet/Program.cs b/src/sdk/src/Cli/dotnet/Program.cs index e57fe3a5635..71feefd8e7f 100644 --- a/src/sdk/src/Cli/dotnet/Program.cs +++ b/src/sdk/src/Cli/dotnet/Program.cs @@ -9,6 +9,7 @@ using System.Runtime.InteropServices; using Microsoft.DotNet.Cli.CommandFactory; using Microsoft.DotNet.Cli.CommandFactory.CommandResolution; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Run; using Microsoft.DotNet.Cli.Commands.Workload; using Microsoft.DotNet.Cli.Extensions; diff --git a/src/sdk/src/Cli/dotnet/Telemetry/TelemetryFilter.cs b/src/sdk/src/Cli/dotnet/Telemetry/TelemetryFilter.cs index 349285a497a..f410cd70c2f 100644 --- a/src/sdk/src/Cli/dotnet/Telemetry/TelemetryFilter.cs +++ b/src/sdk/src/Cli/dotnet/Telemetry/TelemetryFilter.cs @@ -5,6 +5,7 @@ using System.CommandLine; using System.Globalization; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Build; using Microsoft.DotNet.Cli.Commands.Clean; using Microsoft.DotNet.Cli.Commands.Hidden.InternalReportInstallSuccess; diff --git a/src/sdk/src/Cli/dotnet/dotnet.csproj b/src/sdk/src/Cli/dotnet/dotnet.csproj index d72287d4179..3cdb3b20bee 100644 --- a/src/sdk/src/Cli/dotnet/dotnet.csproj +++ b/src/sdk/src/Cli/dotnet/dotnet.csproj @@ -47,6 +47,7 @@ + @@ -100,10 +101,7 @@ $([System.IO.Path]::Combine($(TestHostDotNetRoot), "sdk", $(Version))) - + diff --git a/src/sdk/src/System.CommandLine.StaticCompletions/DynamicSymbolExtensions.cs b/src/sdk/src/System.CommandLine.StaticCompletions/DynamicSymbolExtensions.cs new file mode 100644 index 00000000000..c59d18c7bec --- /dev/null +++ b/src/sdk/src/System.CommandLine.StaticCompletions/DynamicSymbolExtensions.cs @@ -0,0 +1,55 @@ +namespace System.CommandLine.StaticCompletions; + +/// +/// Extensions for marking options or arguments require dynamic completions. Such symbols get special handling +/// in the static completion generation logic. +/// +public static class DynamicSymbolExtensions +{ + /// + /// The state that is used to track which symbols are dynamic. + /// + private static readonly Dictionary s_dynamicSymbols = []; + + extension(Option option) + { + /// + /// Indicates whether this option requires a dynamic call into the dotnet process to compute completions. + /// + public bool IsDynamic + { + get => s_dynamicSymbols.GetValueOrDefault(option, false); + set => s_dynamicSymbols[option] = value; + } + + /// + /// Mark this option as requiring dynamic completions. + /// + /// + public Option RequiresDynamicCompletion() + { + option.IsDynamic = true; + return option; + } + } + + extension(Argument argument) + { + /// Indicates whether this argument requires a dynamic call into the dotnet process to compute completions. + public bool IsDynamic + { + get => s_dynamicSymbols.GetValueOrDefault(argument, false); + set => s_dynamicSymbols[argument] = value; + } + + /// + /// Mark this argument as requiring dynamic completions. + /// + /// + public Argument RequiresDynamicCompletion() + { + argument.IsDynamic = true; + return argument; + } + } +} diff --git a/src/sdk/src/System.CommandLine.StaticCompletions/HelpGenerationExtensions.cs b/src/sdk/src/System.CommandLine.StaticCompletions/HelpGenerationExtensions.cs index a78e854e690..193234dc60d 100644 --- a/src/sdk/src/System.CommandLine.StaticCompletions/HelpGenerationExtensions.cs +++ b/src/sdk/src/System.CommandLine.StaticCompletions/HelpGenerationExtensions.cs @@ -111,8 +111,5 @@ private static IEnumerable