From 91cc7fc92f2db7b8d28062874eb929bf406e3185 Mon Sep 17 00:00:00 2001 From: Steve Dunn Date: Sun, 1 Dec 2024 18:13:15 +0000 Subject: [PATCH 1/4] First pass --- src/Vogen/GenerateCodeForBsonSerializers.cs | 6 ++-- ...CodeForSystemTextJsonConverterFactories.cs | 7 +++-- src/Vogen/Types/ProjectName.cs | 12 ++++++-- src/Vogen/Types/SanitizedAssemblyName.cs | 19 ------------ tests/Shared/ProjectBuilder.cs | 12 ++++++-- ...17_Stj_does_not_escape_dashed_namespace.cs | 30 +++++++++++++++++++ ..._escape_dashed_namespace.Test.verified.txt | 1 + tests/SnapshotTests/SnapshotRunner.cs | 15 ++++++++-- tests/Testbench/Bug717StjNamespace.cs | 7 +++++ tests/Vogen.Tests/ProjectNameTests.cs | 10 ++++--- .../Vogen.Tests/SanitizedAssemblyNameTests.cs | 20 ------------- 11 files changed, 82 insertions(+), 57 deletions(-) delete mode 100644 src/Vogen/Types/SanitizedAssemblyName.cs create mode 100644 tests/SnapshotTests/BugFixes/Bug717_Stj_does_not_escape_dashed_namespace.cs create mode 100644 tests/SnapshotTests/BugFixes/snapshots/snap-v9.0/Bug717_Stj_does_not_escape_dashed_namespace.Test.verified.txt create mode 100644 tests/Testbench/Bug717StjNamespace.cs delete mode 100644 tests/Vogen.Tests/SanitizedAssemblyNameTests.cs diff --git a/src/Vogen/GenerateCodeForBsonSerializers.cs b/src/Vogen/GenerateCodeForBsonSerializers.cs index dc60232dce..9d99fea315 100644 --- a/src/Vogen/GenerateCodeForBsonSerializers.cs +++ b/src/Vogen/GenerateCodeForBsonSerializers.cs @@ -169,12 +169,12 @@ public static void TryRegister() { } string ClassNameForRegistering() { - var assemblyName = new SanitizedAssemblyName(compilation.AssemblyName); + string projectName = ProjectName.FromAssemblyName(compilation.AssemblyName ?? "").Value; string s = "BsonSerializationRegister"; - if(assemblyName.Value.Length > 0) + if(projectName.Length > 0) { - s = $"{s}For{assemblyName}"; + s = $"{s}For{projectName}"; } return s; diff --git a/src/Vogen/GenerateCodeForSystemTextJsonConverterFactories.cs b/src/Vogen/GenerateCodeForSystemTextJsonConverterFactories.cs index f13fc2cbbb..6a2b598e96 100644 --- a/src/Vogen/GenerateCodeForSystemTextJsonConverterFactories.cs +++ b/src/Vogen/GenerateCodeForSystemTextJsonConverterFactories.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; +using Vogen.Types; namespace Vogen; @@ -27,10 +28,10 @@ public static void WriteIfNeeded(VogenConfiguration? globalConfig, var entries = workItems.Where(i => i.Config.Conversions.HasFlag(Conversions.SystemTextJson)).Select(BuildEntry); + + var fullNamespace = ProjectName.FromAssemblyName(compilation.Assembly.Name); - var fullNamespace = compilation.Assembly.Name; - - var ns = string.IsNullOrEmpty(fullNamespace) ? string.Empty : $"namespace {fullNamespace};"; + var ns = $"namespace {fullNamespace};"; string source = $$""" diff --git a/src/Vogen/Types/ProjectName.cs b/src/Vogen/Types/ProjectName.cs index 33a48cf9ae..d506309c70 100644 --- a/src/Vogen/Types/ProjectName.cs +++ b/src/Vogen/Types/ProjectName.cs @@ -4,11 +4,17 @@ internal class ProjectName { private ProjectName(string value) => Value = value; + /// + /// Replaces [., -] with [_] for use as type type names etc. + /// + /// + /// public static ProjectName FromAssemblyName(string assemblyName) { - assemblyName = assemblyName.Replace(".", ""); - assemblyName = assemblyName.Replace(",", ""); - assemblyName = assemblyName.Replace(" ", ""); + assemblyName = assemblyName.Replace(".", "_"); + assemblyName = assemblyName.Replace(",", "_"); + assemblyName = assemblyName.Replace(" ", "_"); + assemblyName = assemblyName.Replace("-", "_"); return new(assemblyName); } diff --git a/src/Vogen/Types/SanitizedAssemblyName.cs b/src/Vogen/Types/SanitizedAssemblyName.cs deleted file mode 100644 index d7b1220e0e..0000000000 --- a/src/Vogen/Types/SanitizedAssemblyName.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Vogen.Types; - -internal class SanitizedAssemblyName -{ - public SanitizedAssemblyName(string? unsanitized) - { - var assemblyName = unsanitized?.Replace(".", "_") ?? ""; - if (assemblyName.EndsWith("_dll") || assemblyName.EndsWith("_exe")) - { - assemblyName = assemblyName[..^4]; - } - - Value = assemblyName; - } - - public string Value { get; set; } - public static implicit operator string(SanitizedAssemblyName name) => name.Value; - public override string ToString() => Value; -} \ No newline at end of file diff --git a/tests/Shared/ProjectBuilder.cs b/tests/Shared/ProjectBuilder.cs index 8094e53b4e..c0ad834e33 100644 --- a/tests/Shared/ProjectBuilder.cs +++ b/tests/Shared/ProjectBuilder.cs @@ -44,6 +44,7 @@ public sealed class ProjectBuilder private TargetFramework? _targetFramework; private bool _excludeStj; private LanguageVersion _languageVersion = LanguageVersion.Default; + private string _assemblyName = "generator"; public ProjectBuilder WithTargetFramework(TargetFramework targetFramework) { @@ -273,6 +274,13 @@ public ProjectBuilder WithUserSource(string userSource) return this; } + public ProjectBuilder WithAssemblyName(string assemblyName) + { + _assemblyName = assemblyName; + + return this; + } + public ProjectBuilder WithNugetPackages(IEnumerable packages) { foreach (var nuGetPackage in packages) @@ -322,8 +330,8 @@ internal static class IsExternalInit {} options = options.WithSpecificDiagnosticOptions(diagnostics); var compilation = CSharpCompilation.Create( - assemblyName: "generator", - syntaxTrees: new[] { usersSyntaxTree, isExternalInitSyntaxTree }, + assemblyName: _assemblyName, + syntaxTrees: [usersSyntaxTree, isExternalInitSyntaxTree], _references, options); diff --git a/tests/SnapshotTests/BugFixes/Bug717_Stj_does_not_escape_dashed_namespace.cs b/tests/SnapshotTests/BugFixes/Bug717_Stj_does_not_escape_dashed_namespace.cs new file mode 100644 index 0000000000..4d4042c083 --- /dev/null +++ b/tests/SnapshotTests/BugFixes/Bug717_Stj_does_not_escape_dashed_namespace.cs @@ -0,0 +1,30 @@ +using System.Threading.Tasks; +using Vogen; + +namespace SnapshotTests.BugFixes; + +// See https://github.com/SteveDunn/Vogen/issues/717 +public class Bug717_Stj_does_not_escape_dashed_namespace +{ + [Fact] + public async Task Test() + { + var source = """ + + using System; + using Vogen; + using System.Text.Json; + + namespace My_Namespace; + + [ValueObject(typeof(Guid))] + public partial struct Vo; + """; + + await new SnapshotRunner() + .WithAssemblyName("MY-PROJECT") + .WithSource(source) + .IgnoreInitialCompilationErrors() + .RunOnAllFrameworks(); + } +} \ No newline at end of file diff --git a/tests/SnapshotTests/BugFixes/snapshots/snap-v9.0/Bug717_Stj_does_not_escape_dashed_namespace.Test.verified.txt b/tests/SnapshotTests/BugFixes/snapshots/snap-v9.0/Bug717_Stj_does_not_escape_dashed_namespace.Test.verified.txt new file mode 100644 index 0000000000..5f282702bb --- /dev/null +++ b/tests/SnapshotTests/BugFixes/snapshots/snap-v9.0/Bug717_Stj_does_not_escape_dashed_namespace.Test.verified.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/SnapshotTests/SnapshotRunner.cs b/tests/SnapshotTests/SnapshotRunner.cs index c6d756816d..27ecf68fca 100644 --- a/tests/SnapshotTests/SnapshotRunner.cs +++ b/tests/SnapshotTests/SnapshotRunner.cs @@ -51,6 +51,7 @@ public SnapshotRunner WithLocale(string locale) private readonly List _additionalNuGetPackages = new(); private LanguageVersion _languageVersion = LanguageVersion.Default; private bool _excludeStj; + private string _assemblyName = "generator"; public async Task RunOnAllFrameworks() => await RunOn(_allFrameworks); @@ -62,6 +63,12 @@ public SnapshotRunner WithSource(string source) return this; } + public SnapshotRunner WithAssemblyName(string assemblyName) + { + _assemblyName = assemblyName; + return this; + } + public SnapshotRunner IgnoreInitialCompilationErrors() { _ignoreInitialCompilationErrors = true; @@ -95,7 +102,7 @@ public async Task RunOn(params TargetFramework[] frameworks) using var scope = new AssertionScope(); - (ImmutableArray diagnostics, SyntaxTree[] syntaxTrees) = await GetGeneratedOutput(_source, eachFramework); + (ImmutableArray diagnostics, SyntaxTree[] syntaxTrees) = await GetGeneratedOutput(eachFramework); diagnostics.Should().BeEmpty(@$"because the following source code should compile on {eachFramework}: " + Environment.NewLine + _source + Environment.NewLine); var outputFolder = Path.Combine(_path, SnapshotUtils.GetSnapshotDirectoryName(eachFramework, _locale)); @@ -111,12 +118,14 @@ public async Task RunOn(params TargetFramework[] frameworks) } } - private async Task<(ImmutableArray Diagnostics, SyntaxTree[] GeneratedSource)> GetGeneratedOutput(string source, TargetFramework targetFramework) + private async Task<(ImmutableArray Diagnostics, SyntaxTree[] GeneratedSource)> GetGeneratedOutput( + TargetFramework targetFramework) { var r = MetadataReference.CreateFromFile(typeof(ValueObjectAttribute).Assembly.Location); var results = await new ProjectBuilder() - .WithUserSource(source) + .WithAssemblyName(_assemblyName) + .WithUserSource(_source!) .WithNugetPackages(_additionalNuGetPackages) .WithTargetFramework(targetFramework) .WithLanguageVersion(_languageVersion) diff --git a/tests/Testbench/Bug717StjNamespace.cs b/tests/Testbench/Bug717StjNamespace.cs new file mode 100644 index 0000000000..e2225c6fbc --- /dev/null +++ b/tests/Testbench/Bug717StjNamespace.cs @@ -0,0 +1,7 @@ +using Vogen; +// ReSharper disable UnusedVariable + +namespace My_Namespace; + +[ValueObject(Conversions.SystemTextJson)] +public partial struct StjVo; diff --git a/tests/Vogen.Tests/ProjectNameTests.cs b/tests/Vogen.Tests/ProjectNameTests.cs index d8b4bb16f3..7cb00dfdb1 100644 --- a/tests/Vogen.Tests/ProjectNameTests.cs +++ b/tests/Vogen.Tests/ProjectNameTests.cs @@ -7,10 +7,12 @@ namespace Vogen.Tests; public class ProjectNameTests { [Theory] - [InlineData("MyAssembly.Core", "MyAssemblyCore")] - [InlineData("MyAssembly . Core", "MyAssemblyCore")] - [InlineData("My._Projéct_Oh.Yes_It.Is1plɹo_MollǝH", "My_Projéct_OhYes_ItIs1plɹo_MollǝH")] - public void Removes_spaces_commas_and_dots_with_with_underscores(string input, string output) => + [InlineData("MyAssembly.Core", "MyAssembly_Core")] + [InlineData("MyAssembly . Core", "MyAssembly___Core")] + [InlineData("CRM-API", "CRM_API")] + [InlineData("My._Projéct_Oh.Yes_It.Is1plɹo_MollǝH", "My__Projéct_Oh_Yes_It_Is1plɹo_MollǝH")] + [InlineData("", "")] + public void Treats_spaces_hyphens_and_dots_with_with_underscores(string input, string output) => ProjectName.FromAssemblyName(input).Value.Should().Be(output); [Fact] diff --git a/tests/Vogen.Tests/SanitizedAssemblyNameTests.cs b/tests/Vogen.Tests/SanitizedAssemblyNameTests.cs deleted file mode 100644 index 4d1b56a1bd..0000000000 --- a/tests/Vogen.Tests/SanitizedAssemblyNameTests.cs +++ /dev/null @@ -1,20 +0,0 @@ -using FluentAssertions; -using Vogen.Types; -using Xunit; - -namespace Vogen.Tests; - -public class SanitizedAssemblyNameTests -{ - [Fact] - public void Replaces_dots_with_underscores() => new SanitizedAssemblyName(".").Value.Should().Be("_"); - - [Fact] - public void Strips_dll_from_end() => new SanitizedAssemblyName("foo.dll").Value.Should().Be("foo"); - - [Fact] - public void Strips_exe_from_end() => new SanitizedAssemblyName("foo.exe").Value.Should().Be("foo"); - - [Fact] - public void Treats_null_as_empty() => new SanitizedAssemblyName(null).Value.Should().Be(string.Empty); -} \ No newline at end of file From 0caef78b01617ee2fc92d3996209f979410aca80 Mon Sep 17 00:00:00 2001 From: Steve Dunn Date: Sun, 1 Dec 2024 18:16:34 +0000 Subject: [PATCH 2/4] Fix consumer project --- samples/WebApplication/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/WebApplication/Program.cs b/samples/WebApplication/Program.cs index 021127b824..0d4688d5e2 100644 --- a/samples/WebApplication/Program.cs +++ b/samples/WebApplication/Program.cs @@ -26,7 +26,7 @@ // the following extension method is available if you specify `GenerateSwashbuckleMappingExtensionMethod` - as shown above opt.MapVogenTypesInWebApplication(); - opt.MapVogenTypesInWebApplicationShared(); + opt.MapVogenTypesInWebApplication_Shared(); // the following schema filter is generated if you specify GenerateSwashbuckleSchemaFilter as shown above // opt.SchemaFilter(); From 79ac109eb2a45edabd79a4eb56ac288cc7fe7fa1 Mon Sep 17 00:00:00 2001 From: Steve Dunn Date: Mon, 2 Dec 2024 05:20:09 +0000 Subject: [PATCH 3/4] Update snapshots --- ..._escape_dashed_namespace.Test.verified.txt | 424 ++++++++++++++ ..._escape_dashed_namespace.Test.verified.txt | 542 +++++++++++++++++ ..._escape_dashed_namespace.Test.verified.txt | 543 +++++++++++++++++- 3 files changed, 1508 insertions(+), 1 deletion(-) create mode 100644 tests/SnapshotTests/BugFixes/snapshots/snap-v4.8/Bug717_Stj_does_not_escape_dashed_namespace.Test.verified.txt create mode 100644 tests/SnapshotTests/BugFixes/snapshots/snap-v8.0/Bug717_Stj_does_not_escape_dashed_namespace.Test.verified.txt diff --git a/tests/SnapshotTests/BugFixes/snapshots/snap-v4.8/Bug717_Stj_does_not_escape_dashed_namespace.Test.verified.txt b/tests/SnapshotTests/BugFixes/snapshots/snap-v4.8/Bug717_Stj_does_not_escape_dashed_namespace.Test.verified.txt new file mode 100644 index 0000000000..085747de3a --- /dev/null +++ b/tests/SnapshotTests/BugFixes/snapshots/snap-v4.8/Bug717_Stj_does_not_escape_dashed_namespace.Test.verified.txt @@ -0,0 +1,424 @@ +[ +// ------------------------------------------------------------------------------ +// +// This code was generated by a source generator named Vogen (https://github.com/SteveDunn/Vogen) +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0618 +// Suppress warnings for 'Override methods on comparable types'. +#pragma warning disable CA1036 +// Suppress Error MA0097 : A class that implements IComparable or IComparable should override comparison operators +#pragma warning disable MA0097 +// Suppress warning for 'The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.' +// The generator copies signatures from the BCL, e.g. for `TryParse`, and some of those have nullable annotations. +#pragma warning disable CS8669, CS8632 +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 +namespace MY_PROJECT; +public class VogenTypesFactory : global::System.Text.Json.Serialization.JsonConverterFactory +{ + public VogenTypesFactory() + { + } + + private static readonly global::System.Collections.Generic.Dictionary> _lookup = new global::System.Collections.Generic.Dictionary> + { + { + typeof(global::My_Namespace.Vo), + new global::System.Lazy(() => new global::My_Namespace.Vo.VoSystemTextJsonConverter()) + } + }; + public override bool CanConvert(global::System.Type typeToConvert) => _lookup.ContainsKey(typeToConvert); + public override global::System.Text.Json.Serialization.JsonConverter CreateConverter(global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) => _lookup[typeToConvert].Value; +} + +// ------------------------------------------------------------------------------ +// +// This code was generated by a source generator named Vogen (https://github.com/SteveDunn/Vogen) +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0618 +// Suppress warnings for 'Override methods on comparable types'. +#pragma warning disable CA1036 +// Suppress Error MA0097 : A class that implements IComparable or IComparable should override comparison operators +#pragma warning disable MA0097 +// Suppress warning for 'The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.' +// The generator copies signatures from the BCL, e.g. for `TryParse`, and some of those have nullable annotations. +#pragma warning disable CS8669, CS8632 +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 +namespace My_Namespace +{ + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Vogen", "1.0.0.0")] + [global::System.Text.Json.Serialization.JsonConverter(typeof(VoSystemTextJsonConverter))] + [global::System.ComponentModel.TypeConverter(typeof(VoTypeConverter))] + [global::System.Diagnostics.DebuggerTypeProxyAttribute(typeof(VoDebugView))] + [global::System.Diagnostics.DebuggerDisplayAttribute("Underlying type: System.Guid, Value = { _value }")] + // ReSharper disable once UnusedType.Global + public partial struct Vo : global::System.IEquatable, global::System.IEquatable, global::System.IComparable, global::System.IComparable, global::System.IFormattable + { +#if DEBUG +private readonly global::System.Diagnostics.StackTrace _stackTrace = null!; +#endif +#if !VOGEN_NO_VALIDATION + private readonly global::System.Boolean _isInitialized; +#endif + private readonly System.Guid _value; + /// + /// Gets the underlying value if set, otherwise a is thrown. + /// + public readonly System.Guid Value + { + [global::System.Diagnostics.DebuggerStepThroughAttribute] + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + get + { + EnsureInitialized(); + return _value; + } + } + + [global::System.Diagnostics.DebuggerStepThroughAttribute] + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public Vo() + { +#if DEBUG + _stackTrace = new global::System.Diagnostics.StackTrace(); +#endif +#if !VOGEN_NO_VALIDATION + _isInitialized = false; +#endif + _value = default; + } + + [global::System.Diagnostics.DebuggerStepThroughAttribute] + private Vo(System.Guid value) + { + _value = value; +#if !VOGEN_NO_VALIDATION + _isInitialized = true; +#endif + } + + /// + /// Builds an instance from the provided underlying type. + /// + /// The underlying type. + /// An instance of this type. + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public static Vo From(System.Guid value) + { + return new Vo(value); + } + + /// + /// Tries to build an instance from the provided underlying type. + /// If a normalization method is provided, it will be called. + /// If validation is provided, and it fails, false will be returned. + /// + /// The underlying type. + /// An instance of the value object. + /// True if the value object can be built, otherwise false. + +#pragma warning disable CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member because of nullability attributes. + + public static bool TryFrom( +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + System.Guid value, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] +#endif + out Vo vo) +#pragma warning restore CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member because of nullability attributes. + + { + vo = new Vo(value); + return true; + } + + /// + /// Tries to build an instance from the provided underlying value. + /// If a normalization method is provided, it will be called. + /// If validation is provided, and it fails, an error will be returned. + /// + /// The primitive value. + /// A containing either the value object, or an error. + public static Vogen.ValueObjectOrError TryFrom(System.Guid value) + { + return new Vogen.ValueObjectOrError(new Vo(value)); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] +#if VOGEN_NO_VALIDATION +#pragma warning disable CS8775 + public readonly bool IsInitialized() => true; +#pragma warning restore CS8775 +#else + public readonly bool IsInitialized() => _isInitialized; +#endif + public static explicit operator Vo(System.Guid value) => From(value); + public static explicit operator System.Guid(Vo value) => value.Value; + // only called internally when something has been deserialized into + // its primitive type. + private static Vo __Deserialize(System.Guid value) + { + return new Vo(value); + } + + public readonly global::System.Boolean Equals(Vo other) + { + // It's possible to create uninitialized instances via converters such as EfCore (HasDefaultValue), which call Equals. + // We treat anything uninitialized as not equal to anything, even other uninitialized instances of this type. + if (!IsInitialized() || !other.IsInitialized()) + return false; + return global::System.Collections.Generic.EqualityComparer.Default.Equals(Value, other.Value); + } + + public global::System.Boolean Equals(Vo other, global::System.Collections.Generic.IEqualityComparer comparer) + { + return comparer.Equals(this, other); + } + + public readonly global::System.Boolean Equals(System.Guid primitive) + { + return Value.Equals(primitive); + } + + public readonly override global::System.Boolean Equals(global::System.Object obj) + { + return obj is Vo && Equals((Vo)obj); + } + + public static global::System.Boolean operator ==(Vo left, Vo right) => left.Equals(right); + public static global::System.Boolean operator !=(Vo left, Vo right) => !(left == right); + public static global::System.Boolean operator ==(Vo left, System.Guid right) => left.Value.Equals(right); + public static global::System.Boolean operator ==(System.Guid left, Vo right) => right.Value.Equals(left); + public static global::System.Boolean operator !=(System.Guid left, Vo right) => !(left == right); + public static global::System.Boolean operator !=(Vo left, System.Guid right) => !(left == right); + public int CompareTo(Vo other) => Value.CompareTo(other.Value); + public int CompareTo(object other) + { + if (other is null) + return 1; + if (other is Vo x) + return CompareTo(x); + ThrowHelper.ThrowArgumentException("Cannot compare to object as it is not of type Vo", nameof(other)); + return 0; + } + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(string input, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out Vo result) + { + if (System.Guid.TryParse(input, out var __v)) + { + result = new Vo(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// The value created by calling the Parse method on the primitive. + /// + /// Thrown when the value can be parsed, but is not valid. + public static Vo Parse(string input) + { + var r = System.Guid.Parse(input); + return From(r); + } + +#nullable disable + /// + [System.Security.SecuritySafeCriticalAttribute] + public string ToString(string format, global::System.IFormatProvider provider) + { + return IsInitialized() ? Value.ToString(format, provider) : "[UNINITIALIZED]"; + } + +#nullable restore + public readonly override global::System.Int32 GetHashCode() + { + return global::System.Collections.Generic.EqualityComparer.Default.GetHashCode(Value); + } + + /// + public override global::System.String ToString() => IsInitialized() ? Value.ToString() ?? "" : "[UNINITIALIZED]"; + /// + public global::System.String ToString(string format) => IsInitialized() ? Value.ToString(format) ?? "" : "[UNINITIALIZED]"; + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + private readonly void EnsureInitialized() + { + if (!IsInitialized()) + { +#if DEBUG + ThrowHelper.ThrowWhenNotInitialized(_stackTrace); +#else + ThrowHelper.ThrowWhenNotInitialized(); +#endif + } + } + +#nullable disable + /// + /// Converts a Vo to or from JSON. + /// + public class VoSystemTextJsonConverter : global::System.Text.Json.Serialization.JsonConverter + { + public override Vo Read(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) + { + return Vo.__Deserialize(reader.GetGuid()); + } + + public override void Write(System.Text.Json.Utf8JsonWriter writer, Vo value, global::System.Text.Json.JsonSerializerOptions options) + { + writer.WriteStringValue(value.Value); + } +#if NET6_0_OR_GREATER // we can't call Read or use GetGuid from JsonReader as it expects a token type of string, but here we have have 'propertyname'. + + public override Vo ReadAsPropertyName(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) + { + if (global::System.Guid.TryParse(reader.GetString(), out global::System.Guid g)) + { + return Vo.__Deserialize(g); + } + + throw new global::System.Text.Json.JsonException("Unable to parse the GUID for an instance of Vo"); + } + + public override void WriteAsPropertyName(System.Text.Json.Utf8JsonWriter writer, Vo value, global::System.Text.Json.JsonSerializerOptions options) + { + writer.WritePropertyName(value.Value.ToString()); + } +#endif + } + +#nullable restore +#nullable disable + class VoTypeConverter : global::System.ComponentModel.TypeConverter + { + public override global::System.Boolean CanConvertFrom(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Type sourceType) + { + return sourceType == typeof(global::System.Guid) || sourceType == typeof(global::System.String) || base.CanConvertFrom(context, sourceType); + } + + public override global::System.Object ConvertFrom(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Globalization.CultureInfo culture, global::System.Object value) + { + return value switch + { + global::System.Guid guidValue => Vo.__Deserialize(guidValue), + global::System.String stringValue when !global::System.String.IsNullOrEmpty(stringValue) && global::System.Guid.TryParse(stringValue, out var result) => Vo.__Deserialize(result), + _ => base.ConvertFrom(context, culture, value), + }; + } + + public override bool CanConvertTo(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Type sourceType) + { + return sourceType == typeof(global::System.Guid) || sourceType == typeof(global::System.String) || base.CanConvertTo(context, sourceType); + } + + public override object ConvertTo(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Globalization.CultureInfo culture, global::System.Object value, global::System.Type destinationType) + { + if (value is Vo idValue) + { + if (destinationType == typeof(global::System.Guid)) + { + return idValue.Value; + } + + if (destinationType == typeof(global::System.String)) + { + return idValue.Value.ToString(); + } + } + + return base.ConvertTo(context, culture, value, destinationType); + } + } + +#nullable restore +#nullable disable + internal sealed class VoDebugView + { + private readonly Vo _t; + VoDebugView(Vo t) + { + _t = t; + } + + public global::System.Boolean IsInitialized => _t.IsInitialized(); + public global::System.String UnderlyingType => "System.Guid"; + public global::System.String Value => _t.IsInitialized() ? _t._value.ToString() : "[not initialized]"; +#if DEBUG + public global::System.String CreatedWith => _t._stackTrace.ToString() ?? "the From method"; +#endif + public global::System.String Conversions => @"Default"; + } + +#nullable restore + static class ThrowHelper + { +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowInvalidOperationException(string message) => throw new global::System.InvalidOperationException(message); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowArgumentException(string message, string arg) => throw new global::System.ArgumentException(message, arg); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenCreatedWithNull() => throw new global::Vogen.ValueObjectValidationException("Cannot create a value object with null."); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenNotInitialized() => throw new global::Vogen.ValueObjectValidationException("Use of uninitialized Value Object."); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenNotInitialized(global::System.Diagnostics.StackTrace stackTrace) => throw new global::Vogen.ValueObjectValidationException("Use of uninitialized Value Object at: " + stackTrace ?? ""); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenValidationFails(Vogen.Validation validation) + { + var ex = new global::Vogen.ValueObjectValidationException(validation.ErrorMessage); + if (validation.Data is not null) + { + foreach (var kvp in validation.Data) + { + ex.Data[kvp.Key] = kvp.Value; + } + } + + throw ex; + } + } + } +} +] \ No newline at end of file diff --git a/tests/SnapshotTests/BugFixes/snapshots/snap-v8.0/Bug717_Stj_does_not_escape_dashed_namespace.Test.verified.txt b/tests/SnapshotTests/BugFixes/snapshots/snap-v8.0/Bug717_Stj_does_not_escape_dashed_namespace.Test.verified.txt new file mode 100644 index 0000000000..09d69f46d0 --- /dev/null +++ b/tests/SnapshotTests/BugFixes/snapshots/snap-v8.0/Bug717_Stj_does_not_escape_dashed_namespace.Test.verified.txt @@ -0,0 +1,542 @@ +[ +// ------------------------------------------------------------------------------ +// +// This code was generated by a source generator named Vogen (https://github.com/SteveDunn/Vogen) +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0618 +// Suppress warnings for 'Override methods on comparable types'. +#pragma warning disable CA1036 +// Suppress Error MA0097 : A class that implements IComparable or IComparable should override comparison operators +#pragma warning disable MA0097 +// Suppress warning for 'The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.' +// The generator copies signatures from the BCL, e.g. for `TryParse`, and some of those have nullable annotations. +#pragma warning disable CS8669, CS8632 +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 +namespace MY_PROJECT; +public class VogenTypesFactory : global::System.Text.Json.Serialization.JsonConverterFactory +{ + public VogenTypesFactory() + { + } + + private static readonly global::System.Collections.Generic.Dictionary> _lookup = new global::System.Collections.Generic.Dictionary> + { + { + typeof(global::My_Namespace.Vo), + new global::System.Lazy(() => new global::My_Namespace.Vo.VoSystemTextJsonConverter()) + } + }; + public override bool CanConvert(global::System.Type typeToConvert) => _lookup.ContainsKey(typeToConvert); + public override global::System.Text.Json.Serialization.JsonConverter CreateConverter(global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) => _lookup[typeToConvert].Value; +} + +// ------------------------------------------------------------------------------ +// +// This code was generated by a source generator named Vogen (https://github.com/SteveDunn/Vogen) +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0618 +// Suppress warnings for 'Override methods on comparable types'. +#pragma warning disable CA1036 +// Suppress Error MA0097 : A class that implements IComparable or IComparable should override comparison operators +#pragma warning disable MA0097 +// Suppress warning for 'The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.' +// The generator copies signatures from the BCL, e.g. for `TryParse`, and some of those have nullable annotations. +#pragma warning disable CS8669, CS8632 +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 +namespace My_Namespace +{ + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Vogen", "1.0.0.0")] + [global::System.Text.Json.Serialization.JsonConverter(typeof(VoSystemTextJsonConverter))] + [global::System.ComponentModel.TypeConverter(typeof(VoTypeConverter))] + [global::System.Diagnostics.DebuggerTypeProxyAttribute(typeof(VoDebugView))] + [global::System.Diagnostics.DebuggerDisplayAttribute("Underlying type: System.Guid, Value = { _value }")] + // ReSharper disable once UnusedType.Global + public partial struct Vo : global::System.IEquatable, global::System.IEquatable, global::System.IComparable, global::System.IComparable, global::System.IParsable, global::System.ISpanParsable, global::System.IFormattable, global::System.ISpanFormattable, global::System.IUtf8SpanFormattable + { +#if DEBUG +private readonly global::System.Diagnostics.StackTrace _stackTrace = null!; +#endif +#if !VOGEN_NO_VALIDATION + private readonly global::System.Boolean _isInitialized; +#endif + private readonly System.Guid _value; + /// + /// Gets the underlying value if set, otherwise a is thrown. + /// + public readonly System.Guid Value + { + [global::System.Diagnostics.DebuggerStepThroughAttribute] + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + get + { + EnsureInitialized(); + return _value; + } + } + + [global::System.Diagnostics.DebuggerStepThroughAttribute] + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public Vo() + { +#if DEBUG + _stackTrace = new global::System.Diagnostics.StackTrace(); +#endif +#if !VOGEN_NO_VALIDATION + _isInitialized = false; +#endif + _value = default; + } + + [global::System.Diagnostics.DebuggerStepThroughAttribute] + private Vo(System.Guid value) + { + _value = value; +#if !VOGEN_NO_VALIDATION + _isInitialized = true; +#endif + } + + /// + /// Builds an instance from the provided underlying type. + /// + /// The underlying type. + /// An instance of this type. + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public static Vo From(System.Guid value) + { + return new Vo(value); + } + + /// + /// Tries to build an instance from the provided underlying type. + /// If a normalization method is provided, it will be called. + /// If validation is provided, and it fails, false will be returned. + /// + /// The underlying type. + /// An instance of the value object. + /// True if the value object can be built, otherwise false. + +#pragma warning disable CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member because of nullability attributes. + + public static bool TryFrom( +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + System.Guid value, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] +#endif + out Vo vo) +#pragma warning restore CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member because of nullability attributes. + + { + vo = new Vo(value); + return true; + } + + /// + /// Tries to build an instance from the provided underlying value. + /// If a normalization method is provided, it will be called. + /// If validation is provided, and it fails, an error will be returned. + /// + /// The primitive value. + /// A containing either the value object, or an error. + public static Vogen.ValueObjectOrError TryFrom(System.Guid value) + { + return new Vogen.ValueObjectOrError(new Vo(value)); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] +#if VOGEN_NO_VALIDATION +#pragma warning disable CS8775 + public readonly bool IsInitialized() => true; +#pragma warning restore CS8775 +#else + public readonly bool IsInitialized() => _isInitialized; +#endif + public static explicit operator Vo(System.Guid value) => From(value); + public static explicit operator System.Guid(Vo value) => value.Value; + // only called internally when something has been deserialized into + // its primitive type. + private static Vo __Deserialize(System.Guid value) + { + return new Vo(value); + } + + public readonly global::System.Boolean Equals(Vo other) + { + // It's possible to create uninitialized instances via converters such as EfCore (HasDefaultValue), which call Equals. + // We treat anything uninitialized as not equal to anything, even other uninitialized instances of this type. + if (!IsInitialized() || !other.IsInitialized()) + return false; + return global::System.Collections.Generic.EqualityComparer.Default.Equals(Value, other.Value); + } + + public global::System.Boolean Equals(Vo other, global::System.Collections.Generic.IEqualityComparer comparer) + { + return comparer.Equals(this, other); + } + + public readonly global::System.Boolean Equals(System.Guid primitive) + { + return Value.Equals(primitive); + } + + public readonly override global::System.Boolean Equals(global::System.Object obj) + { + return obj is Vo && Equals((Vo)obj); + } + + public static global::System.Boolean operator ==(Vo left, Vo right) => left.Equals(right); + public static global::System.Boolean operator !=(Vo left, Vo right) => !(left == right); + public static global::System.Boolean operator ==(Vo left, System.Guid right) => left.Value.Equals(right); + public static global::System.Boolean operator ==(System.Guid left, Vo right) => right.Value.Equals(left); + public static global::System.Boolean operator !=(System.Guid left, Vo right) => !(left == right); + public static global::System.Boolean operator !=(Vo left, System.Guid right) => !(left == right); + public int CompareTo(Vo other) => Value.CompareTo(other.Value); + public int CompareTo(object other) + { + if (other is null) + return 1; + if (other is Vo x) + return CompareTo(x); + ThrowHelper.ThrowArgumentException("Cannot compare to object as it is not of type Vo", nameof(other)); + return 0; + } + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(global::System.ReadOnlySpan input, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out Vo result) + { + if (System.Guid.TryParse(input, out var __v)) + { + result = new Vo(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(global::System.ReadOnlySpan s, global::System.IFormatProvider provider, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out Vo result) + { + if (System.Guid.TryParse(s, provider, out var __v)) + { + result = new Vo(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(string input, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out Vo result) + { + if (System.Guid.TryParse(input, out var __v)) + { + result = new Vo(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(string s, global::System.IFormatProvider provider, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out Vo result) + { + if (System.Guid.TryParse(s, provider, out var __v)) + { + result = new Vo(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// The value created by calling the Parse method on the primitive. + /// + /// Thrown when the value can be parsed, but is not valid. + public static Vo Parse(global::System.ReadOnlySpan input) + { + var r = System.Guid.Parse(input); + return From(r); + } + + /// + /// + /// + /// + /// The value created by calling the Parse method on the primitive. + /// + /// Thrown when the value can be parsed, but is not valid. + public static Vo Parse(global::System.ReadOnlySpan s, global::System.IFormatProvider provider) + { + var r = System.Guid.Parse(s, provider); + return From(r); + } + + /// + /// + /// + /// + /// The value created by calling the Parse method on the primitive. + /// + /// Thrown when the value can be parsed, but is not valid. + public static Vo Parse(string input) + { + var r = System.Guid.Parse(input); + return From(r); + } + + /// + /// + /// + /// + /// The value created by calling the Parse method on the primitive. + /// + /// Thrown when the value can be parsed, but is not valid. + public static Vo Parse(string s, global::System.IFormatProvider provider) + { + var r = System.Guid.Parse(s, provider); + return From(r); + } + +#nullable disable + /// + public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("GuidFormat")] string format, global::System.IFormatProvider provider) + { + return IsInitialized() ? Value.ToString(format, provider) : "[UNINITIALIZED]"; + } + + /// + bool System.ISpanFormattable.TryFormat(global::System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("GuidFormat")] global::System.ReadOnlySpan format, global::System.IFormatProvider provider) + { + charsWritten = default; + return IsInitialized() ? (Value as System.ISpanFormattable).TryFormat(destination, out charsWritten, format, provider) : true; + } + + /// + bool System.IUtf8SpanFormattable.TryFormat(global::System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("GuidFormat")] global::System.ReadOnlySpan format, global::System.IFormatProvider provider) + { + bytesWritten = default; + return IsInitialized() ? (Value as System.IUtf8SpanFormattable).TryFormat(utf8Destination, out bytesWritten, format, provider) : true; + } + +#nullable restore + public readonly override global::System.Int32 GetHashCode() + { + return global::System.Collections.Generic.EqualityComparer.Default.GetHashCode(Value); + } + + /// + public override global::System.String ToString() => IsInitialized() ? Value.ToString() ?? "" : "[UNINITIALIZED]"; + /// + public global::System.String ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("GuidFormat")] string format) => IsInitialized() ? Value.ToString(format) ?? "" : "[UNINITIALIZED]"; + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + private readonly void EnsureInitialized() + { + if (!IsInitialized()) + { +#if DEBUG + ThrowHelper.ThrowWhenNotInitialized(_stackTrace); +#else + ThrowHelper.ThrowWhenNotInitialized(); +#endif + } + } + +#nullable disable + /// + /// Converts a Vo to or from JSON. + /// + public class VoSystemTextJsonConverter : global::System.Text.Json.Serialization.JsonConverter + { + public override Vo Read(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) + { + return Vo.__Deserialize(reader.GetGuid()); + } + + public override void Write(System.Text.Json.Utf8JsonWriter writer, Vo value, global::System.Text.Json.JsonSerializerOptions options) + { + writer.WriteStringValue(value.Value); + } +#if NET6_0_OR_GREATER // we can't call Read or use GetGuid from JsonReader as it expects a token type of string, but here we have have 'propertyname'. + + public override Vo ReadAsPropertyName(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) + { + if (global::System.Guid.TryParse(reader.GetString(), out global::System.Guid g)) + { + return Vo.__Deserialize(g); + } + + throw new global::System.Text.Json.JsonException("Unable to parse the GUID for an instance of Vo"); + } + + public override void WriteAsPropertyName(System.Text.Json.Utf8JsonWriter writer, Vo value, global::System.Text.Json.JsonSerializerOptions options) + { + writer.WritePropertyName(value.Value.ToString()); + } +#endif + } + +#nullable restore +#nullable disable + class VoTypeConverter : global::System.ComponentModel.TypeConverter + { + public override global::System.Boolean CanConvertFrom(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Type sourceType) + { + return sourceType == typeof(global::System.Guid) || sourceType == typeof(global::System.String) || base.CanConvertFrom(context, sourceType); + } + + public override global::System.Object ConvertFrom(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Globalization.CultureInfo culture, global::System.Object value) + { + return value switch + { + global::System.Guid guidValue => Vo.__Deserialize(guidValue), + global::System.String stringValue when !global::System.String.IsNullOrEmpty(stringValue) && global::System.Guid.TryParse(stringValue, out var result) => Vo.__Deserialize(result), + _ => base.ConvertFrom(context, culture, value), + }; + } + + public override bool CanConvertTo(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Type sourceType) + { + return sourceType == typeof(global::System.Guid) || sourceType == typeof(global::System.String) || base.CanConvertTo(context, sourceType); + } + + public override object ConvertTo(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Globalization.CultureInfo culture, global::System.Object value, global::System.Type destinationType) + { + if (value is Vo idValue) + { + if (destinationType == typeof(global::System.Guid)) + { + return idValue.Value; + } + + if (destinationType == typeof(global::System.String)) + { + return idValue.Value.ToString(); + } + } + + return base.ConvertTo(context, culture, value, destinationType); + } + } + +#nullable restore +#nullable disable + internal sealed class VoDebugView + { + private readonly Vo _t; + VoDebugView(Vo t) + { + _t = t; + } + + public global::System.Boolean IsInitialized => _t.IsInitialized(); + public global::System.String UnderlyingType => "System.Guid"; + public global::System.String Value => _t.IsInitialized() ? _t._value.ToString() : "[not initialized]"; +#if DEBUG + public global::System.String CreatedWith => _t._stackTrace.ToString() ?? "the From method"; +#endif + public global::System.String Conversions => @"Default"; + } + +#nullable restore + static class ThrowHelper + { +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowInvalidOperationException(string message) => throw new global::System.InvalidOperationException(message); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowArgumentException(string message, string arg) => throw new global::System.ArgumentException(message, arg); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenCreatedWithNull() => throw new global::Vogen.ValueObjectValidationException("Cannot create a value object with null."); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenNotInitialized() => throw new global::Vogen.ValueObjectValidationException("Use of uninitialized Value Object."); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenNotInitialized(global::System.Diagnostics.StackTrace stackTrace) => throw new global::Vogen.ValueObjectValidationException("Use of uninitialized Value Object at: " + stackTrace ?? ""); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenValidationFails(Vogen.Validation validation) + { + var ex = new global::Vogen.ValueObjectValidationException(validation.ErrorMessage); + if (validation.Data is not null) + { + foreach (var kvp in validation.Data) + { + ex.Data[kvp.Key] = kvp.Value; + } + } + + throw ex; + } + } + } +} +] \ No newline at end of file diff --git a/tests/SnapshotTests/BugFixes/snapshots/snap-v9.0/Bug717_Stj_does_not_escape_dashed_namespace.Test.verified.txt b/tests/SnapshotTests/BugFixes/snapshots/snap-v9.0/Bug717_Stj_does_not_escape_dashed_namespace.Test.verified.txt index 5f282702bb..09d69f46d0 100644 --- a/tests/SnapshotTests/BugFixes/snapshots/snap-v9.0/Bug717_Stj_does_not_escape_dashed_namespace.Test.verified.txt +++ b/tests/SnapshotTests/BugFixes/snapshots/snap-v9.0/Bug717_Stj_does_not_escape_dashed_namespace.Test.verified.txt @@ -1 +1,542 @@ - \ No newline at end of file +[ +// ------------------------------------------------------------------------------ +// +// This code was generated by a source generator named Vogen (https://github.com/SteveDunn/Vogen) +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0618 +// Suppress warnings for 'Override methods on comparable types'. +#pragma warning disable CA1036 +// Suppress Error MA0097 : A class that implements IComparable or IComparable should override comparison operators +#pragma warning disable MA0097 +// Suppress warning for 'The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.' +// The generator copies signatures from the BCL, e.g. for `TryParse`, and some of those have nullable annotations. +#pragma warning disable CS8669, CS8632 +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 +namespace MY_PROJECT; +public class VogenTypesFactory : global::System.Text.Json.Serialization.JsonConverterFactory +{ + public VogenTypesFactory() + { + } + + private static readonly global::System.Collections.Generic.Dictionary> _lookup = new global::System.Collections.Generic.Dictionary> + { + { + typeof(global::My_Namespace.Vo), + new global::System.Lazy(() => new global::My_Namespace.Vo.VoSystemTextJsonConverter()) + } + }; + public override bool CanConvert(global::System.Type typeToConvert) => _lookup.ContainsKey(typeToConvert); + public override global::System.Text.Json.Serialization.JsonConverter CreateConverter(global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) => _lookup[typeToConvert].Value; +} + +// ------------------------------------------------------------------------------ +// +// This code was generated by a source generator named Vogen (https://github.com/SteveDunn/Vogen) +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0618 +// Suppress warnings for 'Override methods on comparable types'. +#pragma warning disable CA1036 +// Suppress Error MA0097 : A class that implements IComparable or IComparable should override comparison operators +#pragma warning disable MA0097 +// Suppress warning for 'The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.' +// The generator copies signatures from the BCL, e.g. for `TryParse`, and some of those have nullable annotations. +#pragma warning disable CS8669, CS8632 +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 +namespace My_Namespace +{ + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Vogen", "1.0.0.0")] + [global::System.Text.Json.Serialization.JsonConverter(typeof(VoSystemTextJsonConverter))] + [global::System.ComponentModel.TypeConverter(typeof(VoTypeConverter))] + [global::System.Diagnostics.DebuggerTypeProxyAttribute(typeof(VoDebugView))] + [global::System.Diagnostics.DebuggerDisplayAttribute("Underlying type: System.Guid, Value = { _value }")] + // ReSharper disable once UnusedType.Global + public partial struct Vo : global::System.IEquatable, global::System.IEquatable, global::System.IComparable, global::System.IComparable, global::System.IParsable, global::System.ISpanParsable, global::System.IFormattable, global::System.ISpanFormattable, global::System.IUtf8SpanFormattable + { +#if DEBUG +private readonly global::System.Diagnostics.StackTrace _stackTrace = null!; +#endif +#if !VOGEN_NO_VALIDATION + private readonly global::System.Boolean _isInitialized; +#endif + private readonly System.Guid _value; + /// + /// Gets the underlying value if set, otherwise a is thrown. + /// + public readonly System.Guid Value + { + [global::System.Diagnostics.DebuggerStepThroughAttribute] + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + get + { + EnsureInitialized(); + return _value; + } + } + + [global::System.Diagnostics.DebuggerStepThroughAttribute] + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public Vo() + { +#if DEBUG + _stackTrace = new global::System.Diagnostics.StackTrace(); +#endif +#if !VOGEN_NO_VALIDATION + _isInitialized = false; +#endif + _value = default; + } + + [global::System.Diagnostics.DebuggerStepThroughAttribute] + private Vo(System.Guid value) + { + _value = value; +#if !VOGEN_NO_VALIDATION + _isInitialized = true; +#endif + } + + /// + /// Builds an instance from the provided underlying type. + /// + /// The underlying type. + /// An instance of this type. + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public static Vo From(System.Guid value) + { + return new Vo(value); + } + + /// + /// Tries to build an instance from the provided underlying type. + /// If a normalization method is provided, it will be called. + /// If validation is provided, and it fails, false will be returned. + /// + /// The underlying type. + /// An instance of the value object. + /// True if the value object can be built, otherwise false. + +#pragma warning disable CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member because of nullability attributes. + + public static bool TryFrom( +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + System.Guid value, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] +#endif + out Vo vo) +#pragma warning restore CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member because of nullability attributes. + + { + vo = new Vo(value); + return true; + } + + /// + /// Tries to build an instance from the provided underlying value. + /// If a normalization method is provided, it will be called. + /// If validation is provided, and it fails, an error will be returned. + /// + /// The primitive value. + /// A containing either the value object, or an error. + public static Vogen.ValueObjectOrError TryFrom(System.Guid value) + { + return new Vogen.ValueObjectOrError(new Vo(value)); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] +#if VOGEN_NO_VALIDATION +#pragma warning disable CS8775 + public readonly bool IsInitialized() => true; +#pragma warning restore CS8775 +#else + public readonly bool IsInitialized() => _isInitialized; +#endif + public static explicit operator Vo(System.Guid value) => From(value); + public static explicit operator System.Guid(Vo value) => value.Value; + // only called internally when something has been deserialized into + // its primitive type. + private static Vo __Deserialize(System.Guid value) + { + return new Vo(value); + } + + public readonly global::System.Boolean Equals(Vo other) + { + // It's possible to create uninitialized instances via converters such as EfCore (HasDefaultValue), which call Equals. + // We treat anything uninitialized as not equal to anything, even other uninitialized instances of this type. + if (!IsInitialized() || !other.IsInitialized()) + return false; + return global::System.Collections.Generic.EqualityComparer.Default.Equals(Value, other.Value); + } + + public global::System.Boolean Equals(Vo other, global::System.Collections.Generic.IEqualityComparer comparer) + { + return comparer.Equals(this, other); + } + + public readonly global::System.Boolean Equals(System.Guid primitive) + { + return Value.Equals(primitive); + } + + public readonly override global::System.Boolean Equals(global::System.Object obj) + { + return obj is Vo && Equals((Vo)obj); + } + + public static global::System.Boolean operator ==(Vo left, Vo right) => left.Equals(right); + public static global::System.Boolean operator !=(Vo left, Vo right) => !(left == right); + public static global::System.Boolean operator ==(Vo left, System.Guid right) => left.Value.Equals(right); + public static global::System.Boolean operator ==(System.Guid left, Vo right) => right.Value.Equals(left); + public static global::System.Boolean operator !=(System.Guid left, Vo right) => !(left == right); + public static global::System.Boolean operator !=(Vo left, System.Guid right) => !(left == right); + public int CompareTo(Vo other) => Value.CompareTo(other.Value); + public int CompareTo(object other) + { + if (other is null) + return 1; + if (other is Vo x) + return CompareTo(x); + ThrowHelper.ThrowArgumentException("Cannot compare to object as it is not of type Vo", nameof(other)); + return 0; + } + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(global::System.ReadOnlySpan input, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out Vo result) + { + if (System.Guid.TryParse(input, out var __v)) + { + result = new Vo(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(global::System.ReadOnlySpan s, global::System.IFormatProvider provider, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out Vo result) + { + if (System.Guid.TryParse(s, provider, out var __v)) + { + result = new Vo(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(string input, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out Vo result) + { + if (System.Guid.TryParse(input, out var __v)) + { + result = new Vo(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(string s, global::System.IFormatProvider provider, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out Vo result) + { + if (System.Guid.TryParse(s, provider, out var __v)) + { + result = new Vo(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// The value created by calling the Parse method on the primitive. + /// + /// Thrown when the value can be parsed, but is not valid. + public static Vo Parse(global::System.ReadOnlySpan input) + { + var r = System.Guid.Parse(input); + return From(r); + } + + /// + /// + /// + /// + /// The value created by calling the Parse method on the primitive. + /// + /// Thrown when the value can be parsed, but is not valid. + public static Vo Parse(global::System.ReadOnlySpan s, global::System.IFormatProvider provider) + { + var r = System.Guid.Parse(s, provider); + return From(r); + } + + /// + /// + /// + /// + /// The value created by calling the Parse method on the primitive. + /// + /// Thrown when the value can be parsed, but is not valid. + public static Vo Parse(string input) + { + var r = System.Guid.Parse(input); + return From(r); + } + + /// + /// + /// + /// + /// The value created by calling the Parse method on the primitive. + /// + /// Thrown when the value can be parsed, but is not valid. + public static Vo Parse(string s, global::System.IFormatProvider provider) + { + var r = System.Guid.Parse(s, provider); + return From(r); + } + +#nullable disable + /// + public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("GuidFormat")] string format, global::System.IFormatProvider provider) + { + return IsInitialized() ? Value.ToString(format, provider) : "[UNINITIALIZED]"; + } + + /// + bool System.ISpanFormattable.TryFormat(global::System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("GuidFormat")] global::System.ReadOnlySpan format, global::System.IFormatProvider provider) + { + charsWritten = default; + return IsInitialized() ? (Value as System.ISpanFormattable).TryFormat(destination, out charsWritten, format, provider) : true; + } + + /// + bool System.IUtf8SpanFormattable.TryFormat(global::System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("GuidFormat")] global::System.ReadOnlySpan format, global::System.IFormatProvider provider) + { + bytesWritten = default; + return IsInitialized() ? (Value as System.IUtf8SpanFormattable).TryFormat(utf8Destination, out bytesWritten, format, provider) : true; + } + +#nullable restore + public readonly override global::System.Int32 GetHashCode() + { + return global::System.Collections.Generic.EqualityComparer.Default.GetHashCode(Value); + } + + /// + public override global::System.String ToString() => IsInitialized() ? Value.ToString() ?? "" : "[UNINITIALIZED]"; + /// + public global::System.String ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("GuidFormat")] string format) => IsInitialized() ? Value.ToString(format) ?? "" : "[UNINITIALIZED]"; + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + private readonly void EnsureInitialized() + { + if (!IsInitialized()) + { +#if DEBUG + ThrowHelper.ThrowWhenNotInitialized(_stackTrace); +#else + ThrowHelper.ThrowWhenNotInitialized(); +#endif + } + } + +#nullable disable + /// + /// Converts a Vo to or from JSON. + /// + public class VoSystemTextJsonConverter : global::System.Text.Json.Serialization.JsonConverter + { + public override Vo Read(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) + { + return Vo.__Deserialize(reader.GetGuid()); + } + + public override void Write(System.Text.Json.Utf8JsonWriter writer, Vo value, global::System.Text.Json.JsonSerializerOptions options) + { + writer.WriteStringValue(value.Value); + } +#if NET6_0_OR_GREATER // we can't call Read or use GetGuid from JsonReader as it expects a token type of string, but here we have have 'propertyname'. + + public override Vo ReadAsPropertyName(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) + { + if (global::System.Guid.TryParse(reader.GetString(), out global::System.Guid g)) + { + return Vo.__Deserialize(g); + } + + throw new global::System.Text.Json.JsonException("Unable to parse the GUID for an instance of Vo"); + } + + public override void WriteAsPropertyName(System.Text.Json.Utf8JsonWriter writer, Vo value, global::System.Text.Json.JsonSerializerOptions options) + { + writer.WritePropertyName(value.Value.ToString()); + } +#endif + } + +#nullable restore +#nullable disable + class VoTypeConverter : global::System.ComponentModel.TypeConverter + { + public override global::System.Boolean CanConvertFrom(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Type sourceType) + { + return sourceType == typeof(global::System.Guid) || sourceType == typeof(global::System.String) || base.CanConvertFrom(context, sourceType); + } + + public override global::System.Object ConvertFrom(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Globalization.CultureInfo culture, global::System.Object value) + { + return value switch + { + global::System.Guid guidValue => Vo.__Deserialize(guidValue), + global::System.String stringValue when !global::System.String.IsNullOrEmpty(stringValue) && global::System.Guid.TryParse(stringValue, out var result) => Vo.__Deserialize(result), + _ => base.ConvertFrom(context, culture, value), + }; + } + + public override bool CanConvertTo(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Type sourceType) + { + return sourceType == typeof(global::System.Guid) || sourceType == typeof(global::System.String) || base.CanConvertTo(context, sourceType); + } + + public override object ConvertTo(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Globalization.CultureInfo culture, global::System.Object value, global::System.Type destinationType) + { + if (value is Vo idValue) + { + if (destinationType == typeof(global::System.Guid)) + { + return idValue.Value; + } + + if (destinationType == typeof(global::System.String)) + { + return idValue.Value.ToString(); + } + } + + return base.ConvertTo(context, culture, value, destinationType); + } + } + +#nullable restore +#nullable disable + internal sealed class VoDebugView + { + private readonly Vo _t; + VoDebugView(Vo t) + { + _t = t; + } + + public global::System.Boolean IsInitialized => _t.IsInitialized(); + public global::System.String UnderlyingType => "System.Guid"; + public global::System.String Value => _t.IsInitialized() ? _t._value.ToString() : "[not initialized]"; +#if DEBUG + public global::System.String CreatedWith => _t._stackTrace.ToString() ?? "the From method"; +#endif + public global::System.String Conversions => @"Default"; + } + +#nullable restore + static class ThrowHelper + { +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowInvalidOperationException(string message) => throw new global::System.InvalidOperationException(message); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowArgumentException(string message, string arg) => throw new global::System.ArgumentException(message, arg); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenCreatedWithNull() => throw new global::Vogen.ValueObjectValidationException("Cannot create a value object with null."); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenNotInitialized() => throw new global::Vogen.ValueObjectValidationException("Use of uninitialized Value Object."); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenNotInitialized(global::System.Diagnostics.StackTrace stackTrace) => throw new global::Vogen.ValueObjectValidationException("Use of uninitialized Value Object at: " + stackTrace ?? ""); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenValidationFails(Vogen.Validation validation) + { + var ex = new global::Vogen.ValueObjectValidationException(validation.ErrorMessage); + if (validation.Data is not null) + { + foreach (var kvp in validation.Data) + { + ex.Data[kvp.Key] = kvp.Value; + } + } + + throw ex; + } + } + } +} +] \ No newline at end of file From 65e3da9b5ecca4b59a61ce49c302d051f4144e53 Mon Sep 17 00:00:00 2001 From: Steve Dunn Date: Mon, 2 Dec 2024 05:46:34 +0000 Subject: [PATCH 4/4] Update documentation --- docs/site/Writerside/topics/tutorials/Using-with-JSON.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/site/Writerside/topics/tutorials/Using-with-JSON.md b/docs/site/Writerside/topics/tutorials/Using-with-JSON.md index 07e1e114f9..fb152088e4 100644 --- a/docs/site/Writerside/topics/tutorials/Using-with-JSON.md +++ b/docs/site/Writerside/topics/tutorials/Using-with-JSON.md @@ -89,9 +89,13 @@ var json = JsonSerializer.Serialize(weather, ctx.Weather); Weather w2 = JsonSerializer.Deserialize(json, ctx.Weather)!; ``` + +The namespace used to generate the factory is based on the project name. If the project name contains illegal characters, such as commas, hyphens, or dots, they are replaced with underscores. + + The STJ converter factory is generated automatically if referencing the new STJ types in the `System.Text.Json.Serialization` namespace. -You can turn this off via the `SystemTextJsonConverterFactoryGeneration` flag in config (either globally, or per value object) +You can turn this off via the `SystemTextJsonConverterFactoryGeneration` flag in config (either globally or per value object) ## Summary