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..bd1f56c717 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(new("", "", errorMessage, CliDiagnosticSeverity.Warning, null), [], symbolResult: AppliesToPublicSymbolResult));
_conversionResult = ArgumentConversionResult.Failure(this, errorMessage, ArgumentConversionResultType.Failed);
}
@@ -171,9 +171,6 @@ private ArgumentConversionResult ValidateAndConvert(bool useValidators)
}
}
*/
-
- // TODO: defaults
- /*
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
new file mode 100644
index 0000000000..8328683f7b
--- /dev/null
+++ b/src/System.CommandLine/Parsing/CliDiagnostic.cs
@@ -0,0 +1,111 @@
+// 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
+ * 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
+{
+ 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: 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,
+ object?[]? messageArgs,
+ ImmutableDictionary? properties = null,
+ SymbolResult? symbolResult = null,
+ Location? location = null)
+ {
+ Descriptor = descriptor;
+ MessageArgs = messageArgs;
+ Properties = properties;
+ SymbolResult = symbolResult;
+ }
+
+ ///
+ /// Gets a message to explain the error to a user.
+ ///
+ 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.
+ ///
+ public SymbolResult? SymbolResult { get; }
+
+ ///
+ public override string ToString() => Message;
+}
diff --git a/src/System.CommandLine/Parsing/CommandResult.cs b/src/System.CommandLine/Parsing/CommandResult.cs
index 7adf32d08f..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;
@@ -64,7 +64,7 @@ internal void Validate(bool completeValidation)
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
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..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;
@@ -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
{
@@ -64,7 +64,7 @@ public IEnumerable Errors
/// 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.
///
@@ -100,7 +100,7 @@ public IEnumerable Errors
///
/// 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 37319d604a..abfaa30953 100644
--- a/src/System.CommandLine/Parsing/SymbolResultTree.cs
+++ b/src/System.CommandLine/Parsing/SymbolResultTree.cs
@@ -1,15 +1,14 @@
-// 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;
-using System.Linq;
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,11 +26,11 @@ 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++)
{
- Errors.Add(new ParseError(tokenizeErrors[i]));
+ Errors.Add(new CliDiagnostic(new("", "", tokenizeErrors[i], CliDiagnosticSeverity.Warning, null), []));
}
}
}
@@ -51,7 +50,6 @@ internal SymbolResultTree(
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)
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)
{
@@ -87,8 +85,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)
{
@@ -104,7 +102,7 @@ internal void AddUnmatchedToken(CliToken token, CommandResult commandResult, Com
}
*/
- 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);
}
@@ -140,7 +137,6 @@ internal void AddUnmatchedToken(CliToken token, CommandResult commandResult, Com
// 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
private void PopulateSymbolsByName(CliCommand command)
{
if (command.HasArguments)
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 @@
+