diff --git a/Directory.Packages.props b/Directory.Packages.props index f46c178e87..1233827813 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.Subsystems.Tests/ErrorReportingFunctionalTests.cs b/src/System.CommandLine.Subsystems.Tests/ErrorReportingFunctionalTests.cs index 47e4808c18..f3e43ea5a0 100644 --- a/src/System.CommandLine.Subsystems.Tests/ErrorReportingFunctionalTests.cs +++ b/src/System.CommandLine.Subsystems.Tests/ErrorReportingFunctionalTests.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. /* @@ -56,9 +56,9 @@ public void Help_display_can_be_disabled() var result = rootCommand.Parse("oops", config); - if (result.Action is ParseErrorAction parseError) + if (result.Action is CliDiagnosticAction cliDiagnostic) { - parseError.ShowHelp = false; + cliDiagnostic.ShowHelp = false; } result.Invoke(); diff --git a/src/System.CommandLine.Subsystems.Tests/ErrorReportingSubsystemTests.cs b/src/System.CommandLine.Subsystems.Tests/ErrorReportingSubsystemTests.cs index 1ce30a9422..b8284a71b5 100644 --- a/src/System.CommandLine.Subsystems.Tests/ErrorReportingSubsystemTests.cs +++ b/src/System.CommandLine.Subsystems.Tests/ErrorReportingSubsystemTests.cs @@ -1,9 +1,9 @@ // 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.CommandLine.Parsing; using FluentAssertions; using Xunit; -using System.CommandLine.Parsing; namespace System.CommandLine.Subsystems.Tests; @@ -12,8 +12,8 @@ public class ErrorReportingSubsystemTests [Fact] public void Report_when_single_error_writes_to_console_hack() { - var error = new ParseError("a sweet error message"); - var errors = new List { error }; + var error = new CliDiagnostic(new("", "", "a sweet error message", CliDiagnosticSeverity.Warning, null), []); + var errors = new List { error }; var errorSubsystem = new ErrorReportingSubsystem(); var consoleHack = new ConsoleHack().RedirectToBuffer(true); @@ -25,9 +25,9 @@ public void Report_when_single_error_writes_to_console_hack() [Fact] public void Report_when_multiple_error_writes_to_console_hack() { - var error = new ParseError("a sweet error message"); - var anotherError = new ParseError("another sweet error message"); - var errors = new List { error, anotherError }; + var error = new CliDiagnostic(new("", "", "a sweet error message", CliDiagnosticSeverity.Warning, null), []); + var anotherError = new CliDiagnostic(new("", "", "another sweet error message", CliDiagnosticSeverity.Warning, null), []); + var errors = new List { error, anotherError }; var errorSubsystem = new ErrorReportingSubsystem(); var consoleHack = new ConsoleHack().RedirectToBuffer(true); @@ -39,7 +39,7 @@ public void Report_when_multiple_error_writes_to_console_hack() [Fact] public void Report_when_no_errors_writes_nothing_to_console_hack() { - var errors = new List { }; + var errors = new List { }; var errorSubsystem = new ErrorReportingSubsystem(); var consoleHack = new ConsoleHack().RedirectToBuffer(true); @@ -53,7 +53,7 @@ public void Report_when_no_errors_writes_nothing_to_console_hack() [InlineData("-non_existant_option")] public void GetIsActivated_GivenInvalidInput_SubsystemIsActive(string input) { - var rootCommand = new CliRootCommand {new CliOption("-v")}; + var rootCommand = new CliRootCommand { new CliOption("-v") }; var configuration = new CliConfiguration(rootCommand); var errorSubsystem = new ErrorReportingSubsystem(); IReadOnlyList args = [""]; diff --git a/src/System.CommandLine.Subsystems/ErrorReportingSubsystem.cs b/src/System.CommandLine.Subsystems/ErrorReportingSubsystem.cs index 27f43ab9f2..039e700217 100644 --- a/src/System.CommandLine.Subsystems/ErrorReportingSubsystem.cs +++ b/src/System.CommandLine.Subsystems/ErrorReportingSubsystem.cs @@ -33,7 +33,7 @@ public override void Execute(PipelineResult pipelineResult) pipelineResult.SetSuccess(); } - public void Report(ConsoleHack consoleHack, IReadOnlyList errors) + public void Report(ConsoleHack consoleHack, IReadOnlyList errors) { ConsoleHelpers.ResetTerminalForegroundColor(); ConsoleHelpers.SetTerminalForegroundRed(); diff --git a/src/System.CommandLine.Subsystems/PipelineResult.cs b/src/System.CommandLine.Subsystems/PipelineResult.cs index cb1763c3d1..8af9b55eda 100644 --- a/src/System.CommandLine.Subsystems/PipelineResult.cs +++ b/src/System.CommandLine.Subsystems/PipelineResult.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.CommandLine.Parsing; @@ -9,8 +9,8 @@ namespace System.CommandLine; public class PipelineResult { // TODO: Try to build workflow so it is illegal to create this without a ParseResult - private readonly List errors = []; - private ValueProvider valueProvider { get; } + private readonly List errors = []; + private ValueProvider valueProvider { get; } public PipelineResult(ParseResult parseResult, string rawInput, Pipeline? pipeline, ConsoleHack? consoleHack = null) { @@ -44,7 +44,7 @@ public PipelineResult(ParseResult parseResult, string rawInput, Pipeline? pipeli => ParseResult.GetValueResult(valueSymbol); - public void AddErrors(IEnumerable errors) + public void AddErrors(IEnumerable errors) { if (errors is not null) { @@ -52,10 +52,10 @@ public void AddErrors(IEnumerable errors) } } - public void AddError(ParseError error) + public void AddError(CliDiagnostic error) => errors.Add(error); - public IEnumerable GetErrors(bool excludeParseErrors = false) + public IEnumerable GetErrors(bool excludeParseErrors = false) => excludeParseErrors || ParseResult is null ? errors : ParseResult.Errors.Concat(errors); diff --git a/src/System.CommandLine.Subsystems/Validation/InclusiveGroupValidator.cs b/src/System.CommandLine.Subsystems/Validation/InclusiveGroupValidator.cs index 6c8a1c28e5..961b4538ab 100644 --- a/src/System.CommandLine.Subsystems/Validation/InclusiveGroupValidator.cs +++ b/src/System.CommandLine.Subsystems/Validation/InclusiveGroupValidator.cs @@ -44,9 +44,9 @@ public override void Validate(CliCommandResult commandResult, // TODO: Rework to allow localization var pluralToBe = "are"; var singularToBe = "is"; - validationContext.AddError(new ParseError( $"The members {string.Join(", ", groupMembers.Select(m => m.Name))} " + - $"must all be used if one is used. {string.Join(", ", missingMembers.Select(m => m.Name))} " + - $"{(missingMembers.Skip(1).Any() ? pluralToBe : singularToBe)} missing.")); + validationContext.AddError(new CliDiagnostic(new("", "", "The members {groupMembers} " + + "must all be used if one is used. {missingMembers} " + + "{toBe} missing.", severity: CliDiagnosticSeverity.Error, null), [string.Join(", ", groupMembers.Select(m => m.Name)), string.Join(", ", missingMembers.Select(m => m.Name)), (missingMembers.Skip(1).Any() ? pluralToBe : singularToBe)])); } } } diff --git a/src/System.CommandLine.Subsystems/Validation/RangeValidator.cs b/src/System.CommandLine.Subsystems/Validation/RangeValidator.cs index cfab72a183..5f0e682a45 100644 --- a/src/System.CommandLine.Subsystems/Validation/RangeValidator.cs +++ b/src/System.CommandLine.Subsystems/Validation/RangeValidator.cs @@ -24,9 +24,7 @@ public override void Validate(object? value, CliValueSymbol valueSymbol, } if (valueCondition.MustHaveValidator) { - validationContext.AddError(new ParseError($"Range validator missing for {valueSymbol.Name}")); + validationContext.AddError(new CliDiagnostic(new("", "", "Range validator missing for {valueSymbol}", severity: CliDiagnosticSeverity.Error, null), [valueSymbol.Name], cliSymbolResult: valueResult)); } } - - } diff --git a/src/System.CommandLine.Subsystems/Validation/ValidationContext.cs b/src/System.CommandLine.Subsystems/Validation/ValidationContext.cs index 3f22a12280..93c8bfe22d 100644 --- a/src/System.CommandLine.Subsystems/Validation/ValidationContext.cs +++ b/src/System.CommandLine.Subsystems/Validation/ValidationContext.cs @@ -3,7 +3,6 @@ using System.CommandLine.Parsing; using System.CommandLine.ValueSources; -using static System.Runtime.InteropServices.JavaScript.JSType; namespace System.CommandLine.Validation; @@ -24,7 +23,7 @@ internal ValidationContext(PipelineResult pipelineResult, ValidationSubsystem va /// Adds an error to the PipelineContext. /// /// The to add - public void AddError(ParseError error) + public void AddError(CliDiagnostic error) => pipelineResult.AddError(error); /// diff --git a/src/System.CommandLine.Subsystems/Validation/Validator.cs b/src/System.CommandLine.Subsystems/Validation/Validator.cs index e33f732c11..e8d64e4a2e 100644 --- a/src/System.CommandLine.Subsystems/Validation/Validator.cs +++ b/src/System.CommandLine.Subsystems/Validation/Validator.cs @@ -31,11 +31,11 @@ internal Validator(string name, Type valueConditionType, params Type[] moreValue /// /// This method needs to be evolved as we replace ParseError with CliError /// - protected static List AddValidationError(ref List? parseErrors, string message, IEnumerable errorValues) + protected static List AddValidationError(ref List? parseErrors, string message, IEnumerable errorValues) { // TODO: Review the handling of errors. They are currently transient and returned by the Validate method, and to avoid allocating in the case of no errors (the common case) this method is used. This adds complexity to creating a new validator. - parseErrors ??= new List(); - parseErrors.Add(new ParseError(message)); + parseErrors ??= new List(); + parseErrors.Add(new CliDiagnostic(new("", "", message, severity: CliDiagnosticSeverity.Error, null), [])); return parseErrors; } diff --git a/src/System.CommandLine.Subsystems/ValidationSubsystem.cs b/src/System.CommandLine.Subsystems/ValidationSubsystem.cs index 00e44e9971..1ae38a131a 100644 --- a/src/System.CommandLine.Subsystems/ValidationSubsystem.cs +++ b/src/System.CommandLine.Subsystems/ValidationSubsystem.cs @@ -81,7 +81,8 @@ private void ValidateValue(CliValueSymbol valueSymbol, ValidationContext validat var value = validationContext.GetValue(valueSymbol); var valueResult = validationContext.GetValueResult(valueSymbol); - do { + do + { ValidateValueCondition(value, valueSymbol, valueResult, enumerator.Current, validationContext); } while (enumerator.MoveNext()); } @@ -123,9 +124,9 @@ private void ValidateValueCondition(object? value, CliValueSymbol valueSymbol, C { if (condition.MustHaveValidator) { - validationContext.AddError(new ParseError($"{valueSymbol.Name} must have {condition.Name} validator.")); + validationContext.AddError(new CliDiagnostic(new("", "", "{valueSymbol} must have {condition} validator.", severity: CliDiagnosticSeverity.Error, null), [valueSymbol.Name, condition.Name], cliSymbolResult: valueResult)); } - return; + return; } validator.Validate(value, valueSymbol, valueResult, condition, validationContext); @@ -169,7 +170,7 @@ private void ValidateCommandCondition(CliCommandResult commandResult, CommandCon { if (condition.MustHaveValidator) { - validationContext.AddError(new ParseError($"{commandResult.Command.Name} must have {condition.Name} validator.")); + validationContext.AddError(new CliDiagnostic(new("", "", "{commandResult} must have {condition} validator.", severity: CliDiagnosticSeverity.Error, null), [commandResult.Command.Name, condition.Name])); } return; } diff --git a/src/System.CommandLine.Subsystems/ValueConditions/Range.cs b/src/System.CommandLine.Subsystems/ValueConditions/Range.cs index 2d029e5e8e..70bb2c07f9 100644 --- a/src/System.CommandLine.Subsystems/ValueConditions/Range.cs +++ b/src/System.CommandLine.Subsystems/ValueConditions/Range.cs @@ -40,8 +40,8 @@ internal Range(ValueSource? lowerBound, ValueSource? upperBound, RangeBoun LowerBound = lowerBound; UpperBound = upperBound; RangeBound = rangeBound; - } - + } + /// public void Validate(object? value, CliValueSymbol valueSymbol, @@ -59,7 +59,7 @@ public void Validate(object? value, && validationContext.TryGetTypedValue(LowerBound, out var lowerValue) && comparableValue.CompareTo(lowerValue) < 0) { - validationContext.AddError(new ParseError($"The value for '{valueSymbol.Name}' is below the lower bound of {LowerBound}")); + validationContext.AddError(new CliDiagnostic(new("", "", "The value for '{valueSymbol}' is below the lower bound of {lowerBound}", severity: CliDiagnosticSeverity.Error, null), [valueSymbol.Name, LowerBound], cliSymbolResult: valueResult)); } @@ -67,7 +67,7 @@ public void Validate(object? value, && validationContext.TryGetTypedValue(UpperBound, out var upperValue) && comparableValue.CompareTo(upperValue) > 0) { - validationContext.AddError(new ParseError($"The value for '{valueSymbol.Name}' is above the upper bound of {UpperBound}")); + validationContext.AddError(new CliDiagnostic(new("", "", "The value for '{valueSymbol}' is above the upper bound of {upperBound}", severity: CliDiagnosticSeverity.Error, null), [valueSymbol.Name, UpperBound], cliSymbolResult: valueResult)); } } diff --git a/src/System.CommandLine/ParseResult.cs b/src/System.CommandLine/ParseResult.cs index 07d068f747..3e91bf1ba0 100644 --- a/src/System.CommandLine/ParseResult.cs +++ b/src/System.CommandLine/ParseResult.cs @@ -1,11 +1,8 @@ -// 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.CommandLine.Parsing; -using System.Linq; -using System.Threading.Tasks; -using System.Threading; namespace System.CommandLine { @@ -39,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 @@ -82,12 +79,11 @@ 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(); } public CliSymbol? GetSymbolByName(string name, bool valuesOnly = false) { - symbolLookupByName ??= new SymbolLookupByName(this); return symbolLookupByName.TryGetSymbol(name, out var symbol, valuesOnly: valuesOnly) ? symbol @@ -121,7 +117,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/CliArgumentResultInternal.cs b/src/System.CommandLine/Parsing/CliArgumentResultInternal.cs index a6479af553..11e0e47efe 100644 --- a/src/System.CommandLine/Parsing/CliArgumentResultInternal.cs +++ b/src/System.CommandLine/Parsing/CliArgumentResultInternal.cs @@ -138,9 +138,9 @@ public void OnlyTake(int numberOfTokens) public override string ToString() => $"{nameof(CliArgumentResultInternal)} {Argument.Name}: {string.Join(" ", Tokens.Select(t => $"<{t.Value}>"))}"; /// - internal override void AddError(string errorMessage) + internal override void AddError(string errorMessage, CliValueResult valueResult) { - SymbolResultTree.AddError(new ParseError(errorMessage, AppliesToPublicSymbolResult)); + SymbolResultTree.AddError(new CliDiagnostic(new("", "", errorMessage, CliDiagnosticSeverity.Warning, null), [], cliSymbolResult: valueResult)); _conversionResult = ArgumentConversionResult.Failure(this, errorMessage, ArgumentConversionResultType.Failed); } @@ -170,7 +170,6 @@ private ArgumentConversionResult ValidateAndConvert(bool useValidators) } } */ - // TODO: defaults /* if (Parent!.UseDefaultValueFor(this)) @@ -222,7 +221,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), [], cliSymbolResult: ValueResult)); } return result; diff --git a/src/System.CommandLine/Parsing/CliCommandResultInternal.cs b/src/System.CommandLine/Parsing/CliCommandResultInternal.cs index 764ecf824b..4861943178 100644 --- a/src/System.CommandLine/Parsing/CliCommandResultInternal.cs +++ b/src/System.CommandLine/Parsing/CliCommandResultInternal.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; @@ -84,7 +84,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), [], valueResult: )); } // TODO: validators diff --git a/src/System.CommandLine/Parsing/CliDiagnostic.cs b/src/System.CommandLine/Parsing/CliDiagnostic.cs new file mode 100644 index 0000000000..d291eae12a --- /dev/null +++ b/src/System.CommandLine/Parsing/CliDiagnostic.cs @@ -0,0 +1,70 @@ +// 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; + +/// +/// Describes an error that occurs while parsing command line input. +/// +public sealed class CliDiagnostic +{ + /// + /// 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. + /// Contains information about a single value entered. + /// The location of the error. + /* + * 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 + * https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html#_Toc141791086 + */ + public CliDiagnostic( + CliDiagnosticDescriptor descriptor, + object?[]? messageArgs, + ImmutableDictionary? properties = null, + CliSymbolResult? cliSymbolResult = null, + Location? location = null) + { + Descriptor = descriptor; + MessageArgs = messageArgs; + Properties = properties; + CliSymbolResult = cliSymbolResult; + Location = location; + } + + /// + /// 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; } + + public CliSymbolResult? CliSymbolResult { get; } + + public Location? Location { get; } + + /// + public override string ToString() => Message; +} diff --git a/src/System.CommandLine/Parsing/CliDiagnosticDescriptor.cs b/src/System.CommandLine/Parsing/CliDiagnosticDescriptor.cs new file mode 100644 index 0000000000..2e3243ac8f --- /dev/null +++ b/src/System.CommandLine/Parsing/CliDiagnosticDescriptor.cs @@ -0,0 +1,47 @@ +// 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.Diagnostics.CodeAnalysis; + +namespace System.CommandLine.Parsing; + +/// +/// Provides a description of a . +/// +public sealed class CliDiagnosticDescriptor +{ + public CliDiagnosticDescriptor(string id, string title, [StringSyntax(StringSyntaxAttribute.CompositeFormat)] string messageFormat, CliDiagnosticSeverity severity, string? helpUri) + { + Id = id; + Title = title; + MessageFormat = messageFormat; + Severity = severity; + HelpUri = helpUri; + } + + /// + /// A unique identifier for the diagnostic. + /// + public string Id { get; } + + /// + /// A short title describing the diagnostic. + /// + public string Title { get; } + + /// + /// A composite format string, which can be passed to to create a message. + /// + [StringSyntax(StringSyntaxAttribute.CompositeFormat)] + public string MessageFormat { get; } + + /// + /// The severity of the diagnostic. + /// + public CliDiagnosticSeverity Severity { get; } + + /// + /// An optional hyperlink that provides more information about the diagnostic. + /// + public string? HelpUri { get; } +} diff --git a/src/System.CommandLine/Parsing/CliDiagnosticSeverity.cs b/src/System.CommandLine/Parsing/CliDiagnosticSeverity.cs new file mode 100644 index 0000000000..956892fb97 --- /dev/null +++ b/src/System.CommandLine/Parsing/CliDiagnosticSeverity.cs @@ -0,0 +1,31 @@ +// 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 how severe a is."/> +/// +// Pattern based on: https://github.com/dotnet/roslyn/blob/1cca63b5d8ea170f8d8e88e1574aa3ebe354c23b/src/Compilers/Core/Portable/Diagnostic/DiagnosticSeverity.cs. +public enum CliDiagnosticSeverity +{ + /// + /// Something that is not surfaced through normal means. + /// + Hidden = 0, + + /// + /// Information that does not indicate a problem (i.e. not prescriptive). + /// + Info, + + /// + /// Something suspicious but allowed. + /// + Warning, + + /// + /// Something that is definitely wrong and needs fixing. + /// + Error +} diff --git a/src/System.CommandLine/Parsing/CliSymbolResultInternal.cs b/src/System.CommandLine/Parsing/CliSymbolResultInternal.cs index bf09249159..4a679578ce 100644 --- a/src/System.CommandLine/Parsing/CliSymbolResultInternal.cs +++ b/src/System.CommandLine/Parsing/CliSymbolResultInternal.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 CliSymbolResultInternal(SymbolResultTree symbolResultTree, Cli /// /// The parse errors associated with this symbol result. /// - public IEnumerable Errors + public IEnumerable Errors { get { @@ -64,7 +64,8 @@ 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, CliValueResult valueResult) => + SymbolResultTree.AddError(new CliDiagnostic(new("", "", errorMessage, severity: CliDiagnosticSeverity.Error, null), [], cliSymbolResult: valueResult)); /// /// Finds a result for the specific argument anywhere in the parse tree, including parent and child symbol results. /// diff --git a/src/System.CommandLine/Parsing/Location.cs b/src/System.CommandLine/Parsing/Location.cs index 30d3a5285e..dea0ccaa30 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 index, Location? outerLocation, public bool IsImplicit => Source == Implicit; + /// public override string ToString() => $"{(OuterLocation is null ? "" : OuterLocation.ToString() + "; ")}{Text} from {Source}[{Index}, {Length}, {Offset}]"; diff --git a/src/System.CommandLine/Parsing/ParseDiagnostics.cs b/src/System.CommandLine/Parsing/ParseDiagnostics.cs new file mode 100644 index 0000000000..b814c441e6 --- /dev/null +++ b/src/System.CommandLine/Parsing/ParseDiagnostics.cs @@ -0,0 +1,17 @@ +// 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; + +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); +} diff --git a/src/System.CommandLine/Parsing/ParseError.cs b/src/System.CommandLine/Parsing/ParseError.cs deleted file mode 100644 index 9be6314ef3..0000000000 --- a/src/System.CommandLine/Parsing/ParseError.cs +++ /dev/null @@ -1,54 +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 CliSymbolResultInternal here - internal ParseError( - string message, - CliSymbolResultInternal? symbolResult = null) - { - if (string.IsNullOrWhiteSpace(message)) - { - throw new ArgumentException("Value cannot be null or whitespace.", nameof(message)); - } - - Message = message; - /* - CliSymbolResultInternal = symbolResult; - */ - } - - public ParseError( - string message) - { - if (string.IsNullOrWhiteSpace(message)) - { - throw new ArgumentException("Value cannot be null or whitespace.", nameof(message)); - } - - Message = message; - } - - /// - /// A message to explain the error to a user. - /// - public string Message { get; } - - /* Consider how results are attached to errors now that we have ValueResult and CommandValueResult. Should there be a common base? - /// - /// The symbol result detailing the symbol that failed to parse and the tokens involved. - /// - public CliSymbolResultInternal? CliSymbolResultInternal { get; } - */ - - /// - public override string ToString() => Message; - } -} diff --git a/src/System.CommandLine/Parsing/ParseOperation.cs b/src/System.CommandLine/Parsing/ParseOperation.cs index 3c941ecf97..50185de4a6 100644 --- a/src/System.CommandLine/Parsing/ParseOperation.cs +++ b/src/System.CommandLine/Parsing/ParseOperation.cs @@ -76,7 +76,7 @@ internal ParseResult Parse() { if (_symbolResultTree.ErrorCount > 0) { - _primaryAction = new ParseErrorAction(); + _primaryAction = new CliDiagnosticAction(); } } */ diff --git a/src/System.CommandLine/Parsing/SymbolResultTree.cs b/src/System.CommandLine/Parsing/SymbolResultTree.cs index 571403bc27..1a6b3aeb9a 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; @@ -26,11 +25,13 @@ 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), [], + cliSymbolResult: null)); } } } @@ -82,8 +83,8 @@ internal IReadOnlyDictionary BuildValueResultDictiona 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 CliDiagnostic) => (Errors ??= new()).Add(CliDiagnostic); + internal void InsertFirstError(CliDiagnostic CliDiagnostic) => (Errors ??= new()).Insert(0, CliDiagnostic); internal void AddUnmatchedToken(CliToken token, CliCommandResultInternal commandResult, CliCommandResultInternal rootCommandResult) { @@ -99,7 +100,7 @@ internal void AddUnmatchedToken(CliToken token, CliCommandResultInternal command } */ - AddError(new ParseError(LocalizationResources.UnrecognizedCommandOrArgument(token.Value), commandResult)); + AddError(new CliDiagnostic(new("", "", LocalizationResources.UnrecognizedCommandOrArgument(token.Value), CliDiagnosticSeverity.Warning, null), [])); /* } */ diff --git a/src/System.CommandLine/System.CommandLine.csproj b/src/System.CommandLine/System.CommandLine.csproj index bee122292b..ea33049a7b 100644 --- a/src/System.CommandLine/System.CommandLine.csproj +++ b/src/System.CommandLine/System.CommandLine.csproj @@ -29,7 +29,10 @@ + + + @@ -58,7 +61,7 @@ - + @@ -76,6 +79,7 @@ +