diff --git a/Directory.Build.props b/Directory.Build.props index f6ff596..b7ffbb3 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -9,7 +9,7 @@ Choice generator ALTA Software llc. Copyright © 2024 ALTA Software llc. - 1.3.3 + 1.3.4 diff --git a/src/AltaSoft.Choice.Generator/Executor.cs b/src/AltaSoft.Choice.Generator/Executor.cs index 06bafaa..354c734 100644 --- a/src/AltaSoft.Choice.Generator/Executor.cs +++ b/src/AltaSoft.Choice.Generator/Executor.cs @@ -151,10 +151,8 @@ private static SourceCodeBuilder Process(INamedTypeSymbol typeSymbol, List instance " + - $"and sets its value using the specified ."); + sb.AppendSummary($"Creates a new instance and sets its value using the specified {prop.TypeSymbol.GetCrefForType()}."); sb.AppendParamDescription("value", "The value to assign to the created choice instance."); sb.Append($"public static {typeFullName} CreateAs").Append(prop.Name).Append("(") @@ -231,8 +229,8 @@ private static bool ProcessImplicitOperators(SourceCodeBuilder sb, string typeNa foreach (var property in processedProperties) { - sb.AppendSummary($"Implicitly converts an to an ."); - sb.AppendParamDescription("value", $"The to convert."); + sb.AppendSummary($"Implicitly converts an {property.TypeSymbol.GetCrefForType()} to an ."); + sb.AppendParamDescription("value", $"The {property.TypeSymbol.GetCrefForType()} to convert."); sb.AppendBlock("returns", $" instance representing the code.").NewLine(); sb.AppendLine("[return: NotNullIfNotNull(parameterName: nameof(value))]"); diff --git a/src/AltaSoft.Choice.Generator/Extensions/CompilationExt.cs b/src/AltaSoft.Choice.Generator/Extensions/CompilationExt.cs index dd19b24..e263bbd 100644 --- a/src/AltaSoft.Choice.Generator/Extensions/CompilationExt.cs +++ b/src/AltaSoft.Choice.Generator/Extensions/CompilationExt.cs @@ -201,6 +201,26 @@ public static bool IsNullableValueType(this INamedTypeSymbol type, out ITypeSymb return false; } + /// + /// Builds a short XML documentation fragment that references the provided using a cref tag. + /// For array types the fragment references the element type and appends the word "array." to indicate the collection nature + /// (for example: <see cref="System.String"/> array.). For non-array types the fragment is simply the cref reference followed by a period. + /// + /// The Roslyn to create a cref for. This should not be null. + /// + /// A string containing an XML <see cref="System.Object" /> tag that references the type. Example results: + /// - For a type: "<see cref="Namespace.TypeName"/>." + /// - For an array: "<see cref="Namespace.ElementType"/> array." + /// + public static string GetCrefForType(this ITypeSymbol type) + { + if (type is IArrayTypeSymbol array) + { + return $" array"; + } + return $""; + } + /// /// A dictionary that provides aliases for common .NET framework types, mapping their full names to shorter aliases. /// diff --git a/src/AltaSoft.Choice.Generator/Helpers/Constants.cs b/src/AltaSoft.Choice.Generator/Helpers/Constants.cs index c9269bb..f9c491d 100644 --- a/src/AltaSoft.Choice.Generator/Helpers/Constants.cs +++ b/src/AltaSoft.Choice.Generator/Helpers/Constants.cs @@ -16,10 +16,4 @@ internal static class Constants /// This constant is used to identify the XmlTagAttribute type by its name. /// internal const string XmlTagAttributeFullName = "AltaSoft.Choice.XmlTagAttribute"; - - /// - /// The fully qualified name of the IXmlSerializable interface. - /// This constant is used to identify the IXmlSerializable type by its name. - /// - internal const string IXmlSerializableFullName = "IXmlSerializable"; } diff --git a/src/AltaSoft.Choice.Generator/Models/PropertyDetails.cs b/src/AltaSoft.Choice.Generator/Models/PropertyDetails.cs index d7e075d..1f42306 100644 --- a/src/AltaSoft.Choice.Generator/Models/PropertyDetails.cs +++ b/src/AltaSoft.Choice.Generator/Models/PropertyDetails.cs @@ -43,14 +43,14 @@ internal sealed class PropertyDetails /// /// The type symbol of the property. /// - internal ITypeSymbol TypeSymbol { get; private set; } + internal ITypeSymbol TypeSymbol { get; } /// /// returns if the type is dateOnly /// internal bool IsDateOnly() { - return TypeSymbol.IsValueType && TypeSymbol.GetFullName()?.Replace("?", "") == "System.DateOnly"; + return TypeSymbol.IsValueType && TypeSymbol.GetFullName().Replace("?", "") == "System.DateOnly"; } /// /// Initializes a new instance of the class. diff --git a/tests/AltaSoft.Choice.Generator.SnapshotTests/ChoiceGeneratorTest.cs b/tests/AltaSoft.Choice.Generator.SnapshotTests/ChoiceGeneratorTest.cs index d3c6916..7402982 100644 --- a/tests/AltaSoft.Choice.Generator.SnapshotTests/ChoiceGeneratorTest.cs +++ b/tests/AltaSoft.Choice.Generator.SnapshotTests/ChoiceGeneratorTest.cs @@ -64,6 +64,37 @@ public enum Authorisation1Code Assert.Single(x); }); } + [Fact] + public Task ChoiceTypeShouldGenerateDocumentationCorrectly_ForArrayInChoice() + { + const string source = + """ + using System; + using System.Xml; + using System.Xml.Schema; + using System.Xml.Serialization; + using AltaSoft.Choice; + + namespace TestNamespace + { + [Choice] + public sealed partial class ArrayInTypeChoice + { + public partial string? StringChoice { get; set; } + + public partial AccountId[]? Accounts { get; set; } + } + + public sealed record AccountId(int Id); + } + + """; + + return TestHelper.Verify(source, (_, x, _) => + { + Assert.Single(x); + }); + } [Fact] public Task ChoiceTypeShouldNotGenerateImplicitMethodsAndCompileCorrectly() @@ -120,7 +151,7 @@ public static class TestHelper internal static Task Verify(string source, Action, List, GeneratorDriver>? additionalChecks = null) { List assemblies = [typeof(XmlElementAttribute).Assembly, typeof(JsonSerializer).Assembly]; - var (diagnostics, output, driver) = TestHelpers.GetGeneratedOutput(source, assemblies); + var (diagnostics, output, driver) = TestHelpers.GetGeneratedOutput(source, assemblies); Assert.Empty(diagnostics.Where(x => x.Severity == DiagnosticSeverity.Error)); additionalChecks?.Invoke(diagnostics, output, driver); diff --git a/tests/AltaSoft.Choice.Generator.SnapshotTests/ModuleInitializer.cs b/tests/AltaSoft.Choice.Generator.SnapshotTests/ModuleInitializer.cs index addf7ce..25b9830 100644 --- a/tests/AltaSoft.Choice.Generator.SnapshotTests/ModuleInitializer.cs +++ b/tests/AltaSoft.Choice.Generator.SnapshotTests/ModuleInitializer.cs @@ -1,4 +1,5 @@ using System.Runtime.CompilerServices; +using DiffEngine; using VerifyTests; namespace AltaSoft.Choice.Generator.SnapshotTests; @@ -8,6 +9,7 @@ public static class ModuleInitializer [ModuleInitializer] public static void Init() { + DiffTools.UseOrder(DiffTool.VisualStudioCode, DiffTool.VisualStudio, DiffTool.AraxisMerge, DiffTool.BeyondCompare); VerifySourceGenerators.Initialize(); } } diff --git a/tests/AltaSoft.Choice.Generator.SnapshotTests/Snapshots/ChoiceGeneratorTest.ChoiceTypeShouldGenerateDocumentationCorrectly_ForArrayInChoice#ArrayInTypeChoice.g.verified.cs b/tests/AltaSoft.Choice.Generator.SnapshotTests/Snapshots/ChoiceGeneratorTest.ChoiceTypeShouldGenerateDocumentationCorrectly_ForArrayInChoice#ArrayInTypeChoice.g.verified.cs new file mode 100644 index 0000000..654e6f1 --- /dev/null +++ b/tests/AltaSoft.Choice.Generator.SnapshotTests/Snapshots/ChoiceGeneratorTest.ChoiceTypeShouldGenerateDocumentationCorrectly_ForArrayInChoice#ArrayInTypeChoice.g.verified.cs @@ -0,0 +1,170 @@ +//HintName: ArrayInTypeChoice.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by 'AltaSoft Choice.Generator'. +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. +// +//------------------------------------------------------------------------------ + +#nullable enable + +using TestNamespace; +using AltaSoft.Choice; +using System; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Xml; +using System.Xml.Serialization; +using System.Xml.Schema; + +namespace TestNamespace; + +#pragma warning disable CS8774 // Member must have a non-null value when exiting. +#pragma warning disable CS0628 // New protected member declared in sealed type + +public sealed partial class ArrayInTypeChoice +{ + /// + /// Constructor for Serialization/Deserialization + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + public ArrayInTypeChoice() + { + } + + /// + /// Choice enum + /// + [JsonIgnore] + [XmlIgnore] + [ChoiceTypeProperty] + public ChoiceOf ChoiceType { get; private set; } + + private string? _stringChoice; + + [DisallowNull] + [XmlElement("StringChoice")] + [ChoiceProperty] + public partial string? StringChoice + { + get => _stringChoice; + set + { + _stringChoice = value ?? throw new InvalidOperationException("Choice value cannot be null"); + _accounts = null; + ChoiceType = ChoiceOf.StringChoice; + } + } + + private TestNamespace.AccountId[]? _accounts; + + [DisallowNull] + [XmlElement("Accounts")] + [ChoiceProperty] + public partial TestNamespace.AccountId[]? Accounts + { + get => _accounts; + set + { + _accounts = value ?? throw new InvalidOperationException("Choice value cannot be null"); + _stringChoice = null; + ChoiceType = ChoiceOf.Accounts; + } + } + + + /// + /// Creates a new instance and sets its value using the specified . + /// + /// The value to assign to the created choice instance. + public static TestNamespace.ArrayInTypeChoice CreateAsStringChoice(string value) => new () { StringChoice = value }; + + /// + /// Creates a new instance and sets its value using the specified array. + /// + /// The value to assign to the created choice instance. + public static TestNamespace.ArrayInTypeChoice CreateAsAccounts(TestNamespace.AccountId[] value) => new () { Accounts = value }; + + /// + /// Applies the appropriate function based on the current choice type + /// + /// The return type of the provided match functions + /// Function to invoke if the choice is a value + /// Function to invoke if the choice is a value + public TResult Match( + Func matchStringChoice, + Func matchAccounts) + { + return ChoiceType switch + { + ChoiceOf.StringChoice => matchStringChoice(StringChoice!), + ChoiceOf.Accounts => matchAccounts(Accounts!), + _ => throw new InvalidOperationException($"Invalid ChoiceType. '{ChoiceType}'") + }; + } + + /// + /// Applies the appropriate Action based on the current choice type + /// + /// Action to invoke if the choice is a value + /// Action to invoke if the choice is a value + public void Switch( + Action matchStringChoice, + Action matchAccounts) + { + switch (ChoiceType) + { + case ChoiceOf.StringChoice: + matchStringChoice(StringChoice!); + return; + + case ChoiceOf.Accounts: + matchAccounts(Accounts!); + return; + + default: + throw new XmlException($"Invalid ChoiceType. '{ChoiceType}'"); + } + } + + /// + /// Implicitly converts an to an . + /// + /// The to convert. + /// + /// instance representing the code. + /// + + [return: NotNullIfNotNull(parameterName: nameof(value))] + public static implicit operator ArrayInTypeChoice? (string? value) + { + return value is null ? null : CreateAsStringChoice(value); + } + + /// + /// Implicitly converts an array to an . + /// + /// The array to convert. + /// + /// instance representing the code. + /// + + [return: NotNullIfNotNull(parameterName: nameof(value))] + public static implicit operator ArrayInTypeChoice? (TestNamespace.AccountId[]? value) + { + return value is null ? null : CreateAsAccounts(value); + } + + /// + /// Choice enumeration + /// + [XmlType("ChoiceOf.ArrayInTypeChoice")] + public enum ChoiceOf + { + StringChoice, + Accounts, + } +} diff --git a/tests/AltaSoft.ChoiceGenerator.Tests/ArrayInTypeChoice.cs b/tests/AltaSoft.ChoiceGenerator.Tests/ArrayInTypeChoice.cs new file mode 100644 index 0000000..dd0f168 --- /dev/null +++ b/tests/AltaSoft.ChoiceGenerator.Tests/ArrayInTypeChoice.cs @@ -0,0 +1,13 @@ +using AltaSoft.Choice; + +namespace AltaSoft.ChoiceGenerator.Tests; + +[Choice] +public sealed partial class ArrayInTypeChoice +{ + public partial string? StringChoice { get; set; } + + public partial AccountId[]? Accounts { get; set; } +} + +public sealed record AccountId(int Id);