Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,11 @@ connection.UseStructId();
connection.Open();
```

The supported types are `Guid`, `int`, `long` and `string` for now.
The value types `Guid`, `int`, `long` and `string` have built-in support, as well as
any other types that implement `IParsable<T>` and `IFormattable` (by persisting them
as strings). This means that you can, for example, use [Ulid](https://github.com/Cysharp/Ulid)
out of the box without any further configuration or customization (since it implements
both interfaces).

## Customization via Templates

Expand Down
11 changes: 11 additions & 0 deletions src/StructId.Analyzer/AnalysisExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ public static class AnalysisExtensions

public static string ToFullName(this ISymbol symbol) => symbol.ToDisplayString(FullNameNullable);

public static CSharpParseOptions GetParseOptions(this Compilation compilation)
=> (CSharpParseOptions?)compilation.SyntaxTrees.FirstOrDefault()?.Options ??
CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest);

/// <summary>
/// Checks whether the <paramref name="this"/> type inherits or implements the
/// <paramref name="baseTypeOrInterface"/> type, even if it's a generic type.
Expand Down Expand Up @@ -152,6 +156,13 @@ public static string ToFileName(this ITypeSymbol type)

public static bool IsStructId(this ITypeSymbol type) => type.AllInterfaces.Any(x => x.Name == "IStructId");

public static bool IsValueTemplate(this AttributeData attribute)
=> attribute.AttributeClass?.Name == "TValue" ||
attribute.AttributeClass?.Name == "TValueAttribute";

public static bool IsValueTemplate(this AttributeSyntax attribute)
=> attribute.Name.ToString() == "TValue" || attribute.Name.ToString() == "TValueAttribute";

public static bool IsStructIdTemplate(this AttributeData attribute)
=> attribute.AttributeClass?.Name == "TStructId" ||
attribute.AttributeClass?.Name == "TStructIdAttribute";
Expand Down
5 changes: 3 additions & 2 deletions src/StructId.Analyzer/BaseGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;

namespace StructId;
Expand Down Expand Up @@ -72,8 +73,8 @@ public virtual void Initialize(IncrementalGeneratorInitializationContext context
void GenerateCode(SourceProductionContext context, TemplateArgs args) => AddFromTemplate(
context, args, $"{args.TSelf.ToFileName()}.cs",
args.TId.Equals(args.KnownTypes.String, SymbolEqualityComparer.Default) ?
(stringSyntax ??= CodeTemplate.Parse(stringTemplate)) :
(typedSyntax ??= CodeTemplate.Parse(typeTemplate)));
(stringSyntax ??= CodeTemplate.Parse(stringTemplate, args.KnownTypes.Compilation.GetParseOptions())) :
(typedSyntax ??= CodeTemplate.Parse(typeTemplate, args.KnownTypes.Compilation.GetParseOptions())));

protected static void AddFromTemplate(SourceProductionContext context, TemplateArgs args, string hintName, SyntaxNode template)
{
Expand Down
108 changes: 104 additions & 4 deletions src/StructId.Analyzer/CodeTemplate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ namespace StructId;

public static class CodeTemplate
{
public static SyntaxNode Parse(string template)
public static SyntaxNode Parse(string template, CSharpParseOptions? parseOptions = default)
{
var tree = CSharpSyntaxTree.ParseText(template,
CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest));
parseOptions ?? CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest));

return tree.GetRoot();
}
Expand All @@ -30,6 +30,17 @@ public static string Apply(string template, string structIdType, string valueTyp
applied.ToFullString();
}

public static string Apply(string template, string valueType, bool normalizeWhitespace = false)
{
var applied = ApplyImpl(Parse(template), valueType);

return normalizeWhitespace ?
applied.NormalizeWhitespace().ToFullString().Trim() :
applied.ToFullString().Trim();
}

public static SyntaxNode ApplyValue(this SyntaxNode node, INamedTypeSymbol valueType) => ApplyImpl(node, valueType.ToFullName());

public static SyntaxNode Apply(this SyntaxNode node, INamedTypeSymbol structId)
{
var root = node.SyntaxTree.GetCompilationUnitRoot();
Expand All @@ -49,6 +60,17 @@ public static SyntaxNode Apply(this SyntaxNode node, INamedTypeSymbol structId)
return ApplyImpl(root, structId.Name, tid, targetNamespace, corens);
}

static SyntaxNode ApplyImpl(this SyntaxNode node, string valueType)
{
var root = node.SyntaxTree.GetCompilationUnitRoot();
if (root == null)
return node;

node = new ValueRewriter(valueType).Visit(root)!;

return node;
}

static SyntaxNode ApplyImpl(this SyntaxNode node, string structIdType, string valueType, string? targetNamespace = default, string coreNamespace = "StructId")
{
var root = node.SyntaxTree.GetCompilationUnitRoot();
Expand Down Expand Up @@ -95,6 +117,84 @@ static SyntaxNode ApplyImpl(this SyntaxNode node, string structIdType, string va
return node;
}

class ValueRewriter(string tvalue) : CSharpSyntaxRewriter
{
public override SyntaxNode? VisitRecordDeclaration(RecordDeclarationSyntax node)
{
if (IsFileLocal(node))
return null;

return node;
}

public override SyntaxNode? VisitStructDeclaration(StructDeclarationSyntax node)
{
if (IsFileLocal(node))
return null;

return base.VisitStructDeclaration(node);
}

public override SyntaxNode? VisitClassDeclaration(ClassDeclarationSyntax node)
{
if (IsFileLocal(node))
return null;

return base.VisitClassDeclaration(node);
}

public override SyntaxNode? VisitAttribute(AttributeSyntax node)
{
if (node.IsValueTemplate())
return null;

return base.VisitAttribute(node);
}

public override SyntaxNode? VisitAttributeList(AttributeListSyntax node)
{
node = (AttributeListSyntax)base.VisitAttributeList(node)!;
if (node.Attributes.Count == 0)
return null;

return base.VisitAttributeList(node);
}

public override SyntaxNode? VisitIdentifierName(IdentifierNameSyntax node)
{
if (node.Identifier.Text == "TValue")
return IdentifierName(tvalue)
.WithLeadingTrivia(node.Identifier.LeadingTrivia)
.WithTrailingTrivia(node.Identifier.TrailingTrivia);

if (node.Identifier.Text.StartsWith("TValue_"))
return IdentifierName(node.Identifier.Text.Replace("TValue_", tvalue.Replace('.', '_') + "_"))
.WithLeadingTrivia(node.Identifier.LeadingTrivia)
.WithTrailingTrivia(node.Identifier.TrailingTrivia);

return base.VisitIdentifierName(node);
}

public override SyntaxToken VisitToken(SyntaxToken token)
{
if (token.IsKind(SyntaxKind.IdentifierToken) && token.Text == "TValue")
return Identifier(tvalue)
.WithLeadingTrivia(token.LeadingTrivia)
.WithTrailingTrivia(token.TrailingTrivia);

if (token.IsKind(SyntaxKind.IdentifierToken) && token.Text.StartsWith("TValue_"))
return Identifier(token.Text.Replace("TValue_", tvalue.Replace('.', '_') + "_"))
.WithLeadingTrivia(token.LeadingTrivia)
.WithTrailingTrivia(token.TrailingTrivia);

return base.VisitToken(token);
}

bool IsFileLocal(TypeDeclarationSyntax node) =>
node.Modifiers.Any(x => x.IsKind(SyntaxKind.FileKeyword)) &&
!node.AttributeLists.Any(list => list.Attributes.Any(a => a.IsValueTemplate()));
}

class TemplateRewriter(string tself, string tid) : CSharpSyntaxRewriter
{
public override SyntaxNode? VisitRecordDeclaration(RecordDeclarationSyntax node)
Expand Down Expand Up @@ -183,7 +283,7 @@ class TemplateRewriter(string tself, string tid) : CSharpSyntaxRewriter
return IdentifierName(tself)
.WithLeadingTrivia(node.Identifier.LeadingTrivia)
.WithTrailingTrivia(node.Identifier.TrailingTrivia);
else if (node.Identifier.Text == "TId")
else if (node.Identifier.Text == "TId" || node.Identifier.Text == "TValue")
return IdentifierName(tid)
.WithLeadingTrivia(node.Identifier.LeadingTrivia)
.WithTrailingTrivia(node.Identifier.TrailingTrivia);
Expand All @@ -198,7 +298,7 @@ public override SyntaxToken VisitToken(SyntaxToken token)
return Identifier(tself)
.WithLeadingTrivia(token.LeadingTrivia)
.WithTrailingTrivia(token.TrailingTrivia);
else if (token.IsKind(SyntaxKind.IdentifierToken) && token.Text == "TId")
else if (token.IsKind(SyntaxKind.IdentifierToken) && (token.Text == "TId" || token.Text == "TValue"))
return Identifier(tid)
.WithLeadingTrivia(token.LeadingTrivia)
.WithTrailingTrivia(token.TrailingTrivia);
Expand Down
20 changes: 14 additions & 6 deletions src/StructId.Analyzer/DapperExtensions.sbn
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,24 @@ public static partial class DapperExtensions
{
{{~ for id in Ids ~}}
if (!SqlMapper.HasTypeHandler(typeof({{ id.TSelf }})))
SqlMapper.AddTypeHandler(new DapperTypeHandler{{ id.TId }}<{{ id.TSelf }}>());
SqlMapper.AddTypeHandler(new DapperTypeHandler{{ id.TValue }}<{{ id.TSelf }}>());

{{~ end ~}}
{{~ for id in CustomIds ~}}
if (!SqlMapper.HasTypeHandler(typeof({{ id.TSelf }})))
SqlMapper.AddTypeHandler(new DapperTypeHandler<{{ id.TSelf }}, {{ id.TId }}, {{ id.THandler }}>());
SqlMapper.AddTypeHandler(new DapperTypeHandler<{{ id.TSelf }}, {{ id.TValue }}, {{ id.THandler }}>());

{{~ end ~}}
{{~ for handler in CustomHandlers ~}}
if (!SqlMapper.HasTypeHandler(typeof({{ handler }})))
SqlMapper.AddTypeHandler(new {{ handler }}());
{{~ for handler in CustomValues ~}}
if (!SqlMapper.HasTypeHandler(typeof({{ handler.TValue }})))
SqlMapper.AddTypeHandler(new {{ handler.THandler }}());

{{~ end ~}}
{{~ for handler in TemplatizedValueHandlers ~}}
if (!SqlMapper.HasTypeHandler(typeof({{ handler.TValue }})))
SqlMapper.AddTypeHandler(new {{ handler.THandler }}());

{{~ end ~}}
return connection;
}

Expand Down Expand Up @@ -159,4 +163,8 @@ public static partial class DapperExtensions
};
}
}
}
}

{{~ for handler in TemplatizedValueHandlers ~}}
{{ handler.Code }}
{{~ end ~}}
Loading