diff --git a/src/Vogen.SharedTypes/StringComparisonDefault.cs b/src/Vogen.SharedTypes/StringComparisonDefault.cs new file mode 100644 index 00000000000..ea72024f1c8 --- /dev/null +++ b/src/Vogen.SharedTypes/StringComparisonDefault.cs @@ -0,0 +1,17 @@ +using System.Diagnostics; +// ReSharper disable UnusedMember.Global + +namespace Vogen; + +public enum StringComparisonDefault +{ + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + Unspecified = -1, + Omit = 0, + Ordinal = 1, + OrdinalIgnoreCase = 2, + CurrentCulture = 3, + CurrentCultureIgnoreCase = 4, + InvariantCulture = 5, + InvariantCultureIgnoreCase = 6, +} diff --git a/src/Vogen.SharedTypes/ValueObjectAttribute.cs b/src/Vogen.SharedTypes/ValueObjectAttribute.cs index 419f8d9b7a8..a84e365cbae 100644 --- a/src/Vogen.SharedTypes/ValueObjectAttribute.cs +++ b/src/Vogen.SharedTypes/ValueObjectAttribute.cs @@ -1,4 +1,4 @@ -// ReSharper disable UnusedParameter.Local +// ReSharper disable UnusedParameter.Local // ReSharper disable NullableWarningSuppressionIsUsed // ReSharper disable UnusedType.Global @@ -88,6 +88,78 @@ public ValueObjectAttribute( numericsGeneration) { } + + /// + /// Configures aspects of this individual value object. + /// + /// Specifies what conversion code is generated - defaults to which generates type converters and a converter to handle serialization using System.Text.Json + /// Specifies the type of exception thrown when validation fails—defaults to . + /// Simple customization switches—defaults to . + /// Specifies how strict deserialization is, e.g. should your Validate method be called, or should pre-defined instances that otherwise invalid be allowed - defaults to . + /// Specifies the level that debug attributes are written as some IDEs don't support all of them, e.g., Rider - defaults to which generates DebuggerDisplay and a debugger proxy type for IDEs that support them + /// Species which comparison code is generated—defaults to which hoists any IComparable implementations from the primitive. + /// Specifies which string comparison code is generated—defaults to which doesn't generate anything related to string comparison. + /// Controls how cast operators are generated for casting from the Value Object to the primitive. + /// Options are implicit or explicit or none. Explicit is preferred over implicit if you really need them, but isn't recommended. + /// See for more information. + /// Controls how cast operators are generated for casting from the primitive to the Value Object. + /// Options are implicit or explicit or none. Explicit is preferred over implicit if you really need them, but isn't recommended. + /// See for more information. + /// + /// Specifies what is generated for IParsable types for strings - defaults to . + /// Specifies what is generated for Parse and TryParse methods - defaults to . + /// Specifies what to write for TryFrom methods—defaults to . + /// Specifies whether to generate an IsInitialized() method - defaults to . + /// + /// Specifies whether to generate primitive comparison operators, allowing this type to be compared for equality to the primitive. + /// Defaults to + /// + /// + /// var vo = MyInt.From(123); + /// + /// + /// bool same = vo == 123; + /// + /// + /// + /// Specifies whether to generate numeric interfaces (INumber<T> or INumberBase<T> depending on the underlying type)—defaults to . + /// Specifies the default used for ==, Equals, and GetHashCode on string-backed value objects—defaults to which uses the underlying string's default comparison. + public ValueObjectAttribute( + Conversions conversions, + Type? throws, + Customizations customizations, + DeserializationStrictness deserializationStrictness, + DebuggerAttributeGeneration debuggerAttributes, + ComparisonGeneration comparison, + StringComparersGeneration stringComparers, + CastOperator toPrimitiveCasting, + CastOperator fromPrimitiveCasting, + ParsableForStrings parsableForStrings, + ParsableForPrimitives parsableForPrimitives, + TryFromGeneration tryFromGeneration, + IsInitializedMethodGeneration isInitializedMethodGeneration, + PrimitiveEqualityGeneration primitiveEqualityGeneration, + NumericsGeneration numericsGeneration, + StringComparisonDefault stringDefaultComparison) : base( + typeof(T), + conversions, + throws, + customizations, + deserializationStrictness, + debuggerAttributes, + comparison, + stringComparers, + toPrimitiveCasting, + fromPrimitiveCasting, + parsableForStrings, + parsableForPrimitives, + tryFromGeneration, + isInitializedMethodGeneration, + primitiveEqualityGeneration, + numericsGeneration, + stringDefaultComparison) + { + } } /// @@ -100,9 +172,9 @@ public class ValueObjectAttribute : Attribute // keep this signature in-line with `VogenConfiguration` // as the syntax/semantics are read in the generator // using parameter indexes (i.e. it expected param 0 to be the underlying type etc). - + // ReSharper disable once MemberCanBeProtected.Global - + /// /// Configures aspects of this individual value object. /// @@ -156,5 +228,61 @@ public ValueObjectAttribute( // of parameters is a binary-breaking change. See https://github.com/dotnet/runtime/issues/103722 // for more information. } + + /// + /// Configures aspects of this individual value object. + /// + /// The type of the primitive that is being wrapped—defaults to int. + /// Specifies what conversion code is generated - defaults to which generates type converters and a converter to handle serialization using System.Text.Json + /// Specifies the type of exception thrown when validation fails—defaults to . + /// Simple customization switches—defaults to . + /// Specifies how strict deserialization is, e.g. should your Validate method be called, or should pre-defined instances that otherwise invalid be allowed - defaults to . + /// Specifies the level that debug attributes are written as some IDEs don't support all of them, e.g., Rider - defaults to which generates DebuggerDisplay and a debugger proxy type for IDEs that support them + /// Species which comparison code is generated—defaults to which hoists any IComparable implementations from the primitive. + /// Specifies which string comparison code is generated—defaults to which doesn't generate anything related to string comparison. + /// Specifies the type of casting from wrapper to primitive - defaults to . + /// Specifies the type of casting from primitive to wrapper - default to . + /// Specifies what is generated for IParsable types for strings - defaults to . + /// Specifies what is generated for Parse and TryParse methods - defaults to . + /// Specifies what to write for TryFrom methods—defaults to . + /// Specifies whether to generate an IsInitialized() method - defaults to . + /// + /// Specifies whether to generate primitive comparison operators, allowing this type to be compared for equality to the primitive. + /// Defaults to + /// + /// + /// var vo = MyInt.From(123); + /// + /// + /// bool same = vo == 123; + /// + /// + /// + /// Specifies whether to generate numeric interfaces (INumber<T> or INumberBase<T> depending on the underlying type)—defaults to . + /// Specifies the default used for ==, Equals, and GetHashCode on string-backed value objects—defaults to which uses the underlying string's default comparison. + public ValueObjectAttribute( + Type? underlyingType, + Conversions conversions, + Type? throws, + Customizations customizations, + DeserializationStrictness deserializationStrictness, + DebuggerAttributeGeneration debuggerAttributes, + ComparisonGeneration comparison, + StringComparersGeneration stringComparers, + CastOperator toPrimitiveCasting, + CastOperator fromPrimitiveCasting, + ParsableForStrings parsableForStrings, + ParsableForPrimitives parsableForPrimitives, + TryFromGeneration tryFromGeneration, + IsInitializedMethodGeneration isInitializedMethodGeneration, + PrimitiveEqualityGeneration primitiveEqualityGeneration, + NumericsGeneration numericsGeneration, + StringComparisonDefault stringDefaultComparison) + { + // DO NOT ADD PARAMETERS HERE, INSTEAD, CREATE OVERLOADS (at least until a new major version). + // This is because some users use reflection to find this attribute, and changing the amount + // of parameters is a binary-breaking change. See https://github.com/dotnet/runtime/issues/103722 + // for more information. + } } -} \ No newline at end of file +} diff --git a/src/Vogen.SharedTypes/VogenDefaultsAttribute.cs b/src/Vogen.SharedTypes/VogenDefaultsAttribute.cs index 1990470c41d..2c4aeed3695 100644 --- a/src/Vogen.SharedTypes/VogenDefaultsAttribute.cs +++ b/src/Vogen.SharedTypes/VogenDefaultsAttribute.cs @@ -1,4 +1,4 @@ -// ReSharper disable MemberInitializerValueIgnored +// ReSharper disable MemberInitializerValueIgnored // ReSharper disable UnusedType.Global // ReSharper disable UnusedParameter.Local @@ -76,4 +76,68 @@ public VogenDefaultsAttribute( NumericsGeneration numericsGeneration = NumericsGeneration.Unspecified) { } + + /// + /// Creates a new instance of a type that represents the default + /// values used for value object generation. + /// + /// The type of the primitive that is being wrapped—defaults to int. + /// Specifies what conversion code is generated - defaults to which generates type converters and a converter to handle serialization using System.Text.Json + /// Specifies the type of exception thrown when validation fails—defaults to . + /// Simple customization switches—defaults to . + /// Specifies how strict deserialization is, e.g. should your Validate method be called, or should pre-defined instances that otherwise invalid be allowed - defaults to . + /// Specifies the level that debug attributes are written as some IDEs don't support all of them, e.g., Rider - defaults to which generates DebuggerDisplay and a debugger proxy type for IDEs that support them + /// Controls how cast operators are generated for casting from the Value Object to the primitive. + /// Options are implicit or explicit or none. Explicit is preferred over implicit if you really need them, but isn't recommended. + /// See for more information. + /// Controls how cast operators are generated for casting from the primitive to the Value Object. + /// Options are implicit or explicit or none. Explicit is preferred over implicit if you really need them, but isn't recommended. + /// See <see href="https://github.com/SteveDunn/Vogen/wiki/Casting"/> for more information. + /// disables stack trace recording; in Debug builds, a stack trace is recorded and is + /// thrown in the exception when something is created in an uninitialized state, e.g. after deserialization + /// Specifies what is generated for IParsable types for strings - defaults to . + /// Specifies what is generated for Parse and TryParse methods - defaults to . + /// Specifies what to write for TryFrom methods—defaults to . + /// Specifies whether to generate an IsInitialized() method - defaults to . + /// + /// Specifies whether to generate primitive comparison operators, allowing this type to be compared for equality to the primitive. + /// Defaults to + /// + /// + /// var vo = MyInt.From(123); + /// + /// + /// bool same = vo == 123; + /// + /// + /// + /// Controls the generation of the type factory for System.Text.Json. + /// Controls the generation of static abstract interfaces. + /// Controls the generation of a Swashbuckle schema filter for OpenAPI. + /// Every ValueObject attribute must explicitly specify the type of the primitive. + /// Specifies whether to generate numeric interfaces (INumber<T> or INumberBase<T> depending on the underlying type)—defaults to . + /// Specifies the default used for ==, Equals, and GetHashCode on string-backed value objects—defaults to which uses the underlying string's default comparison. + public VogenDefaultsAttribute( + Type? underlyingType, + Conversions conversions, + Type? throws, + Customizations customizations, + DeserializationStrictness deserializationStrictness, + DebuggerAttributeGeneration debuggerAttributes, + CastOperator toPrimitiveCasting, + CastOperator fromPrimitiveCasting, + bool disableStackTraceRecordingInDebug, + ParsableForStrings parsableForStrings, + ParsableForPrimitives parsableForPrimitives, + TryFromGeneration tryFromGeneration, + IsInitializedMethodGeneration isInitializedMethodGeneration, + SystemTextJsonConverterFactoryGeneration systemTextJsonConverterFactoryGeneration, + StaticAbstractsGeneration staticAbstractsGeneration, + OpenApiSchemaCustomizations openApiSchemaCustomizations, + bool explicitlySpecifyTypeInValueObject, + PrimitiveEqualityGeneration primitiveEqualityGeneration, + NumericsGeneration numericsGeneration, + StringComparisonDefault stringDefaultComparison) + { + } } \ No newline at end of file diff --git a/src/Vogen/BuildConfigurationFromAttributes.cs b/src/Vogen/BuildConfigurationFromAttributes.cs index aadea943f34..6466c06ffa4 100644 --- a/src/Vogen/BuildConfigurationFromAttributes.cs +++ b/src/Vogen/BuildConfigurationFromAttributes.cs @@ -38,6 +38,7 @@ internal class BuildConfigurationFromAttributes private bool _primitiveTypeMustBeExplicit; private PrimitiveEqualityGeneration _primitiveEqualityGeneration; private NumericsGeneration _numericsGeneration; + private StringComparisonDefault _stringDefaultComparison; private BuildConfigurationFromAttributes(AttributeData att) { @@ -64,6 +65,7 @@ private BuildConfigurationFromAttributes(AttributeData att) _primitiveTypeMustBeExplicit = false; _primitiveEqualityGeneration = PrimitiveEqualityGeneration.Unspecified; _numericsGeneration = NumericsGeneration.Unspecified; + _stringDefaultComparison = StringComparisonDefault.Unspecified; _diagnostics = new List(); @@ -127,7 +129,8 @@ private VogenConfigurationBuildResult Build(bool argsAreFromVogenDefaultAttribut OpenApiSchemaCustomizations: _openApiSchemaCustomizations, ExplicitlySpecifyTypeInValueObject: _primitiveTypeMustBeExplicit, PrimitiveEqualityGeneration: _primitiveEqualityGeneration, - NumericsGeneration: _numericsGeneration), + NumericsGeneration: _numericsGeneration, + StringDefaultComparison: _stringDefaultComparison), diagnostics: _diagnostics); } @@ -194,6 +197,7 @@ private void PopulateDiagnosticsWithAnyValidationIssues() { _diagnostics.Add(DiagnosticsCatalogue.InvalidDeserializationStrictness(syntaxLocation)); } + } // populates all args - it doesn't expect the underlying type argument as that is: @@ -202,7 +206,7 @@ private void PopulateDiagnosticsWithAnyValidationIssues() // ReSharper disable once CognitiveComplexity private void PopulateFromVogenDefaultsAttributeArgs(ImmutableArray argsExcludingUnderlyingType) { - if (argsExcludingUnderlyingType.Length > 18) + if (argsExcludingUnderlyingType.Length > 19) { throw new InvalidOperationException("Too many arguments for the attribute."); } @@ -216,6 +220,11 @@ private void PopulateFromVogenDefaultsAttributeArgs(ImmutableArray args) { - if (args.Length > 15) + if (args.Length > 16) { throw new InvalidOperationException("Too many arguments for the attribute."); } @@ -327,6 +336,11 @@ private void PopulateFromValueObjectAttributeArgs(ImmutableArray continue; } + if (i == 15) + { + _stringDefaultComparison = (StringComparisonDefault) v; + } + if (i == 14) { _numericsGeneration = (NumericsGeneration) v; diff --git a/src/Vogen/BuildWorkItems.cs b/src/Vogen/BuildWorkItems.cs index 36a5a59c0a1..e719a8b4678 100644 --- a/src/Vogen/BuildWorkItems.cs +++ b/src/Vogen/BuildWorkItems.cs @@ -99,6 +99,14 @@ internal static class BuildWorkItems } } + bool hasActiveDefaultComparison = config.StringDefaultComparison is not StringComparisonDefault.Unspecified + and not StringComparisonDefault.Omit; + + if (hasActiveDefaultComparison && underlyingType.SpecialType != Microsoft.CodeAnalysis.SpecialType.System_String) + { + context.ReportDiagnostic(DiagnosticsCatalogue.StringDefaultComparisonNotApplicable(voSymbolInformation, underlyingType)); + } + IEnumerable instanceProperties = TryBuildInstanceProperties(allAttributes, voSymbolInformation, context, underlyingType, vogenKnownSymbols).ToList(); diff --git a/src/Vogen/CombineConfigurations.cs b/src/Vogen/CombineConfigurations.cs index 4f882cc9699..6f30fbca5b6 100644 --- a/src/Vogen/CombineConfigurations.cs +++ b/src/Vogen/CombineConfigurations.cs @@ -158,6 +158,14 @@ public static VogenConfiguration CombineAndResolveAnythingUnspecified( (var local, _) => local, }; + StringComparisonDefault stringDefaultComparison = (localValues.StringDefaultComparison, globalValues?.StringDefaultComparison) switch + { + (StringComparisonDefault.Unspecified, null) => VogenConfiguration.DefaultInstance.StringDefaultComparison, + (StringComparisonDefault.Unspecified, StringComparisonDefault.Unspecified) => VogenConfiguration.DefaultInstance.StringDefaultComparison, + (StringComparisonDefault.Unspecified, var global) => global.Value, + (var local, _) => local, + }; + var validationExceptionType = localValues.ValidationExceptionType ?? globalValues?.ValidationExceptionType ?? VogenConfiguration.DefaultInstance.ValidationExceptionType; @@ -193,7 +201,8 @@ public static VogenConfiguration CombineAndResolveAnythingUnspecified( OpenApiSchemaCustomizations: openApiSchemaCustomizations, ExplicitlySpecifyTypeInValueObject: primitiveTypeMustBeExplicit, PrimitiveEqualityGeneration: primitiveEqualityGeneration, - NumericsGeneration: numericsGeneration); + NumericsGeneration: numericsGeneration, + StringDefaultComparison: stringDefaultComparison); } /// If we don't have a global attribute, just use the default configuration as there diff --git a/src/Vogen/Diagnostics/DiagnosticsCatalogue.cs b/src/Vogen/Diagnostics/DiagnosticsCatalogue.cs index 0dba663c75b..6d1db423dd6 100644 --- a/src/Vogen/Diagnostics/DiagnosticsCatalogue.cs +++ b/src/Vogen/Diagnostics/DiagnosticsCatalogue.cs @@ -135,6 +135,12 @@ internal static class DiagnosticsCatalogue "'{0}' has NumericsGeneration.Generate set, but the underlying type '{1}' does not implement INumberBase. No numeric interface will be generated.", DiagnosticSeverity.Warning); + private static readonly DiagnosticDescriptor _stringDefaultComparisonNotApplicable = CreateDescriptor( + RuleIdentifiers.StringDefaultComparisonNotApplicable, + "StringDefaultComparison has no effect", + "'{0}' has StringDefaultComparison set, but the underlying type '{1}' is not a string. StringDefaultComparison only applies to string-backed value objects.", + DiagnosticSeverity.Warning); + public static Diagnostic TypeCannotBeNested(INamedTypeSymbol typeModel, INamedTypeSymbol container) => Create(_typeCannotBeNested, typeModel.Locations, typeModel.Name, container.Name); @@ -214,6 +220,9 @@ public static Diagnostic TypesReferencedInAConversionMarkerMustBeValueObjects(IN public static Diagnostic NumericsGenerationNotApplicable(INamedTypeSymbol voSymbol, ITypeSymbol underlyingType) => Create(_numericsGenerationNotApplicable, voSymbol.Locations, voSymbol.Name, underlyingType.Name); + public static Diagnostic StringDefaultComparisonNotApplicable(INamedTypeSymbol voSymbol, ITypeSymbol underlyingType) => + Create(_stringDefaultComparisonNotApplicable, voSymbol.Locations, voSymbol.Name, underlyingType.Name); + private static DiagnosticDescriptor CreateDescriptor(string code, string title, string messageFormat, DiagnosticSeverity severity = DiagnosticSeverity.Error) { string[] tags = severity == DiagnosticSeverity.Error ? new[] { WellKnownDiagnosticTags.NotConfigurable } : Array.Empty(); diff --git a/src/Vogen/Diagnostics/RuleIdentifiers.cs b/src/Vogen/Diagnostics/RuleIdentifiers.cs index d58182018f0..d4b5bb8687c 100644 --- a/src/Vogen/Diagnostics/RuleIdentifiers.cs +++ b/src/Vogen/Diagnostics/RuleIdentifiers.cs @@ -45,4 +45,5 @@ public static class RuleIdentifiers public const string BothImplicitAndExplicitCastsSpecified = "VOG036"; public const string NumericsGenerationNotApplicable = "VOG037"; public const string PropertyOfValueObjectShouldBeInitialized = "VOG038"; + public const string StringDefaultComparisonNotApplicable = "VOG039"; } \ No newline at end of file diff --git a/src/Vogen/GenerateCodeForEqualsMethodsAndOperators.cs b/src/Vogen/GenerateCodeForEqualsMethodsAndOperators.cs index bdc70270fbc..857a2e8afb9 100644 --- a/src/Vogen/GenerateCodeForEqualsMethodsAndOperators.cs +++ b/src/Vogen/GenerateCodeForEqualsMethodsAndOperators.cs @@ -13,7 +13,11 @@ public static string GenerateEqualsMethodsForAClass(VoWorkItem item, TypeDeclara string virtualKeyword = item is { IsRecordClass: true, IsSealed: false } ? "virtual " : " "; - var ret = item.UserProvidedOverloads.EqualsForWrapper.WasProvided ? string.Empty : + string classEqualityExpr = item.IsUnderlyingAString && item.Config.GetStringDefaultComparerExpression() is { } classComparer + ? $"{classComparer}.Equals(Value, other.Value)" + : $"global::System.Collections.Generic.EqualityComparer<{item.UnderlyingTypeFullNameWithGlobalAlias}>.Default.Equals(Value, other.Value)"; + + var ret = item.UserProvidedOverloads.EqualsForWrapper.WasProvided ? string.Empty : $$""" public {{virtualKeyword}}global::System.Boolean Equals({{className}}{{wrapperQ}} other) @@ -32,7 +36,7 @@ public static string GenerateEqualsMethodsForAClass(VoWorkItem item, TypeDeclara return true; } - return GetType() == other.GetType() && {{$"global::System.Collections.Generic.EqualityComparer<{item.UnderlyingTypeFullNameWithGlobalAlias}>.Default.Equals(Value, other.Value)"}}; + return GetType() == other.GetType() && {{classEqualityExpr}}; } """; @@ -65,6 +69,10 @@ public static string GenerateEqualsMethodsForAStruct(VoWorkItem item, TypeDeclar { var structName = tds.Identifier; + string structEqualityExpr = item.IsUnderlyingAString && item.Config.GetStringDefaultComparerExpression() is { } structComparer + ? $"{structComparer}.Equals(Value, other.Value)" + : $"global::System.Collections.Generic.EqualityComparer<{item.UnderlyingTypeFullNameWithGlobalAlias}>.Default.Equals(Value, other.Value)"; + var ret = item.UserProvidedOverloads.EqualsForWrapper.WasProvided ? string.Empty : $$""" public readonly global::System.Boolean Equals({{structName}} other) @@ -73,7 +81,7 @@ public static string GenerateEqualsMethodsForAStruct(VoWorkItem item, TypeDeclar // 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<{item.UnderlyingTypeFullNameWithGlobalAlias}>.Default.Equals(Value, other.Value)"}}; + return {{structEqualityExpr}}; } """; @@ -118,12 +126,16 @@ private static string GenerateEqualsMethodForPrimitive(VoWorkItem item, bool isR string readonlyOrEmpty = isReadOnly ? " readonly" : string.Empty; + string primitiveEqualityBody = isString && item.Config.GetStringDefaultComparerExpression() is { } primComparer + ? $"return {primComparer}.Equals(Value, primitive);" + : "return Value.Equals(primitive);"; + string output = item.UserProvidedOverloads.EqualsForUnderlying.WasProvided ? string.Empty : $$""" public{{readonlyOrEmpty}} global::System.Boolean Equals({{item.UnderlyingTypeFullNameWithGlobalAlias}}{{underlyingQ}} primitive) { - return Value.Equals(primitive); + {{primitiveEqualityBody}} } """; @@ -152,11 +164,23 @@ public static string GenerateEqualsOperatorsForPrimitivesIfNeeded(string itemUnd string w = item.Nullable.QuestionMarkForWrapper; string u = item.Nullable.QuestionMarkForUnderlying; - + + if (item.IsUnderlyingAString && item.Config.GetStringDefaultComparerExpression() is { } opComparer) + { + return $""" + + public static global::System.Boolean operator ==({typeName}{w} left, {itemUnderlyingType}{u} right) => {opComparer}.Equals(left{w}.Value, right); + public static global::System.Boolean operator ==({itemUnderlyingType}{u} left, {typeName}{w} right) => {opComparer}.Equals(left, right{w}.Value); + + public static global::System.Boolean operator !=({itemUnderlyingType}{u} left, {typeName}{w} right) => !(left == right); + public static global::System.Boolean operator !=({typeName}{w} left, {itemUnderlyingType}{u} right) => !(left == right); + """; + } + string wDefaultToFalse = item.Nullable.IsEnabled && item.IsTheWrapperAReferenceType ? "?? false" : string.Empty; return $""" - + public static global::System.Boolean operator ==({typeName}{w} left, {itemUnderlyingType}{u} right) => left{w}.Value.Equals(right) {wDefaultToFalse}; public static global::System.Boolean operator ==({itemUnderlyingType}{u} left, {typeName}{w} right) => right{w}.Value.Equals(left) {wDefaultToFalse}; diff --git a/src/Vogen/GenerateCodeForHashCodes.cs b/src/Vogen/GenerateCodeForHashCodes.cs index bdea6ceb97f..c6d6fc97dd5 100644 --- a/src/Vogen/GenerateCodeForHashCodes.cs +++ b/src/Vogen/GenerateCodeForHashCodes.cs @@ -16,13 +16,15 @@ public static string GenerateGetHashCodeForAClass(VoWorkItem item) // When the underlying type is a nullable value type (e.g. ushort?), EqualityComparer.Default.GetHashCode // has [DisallowNull] on its parameter in .NET 10+, causing CS8607. Use null-safe approach instead. // Wrap the expression in parentheses to ensure correct precedence with the ^ operator. - string hashCodeExpression = item.UnderlyingType.IsNullableValueType() - ? "(Value is null ? 0 : Value.GetHashCode())" - : $"global::System.Collections.Generic.EqualityComparer<{itemUnderlyingType}>.Default.GetHashCode(Value)"; + string hashCodeExpression = item.IsUnderlyingAString && item.Config.GetStringDefaultComparerExpression() is { } classHashComparer + ? $"{classHashComparer}.GetHashCode(Value)" + : item.UnderlyingType.IsNullableValueType() + ? "(Value is null ? 0 : Value.GetHashCode())" + : $"global::System.Collections.Generic.EqualityComparer<{itemUnderlyingType}>.Default.GetHashCode(Value)"; return $$""" - + public override global::System.Int32 GetHashCode() { unchecked // Overflow is fine, just wrap @@ -47,13 +49,15 @@ public static string GenerateForAStruct(VoWorkItem item) // When the underlying type is a nullable value type (e.g. ushort?), EqualityComparer.Default.GetHashCode // has [DisallowNull] on its parameter in .NET 10+, causing CS8607. Use null-safe approach instead. - string hashCodeExpression = item.UnderlyingType.IsNullableValueType() - ? "Value is null ? 0 : Value.GetHashCode()" - : $"global::System.Collections.Generic.EqualityComparer<{itemUnderlyingType}>.Default.GetHashCode(Value)"; + string hashCodeExpression = item.IsUnderlyingAString && item.Config.GetStringDefaultComparerExpression() is { } structHashComparer + ? $"{structHashComparer}.GetHashCode(Value)" + : item.UnderlyingType.IsNullableValueType() + ? "Value is null ? 0 : Value.GetHashCode()" + : $"global::System.Collections.Generic.EqualityComparer<{itemUnderlyingType}>.Default.GetHashCode(Value)"; return $$""" - + public readonly override global::System.Int32 GetHashCode() { return {{hashCodeExpression}}; diff --git a/src/Vogen/GenerateCodeForStringComparers.cs b/src/Vogen/GenerateCodeForStringComparers.cs index 0a57885290f..52efe9558cc 100644 --- a/src/Vogen/GenerateCodeForStringComparers.cs +++ b/src/Vogen/GenerateCodeForStringComparers.cs @@ -11,7 +11,7 @@ public static string GenerateIfNeeded(VoWorkItem item, TypeDeclarationSyntax tds return string.Empty; } - if(item.Config.StringComparers != StringComparersGeneration.Generate) + if (item.Config.StringComparers != StringComparersGeneration.Generate) { return string.Empty; } @@ -35,9 +35,9 @@ public bool Equals({{tds.Identifier}} x, {{tds.Identifier}} y) return _comparer.Equals(x._value, y._value); } - public int GetHashCode({{tds.Identifier}} obj) + public int GetHashCode({{tds.Identifier}} obj) { - return _comparer.GetHashCode(); + return _comparer.GetHashCode(obj._value); } } diff --git a/src/Vogen/VogenConfiguration.cs b/src/Vogen/VogenConfiguration.cs index 41d052c7ecc..c031bf5ce5d 100644 --- a/src/Vogen/VogenConfiguration.cs +++ b/src/Vogen/VogenConfiguration.cs @@ -23,7 +23,8 @@ public record VogenConfiguration( OpenApiSchemaCustomizations OpenApiSchemaCustomizations, bool ExplicitlySpecifyTypeInValueObject, PrimitiveEqualityGeneration PrimitiveEqualityGeneration, - NumericsGeneration NumericsGeneration) + NumericsGeneration NumericsGeneration, + StringComparisonDefault StringDefaultComparison) { // Don't add default values here, they should be in DefaultInstance. @@ -52,5 +53,17 @@ public record VogenConfiguration( OpenApiSchemaCustomizations: OpenApiSchemaCustomizations.Omit, ExplicitlySpecifyTypeInValueObject: false, PrimitiveEqualityGeneration: PrimitiveEqualityGeneration.GenerateOperatorsAndMethods, - NumericsGeneration: NumericsGeneration.Omit); + NumericsGeneration: NumericsGeneration.Omit, + StringDefaultComparison: StringComparisonDefault.Omit); + + public string? GetStringDefaultComparerExpression() => StringDefaultComparison switch + { + StringComparisonDefault.Ordinal => "global::System.StringComparer.Ordinal", + StringComparisonDefault.OrdinalIgnoreCase => "global::System.StringComparer.OrdinalIgnoreCase", + StringComparisonDefault.CurrentCulture => "global::System.StringComparer.CurrentCulture", + StringComparisonDefault.CurrentCultureIgnoreCase => "global::System.StringComparer.CurrentCultureIgnoreCase", + StringComparisonDefault.InvariantCulture => "global::System.StringComparer.InvariantCulture", + StringComparisonDefault.InvariantCultureIgnoreCase => "global::System.StringComparer.InvariantCultureIgnoreCase", + _ => null + }; } diff --git a/tests/ConsumerTests/StringComparisons/ForClasses.cs b/tests/ConsumerTests/StringComparisons/ForClasses.cs index 106cf2b3bbb..d43bfec78b0 100644 --- a/tests/ConsumerTests/StringComparisons/ForClasses.cs +++ b/tests/ConsumerTests/StringComparisons/ForClasses.cs @@ -55,6 +55,24 @@ public void OrdinalIgnoreCase_Generic() (left == right).Should().BeFalse(); } + [Fact] + public void StringDefaultComparison_OrdinalIgnoreCase() + { + using var _ = new AssertionScope(); + + StringVo_Class_DefaultOrdinalIgnoreCase left = StringVo_Class_DefaultOrdinalIgnoreCase.From("abc"); + StringVo_Class_DefaultOrdinalIgnoreCase right = StringVo_Class_DefaultOrdinalIgnoreCase.From("ABC"); + + (left == right).Should().BeTrue(); + left.Equals(right).Should().BeTrue(); + left.Equals("ABC").Should().BeTrue(); + left.GetHashCode().Should().Be(right.GetHashCode()); + + Dictionary d = new(); + d.Add(StringVo_Class_DefaultOrdinalIgnoreCase.From("one"), 1); + d.ContainsKey(StringVo_Class_DefaultOrdinalIgnoreCase.From("ONE")).Should().BeTrue(); + } + [Fact] public void OrdinalIgnoreCase_in_a_dictionary() { diff --git a/tests/ConsumerTests/StringComparisons/ForRecordClasses.cs b/tests/ConsumerTests/StringComparisons/ForRecordClasses.cs index 6786b3e5c72..9d59346ea87 100644 --- a/tests/ConsumerTests/StringComparisons/ForRecordClasses.cs +++ b/tests/ConsumerTests/StringComparisons/ForRecordClasses.cs @@ -55,6 +55,24 @@ public void OrdinalIgnoreCase_Generic() (left == right).Should().BeFalse(); } + [Fact] + public void StringDefaultComparison_OrdinalIgnoreCase() + { + using var _ = new AssertionScope(); + + StringVo_RecordClass_DefaultOrdinalIgnoreCase left = StringVo_RecordClass_DefaultOrdinalIgnoreCase.From("abc"); + StringVo_RecordClass_DefaultOrdinalIgnoreCase right = StringVo_RecordClass_DefaultOrdinalIgnoreCase.From("ABC"); + + (left == right).Should().BeTrue(); + left.Equals(right).Should().BeTrue(); + left.Equals("ABC").Should().BeTrue(); + left.GetHashCode().Should().Be(right.GetHashCode()); + + Dictionary d = new(); + d.Add(StringVo_RecordClass_DefaultOrdinalIgnoreCase.From("one"), 1); + d.ContainsKey(StringVo_RecordClass_DefaultOrdinalIgnoreCase.From("ONE")).Should().BeTrue(); + } + [Fact] public void OrdinalIgnoreCase_in_a_dictionary() { diff --git a/tests/ConsumerTests/StringComparisons/ForRecordStructs.cs b/tests/ConsumerTests/StringComparisons/ForRecordStructs.cs index d949613fc44..b91b5de424b 100644 --- a/tests/ConsumerTests/StringComparisons/ForRecordStructs.cs +++ b/tests/ConsumerTests/StringComparisons/ForRecordStructs.cs @@ -58,6 +58,24 @@ public void OrdinalIgnoreCase_Generic() (left == right).Should().BeFalse(); } + [Fact] + public void StringDefaultComparison_OrdinalIgnoreCase() + { + using var _ = new AssertionScope(); + + StringVo_RecordStruct_DefaultOrdinalIgnoreCase left = StringVo_RecordStruct_DefaultOrdinalIgnoreCase.From("abc"); + StringVo_RecordStruct_DefaultOrdinalIgnoreCase right = StringVo_RecordStruct_DefaultOrdinalIgnoreCase.From("ABC"); + + (left == right).Should().BeTrue(); + left.Equals(right).Should().BeTrue(); + left.Equals("ABC").Should().BeTrue(); + left.GetHashCode().Should().Be(right.GetHashCode()); + + Dictionary d = new(); + d.Add(StringVo_RecordStruct_DefaultOrdinalIgnoreCase.From("one"), 1); + d.ContainsKey(StringVo_RecordStruct_DefaultOrdinalIgnoreCase.From("ONE")).Should().BeTrue(); + } + [Fact] public void OrdinalIgnoreCase_in_a_dictionary() { diff --git a/tests/ConsumerTests/StringComparisons/ForStructs.cs b/tests/ConsumerTests/StringComparisons/ForStructs.cs index 0d29d5debc6..f8676bebf25 100644 --- a/tests/ConsumerTests/StringComparisons/ForStructs.cs +++ b/tests/ConsumerTests/StringComparisons/ForStructs.cs @@ -71,6 +71,24 @@ public void OrdinalIgnoreCase_Generic() (left == right).Should().BeFalse(); } + [Fact] + public void StringDefaultComparison_OrdinalIgnoreCase() + { + using var _ = new AssertionScope(); + + StringVo_Struct_DefaultOrdinalIgnoreCase left = StringVo_Struct_DefaultOrdinalIgnoreCase.From("abc"); + StringVo_Struct_DefaultOrdinalIgnoreCase right = StringVo_Struct_DefaultOrdinalIgnoreCase.From("ABC"); + + (left == right).Should().BeTrue(); + left.Equals(right).Should().BeTrue(); + left.Equals("ABC").Should().BeTrue(); + left.GetHashCode().Should().Be(right.GetHashCode()); + + Dictionary d = new(); + d.Add(StringVo_Struct_DefaultOrdinalIgnoreCase.From("one"), 1); + d.ContainsKey(StringVo_Struct_DefaultOrdinalIgnoreCase.From("ONE")).Should().BeTrue(); + } + [Fact] public void OrdinalIgnoreCase_in_a_dictionary() { diff --git a/tests/ConsumerTests/StringComparisons/Types.cs b/tests/ConsumerTests/StringComparisons/Types.cs index cbf16506b24..717ee9feafa 100644 --- a/tests/ConsumerTests/StringComparisons/Types.cs +++ b/tests/ConsumerTests/StringComparisons/Types.cs @@ -90,3 +90,23 @@ public partial record struct StringVo_RecordStruct_Generic public partial struct StringVo_Struct_Generic { } + +[ValueObject(typeof(string), Conversions.Default, null, Customizations.None, DeserializationStrictness.AllowValidAndKnownInstances, DebuggerAttributeGeneration.Default, ComparisonGeneration.Default, StringComparersGeneration.Unspecified, CastOperator.Unspecified, CastOperator.Unspecified, ParsableForStrings.Unspecified, ParsableForPrimitives.Unspecified, TryFromGeneration.Unspecified, IsInitializedMethodGeneration.Unspecified, PrimitiveEqualityGeneration.Unspecified, NumericsGeneration.Unspecified, StringComparisonDefault.OrdinalIgnoreCase)] +public partial class StringVo_Class_DefaultOrdinalIgnoreCase +{ +} + +[ValueObject(typeof(string), Conversions.Default, null, Customizations.None, DeserializationStrictness.AllowValidAndKnownInstances, DebuggerAttributeGeneration.Default, ComparisonGeneration.Default, StringComparersGeneration.Unspecified, CastOperator.Unspecified, CastOperator.Unspecified, ParsableForStrings.Unspecified, ParsableForPrimitives.Unspecified, TryFromGeneration.Unspecified, IsInitializedMethodGeneration.Unspecified, PrimitiveEqualityGeneration.Unspecified, NumericsGeneration.Unspecified, StringComparisonDefault.OrdinalIgnoreCase)] +public partial record class StringVo_RecordClass_DefaultOrdinalIgnoreCase +{ +} + +[ValueObject(typeof(string), Conversions.Default, null, Customizations.None, DeserializationStrictness.AllowValidAndKnownInstances, DebuggerAttributeGeneration.Default, ComparisonGeneration.Default, StringComparersGeneration.Unspecified, CastOperator.Unspecified, CastOperator.Unspecified, ParsableForStrings.Unspecified, ParsableForPrimitives.Unspecified, TryFromGeneration.Unspecified, IsInitializedMethodGeneration.Unspecified, PrimitiveEqualityGeneration.Unspecified, NumericsGeneration.Unspecified, StringComparisonDefault.OrdinalIgnoreCase)] +public partial record struct StringVo_RecordStruct_DefaultOrdinalIgnoreCase +{ +} + +[ValueObject(typeof(string), Conversions.Default, null, Customizations.None, DeserializationStrictness.AllowValidAndKnownInstances, DebuggerAttributeGeneration.Default, ComparisonGeneration.Default, StringComparersGeneration.Unspecified, CastOperator.Unspecified, CastOperator.Unspecified, ParsableForStrings.Unspecified, ParsableForPrimitives.Unspecified, TryFromGeneration.Unspecified, IsInitializedMethodGeneration.Unspecified, PrimitiveEqualityGeneration.Unspecified, NumericsGeneration.Unspecified, StringComparisonDefault.OrdinalIgnoreCase)] +public partial struct StringVo_Struct_DefaultOrdinalIgnoreCase +{ +} diff --git a/tests/SnapshotTests/StringComparison/StringComparisonGenerationTests.cs b/tests/SnapshotTests/StringComparison/StringComparisonGenerationTests.cs index 26d7003cb35..f199c11a0dd 100644 --- a/tests/SnapshotTests/StringComparison/StringComparisonGenerationTests.cs +++ b/tests/SnapshotTests/StringComparison/StringComparisonGenerationTests.cs @@ -39,6 +39,40 @@ public partial class StringThing { } .RunOnAllFrameworks(); } + [Fact] + public Task Generates_with_default_comparison_OrdinalIgnoreCase() + { + string source = $$""" + using System; + using Vogen; + namespace Whatever; + + [ValueObject(typeof(string), Conversions.Default, null, Customizations.None, DeserializationStrictness.AllowValidAndKnownInstances, DebuggerAttributeGeneration.Default, ComparisonGeneration.Default, StringComparersGeneration.Unspecified, CastOperator.Unspecified, CastOperator.Unspecified, ParsableForStrings.Unspecified, ParsableForPrimitives.Unspecified, TryFromGeneration.Unspecified, IsInitializedMethodGeneration.Unspecified, PrimitiveEqualityGeneration.Unspecified, NumericsGeneration.Unspecified, StringComparisonDefault.OrdinalIgnoreCase)] + public partial class StringThing { } + """; + + return new SnapshotRunner() + .WithSource(source) + .RunOnAllFrameworks(); + } + + [Fact] + public Task Generates_comparers_class_with_default_comparison_when_both_are_specified() + { + string source = $$""" + using System; + using Vogen; + namespace Whatever; + + [ValueObject(typeof(string), Conversions.Default, null, Customizations.None, DeserializationStrictness.AllowValidAndKnownInstances, DebuggerAttributeGeneration.Default, ComparisonGeneration.Default, StringComparersGeneration.Generate, CastOperator.Unspecified, CastOperator.Unspecified, ParsableForStrings.Unspecified, ParsableForPrimitives.Unspecified, TryFromGeneration.Unspecified, IsInitializedMethodGeneration.Unspecified, PrimitiveEqualityGeneration.Unspecified, NumericsGeneration.Unspecified, StringComparisonDefault.OrdinalIgnoreCase)] + public partial class StringThing { } + """; + + return new SnapshotRunner() + .WithSource(source) + .RunOnAllFrameworks(); + } + [Fact] public Task Does_not_generate_the_equals_method_that_takes_a_StringComparison_when_not_a_string() { diff --git a/tests/SnapshotTests/StringComparison/snapshots/snap-v4.8/StringComparisonGenerationTests.Generates_comparers_class_with_default_comparison_when_both_are_specified.verified.txt b/tests/SnapshotTests/StringComparison/snapshots/snap-v4.8/StringComparisonGenerationTests.Generates_comparers_class_with_default_comparison_when_both_are_specified.verified.txt new file mode 100644 index 00000000000..cea860f8bfe --- /dev/null +++ b/tests/SnapshotTests/StringComparison/snapshots/snap-v4.8/StringComparisonGenerationTests.Generates_comparers_class_with_default_comparison_when_both_are_specified.verified.txt @@ -0,0 +1,620 @@ +[ +// ------------------------------------------------------------------------------ +// +// 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 +#pragma warning disable CS8604 // Possible null reference argument. + +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 +// Suppress warnings about CS8767 : Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). +#pragma warning disable CS8767 +namespace generator +{ + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Vogen", "1.0.0.0")] + 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::Whatever.StringThing), + new global::System.Lazy(() => new global::Whatever.StringThing.StringThingSystemTextJsonConverter()) + } + }; + 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 +#pragma warning disable CS8604 // Possible null reference argument. + +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 +// Suppress warnings about CS8767 : Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). +#pragma warning disable CS8767 +namespace Whatever +{ + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Vogen", "1.0.0.0")] + [global::System.Text.Json.Serialization.JsonConverter(typeof(StringThingSystemTextJsonConverter))] + [global::System.ComponentModel.TypeConverter(typeof(StringThingTypeConverter))] + [global::System.Diagnostics.DebuggerTypeProxyAttribute(typeof(StringThingDebugView))] + [global::System.Diagnostics.DebuggerDisplayAttribute("Underlying type: global::System.String, Value = { _value }")] + public partial class StringThing : global::System.IEquatable, global::System.IEquatable, global::System.IComparable, global::System.IComparable, global::System.IConvertible + { +#if DEBUG +private readonly global::System.Diagnostics.StackTrace _stackTrace = null!; +#endif +#if !VOGEN_NO_VALIDATION + private readonly global::System.Boolean _isInitialized; +#endif + private readonly global::System.String _value; + /// + /// Gets the underlying value if set, otherwise a is thrown. + /// + public global::System.String 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 StringThing() + { +#if DEBUG + _stackTrace = new global::System.Diagnostics.StackTrace(); +#endif +#if !VOGEN_NO_VALIDATION + _isInitialized = false; +#endif + _value = default; + } + + [global::System.Diagnostics.DebuggerStepThroughAttribute] + private StringThing(global::System.String 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 StringThing From(global::System.String value) + { + if (value is null) + { + ThrowHelper.ThrowWhenCreatedWithNull(); + return default !; + } + + return new StringThing(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. + public static bool TryFrom( +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + global::System.String value, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] +#endif + out StringThing vo) + { + if (value is null) + { + vo = default; + return false; + } + + vo = new StringThing(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 global::Vogen.ValueObjectOrError TryFrom(global::System.String value) + { + if (value is null) + { + return new global::Vogen.ValueObjectOrError(global::Vogen.Validation.Invalid("The value provided was null")); + } + + return new global::Vogen.ValueObjectOrError(new StringThing(value)); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] +#if VOGEN_NO_VALIDATION +#pragma warning disable CS8775 + public bool IsInitialized() => true; +#pragma warning restore CS8775 +#else + public bool IsInitialized() => _isInitialized; +#endif +#nullable disable + public static class Comparers + { + private class __StringEqualityComparer : global::System.Collections.Generic.IEqualityComparer + { + readonly global::System.StringComparer _comparer; + public __StringEqualityComparer(global::System.StringComparer comparer) + { + _comparer = comparer; + } + + public bool Equals(StringThing x, StringThing y) + { + return _comparer.Equals(x._value, y._value); + } + + public int GetHashCode(StringThing obj) + { + return _comparer.GetHashCode(obj._value); + } + } + + public static global::System.Collections.Generic.IEqualityComparer Ordinal => new __StringEqualityComparer(global::System.StringComparer.Ordinal); + public static global::System.Collections.Generic.IEqualityComparer OrdinalIgnoreCase => new __StringEqualityComparer(global::System.StringComparer.OrdinalIgnoreCase); + public static global::System.Collections.Generic.IEqualityComparer CurrentCulture => new __StringEqualityComparer(global::System.StringComparer.CurrentCulture); + public static global::System.Collections.Generic.IEqualityComparer CurrentCultureIgnoreCase => new __StringEqualityComparer(global::System.StringComparer.CurrentCultureIgnoreCase); + public static global::System.Collections.Generic.IEqualityComparer InvariantCulture => new __StringEqualityComparer(global::System.StringComparer.InvariantCulture); + public static global::System.Collections.Generic.IEqualityComparer InvariantCultureIgnoreCase => new __StringEqualityComparer(global::System.StringComparer.InvariantCultureIgnoreCase); +#nullable restore + } + + // only called internally when something has been deserialized into + // its primitive type. + private static StringThing __Deserialize(global::System.String value) + { + if (value is null) + { + ThrowHelper.ThrowWhenCreatedWithNull(); + return default !; + } + + return new StringThing(value); + } + + public global::System.Boolean Equals(StringThing other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + // 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; + if (ReferenceEquals(this, other)) + { + return true; + } + + return GetType() == other.GetType() && global::System.StringComparer.OrdinalIgnoreCase.Equals(Value, other.Value); + } + + public global::System.Boolean Equals(StringThing other, global::System.Collections.Generic.IEqualityComparer comparer) + { + return comparer.Equals(this, other); + } + + public global::System.Boolean Equals(global::System.String primitive) + { + return global::System.StringComparer.OrdinalIgnoreCase.Equals(Value, primitive); + } + + public global::System.Boolean Equals(global::System.String primitive, global::System.StringComparer comparer) + { + return comparer.Equals(Value, primitive); + } + + public override global::System.Boolean Equals(global::System.Object obj) + { + return Equals(obj as StringThing); + } + + public static global::System.Boolean operator ==(StringThing left, StringThing right) => Equals(left, right); + public static global::System.Boolean operator !=(StringThing left, StringThing right) => !Equals(left, right); + public static global::System.Boolean operator ==(StringThing left, global::System.String right) => global::System.StringComparer.OrdinalIgnoreCase.Equals(left.Value, right); + public static global::System.Boolean operator ==(global::System.String left, StringThing right) => global::System.StringComparer.OrdinalIgnoreCase.Equals(left, right.Value); + public static global::System.Boolean operator !=(global::System.String left, StringThing right) => !(left == right); + public static global::System.Boolean operator !=(StringThing left, global::System.String right) => !(left == right); + public static explicit operator StringThing(global::System.String value) => From(value); + public static explicit operator global::System.String(StringThing value) => value.Value; + public int CompareTo(StringThing other) + { + if (other is null) + return 1; + return Value.CompareTo(other.Value); + } + + public int CompareTo(object other) + { + if (other is null) + return 1; + if (other is StringThing x) + return CompareTo(x); + ThrowHelper.ThrowArgumentException("Cannot compare to object as it is not of type StringThing", nameof(other)); + return 0; + } + + /// + /// + /// + /// True if the value passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse( +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + global::System.String s, global::System.IFormatProvider provider, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] +#endif + out StringThing result) + { + if (s is null) + { + result = default; + return false; + } + + result = new StringThing(s); + return true; + } + + /// + /// + /// + /// The value created via the method. + /// + /// Thrown when the value can be parsed, but is not valid. + public static StringThing Parse(global::System.String s, global::System.IFormatProvider provider) + { + return From(s); + } + +#nullable disable +#nullable restore +#nullable disable + /// + public System.TypeCode GetTypeCode() + { + return IsInitialized() ? Value.GetTypeCode() : default; + } + + /// + bool System.IConvertible.ToBoolean(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToBoolean(provider) : default; + } + + /// + char System.IConvertible.ToChar(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToChar(provider) : default; + } + + /// + sbyte System.IConvertible.ToSByte(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToSByte(provider) : default; + } + + /// + byte System.IConvertible.ToByte(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToByte(provider) : default; + } + + /// + short System.IConvertible.ToInt16(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToInt16(provider) : default; + } + + /// + ushort System.IConvertible.ToUInt16(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToUInt16(provider) : default; + } + + /// + int System.IConvertible.ToInt32(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToInt32(provider) : default; + } + + /// + uint System.IConvertible.ToUInt32(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToUInt32(provider) : default; + } + + /// + long System.IConvertible.ToInt64(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToInt64(provider) : default; + } + + /// + ulong System.IConvertible.ToUInt64(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToUInt64(provider) : default; + } + + /// + float System.IConvertible.ToSingle(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToSingle(provider) : default; + } + + /// + double System.IConvertible.ToDouble(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToDouble(provider) : default; + } + + /// + decimal System.IConvertible.ToDecimal(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToDecimal(provider) : default; + } + + /// + System.DateTime System.IConvertible.ToDateTime(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToDateTime(provider) : default; + } + + /// + object System.IConvertible.ToType(global::System.Type @type, global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToType(@type, provider) : default; + } + +#nullable restore + public override global::System.Int32 GetHashCode() + { + unchecked // Overflow is fine, just wrap + { + global::System.Int32 hash = (global::System.Int32)2166136261; + hash = (hash * 16777619) ^ GetType().GetHashCode(); + hash = (hash * 16777619) ^ global::System.StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + return hash; + } + } + + /// + public override global::System.String ToString() => IsInitialized() ? Value.ToString() ?? "" : "[UNINITIALIZED]"; + /// + public global::System.String ToString(global::System.IFormatProvider provider) => IsInitialized() ? Value.ToString(provider) ?? "" : "[UNINITIALIZED]"; + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + private void EnsureInitialized() + { + if (!IsInitialized()) + { +#if DEBUG + ThrowHelper.ThrowWhenNotInitialized(_stackTrace); +#else + ThrowHelper.ThrowWhenNotInitialized(); +#endif + } + } + +#nullable disable + /// + /// Converts a StringThing to or from JSON. + /// + public partial class StringThingSystemTextJsonConverter : global::System.Text.Json.Serialization.JsonConverter + { + public override StringThing Read(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) + { + return DeserializeJson(reader.GetString()); + } + + public override void Write(global::System.Text.Json.Utf8JsonWriter writer, StringThing value, global::System.Text.Json.JsonSerializerOptions options) + { + writer.WriteStringValue(value.Value); + } + +#if NET6_0_OR_GREATER + public override StringThing ReadAsPropertyName(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) + { + return DeserializeJson(reader.GetString()); + } + + public override void WriteAsPropertyName(global::System.Text.Json.Utf8JsonWriter writer, StringThing value, global::System.Text.Json.JsonSerializerOptions options) + { + writer.WritePropertyName(value.Value); + } +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + private static void ThrowJsonExceptionWhenValidationFails(global::Vogen.Validation validation) + { + var e = ThrowHelper.CreateValidationException(validation); + throw new global::System.Text.Json.JsonException(null, e); + } + + private static void ThrowJsonExceptionWhenNull(global::System.String value) + { + if (value == null) + { + var e = ThrowHelper.CreateCannotBeNullException(); + throw new global::System.Text.Json.JsonException(null, e); + } + } + + private static StringThing DeserializeJson(global::System.String value) + { + ThrowJsonExceptionWhenNull(value); + return new StringThing(value); + } + } + +#nullable restore +#nullable disable + class StringThingTypeConverter : global::System.ComponentModel.TypeConverter + { + public override global::System.Boolean CanConvertFrom(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Type sourceType) + { + return 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) + { + var stringValue = value as global::System.String; + if (stringValue != null) + { + return StringThing.__Deserialize(stringValue); + } + + return base.ConvertFrom(context, culture, value); + } + + public override bool CanConvertTo(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Type sourceType) + { + return 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 StringThing idValue) + { + if (destinationType == typeof(global::System.String)) + { + return idValue.Value; + } + } + + return base.ConvertTo(context, culture, value, destinationType); + } + } + +#nullable restore + internal sealed class StringThingDebugView + { + private readonly StringThing _t; + StringThingDebugView(StringThing t) + { + _t = t; + } + + public global::System.String UnderlyingType => "global::System.String"; + public global::System.String Value => _t.Value; + public global::System.String Conversions => @"[global::System.Text.Json.Serialization.JsonConverter(typeof(StringThingSystemTextJsonConverter))] +[global::System.ComponentModel.TypeConverter(typeof(StringThingTypeConverter))] +"; + } + + 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 CreateCannotBeNullException(); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenNotInitialized() => throw CreateValidationException("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 CreateValidationException("Use of uninitialized Value Object at: " + stackTrace ?? ""); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenValidationFails(global::Vogen.Validation validation) + { + throw CreateValidationException(validation); + } + + internal static global::System.Exception CreateValidationException(string message) => new global::Vogen.ValueObjectValidationException(message); + internal static global::System.Exception CreateCannotBeNullException() => new global::Vogen.ValueObjectValidationException("Cannot create a value object with null."); + internal static global::System.Exception CreateValidationException(global::Vogen.Validation validation) + { + var ex = CreateValidationException(validation.ErrorMessage); + if (validation.Data != null) + { + foreach (var kvp in validation.Data) + { + ex.Data[kvp.Key] = kvp.Value; + } + } + + return ex; + } + } + } +} +] \ No newline at end of file diff --git a/tests/SnapshotTests/StringComparison/snapshots/snap-v4.8/StringComparisonGenerationTests.Generates_when_specified.verified.txt b/tests/SnapshotTests/StringComparison/snapshots/snap-v4.8/StringComparisonGenerationTests.Generates_when_specified.verified.txt index 7c9943c0d1a..5662b7abb80 100644 --- a/tests/SnapshotTests/StringComparison/snapshots/snap-v4.8/StringComparisonGenerationTests.Generates_when_specified.verified.txt +++ b/tests/SnapshotTests/StringComparison/snapshots/snap-v4.8/StringComparisonGenerationTests.Generates_when_specified.verified.txt @@ -208,7 +208,7 @@ private readonly global::System.Diagnostics.StackTrace _stackTrace = null!; public int GetHashCode(StringThing obj) { - return _comparer.GetHashCode(); + return _comparer.GetHashCode(obj._value); } } diff --git a/tests/SnapshotTests/StringComparison/snapshots/snap-v4.8/StringComparisonGenerationTests.Generates_with_default_comparison_OrdinalIgnoreCase.verified.txt b/tests/SnapshotTests/StringComparison/snapshots/snap-v4.8/StringComparisonGenerationTests.Generates_with_default_comparison_OrdinalIgnoreCase.verified.txt new file mode 100644 index 00000000000..661b6febf13 --- /dev/null +++ b/tests/SnapshotTests/StringComparison/snapshots/snap-v4.8/StringComparisonGenerationTests.Generates_with_default_comparison_OrdinalIgnoreCase.verified.txt @@ -0,0 +1,589 @@ +[ +// ------------------------------------------------------------------------------ +// +// 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 +#pragma warning disable CS8604 // Possible null reference argument. + +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 +// Suppress warnings about CS8767 : Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). +#pragma warning disable CS8767 +namespace generator +{ + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Vogen", "1.0.0.0")] + 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::Whatever.StringThing), + new global::System.Lazy(() => new global::Whatever.StringThing.StringThingSystemTextJsonConverter()) + } + }; + 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 +#pragma warning disable CS8604 // Possible null reference argument. + +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 +// Suppress warnings about CS8767 : Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). +#pragma warning disable CS8767 +namespace Whatever +{ + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Vogen", "1.0.0.0")] + [global::System.Text.Json.Serialization.JsonConverter(typeof(StringThingSystemTextJsonConverter))] + [global::System.ComponentModel.TypeConverter(typeof(StringThingTypeConverter))] + [global::System.Diagnostics.DebuggerTypeProxyAttribute(typeof(StringThingDebugView))] + [global::System.Diagnostics.DebuggerDisplayAttribute("Underlying type: global::System.String, Value = { _value }")] + public partial class StringThing : global::System.IEquatable, global::System.IEquatable, global::System.IComparable, global::System.IComparable, global::System.IConvertible + { +#if DEBUG +private readonly global::System.Diagnostics.StackTrace _stackTrace = null!; +#endif +#if !VOGEN_NO_VALIDATION + private readonly global::System.Boolean _isInitialized; +#endif + private readonly global::System.String _value; + /// + /// Gets the underlying value if set, otherwise a is thrown. + /// + public global::System.String 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 StringThing() + { +#if DEBUG + _stackTrace = new global::System.Diagnostics.StackTrace(); +#endif +#if !VOGEN_NO_VALIDATION + _isInitialized = false; +#endif + _value = default; + } + + [global::System.Diagnostics.DebuggerStepThroughAttribute] + private StringThing(global::System.String 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 StringThing From(global::System.String value) + { + if (value is null) + { + ThrowHelper.ThrowWhenCreatedWithNull(); + return default !; + } + + return new StringThing(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. + public static bool TryFrom( +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + global::System.String value, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] +#endif + out StringThing vo) + { + if (value is null) + { + vo = default; + return false; + } + + vo = new StringThing(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 global::Vogen.ValueObjectOrError TryFrom(global::System.String value) + { + if (value is null) + { + return new global::Vogen.ValueObjectOrError(global::Vogen.Validation.Invalid("The value provided was null")); + } + + return new global::Vogen.ValueObjectOrError(new StringThing(value)); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] +#if VOGEN_NO_VALIDATION +#pragma warning disable CS8775 + public bool IsInitialized() => true; +#pragma warning restore CS8775 +#else + public bool IsInitialized() => _isInitialized; +#endif + // only called internally when something has been deserialized into + // its primitive type. + private static StringThing __Deserialize(global::System.String value) + { + if (value is null) + { + ThrowHelper.ThrowWhenCreatedWithNull(); + return default !; + } + + return new StringThing(value); + } + + public global::System.Boolean Equals(StringThing other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + // 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; + if (ReferenceEquals(this, other)) + { + return true; + } + + return GetType() == other.GetType() && global::System.StringComparer.OrdinalIgnoreCase.Equals(Value, other.Value); + } + + public global::System.Boolean Equals(StringThing other, global::System.Collections.Generic.IEqualityComparer comparer) + { + return comparer.Equals(this, other); + } + + public global::System.Boolean Equals(global::System.String primitive) + { + return global::System.StringComparer.OrdinalIgnoreCase.Equals(Value, primitive); + } + + public global::System.Boolean Equals(global::System.String primitive, global::System.StringComparer comparer) + { + return comparer.Equals(Value, primitive); + } + + public override global::System.Boolean Equals(global::System.Object obj) + { + return Equals(obj as StringThing); + } + + public static global::System.Boolean operator ==(StringThing left, StringThing right) => Equals(left, right); + public static global::System.Boolean operator !=(StringThing left, StringThing right) => !Equals(left, right); + public static global::System.Boolean operator ==(StringThing left, global::System.String right) => global::System.StringComparer.OrdinalIgnoreCase.Equals(left.Value, right); + public static global::System.Boolean operator ==(global::System.String left, StringThing right) => global::System.StringComparer.OrdinalIgnoreCase.Equals(left, right.Value); + public static global::System.Boolean operator !=(global::System.String left, StringThing right) => !(left == right); + public static global::System.Boolean operator !=(StringThing left, global::System.String right) => !(left == right); + public static explicit operator StringThing(global::System.String value) => From(value); + public static explicit operator global::System.String(StringThing value) => value.Value; + public int CompareTo(StringThing other) + { + if (other is null) + return 1; + return Value.CompareTo(other.Value); + } + + public int CompareTo(object other) + { + if (other is null) + return 1; + if (other is StringThing x) + return CompareTo(x); + ThrowHelper.ThrowArgumentException("Cannot compare to object as it is not of type StringThing", nameof(other)); + return 0; + } + + /// + /// + /// + /// True if the value passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse( +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + global::System.String s, global::System.IFormatProvider provider, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] +#endif + out StringThing result) + { + if (s is null) + { + result = default; + return false; + } + + result = new StringThing(s); + return true; + } + + /// + /// + /// + /// The value created via the method. + /// + /// Thrown when the value can be parsed, but is not valid. + public static StringThing Parse(global::System.String s, global::System.IFormatProvider provider) + { + return From(s); + } + +#nullable disable +#nullable restore +#nullable disable + /// + public System.TypeCode GetTypeCode() + { + return IsInitialized() ? Value.GetTypeCode() : default; + } + + /// + bool System.IConvertible.ToBoolean(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToBoolean(provider) : default; + } + + /// + char System.IConvertible.ToChar(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToChar(provider) : default; + } + + /// + sbyte System.IConvertible.ToSByte(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToSByte(provider) : default; + } + + /// + byte System.IConvertible.ToByte(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToByte(provider) : default; + } + + /// + short System.IConvertible.ToInt16(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToInt16(provider) : default; + } + + /// + ushort System.IConvertible.ToUInt16(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToUInt16(provider) : default; + } + + /// + int System.IConvertible.ToInt32(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToInt32(provider) : default; + } + + /// + uint System.IConvertible.ToUInt32(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToUInt32(provider) : default; + } + + /// + long System.IConvertible.ToInt64(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToInt64(provider) : default; + } + + /// + ulong System.IConvertible.ToUInt64(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToUInt64(provider) : default; + } + + /// + float System.IConvertible.ToSingle(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToSingle(provider) : default; + } + + /// + double System.IConvertible.ToDouble(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToDouble(provider) : default; + } + + /// + decimal System.IConvertible.ToDecimal(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToDecimal(provider) : default; + } + + /// + System.DateTime System.IConvertible.ToDateTime(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToDateTime(provider) : default; + } + + /// + object System.IConvertible.ToType(global::System.Type @type, global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToType(@type, provider) : default; + } + +#nullable restore + public override global::System.Int32 GetHashCode() + { + unchecked // Overflow is fine, just wrap + { + global::System.Int32 hash = (global::System.Int32)2166136261; + hash = (hash * 16777619) ^ GetType().GetHashCode(); + hash = (hash * 16777619) ^ global::System.StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + return hash; + } + } + + /// + public override global::System.String ToString() => IsInitialized() ? Value.ToString() ?? "" : "[UNINITIALIZED]"; + /// + public global::System.String ToString(global::System.IFormatProvider provider) => IsInitialized() ? Value.ToString(provider) ?? "" : "[UNINITIALIZED]"; + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + private void EnsureInitialized() + { + if (!IsInitialized()) + { +#if DEBUG + ThrowHelper.ThrowWhenNotInitialized(_stackTrace); +#else + ThrowHelper.ThrowWhenNotInitialized(); +#endif + } + } + +#nullable disable + /// + /// Converts a StringThing to or from JSON. + /// + public partial class StringThingSystemTextJsonConverter : global::System.Text.Json.Serialization.JsonConverter + { + public override StringThing Read(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) + { + return DeserializeJson(reader.GetString()); + } + + public override void Write(global::System.Text.Json.Utf8JsonWriter writer, StringThing value, global::System.Text.Json.JsonSerializerOptions options) + { + writer.WriteStringValue(value.Value); + } + +#if NET6_0_OR_GREATER + public override StringThing ReadAsPropertyName(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) + { + return DeserializeJson(reader.GetString()); + } + + public override void WriteAsPropertyName(global::System.Text.Json.Utf8JsonWriter writer, StringThing value, global::System.Text.Json.JsonSerializerOptions options) + { + writer.WritePropertyName(value.Value); + } +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + private static void ThrowJsonExceptionWhenValidationFails(global::Vogen.Validation validation) + { + var e = ThrowHelper.CreateValidationException(validation); + throw new global::System.Text.Json.JsonException(null, e); + } + + private static void ThrowJsonExceptionWhenNull(global::System.String value) + { + if (value == null) + { + var e = ThrowHelper.CreateCannotBeNullException(); + throw new global::System.Text.Json.JsonException(null, e); + } + } + + private static StringThing DeserializeJson(global::System.String value) + { + ThrowJsonExceptionWhenNull(value); + return new StringThing(value); + } + } + +#nullable restore +#nullable disable + class StringThingTypeConverter : global::System.ComponentModel.TypeConverter + { + public override global::System.Boolean CanConvertFrom(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Type sourceType) + { + return 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) + { + var stringValue = value as global::System.String; + if (stringValue != null) + { + return StringThing.__Deserialize(stringValue); + } + + return base.ConvertFrom(context, culture, value); + } + + public override bool CanConvertTo(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Type sourceType) + { + return 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 StringThing idValue) + { + if (destinationType == typeof(global::System.String)) + { + return idValue.Value; + } + } + + return base.ConvertTo(context, culture, value, destinationType); + } + } + +#nullable restore + internal sealed class StringThingDebugView + { + private readonly StringThing _t; + StringThingDebugView(StringThing t) + { + _t = t; + } + + public global::System.String UnderlyingType => "global::System.String"; + public global::System.String Value => _t.Value; + public global::System.String Conversions => @"[global::System.Text.Json.Serialization.JsonConverter(typeof(StringThingSystemTextJsonConverter))] +[global::System.ComponentModel.TypeConverter(typeof(StringThingTypeConverter))] +"; + } + + 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 CreateCannotBeNullException(); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenNotInitialized() => throw CreateValidationException("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 CreateValidationException("Use of uninitialized Value Object at: " + stackTrace ?? ""); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenValidationFails(global::Vogen.Validation validation) + { + throw CreateValidationException(validation); + } + + internal static global::System.Exception CreateValidationException(string message) => new global::Vogen.ValueObjectValidationException(message); + internal static global::System.Exception CreateCannotBeNullException() => new global::Vogen.ValueObjectValidationException("Cannot create a value object with null."); + internal static global::System.Exception CreateValidationException(global::Vogen.Validation validation) + { + var ex = CreateValidationException(validation.ErrorMessage); + if (validation.Data != null) + { + foreach (var kvp in validation.Data) + { + ex.Data[kvp.Key] = kvp.Value; + } + } + + return ex; + } + } + } +} +] \ No newline at end of file diff --git a/tests/SnapshotTests/StringComparison/snapshots/snap-v8.0/StringComparisonGenerationTests.Generates_comparers_class_with_default_comparison_when_both_are_specified.verified.txt b/tests/SnapshotTests/StringComparison/snapshots/snap-v8.0/StringComparisonGenerationTests.Generates_comparers_class_with_default_comparison_when_both_are_specified.verified.txt new file mode 100644 index 00000000000..62b4cd9b562 --- /dev/null +++ b/tests/SnapshotTests/StringComparison/snapshots/snap-v8.0/StringComparisonGenerationTests.Generates_comparers_class_with_default_comparison_when_both_are_specified.verified.txt @@ -0,0 +1,620 @@ +[ +// ------------------------------------------------------------------------------ +// +// 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 +#pragma warning disable CS8604 // Possible null reference argument. + +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 +// Suppress warnings about CS8767 : Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). +#pragma warning disable CS8767 +namespace generator +{ + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Vogen", "1.0.0.0")] + 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::Whatever.StringThing), + new global::System.Lazy(() => new global::Whatever.StringThing.StringThingSystemTextJsonConverter()) + } + }; + 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 +#pragma warning disable CS8604 // Possible null reference argument. + +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 +// Suppress warnings about CS8767 : Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). +#pragma warning disable CS8767 +namespace Whatever +{ + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Vogen", "1.0.0.0")] + [global::System.Text.Json.Serialization.JsonConverter(typeof(StringThingSystemTextJsonConverter))] + [global::System.ComponentModel.TypeConverter(typeof(StringThingTypeConverter))] + [global::System.Diagnostics.DebuggerTypeProxyAttribute(typeof(StringThingDebugView))] + [global::System.Diagnostics.DebuggerDisplayAttribute("Underlying type: global::System.String, Value = { _value }")] + public partial class StringThing : global::System.IEquatable, global::System.IEquatable, global::System.IComparable, global::System.IComparable, global::System.IParsable, global::System.IConvertible + { +#if DEBUG +private readonly global::System.Diagnostics.StackTrace _stackTrace = null!; +#endif +#if !VOGEN_NO_VALIDATION + private readonly global::System.Boolean _isInitialized; +#endif + private readonly global::System.String _value; + /// + /// Gets the underlying value if set, otherwise a is thrown. + /// + public global::System.String 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 StringThing() + { +#if DEBUG + _stackTrace = new global::System.Diagnostics.StackTrace(); +#endif +#if !VOGEN_NO_VALIDATION + _isInitialized = false; +#endif + _value = default; + } + + [global::System.Diagnostics.DebuggerStepThroughAttribute] + private StringThing(global::System.String 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 StringThing From(global::System.String value) + { + if (value is null) + { + ThrowHelper.ThrowWhenCreatedWithNull(); + return default !; + } + + return new StringThing(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. + public static bool TryFrom( +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + global::System.String value, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] +#endif + out StringThing vo) + { + if (value is null) + { + vo = default; + return false; + } + + vo = new StringThing(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 global::Vogen.ValueObjectOrError TryFrom(global::System.String value) + { + if (value is null) + { + return new global::Vogen.ValueObjectOrError(global::Vogen.Validation.Invalid("The value provided was null")); + } + + return new global::Vogen.ValueObjectOrError(new StringThing(value)); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] +#if VOGEN_NO_VALIDATION +#pragma warning disable CS8775 + public bool IsInitialized() => true; +#pragma warning restore CS8775 +#else + public bool IsInitialized() => _isInitialized; +#endif +#nullable disable + public static class Comparers + { + private class __StringEqualityComparer : global::System.Collections.Generic.IEqualityComparer + { + readonly global::System.StringComparer _comparer; + public __StringEqualityComparer(global::System.StringComparer comparer) + { + _comparer = comparer; + } + + public bool Equals(StringThing x, StringThing y) + { + return _comparer.Equals(x._value, y._value); + } + + public int GetHashCode(StringThing obj) + { + return _comparer.GetHashCode(obj._value); + } + } + + public static global::System.Collections.Generic.IEqualityComparer Ordinal => new __StringEqualityComparer(global::System.StringComparer.Ordinal); + public static global::System.Collections.Generic.IEqualityComparer OrdinalIgnoreCase => new __StringEqualityComparer(global::System.StringComparer.OrdinalIgnoreCase); + public static global::System.Collections.Generic.IEqualityComparer CurrentCulture => new __StringEqualityComparer(global::System.StringComparer.CurrentCulture); + public static global::System.Collections.Generic.IEqualityComparer CurrentCultureIgnoreCase => new __StringEqualityComparer(global::System.StringComparer.CurrentCultureIgnoreCase); + public static global::System.Collections.Generic.IEqualityComparer InvariantCulture => new __StringEqualityComparer(global::System.StringComparer.InvariantCulture); + public static global::System.Collections.Generic.IEqualityComparer InvariantCultureIgnoreCase => new __StringEqualityComparer(global::System.StringComparer.InvariantCultureIgnoreCase); +#nullable restore + } + + // only called internally when something has been deserialized into + // its primitive type. + private static StringThing __Deserialize(global::System.String value) + { + if (value is null) + { + ThrowHelper.ThrowWhenCreatedWithNull(); + return default !; + } + + return new StringThing(value); + } + + public global::System.Boolean Equals(StringThing other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + // 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; + if (ReferenceEquals(this, other)) + { + return true; + } + + return GetType() == other.GetType() && global::System.StringComparer.OrdinalIgnoreCase.Equals(Value, other.Value); + } + + public global::System.Boolean Equals(StringThing other, global::System.Collections.Generic.IEqualityComparer comparer) + { + return comparer.Equals(this, other); + } + + public global::System.Boolean Equals(global::System.String primitive) + { + return global::System.StringComparer.OrdinalIgnoreCase.Equals(Value, primitive); + } + + public global::System.Boolean Equals(global::System.String primitive, global::System.StringComparer comparer) + { + return comparer.Equals(Value, primitive); + } + + public override global::System.Boolean Equals(global::System.Object obj) + { + return Equals(obj as StringThing); + } + + public static global::System.Boolean operator ==(StringThing left, StringThing right) => Equals(left, right); + public static global::System.Boolean operator !=(StringThing left, StringThing right) => !Equals(left, right); + public static global::System.Boolean operator ==(StringThing left, global::System.String right) => global::System.StringComparer.OrdinalIgnoreCase.Equals(left.Value, right); + public static global::System.Boolean operator ==(global::System.String left, StringThing right) => global::System.StringComparer.OrdinalIgnoreCase.Equals(left, right.Value); + public static global::System.Boolean operator !=(global::System.String left, StringThing right) => !(left == right); + public static global::System.Boolean operator !=(StringThing left, global::System.String right) => !(left == right); + public static explicit operator StringThing(global::System.String value) => From(value); + public static explicit operator global::System.String(StringThing value) => value.Value; + public int CompareTo(StringThing other) + { + if (other is null) + return 1; + return Value.CompareTo(other.Value); + } + + public int CompareTo(object other) + { + if (other is null) + return 1; + if (other is StringThing x) + return CompareTo(x); + ThrowHelper.ThrowArgumentException("Cannot compare to object as it is not of type StringThing", nameof(other)); + return 0; + } + + /// + /// + /// + /// True if the value passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse( +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + global::System.String s, global::System.IFormatProvider provider, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] +#endif + out StringThing result) + { + if (s is null) + { + result = default; + return false; + } + + result = new StringThing(s); + return true; + } + + /// + /// + /// + /// The value created via the method. + /// + /// Thrown when the value can be parsed, but is not valid. + public static StringThing Parse(global::System.String s, global::System.IFormatProvider provider) + { + return From(s); + } + +#nullable disable +#nullable restore +#nullable disable + /// + public System.TypeCode GetTypeCode() + { + return IsInitialized() ? Value.GetTypeCode() : default; + } + + /// + bool System.IConvertible.ToBoolean(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToBoolean(provider) : default; + } + + /// + byte System.IConvertible.ToByte(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToByte(provider) : default; + } + + /// + char System.IConvertible.ToChar(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToChar(provider) : default; + } + + /// + System.DateTime System.IConvertible.ToDateTime(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToDateTime(provider) : default; + } + + /// + decimal System.IConvertible.ToDecimal(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToDecimal(provider) : default; + } + + /// + double System.IConvertible.ToDouble(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToDouble(provider) : default; + } + + /// + short System.IConvertible.ToInt16(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToInt16(provider) : default; + } + + /// + int System.IConvertible.ToInt32(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToInt32(provider) : default; + } + + /// + long System.IConvertible.ToInt64(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToInt64(provider) : default; + } + + /// + sbyte System.IConvertible.ToSByte(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToSByte(provider) : default; + } + + /// + float System.IConvertible.ToSingle(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToSingle(provider) : default; + } + + /// + object System.IConvertible.ToType(global::System.Type @type, global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToType(@type, provider) : default; + } + + /// + ushort System.IConvertible.ToUInt16(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToUInt16(provider) : default; + } + + /// + uint System.IConvertible.ToUInt32(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToUInt32(provider) : default; + } + + /// + ulong System.IConvertible.ToUInt64(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToUInt64(provider) : default; + } + +#nullable restore + public override global::System.Int32 GetHashCode() + { + unchecked // Overflow is fine, just wrap + { + global::System.Int32 hash = (global::System.Int32)2166136261; + hash = (hash * 16777619) ^ GetType().GetHashCode(); + hash = (hash * 16777619) ^ global::System.StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + return hash; + } + } + + /// + public override global::System.String ToString() => IsInitialized() ? Value.ToString() ?? "" : "[UNINITIALIZED]"; + /// + public global::System.String ToString(global::System.IFormatProvider provider) => IsInitialized() ? Value.ToString(provider) ?? "" : "[UNINITIALIZED]"; + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + private void EnsureInitialized() + { + if (!IsInitialized()) + { +#if DEBUG + ThrowHelper.ThrowWhenNotInitialized(_stackTrace); +#else + ThrowHelper.ThrowWhenNotInitialized(); +#endif + } + } + +#nullable disable + /// + /// Converts a StringThing to or from JSON. + /// + public partial class StringThingSystemTextJsonConverter : global::System.Text.Json.Serialization.JsonConverter + { + public override StringThing Read(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) + { + return DeserializeJson(reader.GetString()); + } + + public override void Write(global::System.Text.Json.Utf8JsonWriter writer, StringThing value, global::System.Text.Json.JsonSerializerOptions options) + { + writer.WriteStringValue(value.Value); + } + +#if NET6_0_OR_GREATER + public override StringThing ReadAsPropertyName(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) + { + return DeserializeJson(reader.GetString()); + } + + public override void WriteAsPropertyName(global::System.Text.Json.Utf8JsonWriter writer, StringThing value, global::System.Text.Json.JsonSerializerOptions options) + { + writer.WritePropertyName(value.Value); + } +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + private static void ThrowJsonExceptionWhenValidationFails(global::Vogen.Validation validation) + { + var e = ThrowHelper.CreateValidationException(validation); + throw new global::System.Text.Json.JsonException(null, e); + } + + private static void ThrowJsonExceptionWhenNull(global::System.String value) + { + if (value == null) + { + var e = ThrowHelper.CreateCannotBeNullException(); + throw new global::System.Text.Json.JsonException(null, e); + } + } + + private static StringThing DeserializeJson(global::System.String value) + { + ThrowJsonExceptionWhenNull(value); + return new StringThing(value); + } + } + +#nullable restore +#nullable disable + class StringThingTypeConverter : global::System.ComponentModel.TypeConverter + { + public override global::System.Boolean CanConvertFrom(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Type sourceType) + { + return 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) + { + var stringValue = value as global::System.String; + if (stringValue != null) + { + return StringThing.__Deserialize(stringValue); + } + + return base.ConvertFrom(context, culture, value); + } + + public override bool CanConvertTo(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Type sourceType) + { + return 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 StringThing idValue) + { + if (destinationType == typeof(global::System.String)) + { + return idValue.Value; + } + } + + return base.ConvertTo(context, culture, value, destinationType); + } + } + +#nullable restore + internal sealed class StringThingDebugView + { + private readonly StringThing _t; + StringThingDebugView(StringThing t) + { + _t = t; + } + + public global::System.String UnderlyingType => "global::System.String"; + public global::System.String Value => _t.Value; + public global::System.String Conversions => @"[global::System.Text.Json.Serialization.JsonConverter(typeof(StringThingSystemTextJsonConverter))] +[global::System.ComponentModel.TypeConverter(typeof(StringThingTypeConverter))] +"; + } + + 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 CreateCannotBeNullException(); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenNotInitialized() => throw CreateValidationException("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 CreateValidationException("Use of uninitialized Value Object at: " + stackTrace ?? ""); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenValidationFails(global::Vogen.Validation validation) + { + throw CreateValidationException(validation); + } + + internal static global::System.Exception CreateValidationException(string message) => new global::Vogen.ValueObjectValidationException(message); + internal static global::System.Exception CreateCannotBeNullException() => new global::Vogen.ValueObjectValidationException("Cannot create a value object with null."); + internal static global::System.Exception CreateValidationException(global::Vogen.Validation validation) + { + var ex = CreateValidationException(validation.ErrorMessage); + if (validation.Data != null) + { + foreach (var kvp in validation.Data) + { + ex.Data[kvp.Key] = kvp.Value; + } + } + + return ex; + } + } + } +} +] \ No newline at end of file diff --git a/tests/SnapshotTests/StringComparison/snapshots/snap-v8.0/StringComparisonGenerationTests.Generates_when_specified.verified.txt b/tests/SnapshotTests/StringComparison/snapshots/snap-v8.0/StringComparisonGenerationTests.Generates_when_specified.verified.txt index 351990388f5..26f2975c54d 100644 --- a/tests/SnapshotTests/StringComparison/snapshots/snap-v8.0/StringComparisonGenerationTests.Generates_when_specified.verified.txt +++ b/tests/SnapshotTests/StringComparison/snapshots/snap-v8.0/StringComparisonGenerationTests.Generates_when_specified.verified.txt @@ -208,7 +208,7 @@ private readonly global::System.Diagnostics.StackTrace _stackTrace = null!; public int GetHashCode(StringThing obj) { - return _comparer.GetHashCode(); + return _comparer.GetHashCode(obj._value); } } diff --git a/tests/SnapshotTests/StringComparison/snapshots/snap-v8.0/StringComparisonGenerationTests.Generates_with_default_comparison_OrdinalIgnoreCase.verified.txt b/tests/SnapshotTests/StringComparison/snapshots/snap-v8.0/StringComparisonGenerationTests.Generates_with_default_comparison_OrdinalIgnoreCase.verified.txt new file mode 100644 index 00000000000..461a0c4ec01 --- /dev/null +++ b/tests/SnapshotTests/StringComparison/snapshots/snap-v8.0/StringComparisonGenerationTests.Generates_with_default_comparison_OrdinalIgnoreCase.verified.txt @@ -0,0 +1,589 @@ +[ +// ------------------------------------------------------------------------------ +// +// 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 +#pragma warning disable CS8604 // Possible null reference argument. + +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 +// Suppress warnings about CS8767 : Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). +#pragma warning disable CS8767 +namespace generator +{ + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Vogen", "1.0.0.0")] + 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::Whatever.StringThing), + new global::System.Lazy(() => new global::Whatever.StringThing.StringThingSystemTextJsonConverter()) + } + }; + 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 +#pragma warning disable CS8604 // Possible null reference argument. + +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 +// Suppress warnings about CS8767 : Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). +#pragma warning disable CS8767 +namespace Whatever +{ + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Vogen", "1.0.0.0")] + [global::System.Text.Json.Serialization.JsonConverter(typeof(StringThingSystemTextJsonConverter))] + [global::System.ComponentModel.TypeConverter(typeof(StringThingTypeConverter))] + [global::System.Diagnostics.DebuggerTypeProxyAttribute(typeof(StringThingDebugView))] + [global::System.Diagnostics.DebuggerDisplayAttribute("Underlying type: global::System.String, Value = { _value }")] + public partial class StringThing : global::System.IEquatable, global::System.IEquatable, global::System.IComparable, global::System.IComparable, global::System.IParsable, global::System.IConvertible + { +#if DEBUG +private readonly global::System.Diagnostics.StackTrace _stackTrace = null!; +#endif +#if !VOGEN_NO_VALIDATION + private readonly global::System.Boolean _isInitialized; +#endif + private readonly global::System.String _value; + /// + /// Gets the underlying value if set, otherwise a is thrown. + /// + public global::System.String 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 StringThing() + { +#if DEBUG + _stackTrace = new global::System.Diagnostics.StackTrace(); +#endif +#if !VOGEN_NO_VALIDATION + _isInitialized = false; +#endif + _value = default; + } + + [global::System.Diagnostics.DebuggerStepThroughAttribute] + private StringThing(global::System.String 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 StringThing From(global::System.String value) + { + if (value is null) + { + ThrowHelper.ThrowWhenCreatedWithNull(); + return default !; + } + + return new StringThing(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. + public static bool TryFrom( +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + global::System.String value, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] +#endif + out StringThing vo) + { + if (value is null) + { + vo = default; + return false; + } + + vo = new StringThing(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 global::Vogen.ValueObjectOrError TryFrom(global::System.String value) + { + if (value is null) + { + return new global::Vogen.ValueObjectOrError(global::Vogen.Validation.Invalid("The value provided was null")); + } + + return new global::Vogen.ValueObjectOrError(new StringThing(value)); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] +#if VOGEN_NO_VALIDATION +#pragma warning disable CS8775 + public bool IsInitialized() => true; +#pragma warning restore CS8775 +#else + public bool IsInitialized() => _isInitialized; +#endif + // only called internally when something has been deserialized into + // its primitive type. + private static StringThing __Deserialize(global::System.String value) + { + if (value is null) + { + ThrowHelper.ThrowWhenCreatedWithNull(); + return default !; + } + + return new StringThing(value); + } + + public global::System.Boolean Equals(StringThing other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + // 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; + if (ReferenceEquals(this, other)) + { + return true; + } + + return GetType() == other.GetType() && global::System.StringComparer.OrdinalIgnoreCase.Equals(Value, other.Value); + } + + public global::System.Boolean Equals(StringThing other, global::System.Collections.Generic.IEqualityComparer comparer) + { + return comparer.Equals(this, other); + } + + public global::System.Boolean Equals(global::System.String primitive) + { + return global::System.StringComparer.OrdinalIgnoreCase.Equals(Value, primitive); + } + + public global::System.Boolean Equals(global::System.String primitive, global::System.StringComparer comparer) + { + return comparer.Equals(Value, primitive); + } + + public override global::System.Boolean Equals(global::System.Object obj) + { + return Equals(obj as StringThing); + } + + public static global::System.Boolean operator ==(StringThing left, StringThing right) => Equals(left, right); + public static global::System.Boolean operator !=(StringThing left, StringThing right) => !Equals(left, right); + public static global::System.Boolean operator ==(StringThing left, global::System.String right) => global::System.StringComparer.OrdinalIgnoreCase.Equals(left.Value, right); + public static global::System.Boolean operator ==(global::System.String left, StringThing right) => global::System.StringComparer.OrdinalIgnoreCase.Equals(left, right.Value); + public static global::System.Boolean operator !=(global::System.String left, StringThing right) => !(left == right); + public static global::System.Boolean operator !=(StringThing left, global::System.String right) => !(left == right); + public static explicit operator StringThing(global::System.String value) => From(value); + public static explicit operator global::System.String(StringThing value) => value.Value; + public int CompareTo(StringThing other) + { + if (other is null) + return 1; + return Value.CompareTo(other.Value); + } + + public int CompareTo(object other) + { + if (other is null) + return 1; + if (other is StringThing x) + return CompareTo(x); + ThrowHelper.ThrowArgumentException("Cannot compare to object as it is not of type StringThing", nameof(other)); + return 0; + } + + /// + /// + /// + /// True if the value passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse( +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + global::System.String s, global::System.IFormatProvider provider, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] +#endif + out StringThing result) + { + if (s is null) + { + result = default; + return false; + } + + result = new StringThing(s); + return true; + } + + /// + /// + /// + /// The value created via the method. + /// + /// Thrown when the value can be parsed, but is not valid. + public static StringThing Parse(global::System.String s, global::System.IFormatProvider provider) + { + return From(s); + } + +#nullable disable +#nullable restore +#nullable disable + /// + public System.TypeCode GetTypeCode() + { + return IsInitialized() ? Value.GetTypeCode() : default; + } + + /// + bool System.IConvertible.ToBoolean(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToBoolean(provider) : default; + } + + /// + byte System.IConvertible.ToByte(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToByte(provider) : default; + } + + /// + char System.IConvertible.ToChar(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToChar(provider) : default; + } + + /// + System.DateTime System.IConvertible.ToDateTime(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToDateTime(provider) : default; + } + + /// + decimal System.IConvertible.ToDecimal(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToDecimal(provider) : default; + } + + /// + double System.IConvertible.ToDouble(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToDouble(provider) : default; + } + + /// + short System.IConvertible.ToInt16(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToInt16(provider) : default; + } + + /// + int System.IConvertible.ToInt32(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToInt32(provider) : default; + } + + /// + long System.IConvertible.ToInt64(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToInt64(provider) : default; + } + + /// + sbyte System.IConvertible.ToSByte(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToSByte(provider) : default; + } + + /// + float System.IConvertible.ToSingle(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToSingle(provider) : default; + } + + /// + object System.IConvertible.ToType(global::System.Type @type, global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToType(@type, provider) : default; + } + + /// + ushort System.IConvertible.ToUInt16(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToUInt16(provider) : default; + } + + /// + uint System.IConvertible.ToUInt32(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToUInt32(provider) : default; + } + + /// + ulong System.IConvertible.ToUInt64(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToUInt64(provider) : default; + } + +#nullable restore + public override global::System.Int32 GetHashCode() + { + unchecked // Overflow is fine, just wrap + { + global::System.Int32 hash = (global::System.Int32)2166136261; + hash = (hash * 16777619) ^ GetType().GetHashCode(); + hash = (hash * 16777619) ^ global::System.StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + return hash; + } + } + + /// + public override global::System.String ToString() => IsInitialized() ? Value.ToString() ?? "" : "[UNINITIALIZED]"; + /// + public global::System.String ToString(global::System.IFormatProvider provider) => IsInitialized() ? Value.ToString(provider) ?? "" : "[UNINITIALIZED]"; + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + private void EnsureInitialized() + { + if (!IsInitialized()) + { +#if DEBUG + ThrowHelper.ThrowWhenNotInitialized(_stackTrace); +#else + ThrowHelper.ThrowWhenNotInitialized(); +#endif + } + } + +#nullable disable + /// + /// Converts a StringThing to or from JSON. + /// + public partial class StringThingSystemTextJsonConverter : global::System.Text.Json.Serialization.JsonConverter + { + public override StringThing Read(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) + { + return DeserializeJson(reader.GetString()); + } + + public override void Write(global::System.Text.Json.Utf8JsonWriter writer, StringThing value, global::System.Text.Json.JsonSerializerOptions options) + { + writer.WriteStringValue(value.Value); + } + +#if NET6_0_OR_GREATER + public override StringThing ReadAsPropertyName(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) + { + return DeserializeJson(reader.GetString()); + } + + public override void WriteAsPropertyName(global::System.Text.Json.Utf8JsonWriter writer, StringThing value, global::System.Text.Json.JsonSerializerOptions options) + { + writer.WritePropertyName(value.Value); + } +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + private static void ThrowJsonExceptionWhenValidationFails(global::Vogen.Validation validation) + { + var e = ThrowHelper.CreateValidationException(validation); + throw new global::System.Text.Json.JsonException(null, e); + } + + private static void ThrowJsonExceptionWhenNull(global::System.String value) + { + if (value == null) + { + var e = ThrowHelper.CreateCannotBeNullException(); + throw new global::System.Text.Json.JsonException(null, e); + } + } + + private static StringThing DeserializeJson(global::System.String value) + { + ThrowJsonExceptionWhenNull(value); + return new StringThing(value); + } + } + +#nullable restore +#nullable disable + class StringThingTypeConverter : global::System.ComponentModel.TypeConverter + { + public override global::System.Boolean CanConvertFrom(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Type sourceType) + { + return 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) + { + var stringValue = value as global::System.String; + if (stringValue != null) + { + return StringThing.__Deserialize(stringValue); + } + + return base.ConvertFrom(context, culture, value); + } + + public override bool CanConvertTo(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Type sourceType) + { + return 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 StringThing idValue) + { + if (destinationType == typeof(global::System.String)) + { + return idValue.Value; + } + } + + return base.ConvertTo(context, culture, value, destinationType); + } + } + +#nullable restore + internal sealed class StringThingDebugView + { + private readonly StringThing _t; + StringThingDebugView(StringThing t) + { + _t = t; + } + + public global::System.String UnderlyingType => "global::System.String"; + public global::System.String Value => _t.Value; + public global::System.String Conversions => @"[global::System.Text.Json.Serialization.JsonConverter(typeof(StringThingSystemTextJsonConverter))] +[global::System.ComponentModel.TypeConverter(typeof(StringThingTypeConverter))] +"; + } + + 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 CreateCannotBeNullException(); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenNotInitialized() => throw CreateValidationException("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 CreateValidationException("Use of uninitialized Value Object at: " + stackTrace ?? ""); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenValidationFails(global::Vogen.Validation validation) + { + throw CreateValidationException(validation); + } + + internal static global::System.Exception CreateValidationException(string message) => new global::Vogen.ValueObjectValidationException(message); + internal static global::System.Exception CreateCannotBeNullException() => new global::Vogen.ValueObjectValidationException("Cannot create a value object with null."); + internal static global::System.Exception CreateValidationException(global::Vogen.Validation validation) + { + var ex = CreateValidationException(validation.ErrorMessage); + if (validation.Data != null) + { + foreach (var kvp in validation.Data) + { + ex.Data[kvp.Key] = kvp.Value; + } + } + + return ex; + } + } + } +} +] \ No newline at end of file diff --git a/tests/SnapshotTests/StringComparison/snapshots/snap-v9.0/StringComparisonGenerationTests.Generates_comparers_class_with_default_comparison_when_both_are_specified.verified.txt b/tests/SnapshotTests/StringComparison/snapshots/snap-v9.0/StringComparisonGenerationTests.Generates_comparers_class_with_default_comparison_when_both_are_specified.verified.txt new file mode 100644 index 00000000000..62b4cd9b562 --- /dev/null +++ b/tests/SnapshotTests/StringComparison/snapshots/snap-v9.0/StringComparisonGenerationTests.Generates_comparers_class_with_default_comparison_when_both_are_specified.verified.txt @@ -0,0 +1,620 @@ +[ +// ------------------------------------------------------------------------------ +// +// 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 +#pragma warning disable CS8604 // Possible null reference argument. + +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 +// Suppress warnings about CS8767 : Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). +#pragma warning disable CS8767 +namespace generator +{ + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Vogen", "1.0.0.0")] + 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::Whatever.StringThing), + new global::System.Lazy(() => new global::Whatever.StringThing.StringThingSystemTextJsonConverter()) + } + }; + 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 +#pragma warning disable CS8604 // Possible null reference argument. + +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 +// Suppress warnings about CS8767 : Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). +#pragma warning disable CS8767 +namespace Whatever +{ + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Vogen", "1.0.0.0")] + [global::System.Text.Json.Serialization.JsonConverter(typeof(StringThingSystemTextJsonConverter))] + [global::System.ComponentModel.TypeConverter(typeof(StringThingTypeConverter))] + [global::System.Diagnostics.DebuggerTypeProxyAttribute(typeof(StringThingDebugView))] + [global::System.Diagnostics.DebuggerDisplayAttribute("Underlying type: global::System.String, Value = { _value }")] + public partial class StringThing : global::System.IEquatable, global::System.IEquatable, global::System.IComparable, global::System.IComparable, global::System.IParsable, global::System.IConvertible + { +#if DEBUG +private readonly global::System.Diagnostics.StackTrace _stackTrace = null!; +#endif +#if !VOGEN_NO_VALIDATION + private readonly global::System.Boolean _isInitialized; +#endif + private readonly global::System.String _value; + /// + /// Gets the underlying value if set, otherwise a is thrown. + /// + public global::System.String 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 StringThing() + { +#if DEBUG + _stackTrace = new global::System.Diagnostics.StackTrace(); +#endif +#if !VOGEN_NO_VALIDATION + _isInitialized = false; +#endif + _value = default; + } + + [global::System.Diagnostics.DebuggerStepThroughAttribute] + private StringThing(global::System.String 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 StringThing From(global::System.String value) + { + if (value is null) + { + ThrowHelper.ThrowWhenCreatedWithNull(); + return default !; + } + + return new StringThing(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. + public static bool TryFrom( +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + global::System.String value, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] +#endif + out StringThing vo) + { + if (value is null) + { + vo = default; + return false; + } + + vo = new StringThing(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 global::Vogen.ValueObjectOrError TryFrom(global::System.String value) + { + if (value is null) + { + return new global::Vogen.ValueObjectOrError(global::Vogen.Validation.Invalid("The value provided was null")); + } + + return new global::Vogen.ValueObjectOrError(new StringThing(value)); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] +#if VOGEN_NO_VALIDATION +#pragma warning disable CS8775 + public bool IsInitialized() => true; +#pragma warning restore CS8775 +#else + public bool IsInitialized() => _isInitialized; +#endif +#nullable disable + public static class Comparers + { + private class __StringEqualityComparer : global::System.Collections.Generic.IEqualityComparer + { + readonly global::System.StringComparer _comparer; + public __StringEqualityComparer(global::System.StringComparer comparer) + { + _comparer = comparer; + } + + public bool Equals(StringThing x, StringThing y) + { + return _comparer.Equals(x._value, y._value); + } + + public int GetHashCode(StringThing obj) + { + return _comparer.GetHashCode(obj._value); + } + } + + public static global::System.Collections.Generic.IEqualityComparer Ordinal => new __StringEqualityComparer(global::System.StringComparer.Ordinal); + public static global::System.Collections.Generic.IEqualityComparer OrdinalIgnoreCase => new __StringEqualityComparer(global::System.StringComparer.OrdinalIgnoreCase); + public static global::System.Collections.Generic.IEqualityComparer CurrentCulture => new __StringEqualityComparer(global::System.StringComparer.CurrentCulture); + public static global::System.Collections.Generic.IEqualityComparer CurrentCultureIgnoreCase => new __StringEqualityComparer(global::System.StringComparer.CurrentCultureIgnoreCase); + public static global::System.Collections.Generic.IEqualityComparer InvariantCulture => new __StringEqualityComparer(global::System.StringComparer.InvariantCulture); + public static global::System.Collections.Generic.IEqualityComparer InvariantCultureIgnoreCase => new __StringEqualityComparer(global::System.StringComparer.InvariantCultureIgnoreCase); +#nullable restore + } + + // only called internally when something has been deserialized into + // its primitive type. + private static StringThing __Deserialize(global::System.String value) + { + if (value is null) + { + ThrowHelper.ThrowWhenCreatedWithNull(); + return default !; + } + + return new StringThing(value); + } + + public global::System.Boolean Equals(StringThing other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + // 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; + if (ReferenceEquals(this, other)) + { + return true; + } + + return GetType() == other.GetType() && global::System.StringComparer.OrdinalIgnoreCase.Equals(Value, other.Value); + } + + public global::System.Boolean Equals(StringThing other, global::System.Collections.Generic.IEqualityComparer comparer) + { + return comparer.Equals(this, other); + } + + public global::System.Boolean Equals(global::System.String primitive) + { + return global::System.StringComparer.OrdinalIgnoreCase.Equals(Value, primitive); + } + + public global::System.Boolean Equals(global::System.String primitive, global::System.StringComparer comparer) + { + return comparer.Equals(Value, primitive); + } + + public override global::System.Boolean Equals(global::System.Object obj) + { + return Equals(obj as StringThing); + } + + public static global::System.Boolean operator ==(StringThing left, StringThing right) => Equals(left, right); + public static global::System.Boolean operator !=(StringThing left, StringThing right) => !Equals(left, right); + public static global::System.Boolean operator ==(StringThing left, global::System.String right) => global::System.StringComparer.OrdinalIgnoreCase.Equals(left.Value, right); + public static global::System.Boolean operator ==(global::System.String left, StringThing right) => global::System.StringComparer.OrdinalIgnoreCase.Equals(left, right.Value); + public static global::System.Boolean operator !=(global::System.String left, StringThing right) => !(left == right); + public static global::System.Boolean operator !=(StringThing left, global::System.String right) => !(left == right); + public static explicit operator StringThing(global::System.String value) => From(value); + public static explicit operator global::System.String(StringThing value) => value.Value; + public int CompareTo(StringThing other) + { + if (other is null) + return 1; + return Value.CompareTo(other.Value); + } + + public int CompareTo(object other) + { + if (other is null) + return 1; + if (other is StringThing x) + return CompareTo(x); + ThrowHelper.ThrowArgumentException("Cannot compare to object as it is not of type StringThing", nameof(other)); + return 0; + } + + /// + /// + /// + /// True if the value passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse( +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + global::System.String s, global::System.IFormatProvider provider, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] +#endif + out StringThing result) + { + if (s is null) + { + result = default; + return false; + } + + result = new StringThing(s); + return true; + } + + /// + /// + /// + /// The value created via the method. + /// + /// Thrown when the value can be parsed, but is not valid. + public static StringThing Parse(global::System.String s, global::System.IFormatProvider provider) + { + return From(s); + } + +#nullable disable +#nullable restore +#nullable disable + /// + public System.TypeCode GetTypeCode() + { + return IsInitialized() ? Value.GetTypeCode() : default; + } + + /// + bool System.IConvertible.ToBoolean(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToBoolean(provider) : default; + } + + /// + byte System.IConvertible.ToByte(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToByte(provider) : default; + } + + /// + char System.IConvertible.ToChar(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToChar(provider) : default; + } + + /// + System.DateTime System.IConvertible.ToDateTime(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToDateTime(provider) : default; + } + + /// + decimal System.IConvertible.ToDecimal(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToDecimal(provider) : default; + } + + /// + double System.IConvertible.ToDouble(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToDouble(provider) : default; + } + + /// + short System.IConvertible.ToInt16(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToInt16(provider) : default; + } + + /// + int System.IConvertible.ToInt32(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToInt32(provider) : default; + } + + /// + long System.IConvertible.ToInt64(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToInt64(provider) : default; + } + + /// + sbyte System.IConvertible.ToSByte(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToSByte(provider) : default; + } + + /// + float System.IConvertible.ToSingle(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToSingle(provider) : default; + } + + /// + object System.IConvertible.ToType(global::System.Type @type, global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToType(@type, provider) : default; + } + + /// + ushort System.IConvertible.ToUInt16(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToUInt16(provider) : default; + } + + /// + uint System.IConvertible.ToUInt32(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToUInt32(provider) : default; + } + + /// + ulong System.IConvertible.ToUInt64(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToUInt64(provider) : default; + } + +#nullable restore + public override global::System.Int32 GetHashCode() + { + unchecked // Overflow is fine, just wrap + { + global::System.Int32 hash = (global::System.Int32)2166136261; + hash = (hash * 16777619) ^ GetType().GetHashCode(); + hash = (hash * 16777619) ^ global::System.StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + return hash; + } + } + + /// + public override global::System.String ToString() => IsInitialized() ? Value.ToString() ?? "" : "[UNINITIALIZED]"; + /// + public global::System.String ToString(global::System.IFormatProvider provider) => IsInitialized() ? Value.ToString(provider) ?? "" : "[UNINITIALIZED]"; + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + private void EnsureInitialized() + { + if (!IsInitialized()) + { +#if DEBUG + ThrowHelper.ThrowWhenNotInitialized(_stackTrace); +#else + ThrowHelper.ThrowWhenNotInitialized(); +#endif + } + } + +#nullable disable + /// + /// Converts a StringThing to or from JSON. + /// + public partial class StringThingSystemTextJsonConverter : global::System.Text.Json.Serialization.JsonConverter + { + public override StringThing Read(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) + { + return DeserializeJson(reader.GetString()); + } + + public override void Write(global::System.Text.Json.Utf8JsonWriter writer, StringThing value, global::System.Text.Json.JsonSerializerOptions options) + { + writer.WriteStringValue(value.Value); + } + +#if NET6_0_OR_GREATER + public override StringThing ReadAsPropertyName(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) + { + return DeserializeJson(reader.GetString()); + } + + public override void WriteAsPropertyName(global::System.Text.Json.Utf8JsonWriter writer, StringThing value, global::System.Text.Json.JsonSerializerOptions options) + { + writer.WritePropertyName(value.Value); + } +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + private static void ThrowJsonExceptionWhenValidationFails(global::Vogen.Validation validation) + { + var e = ThrowHelper.CreateValidationException(validation); + throw new global::System.Text.Json.JsonException(null, e); + } + + private static void ThrowJsonExceptionWhenNull(global::System.String value) + { + if (value == null) + { + var e = ThrowHelper.CreateCannotBeNullException(); + throw new global::System.Text.Json.JsonException(null, e); + } + } + + private static StringThing DeserializeJson(global::System.String value) + { + ThrowJsonExceptionWhenNull(value); + return new StringThing(value); + } + } + +#nullable restore +#nullable disable + class StringThingTypeConverter : global::System.ComponentModel.TypeConverter + { + public override global::System.Boolean CanConvertFrom(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Type sourceType) + { + return 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) + { + var stringValue = value as global::System.String; + if (stringValue != null) + { + return StringThing.__Deserialize(stringValue); + } + + return base.ConvertFrom(context, culture, value); + } + + public override bool CanConvertTo(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Type sourceType) + { + return 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 StringThing idValue) + { + if (destinationType == typeof(global::System.String)) + { + return idValue.Value; + } + } + + return base.ConvertTo(context, culture, value, destinationType); + } + } + +#nullable restore + internal sealed class StringThingDebugView + { + private readonly StringThing _t; + StringThingDebugView(StringThing t) + { + _t = t; + } + + public global::System.String UnderlyingType => "global::System.String"; + public global::System.String Value => _t.Value; + public global::System.String Conversions => @"[global::System.Text.Json.Serialization.JsonConverter(typeof(StringThingSystemTextJsonConverter))] +[global::System.ComponentModel.TypeConverter(typeof(StringThingTypeConverter))] +"; + } + + 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 CreateCannotBeNullException(); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenNotInitialized() => throw CreateValidationException("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 CreateValidationException("Use of uninitialized Value Object at: " + stackTrace ?? ""); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenValidationFails(global::Vogen.Validation validation) + { + throw CreateValidationException(validation); + } + + internal static global::System.Exception CreateValidationException(string message) => new global::Vogen.ValueObjectValidationException(message); + internal static global::System.Exception CreateCannotBeNullException() => new global::Vogen.ValueObjectValidationException("Cannot create a value object with null."); + internal static global::System.Exception CreateValidationException(global::Vogen.Validation validation) + { + var ex = CreateValidationException(validation.ErrorMessage); + if (validation.Data != null) + { + foreach (var kvp in validation.Data) + { + ex.Data[kvp.Key] = kvp.Value; + } + } + + return ex; + } + } + } +} +] \ No newline at end of file diff --git a/tests/SnapshotTests/StringComparison/snapshots/snap-v9.0/StringComparisonGenerationTests.Generates_when_specified.verified.txt b/tests/SnapshotTests/StringComparison/snapshots/snap-v9.0/StringComparisonGenerationTests.Generates_when_specified.verified.txt index 351990388f5..26f2975c54d 100644 --- a/tests/SnapshotTests/StringComparison/snapshots/snap-v9.0/StringComparisonGenerationTests.Generates_when_specified.verified.txt +++ b/tests/SnapshotTests/StringComparison/snapshots/snap-v9.0/StringComparisonGenerationTests.Generates_when_specified.verified.txt @@ -208,7 +208,7 @@ private readonly global::System.Diagnostics.StackTrace _stackTrace = null!; public int GetHashCode(StringThing obj) { - return _comparer.GetHashCode(); + return _comparer.GetHashCode(obj._value); } } diff --git a/tests/SnapshotTests/StringComparison/snapshots/snap-v9.0/StringComparisonGenerationTests.Generates_with_default_comparison_OrdinalIgnoreCase.verified.txt b/tests/SnapshotTests/StringComparison/snapshots/snap-v9.0/StringComparisonGenerationTests.Generates_with_default_comparison_OrdinalIgnoreCase.verified.txt new file mode 100644 index 00000000000..461a0c4ec01 --- /dev/null +++ b/tests/SnapshotTests/StringComparison/snapshots/snap-v9.0/StringComparisonGenerationTests.Generates_with_default_comparison_OrdinalIgnoreCase.verified.txt @@ -0,0 +1,589 @@ +[ +// ------------------------------------------------------------------------------ +// +// 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 +#pragma warning disable CS8604 // Possible null reference argument. + +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 +// Suppress warnings about CS8767 : Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). +#pragma warning disable CS8767 +namespace generator +{ + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Vogen", "1.0.0.0")] + 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::Whatever.StringThing), + new global::System.Lazy(() => new global::Whatever.StringThing.StringThingSystemTextJsonConverter()) + } + }; + 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 +#pragma warning disable CS8604 // Possible null reference argument. + +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 +// Suppress warnings about CS8767 : Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). +#pragma warning disable CS8767 +namespace Whatever +{ + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Vogen", "1.0.0.0")] + [global::System.Text.Json.Serialization.JsonConverter(typeof(StringThingSystemTextJsonConverter))] + [global::System.ComponentModel.TypeConverter(typeof(StringThingTypeConverter))] + [global::System.Diagnostics.DebuggerTypeProxyAttribute(typeof(StringThingDebugView))] + [global::System.Diagnostics.DebuggerDisplayAttribute("Underlying type: global::System.String, Value = { _value }")] + public partial class StringThing : global::System.IEquatable, global::System.IEquatable, global::System.IComparable, global::System.IComparable, global::System.IParsable, global::System.IConvertible + { +#if DEBUG +private readonly global::System.Diagnostics.StackTrace _stackTrace = null!; +#endif +#if !VOGEN_NO_VALIDATION + private readonly global::System.Boolean _isInitialized; +#endif + private readonly global::System.String _value; + /// + /// Gets the underlying value if set, otherwise a is thrown. + /// + public global::System.String 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 StringThing() + { +#if DEBUG + _stackTrace = new global::System.Diagnostics.StackTrace(); +#endif +#if !VOGEN_NO_VALIDATION + _isInitialized = false; +#endif + _value = default; + } + + [global::System.Diagnostics.DebuggerStepThroughAttribute] + private StringThing(global::System.String 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 StringThing From(global::System.String value) + { + if (value is null) + { + ThrowHelper.ThrowWhenCreatedWithNull(); + return default !; + } + + return new StringThing(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. + public static bool TryFrom( +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + global::System.String value, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] +#endif + out StringThing vo) + { + if (value is null) + { + vo = default; + return false; + } + + vo = new StringThing(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 global::Vogen.ValueObjectOrError TryFrom(global::System.String value) + { + if (value is null) + { + return new global::Vogen.ValueObjectOrError(global::Vogen.Validation.Invalid("The value provided was null")); + } + + return new global::Vogen.ValueObjectOrError(new StringThing(value)); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] +#if VOGEN_NO_VALIDATION +#pragma warning disable CS8775 + public bool IsInitialized() => true; +#pragma warning restore CS8775 +#else + public bool IsInitialized() => _isInitialized; +#endif + // only called internally when something has been deserialized into + // its primitive type. + private static StringThing __Deserialize(global::System.String value) + { + if (value is null) + { + ThrowHelper.ThrowWhenCreatedWithNull(); + return default !; + } + + return new StringThing(value); + } + + public global::System.Boolean Equals(StringThing other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + // 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; + if (ReferenceEquals(this, other)) + { + return true; + } + + return GetType() == other.GetType() && global::System.StringComparer.OrdinalIgnoreCase.Equals(Value, other.Value); + } + + public global::System.Boolean Equals(StringThing other, global::System.Collections.Generic.IEqualityComparer comparer) + { + return comparer.Equals(this, other); + } + + public global::System.Boolean Equals(global::System.String primitive) + { + return global::System.StringComparer.OrdinalIgnoreCase.Equals(Value, primitive); + } + + public global::System.Boolean Equals(global::System.String primitive, global::System.StringComparer comparer) + { + return comparer.Equals(Value, primitive); + } + + public override global::System.Boolean Equals(global::System.Object obj) + { + return Equals(obj as StringThing); + } + + public static global::System.Boolean operator ==(StringThing left, StringThing right) => Equals(left, right); + public static global::System.Boolean operator !=(StringThing left, StringThing right) => !Equals(left, right); + public static global::System.Boolean operator ==(StringThing left, global::System.String right) => global::System.StringComparer.OrdinalIgnoreCase.Equals(left.Value, right); + public static global::System.Boolean operator ==(global::System.String left, StringThing right) => global::System.StringComparer.OrdinalIgnoreCase.Equals(left, right.Value); + public static global::System.Boolean operator !=(global::System.String left, StringThing right) => !(left == right); + public static global::System.Boolean operator !=(StringThing left, global::System.String right) => !(left == right); + public static explicit operator StringThing(global::System.String value) => From(value); + public static explicit operator global::System.String(StringThing value) => value.Value; + public int CompareTo(StringThing other) + { + if (other is null) + return 1; + return Value.CompareTo(other.Value); + } + + public int CompareTo(object other) + { + if (other is null) + return 1; + if (other is StringThing x) + return CompareTo(x); + ThrowHelper.ThrowArgumentException("Cannot compare to object as it is not of type StringThing", nameof(other)); + return 0; + } + + /// + /// + /// + /// True if the value passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse( +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + global::System.String s, global::System.IFormatProvider provider, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] +#endif + out StringThing result) + { + if (s is null) + { + result = default; + return false; + } + + result = new StringThing(s); + return true; + } + + /// + /// + /// + /// The value created via the method. + /// + /// Thrown when the value can be parsed, but is not valid. + public static StringThing Parse(global::System.String s, global::System.IFormatProvider provider) + { + return From(s); + } + +#nullable disable +#nullable restore +#nullable disable + /// + public System.TypeCode GetTypeCode() + { + return IsInitialized() ? Value.GetTypeCode() : default; + } + + /// + bool System.IConvertible.ToBoolean(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToBoolean(provider) : default; + } + + /// + byte System.IConvertible.ToByte(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToByte(provider) : default; + } + + /// + char System.IConvertible.ToChar(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToChar(provider) : default; + } + + /// + System.DateTime System.IConvertible.ToDateTime(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToDateTime(provider) : default; + } + + /// + decimal System.IConvertible.ToDecimal(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToDecimal(provider) : default; + } + + /// + double System.IConvertible.ToDouble(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToDouble(provider) : default; + } + + /// + short System.IConvertible.ToInt16(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToInt16(provider) : default; + } + + /// + int System.IConvertible.ToInt32(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToInt32(provider) : default; + } + + /// + long System.IConvertible.ToInt64(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToInt64(provider) : default; + } + + /// + sbyte System.IConvertible.ToSByte(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToSByte(provider) : default; + } + + /// + float System.IConvertible.ToSingle(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToSingle(provider) : default; + } + + /// + object System.IConvertible.ToType(global::System.Type @type, global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToType(@type, provider) : default; + } + + /// + ushort System.IConvertible.ToUInt16(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToUInt16(provider) : default; + } + + /// + uint System.IConvertible.ToUInt32(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToUInt32(provider) : default; + } + + /// + ulong System.IConvertible.ToUInt64(global::System.IFormatProvider provider) + { + return IsInitialized() ? (Value as System.IConvertible).ToUInt64(provider) : default; + } + +#nullable restore + public override global::System.Int32 GetHashCode() + { + unchecked // Overflow is fine, just wrap + { + global::System.Int32 hash = (global::System.Int32)2166136261; + hash = (hash * 16777619) ^ GetType().GetHashCode(); + hash = (hash * 16777619) ^ global::System.StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + return hash; + } + } + + /// + public override global::System.String ToString() => IsInitialized() ? Value.ToString() ?? "" : "[UNINITIALIZED]"; + /// + public global::System.String ToString(global::System.IFormatProvider provider) => IsInitialized() ? Value.ToString(provider) ?? "" : "[UNINITIALIZED]"; + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + private void EnsureInitialized() + { + if (!IsInitialized()) + { +#if DEBUG + ThrowHelper.ThrowWhenNotInitialized(_stackTrace); +#else + ThrowHelper.ThrowWhenNotInitialized(); +#endif + } + } + +#nullable disable + /// + /// Converts a StringThing to or from JSON. + /// + public partial class StringThingSystemTextJsonConverter : global::System.Text.Json.Serialization.JsonConverter + { + public override StringThing Read(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) + { + return DeserializeJson(reader.GetString()); + } + + public override void Write(global::System.Text.Json.Utf8JsonWriter writer, StringThing value, global::System.Text.Json.JsonSerializerOptions options) + { + writer.WriteStringValue(value.Value); + } + +#if NET6_0_OR_GREATER + public override StringThing ReadAsPropertyName(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) + { + return DeserializeJson(reader.GetString()); + } + + public override void WriteAsPropertyName(global::System.Text.Json.Utf8JsonWriter writer, StringThing value, global::System.Text.Json.JsonSerializerOptions options) + { + writer.WritePropertyName(value.Value); + } +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + private static void ThrowJsonExceptionWhenValidationFails(global::Vogen.Validation validation) + { + var e = ThrowHelper.CreateValidationException(validation); + throw new global::System.Text.Json.JsonException(null, e); + } + + private static void ThrowJsonExceptionWhenNull(global::System.String value) + { + if (value == null) + { + var e = ThrowHelper.CreateCannotBeNullException(); + throw new global::System.Text.Json.JsonException(null, e); + } + } + + private static StringThing DeserializeJson(global::System.String value) + { + ThrowJsonExceptionWhenNull(value); + return new StringThing(value); + } + } + +#nullable restore +#nullable disable + class StringThingTypeConverter : global::System.ComponentModel.TypeConverter + { + public override global::System.Boolean CanConvertFrom(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Type sourceType) + { + return 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) + { + var stringValue = value as global::System.String; + if (stringValue != null) + { + return StringThing.__Deserialize(stringValue); + } + + return base.ConvertFrom(context, culture, value); + } + + public override bool CanConvertTo(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Type sourceType) + { + return 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 StringThing idValue) + { + if (destinationType == typeof(global::System.String)) + { + return idValue.Value; + } + } + + return base.ConvertTo(context, culture, value, destinationType); + } + } + +#nullable restore + internal sealed class StringThingDebugView + { + private readonly StringThing _t; + StringThingDebugView(StringThing t) + { + _t = t; + } + + public global::System.String UnderlyingType => "global::System.String"; + public global::System.String Value => _t.Value; + public global::System.String Conversions => @"[global::System.Text.Json.Serialization.JsonConverter(typeof(StringThingSystemTextJsonConverter))] +[global::System.ComponentModel.TypeConverter(typeof(StringThingTypeConverter))] +"; + } + + 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 CreateCannotBeNullException(); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenNotInitialized() => throw CreateValidationException("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 CreateValidationException("Use of uninitialized Value Object at: " + stackTrace ?? ""); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenValidationFails(global::Vogen.Validation validation) + { + throw CreateValidationException(validation); + } + + internal static global::System.Exception CreateValidationException(string message) => new global::Vogen.ValueObjectValidationException(message); + internal static global::System.Exception CreateCannotBeNullException() => new global::Vogen.ValueObjectValidationException("Cannot create a value object with null."); + internal static global::System.Exception CreateValidationException(global::Vogen.Validation validation) + { + var ex = CreateValidationException(validation.ErrorMessage); + if (validation.Data != null) + { + foreach (var kvp in validation.Data) + { + ex.Data[kvp.Key] = kvp.Value; + } + } + + return ex; + } + } + } +} +] \ No newline at end of file diff --git a/tests/Vogen.Tests/ConfigurationTests/VogenConfigurationTests.cs b/tests/Vogen.Tests/ConfigurationTests/VogenConfigurationTests.cs index 541c8153692..fabe071abbf 100644 --- a/tests/Vogen.Tests/ConfigurationTests/VogenConfigurationTests.cs +++ b/tests/Vogen.Tests/ConfigurationTests/VogenConfigurationTests.cs @@ -74,7 +74,8 @@ private static VogenConfiguration ConfigWithOmitDebugAs(DebuggerAttributeGenerat OpenApiSchemaCustomizations.Unspecified, false, PrimitiveEqualityGeneration.GenerateOperatorsAndMethods, - NumericsGeneration.Omit); + NumericsGeneration.Omit, + StringComparisonDefault.Omit); } public class Primitive_equality_generation @@ -150,7 +151,8 @@ private static VogenConfiguration ConfigWithCastingAs(CastOperator toPrimitiveCa OpenApiSchemaCustomizations: OpenApiSchemaCustomizations.Unspecified, false, PrimitiveEqualityGeneration.GenerateOperatorsAndMethods, - NumericsGeneration.Omit); + NumericsGeneration.Omit, + StringComparisonDefault.Omit); } public class Conversion @@ -256,7 +258,8 @@ private static VogenConfiguration ConfigWithOmitConversionsAs(Conversions conver OpenApiSchemaCustomizations.Unspecified, false, PrimitiveEqualityGeneration.GenerateOperatorsAndMethods, - NumericsGeneration.Omit); + NumericsGeneration.Omit, + StringComparisonDefault.Omit); } public class Comparable