Skip to content

Commit

Permalink
Consider ErrorMessageFormatProvider in the generator
Browse files Browse the repository at this point in the history
  • Loading branch information
DoctorKrolic committed Oct 22, 2024
1 parent 79e801f commit c7da660
Show file tree
Hide file tree
Showing 6 changed files with 576 additions and 44 deletions.
244 changes: 200 additions & 44 deletions src/ArgumentParsing.Generators/ArgumentParserGenerator.CodeGen.cs

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions src/ArgumentParsing.Generators/ArgumentParserGenerator.Extract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@ public partial class ArgumentParserGenerator

var namedArgs = genArgParserAttrData.NamedArguments;

string? errorMessageFormatProvider = null;
if (namedArgs.FirstOrDefault(static n => n.Key == "ErrorMessageFormatProvider") is { Key: not null, Value: { } errorMessageFormatProviderVal })
{
if (errorMessageFormatProviderVal.Kind == TypedConstantKind.Error ||
!errorMessageFormatProviderVal.IsNull && errorMessageFormatProviderVal.Value is not INamedTypeSymbol { TypeKind: not TypeKind.Error, SpecialType: SpecialType.None })
{
return null;
}

errorMessageFormatProvider = ((INamedTypeSymbol?)errorMessageFormatProviderVal.Value)?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
}

if (namedArgs.FirstOrDefault(static n => n.Key == "BuiltInCommandHandlers").Value is { Value: byte builtInHandlersByte })
{
builtInCommandHandlers = (BuiltInCommandHandlers)builtInHandlersByte;
Expand Down Expand Up @@ -202,6 +214,7 @@ public partial class ArgumentParserGenerator
HierarchyInfo.From(argumentParserMethodSymbol.ContainingType),
methodInfo,
optionsInfo,
errorMessageFormatProvider,
builtInCommandInfos.ToImmutable(),
additionalCommandHandlerInfosBuilder.ToImmutable());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ internal sealed record ArgumentParserInfo(
HierarchyInfo ContainingTypeHierarchy,
ArgumentParserMethodInfo MethodInfo,
OptionsInfo OptionsInfo,
string? ErrorMessageFormatProvider,
ImmutableEquatableArray<BuiltInCommandInfo> BuiltInCommandInfos,
ImmutableEquatableArray<AdditionalCommandHandlerInfo> AdditionalCommandHandlersInfos);
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
using System.Collections.Immutable;
using ArgumentParsing.Results;
using ArgumentParsing.Results.Errors;
using ArgumentParsing.Tests.Functional.Utils;

namespace ArgumentParsing.Tests.Functional;

public sealed partial class ErrorMessageFormatProviderTests
{
#region OptionsAndParser
[OptionsType]
private sealed class Options1
{
[Option]
public string? Opt1 { get; init; }

[Option]
public int Opt2 { get; init; }

[Option]
public bool Flag { get; init; }
}

[GeneratedArgumentParser(ErrorMessageFormatProvider = typeof(MyErrorMessageFormatProvider))]
private static partial ParseResult<Options1> ParseArguments1(ReadOnlySpan<string> args);

[OptionsType]
private sealed class Options2
{
[Option('o', null)]
public required string Opt1 { get; init; }

[Option]
public required string Opt2 { get; init; }

[Option('r', "opt3")]
public required string Opt3 { get; init; }
}

[GeneratedArgumentParser(ErrorMessageFormatProvider = typeof(MyErrorMessageFormatProvider))]
private static partial ParseResult<Options2> ParseArguments2(Span<string> args);

[OptionsType]
private sealed class Options3
{
[Parameter(0)]
public required int Param { get; init; }

[RemainingParameters]
public ImmutableArray<TimeSpan> RemainingParams { get; init; }
}

[GeneratedArgumentParser(ErrorMessageFormatProvider = typeof(MyErrorMessageFormatProvider))]
private static partial ParseResult<Options3> ParseArguments3(string[] args);
#endregion

[Fact]
public void UnknownOptionError()
{
var result = ParseArguments1(["-d", "a"]);

Assert.Equal(ParseResultState.ParsedWithErrors, result.State);

Assert.Null(result.Options);

var errors = result.Errors;
Assert.NotNull(errors);

var error = Assert.Single(errors);
var unknownOptionError = Assert.IsType<UnknownOptionError>(error);

Assert.Equal(string.Format(MyErrorMessageFormatProvider.UnknownOptionError, unknownOptionError.OptionName, unknownOptionError.ContainingArgument), unknownOptionError.GetMessage());
}

[Fact]
public void UnrecognizedArgumentError()
{
var result = ParseArguments1(["a"]);

Assert.Equal(ParseResultState.ParsedWithErrors, result.State);

Assert.Null(result.Options);

var errors = result.Errors;
Assert.NotNull(errors);

var error = Assert.Single(errors);
var unrecognizedArgumentError = Assert.IsType<UnrecognizedArgumentError>(error);

Assert.Equal(string.Format(MyErrorMessageFormatProvider.UnrecognizedArgumentError, unrecognizedArgumentError.Argument), unrecognizedArgumentError.GetMessage());
}

[Fact]
public void OptionValueIsNotProvidedError()
{
var result = ParseArguments1(["--opt1"]);

Assert.Equal(ParseResultState.ParsedWithErrors, result.State);

Assert.Null(result.Options);

var errors = result.Errors;
Assert.NotNull(errors);

var error = Assert.Single(errors);
var optionValueIsNotProvidedError = Assert.IsType<OptionValueIsNotProvidedError>(error);

Assert.Equal(string.Format(MyErrorMessageFormatProvider.OptionValueIsNotProvidedError, optionValueIsNotProvidedError.PrecedingArgument), optionValueIsNotProvidedError.GetMessage());
}

[Fact]
public void DuplicateOptionError()
{
var result = ParseArguments1(["--opt1", "a", "--opt1", "b"]);

Assert.Equal(ParseResultState.ParsedWithErrors, result.State);

Assert.Null(result.Options);

var errors = result.Errors;
Assert.NotNull(errors);

var error = Assert.Single(errors);
var duplicateOptionError = Assert.IsType<DuplicateOptionError>(error);

Assert.Equal(string.Format(MyErrorMessageFormatProvider.DuplicateOptionError, duplicateOptionError.OptionName), duplicateOptionError.GetMessage());
}

[Fact]
public void MissingRequiredOptionError_OnlyShortOptionName()
{
var result = ParseArguments2(["--opt2", "a", "--opt3", "b"]);

Assert.Equal(ParseResultState.ParsedWithErrors, result.State);

Assert.Null(result.Options);

var errors = result.Errors;
Assert.NotNull(errors);

var error = Assert.Single(errors);
var missingRequiredOptionError = Assert.IsType<MissingRequiredOptionError>(error);

Assert.Equal(string.Format(MyErrorMessageFormatProvider.MissingRequiredOptionError_OnlyShortOptionName, missingRequiredOptionError.ShortOptionName), missingRequiredOptionError.GetMessage());
}

[Fact]
public void MissingRequiredOptionError_OnlyLongOptionName()
{
var result = ParseArguments2(["-o", "a", "--opt3", "b"]);

Assert.Equal(ParseResultState.ParsedWithErrors, result.State);

Assert.Null(result.Options);

var errors = result.Errors;
Assert.NotNull(errors);

var error = Assert.Single(errors);
var missingRequiredOptionError = Assert.IsType<MissingRequiredOptionError>(error);

Assert.Equal(string.Format(MyErrorMessageFormatProvider.MissingRequiredOptionError_OnlyLongOptionName, missingRequiredOptionError.LongOptionName), missingRequiredOptionError.GetMessage());
}

[Fact]
public void MissingRequiredOptionError_BothOptionNames()
{
var result = ParseArguments2(["-o", "a", "--opt2", "b"]);

Assert.Equal(ParseResultState.ParsedWithErrors, result.State);

Assert.Null(result.Options);

var errors = result.Errors;
Assert.NotNull(errors);

var error = Assert.Single(errors);
var missingRequiredOptionError = Assert.IsType<MissingRequiredOptionError>(error);

Assert.Equal(string.Format(MyErrorMessageFormatProvider.MissingRequiredOptionError_BothOptionNames, missingRequiredOptionError.ShortOptionName, missingRequiredOptionError.LongOptionName), missingRequiredOptionError.GetMessage());
}

[Fact]
public void BadOptionValueFormatError()
{
var result = ParseArguments1(["--opt2", "a"]);

Assert.Equal(ParseResultState.ParsedWithErrors, result.State);

Assert.Null(result.Options);

var errors = result.Errors;
Assert.NotNull(errors);

var error = Assert.Single(errors);
var badOptionValueFormatError = Assert.IsType<BadOptionValueFormatError>(error);

Assert.Equal(string.Format(MyErrorMessageFormatProvider.BadOptionValueFormatError, badOptionValueFormatError.Value, badOptionValueFormatError.OptionName), badOptionValueFormatError.GetMessage());
}

[Fact]
public void FlagOptionValueError()
{
var result = ParseArguments1(["--flag", "true"]);

Assert.Equal(ParseResultState.ParsedWithErrors, result.State);

Assert.Null(result.Options);

var errors = result.Errors;
Assert.NotNull(errors);

var error = Assert.Single(errors);
var flagOptionValueError = Assert.IsType<FlagOptionValueError>(error);

Assert.Equal(string.Format(MyErrorMessageFormatProvider.FlagOptionValueError, flagOptionValueError.OptionName), flagOptionValueError.GetMessage());
}

[Fact]
public void BadParameterValueFormatError()
{
var result = ParseArguments3(["a"]);

Assert.Equal(ParseResultState.ParsedWithErrors, result.State);

Assert.Null(result.Options);

var errors = result.Errors;
Assert.NotNull(errors);

var error = Assert.Single(errors);
var badParameterValueFormatError = Assert.IsType<BadParameterValueFormatError>(error);

Assert.Equal(string.Format(MyErrorMessageFormatProvider.BadParameterValueFormatError, badParameterValueFormatError.Value, badParameterValueFormatError.ParameterName, badParameterValueFormatError.ParameterIndex), badParameterValueFormatError.GetMessage());
}

[Fact]
public void MissingRequiredParameterError()
{
var result = ParseArguments3([]);

Assert.Equal(ParseResultState.ParsedWithErrors, result.State);

Assert.Null(result.Options);

var errors = result.Errors;
Assert.NotNull(errors);

var error = Assert.Single(errors);
var missingRequiredParameterError = Assert.IsType<MissingRequiredParameterError>(error);

Assert.Equal(string.Format(MyErrorMessageFormatProvider.MissingRequiredParameterError, missingRequiredParameterError.ParameterName, missingRequiredParameterError.ParameterIndex), missingRequiredParameterError.GetMessage());
}

[Fact]
public void BadRemainingParameterValueFormatError()
{
var result = ParseArguments3(["2", "a"]);

Assert.Equal(ParseResultState.ParsedWithErrors, result.State);

Assert.Null(result.Options);

var errors = result.Errors;
Assert.NotNull(errors);

var error = Assert.Single(errors);
var badRemainingParameterValueFormatError = Assert.IsType<BadRemainingParameterValueFormatError>(error);

Assert.Equal(string.Format(MyErrorMessageFormatProvider.BadRemainingParameterValueFormatError, badRemainingParameterValueFormatError.Value, badRemainingParameterValueFormatError.ParameterIndex), badRemainingParameterValueFormatError.GetMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace ArgumentParsing.Tests.Functional.Utils;

public static class MyErrorMessageFormatProvider
{
public const string UnknownOptionError = "[Error]: Unknown option '{0}' in argument '{1}'";
public const string UnrecognizedArgumentError = "[Error]: Unrecognized argument '{0}'";
public const string OptionValueIsNotProvidedError = "[Error]: No option value is provided after argument '{0}'";
public const string DuplicateOptionError = "[Error]: Duplicate option '{0}'";
public const string MissingRequiredOptionError_OnlyShortOptionName = "[Error]: Missing required option with short name '{0}'";
public const string MissingRequiredOptionError_OnlyLongOptionName = "[Error]: Missing required option with long name '{0}'";
public const string MissingRequiredOptionError_BothOptionNames = "[Error]: Missing required option '{0}' ('{1}')";
public const string BadOptionValueFormatError = "[Error]: Value '{0}' is in incorrect format for option '{1}'";
public const string FlagOptionValueError = "[Error]: Flag option '{0}' does not accept a value";
public const string BadParameterValueFormatError = "[Error]: Value '{0}' is in incorrect format for parameter '{1}' (parameter index {2})";
public const string MissingRequiredParameterError = "[Error]: Missing required parameter '{0}' (parameter index {1})";
public const string BadRemainingParameterValueFormatError = "[Error]: Value '{0}' is in incorrect format for parameter at index {1}";
}
Loading

0 comments on commit c7da660

Please sign in to comment.