Skip to content

Commit

Permalink
init Source Generators
Browse files Browse the repository at this point in the history
  • Loading branch information
j0nimost committed Nov 4, 2023
1 parent deddfd3 commit dd86cf3
Show file tree
Hide file tree
Showing 10 changed files with 270 additions and 24 deletions.
11 changes: 10 additions & 1 deletion src/Kafa.Generators/Kafa.Generators.csproj
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFrameworks>net7.0;netstandard2.0</TargetFrameworks>
<LangVersion>11.0</LangVersion>
</PropertyGroup>

<PropertyGroup>
<IsPackable>true</IsPackable>
<IncludeBuildOutput>false</IncludeBuildOutput>
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
<DevelopmentDependency>true</DevelopmentDependency>
<IsRoslynComponent>true</IsRoslynComponent>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
<PrivateAssets>all</PrivateAssets>
Expand Down
27 changes: 27 additions & 0 deletions src/Kafa.Generators/KafaSourceGenerator.Attribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace nyingi.Kafa.Generators;

internal sealed partial class KafaSourceGenerator
{
private static readonly string GeneratedAttribute = $@"[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""{typeof(KafaSourceGenerator).Assembly.GetName().Name}"", ""0.1.0"")]";

//lang=c#
private static readonly string KafaSerializableAttribute = $@"
//<auto-generated/>
#nullable enable
namespace nyingi.Kafa.Generators;
{GeneratedAttribute}
[global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple =true, Inherited = false)]
internal sealed class KafaSerializableAttribute : global::System.Attribute
{{
public global::System.Type Type {{ get; }}
public KafaSerializableAttribute(global::System.Type type)
{{
Type = type;
}}
}}
";
}
17 changes: 17 additions & 0 deletions src/Kafa.Generators/KafaSourceGenerator.Context.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;

namespace nyingi.Kafa.Generators
{
internal sealed partial class KafaSourceGenerator
{
private record struct KafaGeneratorContext(string? actualnamespace, string name, TypeContext typeContext);

Check warning on line 8 in src/Kafa.Generators/KafaSourceGenerator.Context.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

private record struct TypeContext(bool isReferenceType, string name, ImmutableArray<PropertyContext> propertyContext);

private record struct PropertyContext(string type, string name);

private static readonly SymbolDisplayFormat Format = SymbolDisplayFormat.FullyQualifiedFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes | SymbolDisplayMiscellaneousOptions.ExpandNullable | SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier);

}
}
19 changes: 0 additions & 19 deletions src/Kafa.Generators/KafaSourceGenerator.Emitter.cs

This file was deleted.

171 changes: 171 additions & 0 deletions src/Kafa.Generators/KafaSourceGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
using System;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;

namespace nyingi.Kafa.Generators
{

[Generator]
internal sealed partial class KafaSourceGenerator : IIncrementalGenerator
{
private const string _kafaAttributeName = "nyingi.Kafa.Generators.KafaSerializableAttribute";
public void Initialize(IncrementalGeneratorInitializationContext context)
{
context.RegisterPostInitializationOutput(PostInitializationCallback);


IncrementalValuesProvider<KafaGeneratorContext> provider = context.SyntaxProvider
.CreateSyntaxProvider(SyntacticPredicate, SemanticTransformer)
.Where(static ((INamedTypeSymbol, INamedTypeSymbol)? types) => types.HasValue)
.Select(static ((INamedTypeSymbol, INamedTypeSymbol)? types, CancellationToken cancellationToken) =>
TransformType(types!.Value));

}

private static void PostInitializationCallback(IncrementalGeneratorPostInitializationContext context)
{
context.AddSource("KafaSerializableAttribute.g.cs", SourceText.From(KafaSerializableAttribute, Encoding.UTF8));
}
private static bool SyntacticPredicate(SyntaxNode syntaxNode, CancellationToken cancellationToken)
{
return syntaxNode is ClassDeclarationSyntax
{
AttributeLists.Count: > 0,
} candidate
&& candidate.Modifiers.Any(SyntaxKind.PartialKeyword)
&& !candidate.Modifiers.Any(SyntaxKind.StaticKeyword);
}

private static (INamedTypeSymbol, INamedTypeSymbol)? SemanticTransformer(GeneratorSyntaxContext context, CancellationToken cancellationToken)
{
var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node;

INamedTypeSymbol? namedTypeSymbol = context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax, cancellationToken);

Check warning on line 51 in src/Kafa.Generators/KafaSourceGenerator.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.


if (namedTypeSymbol is not null
&& TryGetAttribute(classDeclarationSyntax, _kafaAttributeName, context.SemanticModel, cancellationToken,
out AttributeSyntax? attributeSyntax)

Check warning on line 56 in src/Kafa.Generators/KafaSourceGenerator.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
&& TryGetType(attributeSyntax, context.SemanticModel, cancellationToken,
out INamedTypeSymbol? typeSymbol))

Check warning on line 58 in src/Kafa.Generators/KafaSourceGenerator.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
return (namedTypeSymbol, typeSymbol);
}

return null;
}


private static bool TryGetAttribute(ClassDeclarationSyntax classDeclarationSyntax, string attributeName,
SemanticModel semanticModel, CancellationToken cancellationToken,
out AttributeSyntax? value)

Check warning on line 69 in src/Kafa.Generators/KafaSourceGenerator.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
foreach (AttributeListSyntax attributeList in classDeclarationSyntax.AttributeLists)
{
foreach (AttributeSyntax attributeSyntax in attributeList.Attributes)
{
SymbolInfo info = semanticModel.GetSymbolInfo(attributeSyntax, cancellationToken);
ISymbol? symbol = info.Symbol;

Check warning on line 76 in src/Kafa.Generators/KafaSourceGenerator.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

if (symbol is IMethodSymbol methodSymbol
&& methodSymbol.ContainingType.ToDisplayString()
.Equals(attributeName, StringComparison.Ordinal))
{
value = attributeSyntax;
return true;
}
}
}

value = null;
return false;
}


private static bool TryGetType(AttributeSyntax attributeSyntax, SemanticModel semanticModel,
CancellationToken cancellationToken, out INamedTypeSymbol? namedTypeSymbol)

Check warning on line 94 in src/Kafa.Generators/KafaSourceGenerator.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
var argumentList = attributeSyntax.ArgumentList;
if (argumentList!.Arguments.Count > 1) ;

Check warning on line 97 in src/Kafa.Generators/KafaSourceGenerator.cs

View workflow job for this annotation

GitHub Actions / build

Possible mistaken empty statement
{
AttributeArgumentSyntax argumentSyntax = argumentList.Arguments[0];

if (argumentSyntax.Expression is TypeOfExpressionSyntax typeOf)
{
SymbolInfo info = semanticModel.GetSymbolInfo(typeOf.Type, cancellationToken);
ISymbol? symbol = info.Symbol;

if (symbol is INamedTypeSymbol typeSymbol)
{
namedTypeSymbol = typeSymbol;
return true;
}
}
}

namedTypeSymbol = null;
return false;
}

private static KafaGeneratorContext TransformType((INamedTypeSymbol kafaPartialGeneratorType, INamedTypeSymbol targetType) types)
{
string? @namespace = types.kafaPartialGeneratorType.ContainingNamespace.IsGlobalNamespace
? null
: types.kafaPartialGeneratorType.ContainingNamespace.ToDisplayString();

string name = types.kafaPartialGeneratorType.Name;

bool isReferenceType = types.kafaPartialGeneratorType.IsReferenceType;
string targetTypeName = types.targetType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);

bool areInternalSymbolsAccessible =
types.targetType.ContainingAssembly.GivesAccessTo((types.kafaPartialGeneratorType.ContainingAssembly));

ImmutableArray<PropertyContext> propertyContexts = types.targetType.GetTypeAndSubtypes()
.Reverse()
.SelectMany(static type => type.GetMembers())
.Where(member => FilterProperty(member, areInternalSymbolsAccessible))
.Select(static member => TransformProperty(member))
.Distinct()
.ToImmutableArray();


return new KafaGeneratorContext(@namespace, name,
new TypeContext(isReferenceType, targetTypeName, propertyContexts));
}


private static bool FilterProperty(ISymbol member, bool areInernalSymbolsAccessible)
{
if (!member.IsStatic && member.Kind == SymbolKind.Property)
{
var property = (IPropertySymbol)member;

return property.GetMethod is { } get
&& (get.DeclaredAccessibility is Accessibility.Public
|| (areInernalSymbolsAccessible &&
(get.DeclaredAccessibility is Accessibility.Internal
or Accessibility.ProtectedOrInternal)));
}

return false;
}

private static PropertyContext TransformProperty(ISymbol member)
{
var property = (IPropertySymbol)member;
string type = property.Type.ToDisplayString(Format);
string name = property.Name;

return new PropertyContext(type, name);
}
}
}
19 changes: 19 additions & 0 deletions src/Kafa.Generators/NameTypeSymbolExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis;

namespace nyingi.Kafa.Generators;

internal static class NameTypeSymbolExtensions
{

public static IEnumerable<INamedTypeSymbol> GetTypeAndSubtypes(this INamedTypeSymbol? type)

Check warning on line 9 in src/Kafa.Generators/NameTypeSymbolExtensions.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
INamedTypeSymbol? current = type;

Check warning on line 11 in src/Kafa.Generators/NameTypeSymbolExtensions.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
while (current is not null)
{
yield return current;
current = current.BaseType;
}
}

}
6 changes: 6 additions & 0 deletions src/Kafa.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kafa", "Kafa\Kafa.csproj",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KafaTests", "KafaTests\KafaTests.csproj", "{BB863E7D-755B-4626-90C3-6AAA1A8B4FAD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kafa.Generators", "Kafa.Generators\Kafa.Generators.csproj", "{65208E1E-7289-451E-8C66-578B98F4CC90}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -21,6 +23,10 @@ Global
{BB863E7D-755B-4626-90C3-6AAA1A8B4FAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BB863E7D-755B-4626-90C3-6AAA1A8B4FAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BB863E7D-755B-4626-90C3-6AAA1A8B4FAD}.Release|Any CPU.Build.0 = Release|Any CPU
{65208E1E-7289-451E-8C66-578B98F4CC90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{65208E1E-7289-451E-8C66-578B98F4CC90}.Debug|Any CPU.Build.0 = Debug|Any CPU
{65208E1E-7289-451E-8C66-578B98F4CC90}.Release|Any CPU.ActiveCfg = Release|Any CPU
{65208E1E-7289-451E-8C66-578B98F4CC90}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
10 changes: 10 additions & 0 deletions src/Kafa/IKafaFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using nyingi.Kafa.Reader;
using nyingi.Kafa.Writer;

namespace nyingi.Kafa;

public interface IKafaFormatter<T>
{
public static abstract void Serialize(ref KafaWriter writer, scoped ref T? entity);
public static abstract void Deserialize(ref KafaReader reader, scoped ref T? entity);
}
7 changes: 6 additions & 1 deletion src/Kafa/Kafa.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
<!-- <CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>-->
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Kafa.Generators\Kafa.Generators.csproj" OutputItemType="Analyzer"
ReferenceOutputAssembly="false"/>
</ItemGroup>
</Project>
7 changes: 4 additions & 3 deletions src/Kafa/Writer/KafaWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,22 @@ public class KafaWriter : IDisposable
private readonly byte[] _winNewLine = new byte[2] {(byte)'\r',(byte)'\n'};

private readonly KafaOptions _options;

private readonly byte[] _cachedNewLine;

private byte[] _separator = new byte[1];
public KafaWriter(in IBufferWriter<byte> bufferWriter, KafaOptions options)
{
_bufferWriter = bufferWriter ?? throw new NullReferenceException(nameof(bufferWriter));
_options = options;
_separator[0] = (byte)_options.Separator;
_cachedNewLine = Environment.OSVersion.Platform == PlatformID.Unix ? _unixNewLine : _winNewLine;
}
public KafaWriter(in Stream stream, KafaOptions options)
{
_stream = stream;
_options = options;
_kafaPooledWriter = new KafaPooledWriter(); // use the default 65k
_cachedNewLine = Environment.OSVersion.Platform == PlatformID.Unix ? _unixNewLine : _winNewLine;
}

public void WriteSeparator()
Expand All @@ -35,8 +37,7 @@ public void WriteSeparator()

public void WriteLine()
{
var newLine = Environment.OSVersion.Platform == PlatformID.Unix ? _unixNewLine : _winNewLine;
Write(newLine);
Write(_cachedNewLine);
}

public void Write(string str)
Expand Down

0 comments on commit dd86cf3

Please sign in to comment.