Skip to content

Commit 3a72a6f

Browse files
authored
Merge pull request #17 from altasoft/feature/TypeExtensions
`TypeExtensions` class added and 1 bug fixed
2 parents 1cb9f90 + 736c5f2 commit 3a72a6f

9 files changed

+586
-5
lines changed

src/AltaSoft.DomainPrimitives.Generator/Extensions/CompilationExt.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ public static IEnumerable<TMember> GetMembersOfType<TMember>(this ITypeSymbol? s
4646
[MethodImpl(MethodImplOptions.AggressiveInlining)]
4747
public static bool IsPublic(this ISymbol symbol) => symbol.DeclaredAccessibility == Accessibility.Public;
4848

49-
#endregion Accessibility
5049

5150
/// <summary>
5251
/// Gets the modifiers for the named type symbol.
@@ -74,6 +73,21 @@ public static IEnumerable<TMember> GetMembersOfType<TMember>(this ITypeSymbol? s
7473
return null;
7574
}
7675

76+
public static string GetAccessibility(this INamedTypeSymbol symbol)
77+
{
78+
return symbol.DeclaredAccessibility switch
79+
{
80+
Accessibility.Public => "public",
81+
Accessibility.Private => "private",
82+
Accessibility.ProtectedAndInternal => "protected internal",
83+
Accessibility.Protected => "protected",
84+
Accessibility.Internal => "internal",
85+
Accessibility.ProtectedOrInternal => "private protected", // Since C# 7.2
86+
_ => "internal" // Default for top-level types
87+
};
88+
}
89+
90+
#endregion Accessibility
7791
/// <summary>
7892
/// Gets the class name including generic arguments as a string.
7993
/// </summary>

src/AltaSoft.DomainPrimitives.Generator/Helpers/MethodGeneratorHelper.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ void AddMapping(bool isNullable)
131131
/// <param name="context">The source production context.</param>
132132
internal static void ProcessTypeConverter(GeneratorData data, SourceProductionContext context)
133133
{
134+
var accessibiliy = data.TypeSymbol.GetAccessibility();
135+
134136
var friendlyName = data.UnderlyingType.ToString();
135137
var builder = new SourceCodeBuilder();
136138

@@ -146,7 +148,7 @@ internal static void ProcessTypeConverter(GeneratorData data, SourceProductionCo
146148

147149
builder.AppendNamespace(data.Namespace + ".Converters");
148150
builder.AppendSummary($"TypeConverter for <see cref = \"{data.ClassName}\"/>");
149-
builder.AppendClass(false, "public sealed", data.ClassName + "TypeConverter", $"{friendlyName}Converter");
151+
builder.AppendClass(false, accessibiliy + " sealed", data.ClassName + "TypeConverter", $"{friendlyName}Converter");
150152
builder.AppendInheritDoc()
151153
.AppendLine("public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)")
152154
.OpenBracket();
@@ -279,6 +281,8 @@ internal static void ProcessEntityFrameworkValueConverter(GeneratorData data, So
279281
/// <param name="context">The source production context.</param>
280282
internal static void ProcessJsonConverter(GeneratorData data, SourceProductionContext context)
281283
{
284+
var accessibiliy = data.TypeSymbol.GetAccessibility();
285+
282286
var builder = new SourceCodeBuilder();
283287

284288
builder.AppendSourceHeader("AltaSoft DomainPrimitives Generator");
@@ -301,11 +305,12 @@ internal static void ProcessJsonConverter(GeneratorData data, SourceProductionCo
301305

302306
builder.AppendNamespace(data.Namespace + ".Converters");
303307
builder.AppendSummary($"JsonConverter for <see cref = \"{data.ClassName}\"/>");
304-
builder.AppendClass(false, "public sealed", data.ClassName + "JsonConverter", $"JsonConverter<{data.ClassName}>");
308+
builder.AppendClass(false, accessibiliy + " sealed", data.ClassName + "JsonConverter", $"JsonConverter<{data.ClassName}>");
305309

306310
builder.AppendInheritDoc()
307311
.Append("public override ").Append(data.ClassName).AppendLine(" Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)")
308312
.OpenBracket();
313+
309314
if (data.SerializationFormat is null)
310315
{
311316
builder.AppendLine("try")
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
using System;
2+
using System.Diagnostics.CodeAnalysis;
3+
using System.Reflection;
4+
5+
// ReSharper disable UnusedMember.Global
6+
7+
namespace AltaSoft.DomainPrimitives;
8+
9+
/// <summary>
10+
/// Provides extension methods for working with types related to domain primitives.
11+
/// </summary>
12+
public static class TypeExtensions
13+
{
14+
/// <summary>
15+
/// Determines whether the specified type implements <see cref="IDomainValue"/>.
16+
/// </summary>
17+
/// <param name="type">The type to check.</param>
18+
/// <returns><c>true</c> if the type implements <see cref="IDomainValue"/>; otherwise, <c>false</c>.</returns>
19+
public static bool IsDomainPrimitive(this Type type)
20+
{
21+
return typeof(IDomainValue).IsAssignableFrom(type);
22+
}
23+
24+
/// <summary>
25+
/// Gets the underlying primitive type associated with the given domain value type, if applicable.
26+
/// </summary>
27+
/// <param name="type">The type to analyze.</param>
28+
/// <returns>
29+
/// The underlying primitive type if the type represents a domain value; otherwise, <c>null</c>.
30+
/// </returns>
31+
public static Type? GetUnderlyingDomainPrimitiveType(this Type type)
32+
{
33+
return TryGetUnderlyingDomainPrimitiveType(type, out var primitiveType) ? primitiveType : null;
34+
}
35+
36+
/// <summary>
37+
/// Attempts to retrieve the underlying primitive type of a domain value type.
38+
/// </summary>
39+
/// <param name="type">The type to analyze.</param>
40+
/// <param name="primitiveType">
41+
/// When this method returns, contains the underlying primitive type if found; otherwise, <c>null</c>.
42+
/// </param>
43+
/// <returns>
44+
/// <c>true</c> if the underlying primitive type was found; otherwise, <c>false</c>.
45+
/// </returns>
46+
public static bool TryGetUnderlyingDomainPrimitiveType(this Type type, [NotNullWhen(true)] out Type? primitiveType)
47+
{
48+
var isNullableT = type.TryGetNullableUnderlyingType(out var underlyingType);
49+
if (isNullableT)
50+
{
51+
type = underlyingType!;
52+
}
53+
54+
if (!type.IsDomainPrimitive())
55+
{
56+
primitiveType = null;
57+
return false;
58+
}
59+
60+
primitiveType = type.GetCustomAttribute<UnderlyingPrimitiveTypeAttribute>()?.UnderlyingPrimitiveType;
61+
if (primitiveType is null)
62+
{
63+
return false;
64+
}
65+
66+
if (isNullableT)
67+
{
68+
primitiveType = typeof(Nullable<>).MakeGenericType(primitiveType);
69+
}
70+
71+
return true;
72+
}
73+
74+
/// <summary>
75+
/// Determines whether the specified type is a nullable value type and retrieves its underlying type.
76+
/// </summary>
77+
/// <param name="type">The type to analyze.</param>
78+
/// <param name="underlyingType">
79+
/// When this method returns, contains the underlying type if the specified type is nullable; otherwise, <c>null</c>.
80+
/// </param>
81+
/// <returns>
82+
/// <c>true</c> if the type is a nullable value type; otherwise, <c>false</c>.
83+
/// </returns>
84+
private static bool TryGetNullableUnderlyingType(this Type type, [NotNullWhen(true)] out Type? underlyingType)
85+
{
86+
underlyingType = Nullable.GetUnderlyingType(type);
87+
return underlyingType is not null;
88+
}
89+
}

src/AltaSoft.DomainPrimitives/XmlReaderExt.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,7 @@ public static class XmlReaderExt
1919
/// <returns>A byte object representing the value read from the element.</returns>
2020
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2121
public static T ReadElementContentAs<T>(this XmlReader reader) where T : IParsable<T>
22-
=> T.Parse(reader.ReadElementContentAsString(), CultureInfo.InvariantCulture);
23-
}
22+
{
23+
return T.Parse(reader.ReadElementContentAsString(), CultureInfo.InvariantCulture);
24+
}
25+
}

tests/AltaSoft.DomainPrimitives.Generator.Tests/DomainPrimitiveGeneratorTest.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,36 @@ namespace AltaSoft.DomainPrimitives.Generator.Tests;
1010

1111
public class DomainPrimitiveGeneratorTest
1212
{
13+
[Fact]
14+
public Task StringValue_GeneratesAllInterfacesAndConvertersForInternalClass()
15+
{
16+
const string source = """
17+
using System;
18+
using System.Collections.Generic;
19+
using System.Linq;
20+
using System.Text;
21+
using System.Threading.Tasks;
22+
using AltaSoft.DomainPrimitives;
23+
24+
namespace AltaSoft.DomainPrimitives;
25+
26+
/// <inheritdoc/>
27+
internal partial class InternalStringValue : IDomainValue<string>
28+
{
29+
/// <inheritdoc/>
30+
public static PrimitiveValidationResult Validate(string value)
31+
{
32+
if (value=="Test")
33+
return "Invalid Value";
34+
35+
return PrimitiveValidationResult.Ok;
36+
}
37+
}
38+
""";
39+
40+
return TestHelper.Verify(source, (_, x, _) => Assert.Equal(4, x.Count));
41+
}
42+
1343
[Fact]
1444
public Task StringValue_GeneratesAllInterfacesAndConverters()
1545
{

0 commit comments

Comments
 (0)