Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
  • Loading branch information
feast107 committed Dec 20, 2023
1 parent 269c220 commit e43293b
Show file tree
Hide file tree
Showing 20 changed files with 278 additions and 209 deletions.
14 changes: 10 additions & 4 deletions src/Antelcat.ClaimSerialization.Sample/ClaimSerializableClass.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
using System;
using System.Collections.Generic;
using System.Security.Claims;
using Antelcat.ClaimSerialization.ComponentModel;
using Antelcat.ClaimSerialization.Metadata;

namespace Antelcat.ClaimSerialization.Sample;

[ClaimSerializable]
public unsafe partial class ClaimSerializableClass
public partial class ClaimSerializableClass
{

[ClaimType(ClaimTypes.Role)]
Expand All @@ -28,4 +27,11 @@ public enum E
}

}

[ClaimSerializable(typeof(ClaimSerializableClass))]
public partial class ClaimSerializationClass : ClaimSerializerContext
{
public override ClaimTypeInfo? GetTypeInfo(Type type)
{
throw new NotImplementedException();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
namespace Antelcat.ClaimSerialization.ComponentModel;
#if !NET46
namespace Antelcat.ClaimSerialization.ComponentModel;

/// <summary>
/// Types marked this attribute will auto generate implement methods of <see cref="Antelcat.ClaimSerialization.IClaimSerializable"/>
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class ClaimSerializableAttribute : Attribute;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class ClaimSerializableAttribute(Type type) : Attribute
{
internal Type Type { get; } = type;
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,4 @@
</ItemGroup>

<Import Project="..\Antelcat.ClaimSerialization.Shared\Antelcat.ClaimSerialization.Shared.projitems" Label="Shared"/>


</Project>
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Security.Claims;
using System.Text;
using Antelcat.ClaimSerialization.ComponentModel;
using Feast.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using Assembly = Feast.CodeAnalysis.CompileTime.Assembly;


namespace Antelcat.ClaimSerialization.SourceGenerators.Generators
{
[Generator]
public class ClaimSerializeGenerator : IIncrementalGenerator
public class ClaimSerializeContextGenerator : IIncrementalGenerator
{
private static readonly string Attribute = $"{typeof(ClaimSerializableAttribute).FullName}";
private static readonly string IClaimSerializable = $"global::{typeof(IClaimSerializable).FullName}";
Expand Down Expand Up @@ -133,117 +135,120 @@ private static string GenerateClaimToProp(
$"{(!inner ? $"{propName} = " : string.Empty)}{group}.Select(x=> {GenerateClaimToProp(propName, "x.Value", argument, true)}){suffix}";
}


public void Initialize(IncrementalGeneratorInitializationContext context)
{
var provider = context.SyntaxProvider
.ForAttributeWithMetadataName(
Attribute,
(s, _) => s is ClassDeclarationSyntax,
(ctx, _) => (ctx.TargetNode as ClassDeclarationSyntax)!);
(ctx, _) => ctx);

context.RegisterSourceOutput(
context.CompilationProvider.Combine(provider.Collect()),
(ctx, t) => GenerateCode(ctx, t.Left, t.Right));
}

private static void GenerateCode(SourceProductionContext context, Compilation compilation,
ImmutableArray<ClassDeclarationSyntax> classDeclarations)
{
// Go through all filtered class declarations.
foreach (var classDeclarationSyntax in classDeclarations)
{
// We need to get semantic model of the class to retrieve metadata.
var semanticModel = compilation.GetSemanticModel(classDeclarationSyntax.SyntaxTree);
// Symbols allow us to get the compile-time information.
if (ModelExtensions.GetDeclaredSymbol(semanticModel, classDeclarationSyntax) is not INamedTypeSymbol
classSymbol)
continue;

var namespaceName = classSymbol.ContainingNamespace.ToDisplayString();

// 'Identifier' means the token of the node. Get class name from the syntax node.
var className = classDeclarationSyntax.Identifier.Text;

// Go through all class members with a particular type (property) to generate method lines.
var transform = classSymbol.GetMembers()
.OfType<IPropertySymbol>()
.Where(x => FilterAttribute(x, context))
.Select(x => Transform(x, context))
.ToList();
var propToClaims = transform
.Where(x => !x.typeSymbol.IsReadOnly)
.Select(p => p.converter == null
? GeneratePropToClaim(p.claimType, p.typeSymbol.Type, p.propName)
: $"yield return new {Claim}({p.claimType}, new {p.converter.GetFullyQualifiedName()}().ConvertToString({p.propName}));"
);

var claimsToProps = transform
.Where(x =>
(ctx, t) =>
{
foreach (var syntax in t.Right)
{
if (x.typeSymbol.IsInitOnly())
// We need to get semantic model of the class to retrieve metadata.
var semanticModel = t.Left.GetSemanticModel(syntax.TargetNode.SyntaxTree);
// Symbols allow us to get the compile-time information.
if (semanticModel.GetDeclaredSymbol(syntax.TargetNode) is not INamedTypeSymbol
classSymbol)
continue;
var type = (syntax.TargetSymbol as INamedTypeSymbol).ToType();
var ass = type.Assembly;
var attr = syntax.Attributes.First().ToAttribute<ClaimSerializableAttribute>();
if (type.Assembly.Equals(attr.Type.Assembly))
{
context.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor(
nameof(CS0002),
"Error",
string.Format(CS0002, nameof(ClaimTypeAttribute)),
nameof(ClaimSerializableAttribute),
DiagnosticSeverity.Error,
true
), x.typeSymbol.Locations.First()));

}

return !x.typeSymbol.IsWriteOnly;
})
.Select(p => $"case {p.claimType} :"
+ (p.converter == null
? GenerateClaimToProp(p.propName, $"{group}.First().Value", p.typeSymbol.Type)
: $"{p.propName} = ({p.typeSymbol.Type.GetFullyQualifiedName()})new {p.converter.GetFullyQualifiedName()}().ConvertFromString({group}.First().Value)")
+ "; break;");

var head = TriviaList(
Comment("// <auto-generated/> By Antelcat.ClaimSerialization.SourceGenerators"),
Trivia(NullableDirectiveTrivia(Token(SyntaxKind.EnableKeyword), true)),
Trivia(PragmaWarningDirectiveTrivia(Token(SyntaxKind.DisableKeyword), true)));

var codes = CompilationUnit()
.AddUsings( //using
"System",
"System.Collections.Generic",
"System.Linq",
"System.ComponentModel"
).WithLeadingTrivia(head)
.AddMembers( // namespace
namespaceName.ToNameSyntax().ToNamespaceDeclaration()
.AddMembers( //class
className.ToClassDeclaration()
.AddModifiers(SyntaxKind.PartialKeyword)
.AddBaseListTypes(IClaimSerializable)
.AddMembers(
$$"""
public {{IEnumerable}}<{{Claim}}> {{nameof(ClaimSerialization.IClaimSerializable.GetClaims)}}()
{
{{string.Join("\n", propToClaims).Replace("\n", "\n\t\t")}}
}
""",
$$"""
public void {{nameof(ClaimSerialization.IClaimSerializable.FromClaims)}}({{IEnumerable}}<{{Claim}}> claims)
{
foreach (var {{group}} in claims.GroupBy(x => x.Type))
{
switch ({{group}}.Key)
var namespaceName = classSymbol.ContainingNamespace.ToDisplayString();

// 'Identifier' means the token of the node. Get class name from the syntax node.
var className = (syntax.TargetSymbol as INamedTypeSymbol).Name;

// Go through all class members with a particular type (property) to generate method lines.
var transform = classSymbol.GetMembers()
.OfType<IPropertySymbol>()
.Where(x => FilterAttribute(x, ctx))
.Select(x => Transform(x, ctx))
.ToList();
var propToClaims = transform
.Where(x => !x.typeSymbol.IsReadOnly)
.Select(p => p.converter == null
? GeneratePropToClaim(p.claimType, p.typeSymbol.Type, p.propName)
: $"yield return new {Claim}({p.claimType}, new {p.converter.GetFullyQualifiedName()}().ConvertToString({p.propName}));"
);

var claimsToProps = transform
.Where(x =>
{
if (x.typeSymbol.IsInitOnly())
{
ctx.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor(
nameof(CS0002),
"Error",
string.Format(CS0002, nameof(ClaimTypeAttribute)),
nameof(ClaimSerializableAttribute),
DiagnosticSeverity.Error,
true
), x.typeSymbol.Locations.First()));
}

return !x.typeSymbol.IsWriteOnly;
})
.Select(p => $"case {p.claimType} :"
+ (p.converter == null
? GenerateClaimToProp(p.propName, $"{group}.First().Value",
p.typeSymbol.Type)
: $"{p.propName} = ({p.typeSymbol.Type.GetFullyQualifiedName()})new {p.converter.GetFullyQualifiedName()}().ConvertFromString({group}.First().Value)")
+ "; break;");

var head = TriviaList(
Comment("// <auto-generated/> By Antelcat.ClaimSerialization.SourceGenerators"),
Trivia(NullableDirectiveTrivia(Token(SyntaxKind.EnableKeyword), true)),
Trivia(PragmaWarningDirectiveTrivia(Token(SyntaxKind.DisableKeyword), true)));

var codes = CompilationUnit()
.AddUsings( //using
"System",
"System.Collections.Generic",
"System.Linq",
"System.ComponentModel"
).WithLeadingTrivia(head)
.AddMembers( // namespace
namespaceName.ToNameSyntax().ToNamespaceDeclaration()
.AddMembers( //class
className.ToClassDeclaration()
.AddModifiers(SyntaxKind.PartialKeyword)
.AddBaseListTypes(IClaimSerializable)
.AddMembers(
$$"""
public {{IEnumerable}}<{{Claim}}> {{nameof(ClaimSerialization.IClaimSerializable.GetClaims)}}()
{
{{string.Join("\n", claimsToProps).Replace("\n", "\n\t\t\t\t")}}
{{string.Join("\n", propToClaims).Replace("\n", "\n\t\t")}}
}
}
}
"""
)
)).NormalizeWhitespace();

// Add the source code to the compilation.
context.AddSource($"{className}.g.cs", codes.GetText(Encoding.UTF8));
}
""",
$$"""
public void {{nameof(ClaimSerialization.IClaimSerializable.FromClaims)}}({{IEnumerable}}<{{Claim}}> claims)
{
foreach (var {{group}} in claims.GroupBy(x => x.Type))
{
switch ({{group}}.Key)
{
{{string.Join("\n", claimsToProps).Replace("\n", "\n\t\t\t\t")}}
}
}
}
"""
)
)).NormalizeWhitespace();

// Add the source code to the compilation.
ctx.AddSource($"{className}.g.cs", codes.GetText(Encoding.UTF8));
}
});
}

private static bool FilterAttribute(IPropertySymbol symbol, SourceProductionContext context)
Expand Down Expand Up @@ -324,4 +329,9 @@ internal partial class Type
{

}
}

internal partial class Assembly
{

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,11 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit" Version="1.1.1"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.2"/>
<PackageReference Include="xunit" Version="2.4.2"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.1"/>
<PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2"/>
<PackageReference Include="NUnit.Analyzers" Version="3.6.1"/>
<PackageReference Include="coverlet.collector" Version="3.2.0"/>
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@
using Antelcat.ClaimSerialization.SourceGenerators.Generators;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Xunit;
using NUnit.Framework;

namespace Antelcat.ClaimSerialization.Tests.CompileTime;

public class CompileTimeTests
{
[Fact]
[Test]
public void Generate()
{
var path = Path.GetFullPath(@"..\..\..\..\");
// Create an instance of the source generator.
var generator = new ClaimSerializeGenerator();
var generator = new ClaimSerializeContextGenerator();

// Source generators should be tested using 'GeneratorDriver'.
var driver = CSharpGeneratorDriver.Create(generator);
Expand All @@ -40,7 +40,6 @@ public void Generate()
// Run generators and retrieve all results.
var runResult = driver.RunGenerators(compilation).GetRunResult();
// All generated files can be found in 'RunResults.GeneratedTrees'.
var generatedFileSyntax = runResult.GeneratedTrees.Single(t => t.FilePath.EndsWith("Vector3.g.cs"));
}

public class Foo : JsonConverter<string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Security.Claims;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using Antelcat.ClaimSerialization.ComponentModel;
Expand All @@ -7,8 +8,11 @@ namespace Antelcat.ClaimSerialization.Tests.Runtime;

public class IdentityModel
{

public string? Name { get; set; } = nameof(Name);
public IdentityModel(int count)
{

}
public required string? Name { get; set; } = nameof(Name);

public int Id { get; set; } = 123456;

Expand All @@ -27,7 +31,6 @@ public enum Role
Guest
}
}

[JsonSerializable(typeof(IdentityModel))]
public partial class JsonContext : JsonSerializerContext
{
Expand Down
Loading

0 comments on commit e43293b

Please sign in to comment.