From a64077ddd7e8f6b55bd95077489d0cec9ac640f6 Mon Sep 17 00:00:00 2001 From: Kevin Bost Date: Tue, 16 Apr 2024 12:04:18 -0700 Subject: [PATCH 1/7] WIP on CliError --- Directory.Packages.props | 9 +- src/System.CommandLine/ParseResult.cs | 10 +- .../Parsing/ArgumentResult.cs | 2 +- .../Parsing/CliDiagnostic.cs | 96 +++++++++++++++++++ src/System.CommandLine/Parsing/Location.cs | 1 + src/System.CommandLine/Parsing/ParseError.cs | 39 -------- .../Parsing/SymbolResult.cs | 2 +- .../Parsing/SymbolResultTree.cs | 10 +- .../System.CommandLine.csproj | 3 +- 9 files changed, 114 insertions(+), 58 deletions(-) create mode 100644 src/System.CommandLine/Parsing/CliDiagnostic.cs delete mode 100644 src/System.CommandLine/Parsing/ParseError.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index a8a61f0901..e31ef9f32c 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,12 +1,10 @@ - true false $(NoWarn);NU1507 - @@ -24,14 +22,13 @@ - + + - - - + \ No newline at end of file diff --git a/src/System.CommandLine/ParseResult.cs b/src/System.CommandLine/ParseResult.cs index c0001edb14..3a7e1b7b86 100644 --- a/src/System.CommandLine/ParseResult.cs +++ b/src/System.CommandLine/ParseResult.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Generic; @@ -36,7 +36,7 @@ internal ParseResult( */ // TODO: unmatched tokens // List? unmatchedTokens, - List? errors, + List? errors, // TODO: commandLineText should be string array string? commandLineText = null //, // TODO: invocation @@ -75,8 +75,8 @@ internal ParseResult( // TODO: unmatched tokens // _unmatchedTokens = unmatchedTokens is null ? Array.Empty() : unmatchedTokens; - - Errors = errors is not null ? errors : Array.Empty(); + + Errors = errors is not null ? errors : Array.Empty(); } // TODO: check that constructing empty ParseResult directly is correct @@ -102,7 +102,7 @@ internal ParseResult( /// /// Gets the parse errors found while parsing command line input. /// - public IReadOnlyList Errors { get; } + public IReadOnlyList Errors { get; } /* // TODO: don't expose tokens diff --git a/src/System.CommandLine/Parsing/ArgumentResult.cs b/src/System.CommandLine/Parsing/ArgumentResult.cs index 1a7c6e4cd7..90bcdba2d5 100644 --- a/src/System.CommandLine/Parsing/ArgumentResult.cs +++ b/src/System.CommandLine/Parsing/ArgumentResult.cs @@ -141,7 +141,7 @@ public void OnlyTake(int numberOfTokens) /// internal override void AddError(string errorMessage) { - SymbolResultTree.AddError(new ParseError(errorMessage, AppliesToPublicSymbolResult)); + SymbolResultTree.AddError(new CliDiagnostic(errorMessage, AppliesToPublicSymbolResult)); _conversionResult = ArgumentConversionResult.Failure(this, errorMessage, ArgumentConversionResultType.Failed); } diff --git a/src/System.CommandLine/Parsing/CliDiagnostic.cs b/src/System.CommandLine/Parsing/CliDiagnostic.cs new file mode 100644 index 0000000000..082e050df9 --- /dev/null +++ b/src/System.CommandLine/Parsing/CliDiagnostic.cs @@ -0,0 +1,96 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Immutable; + +namespace System.CommandLine.Parsing +{ + /* + * Pattern based on: + * https://github.com/mhutch/MonoDevelop.MSBuildEditor/blob/main/MonoDevelop.MSBuild/Analysis/MSBuildDiagnostic.cs + * https://github.com/mhutch/MonoDevelop.MSBuildEditor/blob/main/MonoDevelop.MSBuild/Analysis/MSBuildDiagnosticDescriptor.cs + */ + internal static class ParseDiagnostics + { + public const string DirectiveIsNotDefinedId = "CMD0001"; + public static readonly CliDiagnosticDescriptor DirectiveIsNotDefined = + new( + DirectiveIsNotDefinedId, + //TODO: use localized strings + "Directive is not defined", + "The directive '{0}' is not defined.", + CliDiagnosticSeverity.Error, + null); + } + + public sealed class CliDiagnosticDescriptor + { + public CliDiagnosticDescriptor(string id, string title, string messageFormat, CliDiagnosticSeverity severity, string? helpUri) + { + Id = id; + Title = title; + MessageFormat = messageFormat; + Severity = severity; + HelpUri = helpUri; + } + + public string Id { get; } + public string Title { get; } + public string MessageFormat { get; } + public CliDiagnosticSeverity Severity { get; } + public string? HelpUri { get; } + } + + public enum CliDiagnosticSeverity + { + Hidden = 0, + Info, + Warning, + Error + } + + /// + /// Describes an error that occurs while parsing command line input. + /// + public sealed class CliDiagnostic + { + // TODO: add position + // TODO: reevaluate whether we should be exposing a SymbolResult here + // TODO: Rename to CliError + + /// + /// Initializes a new instance of the class. + /// + /// A message to explain the error to a user. + /// The symbol result detailing the symbol that failed to parse and the tokens involved. + /// The location of the error. + public CliDiagnostic( + CliDiagnosticDescriptor descriptor, + string[] messageArgs, + ImmutableDictionary? properties = null, + SymbolResult? symbolResult = null, + Location? location = null) + { + //if (string.IsNullOrWhiteSpace(message)) + //{ + // throw new ArgumentException("Value cannot be null or whitespace.", nameof(message)); + //} + + //Message = message; + SymbolResult = symbolResult; + } + + /// + /// Gets a message to explain the error to a user. + /// + public string Message { get; } + + /// + /// Gets the symbol result detailing the symbol that failed to parse and the tokens involved. + /// + public SymbolResult? SymbolResult { get; } + + /// + public override string ToString() => Message; + } +} diff --git a/src/System.CommandLine/Parsing/Location.cs b/src/System.CommandLine/Parsing/Location.cs index 18d97ce681..00be871373 100644 --- a/src/System.CommandLine/Parsing/Location.cs +++ b/src/System.CommandLine/Parsing/Location.cs @@ -49,6 +49,7 @@ public Location(string text, string source, int start, Location? outerLocation, public bool IsImplicit => Source == Implicit; + /// public override string ToString() => $"{(OuterLocation is null ? "" : OuterLocation.ToString() + "; ")}{Text} from {Source}[{Start}, {Length}, {Offset}]"; diff --git a/src/System.CommandLine/Parsing/ParseError.cs b/src/System.CommandLine/Parsing/ParseError.cs deleted file mode 100644 index 5c5453a48e..0000000000 --- a/src/System.CommandLine/Parsing/ParseError.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace System.CommandLine.Parsing -{ - /// - /// Describes an error that occurs while parsing command line input. - /// - public sealed class ParseError - { - // TODO: add position - // TODO: reevaluate whether we should be exposing a SymbolResult here - internal ParseError( - string message, - SymbolResult? symbolResult = null) - { - if (string.IsNullOrWhiteSpace(message)) - { - throw new ArgumentException("Value cannot be null or whitespace.", nameof(message)); - } - - Message = message; - SymbolResult = symbolResult; - } - - /// - /// A message to explain the error to a user. - /// - public string Message { get; } - - /// - /// The symbol result detailing the symbol that failed to parse and the tokens involved. - /// - public SymbolResult? SymbolResult { get; } - - /// - public override string ToString() => Message; - } -} diff --git a/src/System.CommandLine/Parsing/SymbolResult.cs b/src/System.CommandLine/Parsing/SymbolResult.cs index 25b1c17b08..aba22d4c8f 100644 --- a/src/System.CommandLine/Parsing/SymbolResult.cs +++ b/src/System.CommandLine/Parsing/SymbolResult.cs @@ -24,7 +24,7 @@ private protected SymbolResult(SymbolResultTree symbolResultTree, SymbolResult? /// /// The parse errors associated with this symbol result. /// - public IEnumerable Errors + public IEnumerable Errors { get { diff --git a/src/System.CommandLine/Parsing/SymbolResultTree.cs b/src/System.CommandLine/Parsing/SymbolResultTree.cs index 37319d604a..1e9d03026b 100644 --- a/src/System.CommandLine/Parsing/SymbolResultTree.cs +++ b/src/System.CommandLine/Parsing/SymbolResultTree.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Generic; @@ -9,7 +9,7 @@ namespace System.CommandLine.Parsing internal sealed class SymbolResultTree : Dictionary { private readonly CliCommand _rootCommand; - internal List? Errors; + internal List? Errors; // TODO: unmatched tokens /* internal List? UnmatchedTokens; @@ -27,7 +27,7 @@ internal SymbolResultTree( if (tokenizeErrors is not null) { - Errors = new List(tokenizeErrors.Count); + Errors = new List(tokenizeErrors.Count); for (var i = 0; i < tokenizeErrors.Count; i++) { @@ -87,8 +87,8 @@ internal Dictionary GetValueResultDictionary() return dict; } - internal void AddError(ParseError parseError) => (Errors ??= new()).Add(parseError); - internal void InsertFirstError(ParseError parseError) => (Errors ??= new()).Insert(0, parseError); + internal void AddError(CliDiagnostic parseError) => (Errors ??= new()).Add(parseError); + internal void InsertFirstError(CliDiagnostic parseError) => (Errors ??= new()).Insert(0, parseError); internal void AddUnmatchedToken(CliToken token, CommandResult commandResult, CommandResult rootCommandResult) { diff --git a/src/System.CommandLine/System.CommandLine.csproj b/src/System.CommandLine/System.CommandLine.csproj index 6cd32ef4d8..f394e92420 100644 --- a/src/System.CommandLine/System.CommandLine.csproj +++ b/src/System.CommandLine/System.CommandLine.csproj @@ -55,7 +55,7 @@ - + @@ -72,6 +72,7 @@ + From 8988493de65f83bae6c1e0096ee03429f85a02e2 Mon Sep 17 00:00:00 2001 From: Benjamin Michaelis Date: Tue, 16 Apr 2024 21:58:30 -0700 Subject: [PATCH 2/7] Get rid of compile error and clean up some docs --- .../Parsing/ArgumentResult.cs | 48 ++++++------ .../Parsing/CliDiagnostic.cs | 8 +- .../Parsing/CommandResult.cs | 76 +++++++++---------- .../Parsing/SymbolResult.cs | 74 +++++++++--------- .../Parsing/SymbolResultTree.cs | 56 +++++++------- 5 files changed, 128 insertions(+), 134 deletions(-) diff --git a/src/System.CommandLine/Parsing/ArgumentResult.cs b/src/System.CommandLine/Parsing/ArgumentResult.cs index 90bcdba2d5..fb44876e64 100644 --- a/src/System.CommandLine/Parsing/ArgumentResult.cs +++ b/src/System.CommandLine/Parsing/ArgumentResult.cs @@ -141,7 +141,7 @@ public void OnlyTake(int numberOfTokens) /// internal override void AddError(string errorMessage) { - SymbolResultTree.AddError(new CliDiagnostic(errorMessage, AppliesToPublicSymbolResult)); + SymbolResultTree.AddError(new CliDiagnostic(new("", "", errorMessage, CliDiagnosticSeverity.Warning, null), [], symbolResult: AppliesToPublicSymbolResult)); _conversionResult = ArgumentConversionResult.Failure(this, errorMessage, ArgumentConversionResultType.Failed); } @@ -151,29 +151,26 @@ private ArgumentConversionResult ValidateAndConvert(bool useValidators) { return ReportErrorIfNeeded(arityFailure); } -// TODO: validators -/* - // There is nothing that stops user-defined Validator from calling ArgumentResult.GetValueOrDefault. - // In such cases, we can't call the validators again, as it would create infinite recursion. - // GetArgumentConversionResult => ValidateAndConvert => Validator - // => GetValueOrDefault => ValidateAndConvert (again) - if (useValidators && Argument.HasValidators) - { - for (var i = 0; i < Argument.Validators.Count; i++) - { - Argument.Validators[i](this); - } - - // validator provided by the user might report an error, which sets _conversionResult - if (_conversionResult is not null) - { - return _conversionResult; - } - } -*/ - - // TODO: defaults - /* + // TODO: validators + /* + // There is nothing that stops user-defined Validator from calling ArgumentResult.GetValueOrDefault. + // In such cases, we can't call the validators again, as it would create infinite recursion. + // GetArgumentConversionResult => ValidateAndConvert => Validator + // => GetValueOrDefault => ValidateAndConvert (again) + if (useValidators && Argument.HasValidators) + { + for (var i = 0; i < Argument.Validators.Count; i++) + { + Argument.Validators[i](this); + } + + // validator provided by the user might report an error, which sets _conversionResult + if (_conversionResult is not null) + { + return _conversionResult; + } + } + */ if (Parent!.UseDefaultValueFor(this)) { var defaultValue = Argument.GetDefaultValue(this); @@ -181,7 +178,6 @@ private ArgumentConversionResult ValidateAndConvert(bool useValidators) // default value factory provided by the user might report an error, which sets _conversionResult return _conversionResult ?? ArgumentConversionResult.Success(this, defaultValue); } - */ if (Argument.ConvertArguments is null) { @@ -223,7 +219,7 @@ ArgumentConversionResult ReportErrorIfNeeded(ArgumentConversionResult result) { if (result.Result >= ArgumentConversionResultType.Failed) { - SymbolResultTree.AddError(new ParseError(result.ErrorMessage!, AppliesToPublicSymbolResult)); + SymbolResultTree.AddError(new CliDiagnostic(new("ArgumentConversionResultTypeFailed", "Type Conversion Failed", result.ErrorMessage!, CliDiagnosticSeverity.Warning, null), [], symbolResult: AppliesToPublicSymbolResult)); } return result; diff --git a/src/System.CommandLine/Parsing/CliDiagnostic.cs b/src/System.CommandLine/Parsing/CliDiagnostic.cs index 082e050df9..67b1394d9d 100644 --- a/src/System.CommandLine/Parsing/CliDiagnostic.cs +++ b/src/System.CommandLine/Parsing/CliDiagnostic.cs @@ -13,7 +13,7 @@ namespace System.CommandLine.Parsing internal static class ParseDiagnostics { public const string DirectiveIsNotDefinedId = "CMD0001"; - public static readonly CliDiagnosticDescriptor DirectiveIsNotDefined = + public static readonly CliDiagnosticDescriptor DirectiveIsNotDefined = new( DirectiveIsNotDefinedId, //TODO: use localized strings @@ -61,7 +61,9 @@ public sealed class CliDiagnostic /// /// Initializes a new instance of the class. /// - /// A message to explain the error to a user. + /// Contains information about the error. + /// The arguments to be passed to the in the . + /// Properties to be associated with the diagnostic. /// The symbol result detailing the symbol that failed to parse and the tokens involved. /// The location of the error. public CliDiagnostic( @@ -76,7 +78,7 @@ public CliDiagnostic( // throw new ArgumentException("Value cannot be null or whitespace.", nameof(message)); //} - //Message = message; + Message = string.Format(descriptor.MessageFormat, messageArgs); SymbolResult = symbolResult; } diff --git a/src/System.CommandLine/Parsing/CommandResult.cs b/src/System.CommandLine/Parsing/CommandResult.cs index 7adf32d08f..d7759f4010 100644 --- a/src/System.CommandLine/Parsing/CommandResult.cs +++ b/src/System.CommandLine/Parsing/CommandResult.cs @@ -59,30 +59,30 @@ internal void Validate(bool completeValidation) { if (completeValidation) { -// TODO: invocation -// if (Command.Action is null && Command.HasSubcommands) + // TODO: invocation + // if (Command.Action is null && Command.HasSubcommands) if (Command.HasSubcommands) { SymbolResultTree.InsertFirstError( - new ParseError(LocalizationResources.RequiredCommandWasNotProvided(), this)); + new CliDiagnostic(new("validateSubCommandError", "Validation Error", LocalizationResources.RequiredCommandWasNotProvided(), CliDiagnosticSeverity.Warning, null), [], symbolResult: this)); } -// TODO: validators -/* - if (Command.HasValidators) - { - int errorCountBefore = SymbolResultTree.ErrorCount; - for (var i = 0; i < Command.Validators.Count; i++) - { - Command.Validators[i](this); - } - - if (SymbolResultTree.ErrorCount != errorCountBefore) - { - return; - } - } -*/ + // TODO: validators + /* + if (Command.HasValidators) + { + int errorCountBefore = SymbolResultTree.ErrorCount; + for (var i = 0; i < Command.Validators.Count; i++) + { + Command.Validators[i](this); + } + + if (SymbolResultTree.ErrorCount != errorCountBefore) + { + return; + } + } + */ } // TODO: Validation @@ -104,8 +104,8 @@ private void ValidateOptions(bool completeValidation) { var option = options[i]; -// TODO: VersionOption, recursive options -// if (!completeValidation && !(option.Recursive || option.Argument.HasDefaultValue || option is VersionOption)) + // TODO: VersionOption, recursive options + // if (!completeValidation && !(option.Recursive || option.Argument.HasDefaultValue || option is VersionOption)) if (!completeValidation && !option.Argument.HasDefaultValue) { continue; @@ -148,23 +148,23 @@ private void ValidateOptions(bool completeValidation) continue; } -// TODO: validators -/* - if (optionResult.Option.HasValidators) - { - int errorsBefore = SymbolResultTree.ErrorCount; - - for (var j = 0; j < optionResult.Option.Validators.Count; j++) - { - optionResult.Option.Validators[j](optionResult); - } - - if (errorsBefore != SymbolResultTree.ErrorCount) - { - continue; - } - } -*/ + // TODO: validators + /* + if (optionResult.Option.HasValidators) + { + int errorsBefore = SymbolResultTree.ErrorCount; + + for (var j = 0; j < optionResult.Option.Validators.Count; j++) + { + optionResult.Option.Validators[j](optionResult); + } + + if (errorsBefore != SymbolResultTree.ErrorCount) + { + continue; + } + } + */ // TODO: Ensure all argument conversions are run for entered values /* diff --git a/src/System.CommandLine/Parsing/SymbolResult.cs b/src/System.CommandLine/Parsing/SymbolResult.cs index aba22d4c8f..49e7a21de4 100644 --- a/src/System.CommandLine/Parsing/SymbolResult.cs +++ b/src/System.CommandLine/Parsing/SymbolResult.cs @@ -10,7 +10,7 @@ namespace System.CommandLine.Parsing /// public abstract class SymbolResult { -// TODO: make this a property and protected if possible + // TODO: make this a property and protected if possible internal readonly SymbolResultTree SymbolResultTree; private protected List? _tokens; @@ -19,39 +19,39 @@ private protected SymbolResult(SymbolResultTree symbolResultTree, SymbolResult? SymbolResultTree = symbolResultTree; Parent = parent; } -// TODO: this can be an extension method, do we need it? -/* - /// - /// The parse errors associated with this symbol result. - /// - public IEnumerable Errors - { - get - { - var parseErrors = SymbolResultTree.Errors; - - if (parseErrors is null) - { - yield break; - } - - for (var i = 0; i < parseErrors.Count; i++) + // TODO: this can be an extension method, do we need it? + /* + /// + /// The parse errors associated with this symbol result. + /// + public IEnumerable Errors { - var parseError = parseErrors[i]; - if (parseError.SymbolResult == this) + get { - yield return parseError; + var parseErrors = SymbolResultTree.Errors; + + if (parseErrors is null) + { + yield break; + } + + for (var i = 0; i < parseErrors.Count; i++) + { + var parseError = parseErrors[i]; + if (parseError.SymbolResult == this) + { + yield return parseError; + } + } } } - } - } -*/ + */ /// /// The parent symbol result in the parse tree. /// public SymbolResult? Parent { get; } -// TODO: make internal because exposes tokens + // TODO: make internal because exposes tokens /// /// The list of tokens associated with this symbol result during parsing. /// @@ -59,12 +59,12 @@ public IEnumerable Errors internal void AddToken(CliToken token) => (_tokens ??= new()).Add(token); -// TODO: made nonpublic, should we make public again? + // TODO: made nonpublic, should we make public again? /// /// Adds an error message for this symbol result to it's parse tree. /// /// Setting an error will cause the parser to indicate an error for the user and prevent invocation of the command line. - internal virtual void AddError(string errorMessage) => SymbolResultTree.AddError(new ParseError(errorMessage, this)); + internal virtual void AddError(string errorMessage) => SymbolResultTree.AddError(new CliDiagnostic(new("", "", errorMessage, severity: CliDiagnosticSeverity.Error, null), [], symbolResult: this)); /// /// Finds a result for the specific argument anywhere in the parse tree, including parent and child symbol results. /// @@ -86,21 +86,21 @@ public IEnumerable Errors /// An option result if the option was matched by the parser or has a default value; otherwise, null. public OptionResult? GetResult(CliOption option) => SymbolResultTree.GetResult(option); -// TODO: directives -/* - /// - /// Finds a result for the specific directive anywhere in the parse tree. - /// - /// The directive for which to find a result. - /// A directive result if the directive was matched by the parser, null otherwise. - public DirectiveResult? GetResult(CliDirective directive) => SymbolResultTree.GetResult(directive); -*/ + // TODO: directives + /* + /// + /// Finds a result for the specific directive anywhere in the parse tree. + /// + /// The directive for which to find a result. + /// A directive result if the directive was matched by the parser, null otherwise. + public DirectiveResult? GetResult(CliDirective directive) => SymbolResultTree.GetResult(directive); + */ /// /// Finds a result for a symbol having the specified name anywhere in the parse tree. /// /// The name of the symbol for which to find a result. /// An argument result if the argument was matched by the parser or has a default value; otherwise, null. - public SymbolResult? GetResult(string name) => + public SymbolResult? GetResult(string name) => SymbolResultTree.GetResult(name); /// diff --git a/src/System.CommandLine/Parsing/SymbolResultTree.cs b/src/System.CommandLine/Parsing/SymbolResultTree.cs index 1e9d03026b..d5189989b5 100644 --- a/src/System.CommandLine/Parsing/SymbolResultTree.cs +++ b/src/System.CommandLine/Parsing/SymbolResultTree.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Generic; -using System.Linq; namespace System.CommandLine.Parsing { @@ -10,10 +9,10 @@ internal sealed class SymbolResultTree : Dictionary { private readonly CliCommand _rootCommand; internal List? Errors; -// TODO: unmatched tokens -/* - internal List? UnmatchedTokens; -*/ + // TODO: unmatched tokens + /* + internal List? UnmatchedTokens; + */ // TODO: Looks like this is a SymboNode/linked list because a symbol may appear multiple // places in the tree and multiple symbols will have the same short name. The question is @@ -31,7 +30,7 @@ internal SymbolResultTree( for (var i = 0; i < tokenizeErrors.Count; i++) { - Errors.Add(new ParseError(tokenizeErrors[i])); + Errors.Add(new CliDiagnostic(new("", "", tokenizeErrors[i], CliDiagnosticSeverity.Warning, null), [])); } } } @@ -46,12 +45,11 @@ internal SymbolResultTree( internal OptionResult? GetResult(CliOption option) => TryGetValue(option, out SymbolResult? result) ? (OptionResult)result : default; -//TODO: directives -/* - internal DirectiveResult? GetResult(CliDirective directive) - => TryGetValue(directive, out SymbolResult? result) ? (DirectiveResult)result : default; -*/ - // TODO: Determine how this is used. It appears to be O^n in the size of the tree and so if it is called multiple times, we should reconsider to avoid O^(N*M) + //TODO: directives + /* + internal DirectiveResult? GetResult(CliDirective directive) + => TryGetValue(directive, out SymbolResult? result) ? (DirectiveResult)result : default; + */ internal IEnumerable GetChildren(SymbolResult parent) { // Argument can't have children @@ -71,7 +69,7 @@ internal Dictionary GetValueResultDictionary() { var dict = new Dictionary(); foreach (KeyValuePair pair in this) - { + { var result = pair.Value; if (result is OptionResult optionResult) { @@ -92,19 +90,19 @@ internal Dictionary GetValueResultDictionary() internal void AddUnmatchedToken(CliToken token, CommandResult commandResult, CommandResult rootCommandResult) { -/* -// TODO: unmatched tokens - (UnmatchedTokens ??= new()).Add(token); + /* + // TODO: unmatched tokens + (UnmatchedTokens ??= new()).Add(token); - if (commandResult.Command.TreatUnmatchedTokensAsErrors) - { - if (commandResult != rootCommandResult && !rootCommandResult.Command.TreatUnmatchedTokensAsErrors) - { - return; - } + if (commandResult.Command.TreatUnmatchedTokensAsErrors) + { + if (commandResult != rootCommandResult && !rootCommandResult.Command.TreatUnmatchedTokensAsErrors) + { + return; + } */ - AddError(new ParseError(LocalizationResources.UnrecognizedCommandOrArgument(token.Value), commandResult)); + AddError(new CliDiagnostic(new("", "", LocalizationResources.UnrecognizedCommandOrArgument(token.Value), CliDiagnosticSeverity.Warning, null), [], symbolResult: commandResult)); // } } @@ -113,7 +111,6 @@ internal void AddUnmatchedToken(CliToken token, CommandResult commandResult, Com if (_symbolsByName is null) { _symbolsByName = new(); - // TODO: See if we can avoid populating the entire tree and just populate the portion/cone we need PopulateSymbolsByName(_rootCommand); } @@ -135,12 +132,11 @@ internal void AddUnmatchedToken(CliToken token, CommandResult commandResult, Com return null; } -// TODO: symbolsbyname - this is inefficient -// results for some values may not be queried at all, dependent on other options -// so we could avoid using their value factories and adding them to the dictionary -// could we sort by name allowing us to do a binary search instead of allocating a dictionary? -// could we add codepaths that query for specific kinds of symbols so they don't have to search all symbols? -// Additional Note: Couldn't commands know their children, and thus this involves querying the active command, and possibly the parents + // TODO: symbolsbyname - this is inefficient + // results for some values may not be queried at all, dependent on other options + // so we could avoid using their value factories and adding them to the dictionary + // could we sort by name allowing us to do a binary search instead of allocating a dictionary? + // could we add codepaths that query for specific kinds of symbols so they don't have to search all symbols? private void PopulateSymbolsByName(CliCommand command) { if (command.HasArguments) From 20b3ae0cbfd48392436b76110515f033b925a643 Mon Sep 17 00:00:00 2001 From: Benjamin Michaelis Date: Tue, 30 Apr 2024 11:31:59 -0700 Subject: [PATCH 3/7] File scoped namespace --- .../Parsing/CliDiagnostic.cs | 155 +++++++++--------- 1 file changed, 77 insertions(+), 78 deletions(-) diff --git a/src/System.CommandLine/Parsing/CliDiagnostic.cs b/src/System.CommandLine/Parsing/CliDiagnostic.cs index 67b1394d9d..e09d6cb930 100644 --- a/src/System.CommandLine/Parsing/CliDiagnostic.cs +++ b/src/System.CommandLine/Parsing/CliDiagnostic.cs @@ -3,96 +3,95 @@ using System.Collections.Immutable; -namespace System.CommandLine.Parsing +namespace System.CommandLine.Parsing; + +/* + * Pattern based on: + * https://github.com/mhutch/MonoDevelop.MSBuildEditor/blob/main/MonoDevelop.MSBuild/Analysis/MSBuildDiagnostic.cs + * https://github.com/mhutch/MonoDevelop.MSBuildEditor/blob/main/MonoDevelop.MSBuild/Analysis/MSBuildDiagnosticDescriptor.cs + */ +internal static class ParseDiagnostics { - /* - * Pattern based on: - * https://github.com/mhutch/MonoDevelop.MSBuildEditor/blob/main/MonoDevelop.MSBuild/Analysis/MSBuildDiagnostic.cs - * https://github.com/mhutch/MonoDevelop.MSBuildEditor/blob/main/MonoDevelop.MSBuild/Analysis/MSBuildDiagnosticDescriptor.cs - */ - internal static class ParseDiagnostics + public const string DirectiveIsNotDefinedId = "CMD0001"; + public static readonly CliDiagnosticDescriptor DirectiveIsNotDefined = + new( + DirectiveIsNotDefinedId, + //TODO: use localized strings + "Directive is not defined", + "The directive '{0}' is not defined.", + CliDiagnosticSeverity.Error, + null); +} + +public sealed class CliDiagnosticDescriptor +{ + public CliDiagnosticDescriptor(string id, string title, string messageFormat, CliDiagnosticSeverity severity, string? helpUri) { - public const string DirectiveIsNotDefinedId = "CMD0001"; - public static readonly CliDiagnosticDescriptor DirectiveIsNotDefined = - new( - DirectiveIsNotDefinedId, - //TODO: use localized strings - "Directive is not defined", - "The directive '{0}' is not defined.", - CliDiagnosticSeverity.Error, - null); + Id = id; + Title = title; + MessageFormat = messageFormat; + Severity = severity; + HelpUri = helpUri; } - public sealed class CliDiagnosticDescriptor - { - public CliDiagnosticDescriptor(string id, string title, string messageFormat, CliDiagnosticSeverity severity, string? helpUri) - { - Id = id; - Title = title; - MessageFormat = messageFormat; - Severity = severity; - HelpUri = helpUri; - } + public string Id { get; } + public string Title { get; } + public string MessageFormat { get; } + public CliDiagnosticSeverity Severity { get; } + public string? HelpUri { get; } +} - public string Id { get; } - public string Title { get; } - public string MessageFormat { get; } - public CliDiagnosticSeverity Severity { get; } - public string? HelpUri { get; } - } +public enum CliDiagnosticSeverity +{ + Hidden = 0, + Info, + Warning, + Error +} - public enum CliDiagnosticSeverity - { - Hidden = 0, - Info, - Warning, - Error - } +/// +/// Describes an error that occurs while parsing command line input. +/// +public sealed class CliDiagnostic +{ + // TODO: add position + // TODO: reevaluate whether we should be exposing a SymbolResult here + // TODO: Rename to CliError /// - /// Describes an error that occurs while parsing command line input. + /// Initializes a new instance of the class. /// - public sealed class CliDiagnostic + /// Contains information about the error. + /// The arguments to be passed to the in the . + /// Properties to be associated with the diagnostic. + /// The symbol result detailing the symbol that failed to parse and the tokens involved. + /// The location of the error. + public CliDiagnostic( + CliDiagnosticDescriptor descriptor, + string[] messageArgs, + ImmutableDictionary? properties = null, + SymbolResult? symbolResult = null, + Location? location = null) { - // TODO: add position - // TODO: reevaluate whether we should be exposing a SymbolResult here - // TODO: Rename to CliError - - /// - /// Initializes a new instance of the class. - /// - /// Contains information about the error. - /// The arguments to be passed to the in the . - /// Properties to be associated with the diagnostic. - /// The symbol result detailing the symbol that failed to parse and the tokens involved. - /// The location of the error. - public CliDiagnostic( - CliDiagnosticDescriptor descriptor, - string[] messageArgs, - ImmutableDictionary? properties = null, - SymbolResult? symbolResult = null, - Location? location = null) - { - //if (string.IsNullOrWhiteSpace(message)) - //{ - // throw new ArgumentException("Value cannot be null or whitespace.", nameof(message)); - //} + //if (string.IsNullOrWhiteSpace(message)) + //{ + // throw new ArgumentException("Value cannot be null or whitespace.", nameof(message)); + //} - Message = string.Format(descriptor.MessageFormat, messageArgs); - SymbolResult = symbolResult; - } + Message = string.Format(descriptor.MessageFormat, messageArgs); + SymbolResult = symbolResult; + } - /// - /// Gets a message to explain the error to a user. - /// - public string Message { get; } + /// + /// Gets a message to explain the error to a user. + /// + public string Message { get; } - /// - /// Gets the symbol result detailing the symbol that failed to parse and the tokens involved. - /// - public SymbolResult? SymbolResult { get; } + /// + /// Gets the symbol result detailing the symbol that failed to parse and the tokens involved. + /// + public SymbolResult? SymbolResult { get; } - /// - public override string ToString() => Message; - } + /// + public override string ToString() => Message; } From e89d5e099e645830b2d7c0bf58fb7a5f14e17592 Mon Sep 17 00:00:00 2001 From: Kevin Bost Date: Tue, 30 Apr 2024 11:32:55 -0700 Subject: [PATCH 4/7] Reverting whitespace changes --- .../Parsing/ArgumentResult.cs | 40 +++++----- .../Parsing/CommandResult.cs | 76 +++++++++---------- .../Parsing/SymbolResult.cs | 72 +++++++++--------- .../Parsing/SymbolResultTree.cs | 46 +++++------ 4 files changed, 117 insertions(+), 117 deletions(-) diff --git a/src/System.CommandLine/Parsing/ArgumentResult.cs b/src/System.CommandLine/Parsing/ArgumentResult.cs index fb44876e64..bd1f56c717 100644 --- a/src/System.CommandLine/Parsing/ArgumentResult.cs +++ b/src/System.CommandLine/Parsing/ArgumentResult.cs @@ -151,26 +151,26 @@ private ArgumentConversionResult ValidateAndConvert(bool useValidators) { return ReportErrorIfNeeded(arityFailure); } - // TODO: validators - /* - // There is nothing that stops user-defined Validator from calling ArgumentResult.GetValueOrDefault. - // In such cases, we can't call the validators again, as it would create infinite recursion. - // GetArgumentConversionResult => ValidateAndConvert => Validator - // => GetValueOrDefault => ValidateAndConvert (again) - if (useValidators && Argument.HasValidators) - { - for (var i = 0; i < Argument.Validators.Count; i++) - { - Argument.Validators[i](this); - } - - // validator provided by the user might report an error, which sets _conversionResult - if (_conversionResult is not null) - { - return _conversionResult; - } - } - */ +// TODO: validators +/* + // There is nothing that stops user-defined Validator from calling ArgumentResult.GetValueOrDefault. + // In such cases, we can't call the validators again, as it would create infinite recursion. + // GetArgumentConversionResult => ValidateAndConvert => Validator + // => GetValueOrDefault => ValidateAndConvert (again) + if (useValidators && Argument.HasValidators) + { + for (var i = 0; i < Argument.Validators.Count; i++) + { + Argument.Validators[i](this); + } + + // validator provided by the user might report an error, which sets _conversionResult + if (_conversionResult is not null) + { + return _conversionResult; + } + } +*/ if (Parent!.UseDefaultValueFor(this)) { var defaultValue = Argument.GetDefaultValue(this); diff --git a/src/System.CommandLine/Parsing/CommandResult.cs b/src/System.CommandLine/Parsing/CommandResult.cs index d7759f4010..f654bac87d 100644 --- a/src/System.CommandLine/Parsing/CommandResult.cs +++ b/src/System.CommandLine/Parsing/CommandResult.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Generic; @@ -59,30 +59,30 @@ internal void Validate(bool completeValidation) { if (completeValidation) { - // TODO: invocation - // if (Command.Action is null && Command.HasSubcommands) +// TODO: invocation +// if (Command.Action is null && Command.HasSubcommands) if (Command.HasSubcommands) { SymbolResultTree.InsertFirstError( new CliDiagnostic(new("validateSubCommandError", "Validation Error", LocalizationResources.RequiredCommandWasNotProvided(), CliDiagnosticSeverity.Warning, null), [], symbolResult: this)); } - // TODO: validators - /* - if (Command.HasValidators) - { - int errorCountBefore = SymbolResultTree.ErrorCount; - for (var i = 0; i < Command.Validators.Count; i++) - { - Command.Validators[i](this); - } - - if (SymbolResultTree.ErrorCount != errorCountBefore) - { - return; - } - } - */ +// TODO: validators +/* + if (Command.HasValidators) + { + int errorCountBefore = SymbolResultTree.ErrorCount; + for (var i = 0; i < Command.Validators.Count; i++) + { + Command.Validators[i](this); + } + + if (SymbolResultTree.ErrorCount != errorCountBefore) + { + return; + } + } +*/ } // TODO: Validation @@ -104,8 +104,8 @@ private void ValidateOptions(bool completeValidation) { var option = options[i]; - // TODO: VersionOption, recursive options - // if (!completeValidation && !(option.Recursive || option.Argument.HasDefaultValue || option is VersionOption)) +// TODO: VersionOption, recursive options +// if (!completeValidation && !(option.Recursive || option.Argument.HasDefaultValue || option is VersionOption)) if (!completeValidation && !option.Argument.HasDefaultValue) { continue; @@ -148,23 +148,23 @@ private void ValidateOptions(bool completeValidation) continue; } - // TODO: validators - /* - if (optionResult.Option.HasValidators) - { - int errorsBefore = SymbolResultTree.ErrorCount; - - for (var j = 0; j < optionResult.Option.Validators.Count; j++) - { - optionResult.Option.Validators[j](optionResult); - } - - if (errorsBefore != SymbolResultTree.ErrorCount) - { - continue; - } - } - */ +// TODO: validators +/* + if (optionResult.Option.HasValidators) + { + int errorsBefore = SymbolResultTree.ErrorCount; + + for (var j = 0; j < optionResult.Option.Validators.Count; j++) + { + optionResult.Option.Validators[j](optionResult); + } + + if (errorsBefore != SymbolResultTree.ErrorCount) + { + continue; + } + } +*/ // TODO: Ensure all argument conversions are run for entered values /* diff --git a/src/System.CommandLine/Parsing/SymbolResult.cs b/src/System.CommandLine/Parsing/SymbolResult.cs index 49e7a21de4..d716e14870 100644 --- a/src/System.CommandLine/Parsing/SymbolResult.cs +++ b/src/System.CommandLine/Parsing/SymbolResult.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Generic; @@ -10,7 +10,7 @@ namespace System.CommandLine.Parsing /// public abstract class SymbolResult { - // TODO: make this a property and protected if possible +// TODO: make this a property and protected if possible internal readonly SymbolResultTree SymbolResultTree; private protected List? _tokens; @@ -19,39 +19,39 @@ private protected SymbolResult(SymbolResultTree symbolResultTree, SymbolResult? SymbolResultTree = symbolResultTree; Parent = parent; } - // TODO: this can be an extension method, do we need it? - /* - /// - /// The parse errors associated with this symbol result. - /// - public IEnumerable Errors +// TODO: this can be an extension method, do we need it? +/* + /// + /// The parse errors associated with this symbol result. + /// + public IEnumerable Errors + { + get + { + var parseErrors = SymbolResultTree.Errors; + + if (parseErrors is null) + { + yield break; + } + + for (var i = 0; i < parseErrors.Count; i++) { - get + var parseError = parseErrors[i]; + if (parseError.SymbolResult == this) { - var parseErrors = SymbolResultTree.Errors; - - if (parseErrors is null) - { - yield break; - } - - for (var i = 0; i < parseErrors.Count; i++) - { - var parseError = parseErrors[i]; - if (parseError.SymbolResult == this) - { - yield return parseError; - } - } + yield return parseError; } } - */ + } + } +*/ /// /// The parent symbol result in the parse tree. /// public SymbolResult? Parent { get; } - // TODO: make internal because exposes tokens +// TODO: make internal because exposes tokens /// /// The list of tokens associated with this symbol result during parsing. /// @@ -59,7 +59,7 @@ public IEnumerable Errors internal void AddToken(CliToken token) => (_tokens ??= new()).Add(token); - // TODO: made nonpublic, should we make public again? +// TODO: made nonpublic, should we make public again? /// /// Adds an error message for this symbol result to it's parse tree. /// @@ -86,15 +86,15 @@ public IEnumerable Errors /// An option result if the option was matched by the parser or has a default value; otherwise, null. public OptionResult? GetResult(CliOption option) => SymbolResultTree.GetResult(option); - // TODO: directives - /* - /// - /// Finds a result for the specific directive anywhere in the parse tree. - /// - /// The directive for which to find a result. - /// A directive result if the directive was matched by the parser, null otherwise. - public DirectiveResult? GetResult(CliDirective directive) => SymbolResultTree.GetResult(directive); - */ +// TODO: directives +/* + /// + /// Finds a result for the specific directive anywhere in the parse tree. + /// + /// The directive for which to find a result. + /// A directive result if the directive was matched by the parser, null otherwise. + public DirectiveResult? GetResult(CliDirective directive) => SymbolResultTree.GetResult(directive); +*/ /// /// Finds a result for a symbol having the specified name anywhere in the parse tree. /// diff --git a/src/System.CommandLine/Parsing/SymbolResultTree.cs b/src/System.CommandLine/Parsing/SymbolResultTree.cs index d5189989b5..abfaa30953 100644 --- a/src/System.CommandLine/Parsing/SymbolResultTree.cs +++ b/src/System.CommandLine/Parsing/SymbolResultTree.cs @@ -9,10 +9,10 @@ internal sealed class SymbolResultTree : Dictionary { private readonly CliCommand _rootCommand; internal List? Errors; - // TODO: unmatched tokens - /* - internal List? UnmatchedTokens; - */ +// TODO: unmatched tokens +/* + internal List? UnmatchedTokens; +*/ // TODO: Looks like this is a SymboNode/linked list because a symbol may appear multiple // places in the tree and multiple symbols will have the same short name. The question is @@ -45,11 +45,11 @@ internal SymbolResultTree( internal OptionResult? GetResult(CliOption option) => TryGetValue(option, out SymbolResult? result) ? (OptionResult)result : default; - //TODO: directives - /* - internal DirectiveResult? GetResult(CliDirective directive) - => TryGetValue(directive, out SymbolResult? result) ? (DirectiveResult)result : default; - */ +//TODO: directives +/* + internal DirectiveResult? GetResult(CliDirective directive) + => TryGetValue(directive, out SymbolResult? result) ? (DirectiveResult)result : default; +*/ internal IEnumerable GetChildren(SymbolResult parent) { // Argument can't have children @@ -90,16 +90,16 @@ internal Dictionary GetValueResultDictionary() internal void AddUnmatchedToken(CliToken token, CommandResult commandResult, CommandResult rootCommandResult) { - /* - // TODO: unmatched tokens - (UnmatchedTokens ??= new()).Add(token); +/* +// TODO: unmatched tokens + (UnmatchedTokens ??= new()).Add(token); - if (commandResult.Command.TreatUnmatchedTokensAsErrors) - { - if (commandResult != rootCommandResult && !rootCommandResult.Command.TreatUnmatchedTokensAsErrors) - { - return; - } + if (commandResult.Command.TreatUnmatchedTokensAsErrors) + { + if (commandResult != rootCommandResult && !rootCommandResult.Command.TreatUnmatchedTokensAsErrors) + { + return; + } */ AddError(new CliDiagnostic(new("", "", LocalizationResources.UnrecognizedCommandOrArgument(token.Value), CliDiagnosticSeverity.Warning, null), [], symbolResult: commandResult)); @@ -132,11 +132,11 @@ internal void AddUnmatchedToken(CliToken token, CommandResult commandResult, Com return null; } - // TODO: symbolsbyname - this is inefficient - // results for some values may not be queried at all, dependent on other options - // so we could avoid using their value factories and adding them to the dictionary - // could we sort by name allowing us to do a binary search instead of allocating a dictionary? - // could we add codepaths that query for specific kinds of symbols so they don't have to search all symbols? +// TODO: symbolsbyname - this is inefficient +// results for some values may not be queried at all, dependent on other options +// so we could avoid using their value factories and adding them to the dictionary +// could we sort by name allowing us to do a binary search instead of allocating a dictionary? +// could we add codepaths that query for specific kinds of symbols so they don't have to search all symbols? private void PopulateSymbolsByName(CliCommand command) { if (command.HasArguments) From cdb4127f8a01fc8c476dfe6edbed27d86b50fd90 Mon Sep 17 00:00:00 2001 From: Kevin Bost Date: Tue, 30 Apr 2024 11:36:46 -0700 Subject: [PATCH 5/7] Adding link Removing out-dated TODO --- src/System.CommandLine/Parsing/CliDiagnostic.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/System.CommandLine/Parsing/CliDiagnostic.cs b/src/System.CommandLine/Parsing/CliDiagnostic.cs index e09d6cb930..cbdd463341 100644 --- a/src/System.CommandLine/Parsing/CliDiagnostic.cs +++ b/src/System.CommandLine/Parsing/CliDiagnostic.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Immutable; @@ -9,6 +9,7 @@ namespace System.CommandLine.Parsing; * Pattern based on: * https://github.com/mhutch/MonoDevelop.MSBuildEditor/blob/main/MonoDevelop.MSBuild/Analysis/MSBuildDiagnostic.cs * https://github.com/mhutch/MonoDevelop.MSBuildEditor/blob/main/MonoDevelop.MSBuild/Analysis/MSBuildDiagnosticDescriptor.cs + * https://github.com/dotnet/roslyn/blob/main/src/Compilers/Core/Portable/Diagnostic/DiagnosticDescriptor.cs */ internal static class ParseDiagnostics { @@ -54,7 +55,6 @@ public enum CliDiagnosticSeverity /// public sealed class CliDiagnostic { - // TODO: add position // TODO: reevaluate whether we should be exposing a SymbolResult here // TODO: Rename to CliError From ede6762d5e1ec57d2f6be046e1f814d79ba60d85 Mon Sep 17 00:00:00 2001 From: Kevin Bost Date: Tue, 30 Apr 2024 11:41:38 -0700 Subject: [PATCH 6/7] Additional review feedback --- .../Parsing/CliDiagnostic.cs | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/System.CommandLine/Parsing/CliDiagnostic.cs b/src/System.CommandLine/Parsing/CliDiagnostic.cs index cbdd463341..23b26e549d 100644 --- a/src/System.CommandLine/Parsing/CliDiagnostic.cs +++ b/src/System.CommandLine/Parsing/CliDiagnostic.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Immutable; @@ -68,24 +68,37 @@ public sealed class CliDiagnostic /// The location of the error. public CliDiagnostic( CliDiagnosticDescriptor descriptor, - string[] messageArgs, + object?[]? messageArgs, ImmutableDictionary? properties = null, SymbolResult? symbolResult = null, Location? location = null) { - //if (string.IsNullOrWhiteSpace(message)) - //{ - // throw new ArgumentException("Value cannot be null or whitespace.", nameof(message)); - //} - - Message = string.Format(descriptor.MessageFormat, messageArgs); + Descriptor = descriptor; + MessageArgs = messageArgs; + Properties = properties; SymbolResult = symbolResult; } /// /// Gets a message to explain the error to a user. /// - public string Message { get; } + public string Message + { + get + { + if (MessageArgs is not null) + { + return string.Format(Descriptor.MessageFormat, MessageArgs); + } + return Descriptor.MessageFormat; + } + } + + public ImmutableDictionary? Properties { get; } + + public CliDiagnosticDescriptor Descriptor { get; } + + public object?[]? MessageArgs { get; } /// /// Gets the symbol result detailing the symbol that failed to parse and the tokens involved. From da1691af05918d77f948c9d2797da28f28937eae Mon Sep 17 00:00:00 2001 From: Kevin Bost Date: Tue, 30 Apr 2024 11:42:30 -0700 Subject: [PATCH 7/7] Adding additional reference link --- src/System.CommandLine/Parsing/CliDiagnostic.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/System.CommandLine/Parsing/CliDiagnostic.cs b/src/System.CommandLine/Parsing/CliDiagnostic.cs index 23b26e549d..8328683f7b 100644 --- a/src/System.CommandLine/Parsing/CliDiagnostic.cs +++ b/src/System.CommandLine/Parsing/CliDiagnostic.cs @@ -10,6 +10,7 @@ namespace System.CommandLine.Parsing; * https://github.com/mhutch/MonoDevelop.MSBuildEditor/blob/main/MonoDevelop.MSBuild/Analysis/MSBuildDiagnostic.cs * https://github.com/mhutch/MonoDevelop.MSBuildEditor/blob/main/MonoDevelop.MSBuild/Analysis/MSBuildDiagnosticDescriptor.cs * https://github.com/dotnet/roslyn/blob/main/src/Compilers/Core/Portable/Diagnostic/DiagnosticDescriptor.cs + * https://github.com/dotnet/roslyn/blob/main/src/Compilers/Core/Portable/Diagnostic/Diagnostic.cs */ internal static class ParseDiagnostics {