diff --git a/PacketMediator.Samples/Program.cs b/PacketMediator.Samples/Program.cs
new file mode 100644
index 0000000..cf121b5
--- /dev/null
+++ b/PacketMediator.Samples/Program.cs
@@ -0,0 +1,3 @@
+// Licensed to Timothy Schenk under the Apache 2.0 License.
+
+Console.WriteLine("Hello, World!");
diff --git a/PacketMediator.Tests/PacketMediator.Tests.csproj b/PacketMediator.Tests/PacketMediator.Tests.csproj
index 2a25ccb..0ec6f08 100644
--- a/PacketMediator.Tests/PacketMediator.Tests.csproj
+++ b/PacketMediator.Tests/PacketMediator.Tests.csproj
@@ -15,9 +15,9 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/PacketMediator.Tests/UnitTest1.cs b/PacketMediator.Tests/UnitTest1.cs
new file mode 100644
index 0000000..f6d6791
--- /dev/null
+++ b/PacketMediator.Tests/UnitTest1.cs
@@ -0,0 +1,11 @@
+// Licensed to Timothy Schenk under the Apache 2.0 License.
+
+namespace PacketMediator.Tests;
+
+public class UnitTest1
+{
+ [Fact]
+ public void Test1()
+ {
+ }
+}
diff --git a/PacketMediator.sln b/PacketMediator.sln
index 2542cb5..68a3d38 100644
--- a/PacketMediator.sln
+++ b/PacketMediator.sln
@@ -9,6 +9,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PacketMediator.Samples", "P
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PacketMediator.Tests", "PacketMediator.Tests\PacketMediator.Tests.csproj", "{C0508314-2B9C-4285-9EE5-6DED3DD8C2DA}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceGenerators1", "SourceGenerators1\SourceGenerators1\SourceGenerators1.csproj", "{268AA6B4-0EBD-483B-A147-6B8D0E5B9F92}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceGenerators1.Sample", "SourceGenerators1\SourceGenerators1.Sample\SourceGenerators1.Sample.csproj", "{AA60BE32-4D65-4657-8ACF-ACF1437E2D1A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceGenerators1.Tests", "SourceGenerators1\SourceGenerators1.Tests\SourceGenerators1.Tests.csproj", "{60921557-6F31-43D7-A0CE-ED5C6E7DCB57}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -30,5 +36,17 @@ Global
{C0508314-2B9C-4285-9EE5-6DED3DD8C2DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C0508314-2B9C-4285-9EE5-6DED3DD8C2DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C0508314-2B9C-4285-9EE5-6DED3DD8C2DA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {268AA6B4-0EBD-483B-A147-6B8D0E5B9F92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {268AA6B4-0EBD-483B-A147-6B8D0E5B9F92}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {268AA6B4-0EBD-483B-A147-6B8D0E5B9F92}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {268AA6B4-0EBD-483B-A147-6B8D0E5B9F92}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AA60BE32-4D65-4657-8ACF-ACF1437E2D1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AA60BE32-4D65-4657-8ACF-ACF1437E2D1A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AA60BE32-4D65-4657-8ACF-ACF1437E2D1A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AA60BE32-4D65-4657-8ACF-ACF1437E2D1A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {60921557-6F31-43D7-A0CE-ED5C6E7DCB57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {60921557-6F31-43D7-A0CE-ED5C6E7DCB57}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {60921557-6F31-43D7-A0CE-ED5C6E7DCB57}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {60921557-6F31-43D7-A0CE-ED5C6E7DCB57}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/RaiNote.PacketMediator/PacketHandlerGenerator.cs b/RaiNote.PacketMediator/PacketHandlerGenerator.cs
new file mode 100644
index 0000000..f468967
--- /dev/null
+++ b/RaiNote.PacketMediator/PacketHandlerGenerator.cs
@@ -0,0 +1,66 @@
+using System.CodeDom.Compiler;
+using System.Collections.Immutable;
+using System.Globalization;
+using System.Reflection;
+using System.Resources;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+
+namespace RaiNote.PacketMediator;
+
+[Generator]
+public class PacketHandlerGenerator : IIncrementalGenerator
+{
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ var structProvider = context.SyntaxProvider.CreateSyntaxProvider(
+ predicate: static (node, _) => node is StructDeclarationSyntax,
+ transform: static (ctx, _) => GetAttributesInheriting(ctx)
+ )
+ .Where(m => m is not null);
+ var classProvider = context.SyntaxProvider.CreateSyntaxProvider(
+ predicate: static (node, _) => node is ClassDeclarationSyntax,
+ transform: static (ctx, _) => ctx.Node as ClassDeclarationSyntax
+ )
+ .Where(m => m is not null);
+ var compilation = context.CompilationProvider.Combine(structProvider.Collect());
+ context.RegisterSourceOutput(compilation, Execute);
+ var writer = new IndentedTextWriter(new StringWriter());
+ }
+
+ private static ICollection GetAttributesInheriting(GeneratorSyntaxContext context)
+ {
+ var arrayOfInheritingTypes = new List();
+ var structDeclarationSyntax = (StructDeclarationSyntax)context.Node;
+ foreach (var attributeList in structDeclarationSyntax.AttributeLists)
+ {
+ foreach (var attribute in attributeList.Attributes)
+ {
+ if (context.SemanticModel.GetSymbolInfo(attribute).Symbol is not IMethodSymbol attributeSymbol)
+ continue; // if we can't get the symbol, ignore it
+ if (context.SemanticModel.GetSymbolInfo(attribute).Symbol is not ITypeSymbol attributeTypeSymbol)
+ continue;
+ if (!attributeTypeSymbol.AllInterfaces
+ .Select(x => x.Name.Equals(typeof(PacketIdAttribute<>).ToString(), StringComparison.Ordinal))
+ .Any())
+ continue;
+
+ arrayOfInheritingTypes.Add(attributeTypeSymbol.Name);
+ }
+ }
+
+ return arrayOfInheritingTypes;
+ }
+
+ private void Execute(SourceProductionContext context,
+ (Compilation Left, ImmutableArray> Right) valueTuple)
+ {
+ var (compilation, attributeNames) = valueTuple;
+ var code = $@"{string.Join('\n', attributeNames)}";
+ code += "\r\n";
+
+ context.AddSource("Test.g.cs", SourceText.From(code, Encoding.UTF8));
+ }
+}
diff --git a/RaiNote.PacketMediator/RaiNote.PacketMediator.csproj b/RaiNote.PacketMediator/RaiNote.PacketMediator.csproj
index fc6427c..a8b4d55 100644
--- a/RaiNote.PacketMediator/RaiNote.PacketMediator.csproj
+++ b/RaiNote.PacketMediator/RaiNote.PacketMediator.csproj
@@ -20,20 +20,26 @@
README.md
true
snupkg
+ true
-
-
-
-
+
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/SourceGenerators1/SourceGenerators1.Sample/Examples.cs b/SourceGenerators1/SourceGenerators1.Sample/Examples.cs
new file mode 100644
index 0000000..131971e
--- /dev/null
+++ b/SourceGenerators1/SourceGenerators1.Sample/Examples.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using JetBrains.Annotations;
+
+namespace SourceGenerators1.Sample;
+
+[GamePacketIdAttribute(OperationCode.LoginRequest)]
+public class LoginRequestPacket : IIncomingPacket
+{
+ public string Username { get; set; }
+
+ public string Password { get; set; }
+
+ public void Deserialize(byte[] data)
+ {
+ Username = Encoding.ASCII.GetString(data, 0, 32);
+ Password = Encoding.ASCII.GetString(data, 32, 64);
+ }
+}
+
+// GamePacketIdAttribute.cs
+[AttributeUsage(AttributeTargets.Class, Inherited = false)]
+public class GamePacketIdAttribute : PacketIdAttribute
+{
+ public GamePacketIdAttribute(OperationCode code) : base(code)
+ {
+ }
+}
+
+public enum OperationCode : ushort
+{
+ LoginRequest = 1
+}
+
diff --git a/SourceGenerators1/SourceGenerators1.Sample/SourceGenerators1.Sample.csproj b/SourceGenerators1/SourceGenerators1.Sample/SourceGenerators1.Sample.csproj
new file mode 100644
index 0000000..2b388ba
--- /dev/null
+++ b/SourceGenerators1/SourceGenerators1.Sample/SourceGenerators1.Sample.csproj
@@ -0,0 +1,21 @@
+
+
+
+ net8.0
+ enable
+ SourceGenerators1.Sample
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SourceGenerators1/SourceGenerators1.Tests/SampleIncrementalSourceGeneratorTests.cs b/SourceGenerators1/SourceGenerators1.Tests/SampleIncrementalSourceGeneratorTests.cs
new file mode 100644
index 0000000..7ac24dd
--- /dev/null
+++ b/SourceGenerators1/SourceGenerators1.Tests/SampleIncrementalSourceGeneratorTests.cs
@@ -0,0 +1,70 @@
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Xunit;
+
+namespace SourceGenerators1.Tests;
+
+public class SampleIncrementalSourceGeneratorTests
+{
+ private const string VectorClassText = @"
+namespace TestNamespace;
+
+[Generators.Report]
+public partial class Vector3
+{
+ public float X { get; set; }
+ public float Y { get; set; }
+ public float Z { get; set; }
+}";
+
+ private const string ExpectedGeneratedClassText = @"//
+
+using System;
+using System.Collections.Generic;
+
+namespace TestNamespace;
+
+partial class Vector3
+{
+ public IEnumerable Report()
+ {
+ yield return $""X:{this.X}"";
+ yield return $""Y:{this.Y}"";
+ yield return $""Z:{this.Z}"";
+ }
+}
+";
+
+ [Fact]
+ public void GenerateReportMethod()
+ {
+ // Create an instance of the source generator.
+ var generator = new SampleIncrementalSourceGenerator();
+
+ // Source generators should be tested using 'GeneratorDriver'.
+ var driver = CSharpGeneratorDriver.Create(generator);
+
+ // We need to create a compilation with the required source code.
+ var compilation = CSharpCompilation.Create(nameof(SampleSourceGeneratorTests),
+ new[] { CSharpSyntaxTree.ParseText(VectorClassText) },
+ new[]
+ {
+ // To support 'System.Attribute' inheritance, add reference to 'System.Private.CoreLib'.
+ MetadataReference.CreateFromFile(typeof(object).Assembly.Location)
+ }
+ );
+
+ // 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"));
+
+ // Complex generators should be tested using text comparison.
+ Assert.Equal(ExpectedGeneratedClassText,
+ generatedFileSyntax.GetText().ToString(),
+ ignoreLineEndingDifferences: true
+ );
+ }
+}
diff --git a/SourceGenerators1/SourceGenerators1.Tests/SampleSourceGeneratorTests.cs b/SourceGenerators1/SourceGenerators1.Tests/SampleSourceGeneratorTests.cs
new file mode 100644
index 0000000..9d272f6
--- /dev/null
+++ b/SourceGenerators1/SourceGenerators1.Tests/SampleSourceGeneratorTests.cs
@@ -0,0 +1,44 @@
+using System.IO;
+using System.Linq;
+using SourceGenerators1.Tests.Utils;
+using Microsoft.CodeAnalysis.CSharp;
+using Xunit;
+
+namespace SourceGenerators1.Tests;
+
+public class SampleSourceGeneratorTests
+{
+ private const string DddRegistryText = @"User
+Document
+Customer";
+
+ [Fact]
+ public void GenerateClassesBasedOnDDDRegistry()
+ {
+ // Create an instance of the source generator.
+ var generator = new SampleSourceGenerator();
+
+ // Source generators should be tested using 'GeneratorDriver'.
+ var driver = CSharpGeneratorDriver.Create(new[] { generator },
+ new[]
+ {
+ // Add the additional file separately from the compilation.
+ new TestAdditionalFile("./DDD.UbiquitousLanguageRegistry.txt", DddRegistryText)
+ }
+ );
+
+ // To run generators, we can use an empty compilation.
+ var compilation = CSharpCompilation.Create(nameof(SampleSourceGeneratorTests));
+
+ // Run generators. Don't forget to use the new compilation rather than the previous one.
+ driver.RunGeneratorsAndUpdateCompilation(compilation, out var newCompilation, out _);
+
+ // Retrieve all files in the compilation.
+ var generatedFiles = newCompilation.SyntaxTrees
+ .Select(t => Path.GetFileName(t.FilePath))
+ .ToArray();
+
+ // In this case, it is enough to check the file name.
+ Assert.Equivalent(new[] { "User.g.cs", "Document.g.cs", "Customer.g.cs" }, generatedFiles);
+ }
+}
diff --git a/SourceGenerators1/SourceGenerators1.Tests/SourceGenerators1.Tests.csproj b/SourceGenerators1/SourceGenerators1.Tests/SourceGenerators1.Tests.csproj
new file mode 100644
index 0000000..abb5fc2
--- /dev/null
+++ b/SourceGenerators1/SourceGenerators1.Tests/SourceGenerators1.Tests.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net8.0
+ enable
+
+ false
+
+ SourceGenerators1.Tests
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
diff --git a/SourceGenerators1/SourceGenerators1.Tests/Utils/TestAdditionalFile.cs b/SourceGenerators1/SourceGenerators1.Tests/Utils/TestAdditionalFile.cs
new file mode 100644
index 0000000..40297c4
--- /dev/null
+++ b/SourceGenerators1/SourceGenerators1.Tests/Utils/TestAdditionalFile.cs
@@ -0,0 +1,20 @@
+using System.Threading;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Text;
+
+namespace SourceGenerators1.Tests.Utils;
+
+public class TestAdditionalFile : AdditionalText
+{
+ private readonly SourceText _text;
+
+ public TestAdditionalFile(string path, string text)
+ {
+ Path = path;
+ _text = SourceText.From(text);
+ }
+
+ public override SourceText GetText(CancellationToken cancellationToken = new()) => _text;
+
+ public override string Path { get; }
+}
diff --git a/SourceGenerators1/SourceGenerators1/Properties/launchSettings.json b/SourceGenerators1/SourceGenerators1/Properties/launchSettings.json
new file mode 100644
index 0000000..7254224
--- /dev/null
+++ b/SourceGenerators1/SourceGenerators1/Properties/launchSettings.json
@@ -0,0 +1,9 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "DebugRoslynSourceGenerator": {
+ "commandName": "DebugRoslynComponent",
+ "targetProject": "../SourceGenerators1.Sample/SourceGenerators1.Sample.csproj"
+ }
+ }
+}
\ No newline at end of file
diff --git a/SourceGenerators1/SourceGenerators1/Readme.md b/SourceGenerators1/SourceGenerators1/Readme.md
new file mode 100644
index 0000000..cc30b4f
--- /dev/null
+++ b/SourceGenerators1/SourceGenerators1/Readme.md
@@ -0,0 +1,29 @@
+# Roslyn Source Generators Sample
+
+A set of three projects that illustrates Roslyn source generators. Enjoy this template to learn from and modify source generators for your own needs.
+
+## Content
+### SourceGenerators1
+A .NET Standard project with implementations of sample source generators.
+**You must build this project to see the result (generated code) in the IDE.**
+
+- [SampleSourceGenerator.cs](SampleSourceGenerator.cs): A source generator that creates C# classes based on a text file (in this case, Domain Driven Design ubiquitous language registry).
+- [SampleIncrementalSourceGenerator.cs](SampleIncrementalSourceGenerator.cs): A source generator that creates a custom report based on class properties. The target class should be annotated with the `Generators.ReportAttribute` attribute.
+
+### SourceGenerators1.Sample
+A project that references source generators. Note the parameters of `ProjectReference` in [SourceGenerators1.Sample.csproj](../SourceGenerators1.Sample/SourceGenerators1.Sample.csproj), they make sure that the project is referenced as a set of source generators.
+
+### SourceGenerators1.Tests
+Unit tests for source generators. The easiest way to develop language-related features is to start with unit tests.
+
+## How To?
+### How to debug?
+- Use the [launchSettings.json](Properties/launchSettings.json) profile.
+- Debug tests.
+
+### How can I determine which syntax nodes I should expect?
+Consider installing the Roslyn syntax tree viewer plugin [Rossynt](https://plugins.jetbrains.com/plugin/16902-rossynt/).
+
+### How to learn more about wiring source generators?
+Watch the walkthrough video: [Let’s Build an Incremental Source Generator With Roslyn, by Stefan Pölz](https://youtu.be/azJm_Y2nbAI)
+The complete set of information is available in [Source Generators Cookbook](https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.cookbook.md).
\ No newline at end of file
diff --git a/SourceGenerators1/SourceGenerators1/SampleIncrementalSourceGenerator.cs b/SourceGenerators1/SourceGenerators1/SampleIncrementalSourceGenerator.cs
new file mode 100644
index 0000000..6e92121
--- /dev/null
+++ b/SourceGenerators1/SourceGenerators1/SampleIncrementalSourceGenerator.cs
@@ -0,0 +1,135 @@
+using System.Collections.Immutable;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+
+
+namespace SourceGenerators1;
+
+///
+/// A sample source generator that creates a custom report based on class properties. The target class should be annotated with the 'Generators.ReportAttribute' attribute.
+/// When using the source code as a baseline, an incremental source generator is preferable because it reduces the performance overhead.
+///
+[Generator]
+public class SampleIncrementalSourceGenerator : IIncrementalGenerator
+{
+ private const string Namespace = "Generators";
+ private const string AttributeName = "ReportAttribute";
+
+ private const string AttributeSourceCode = $@"//
+
+namespace {Namespace}
+{{
+ [System.AttributeUsage(System.AttributeTargets.Class)]
+ public class {AttributeName} : System.Attribute
+ {{
+ }}
+}}";
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ // Add the marker attribute to the compilation.
+ context.RegisterPostInitializationOutput(ctx => ctx.AddSource(
+ "ReportAttribute.g.cs",
+ SourceText.From(AttributeSourceCode, Encoding.UTF8)
+ )
+ );
+
+ // Filter classes annotated with the [Report] attribute. Only filtered Syntax Nodes can trigger code generation.
+ var provider = context.SyntaxProvider
+ .CreateSyntaxProvider(
+ (s, _) => s is ClassDeclarationSyntax,
+ (ctx, _) => GetClassDeclarationForSourceGen(ctx)
+ )
+ .Where(t => t.reportAttributeFound)
+ .Select((t, _) => t.Item1);
+
+ // Generate the source code.
+ context.RegisterSourceOutput(context.CompilationProvider.Combine(provider.Collect()),
+ ((ctx, t) => GenerateCode(ctx, t.Left, t.Right))
+ );
+ }
+
+ ///
+ /// Checks whether the Node is annotated with the [Report] attribute and maps syntax context to the specific node type (ClassDeclarationSyntax).
+ ///
+ /// Syntax context, based on CreateSyntaxProvider predicate
+ /// The specific cast and whether the attribute was found.
+ private static (ClassDeclarationSyntax, bool reportAttributeFound) GetClassDeclarationForSourceGen(
+ GeneratorSyntaxContext context)
+ {
+ var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node;
+
+ // Go through all attributes of the class.
+ foreach (AttributeListSyntax attributeListSyntax in classDeclarationSyntax.AttributeLists)
+ foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes)
+ {
+ if (context.SemanticModel.GetSymbolInfo(attributeSyntax).Symbol is not IMethodSymbol attributeSymbol)
+ continue; // if we can't get the symbol, ignore it
+
+ string attributeName = attributeSymbol.ContainingType.ToDisplayString();
+
+ // Check the full name of the [Report] attribute.
+ if (attributeName == $"{Namespace}.{AttributeName}")
+ return (classDeclarationSyntax, true);
+ }
+
+ return (classDeclarationSyntax, false);
+ }
+
+ ///
+ /// Generate code action.
+ /// It will be executed on specific nodes (ClassDeclarationSyntax annotated with the [Report] attribute) changed by the user.
+ ///
+ /// Source generation context used to add source files.
+ /// Compilation used to provide access to the Semantic Model.
+ /// Nodes annotated with the [Report] attribute that trigger the generate action.
+ private void GenerateCode(SourceProductionContext context, Compilation compilation,
+ ImmutableArray 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 (semanticModel.GetDeclaredSymbol(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 methodBody = classSymbol.GetMembers()
+ .OfType()
+ .Select(p =>
+ $@" yield return $""{p.Name}:{{this.{p.Name}}}"";"
+ ); // e.g. yield return $"Id:{this.Id}";
+
+ // Build up the source code
+ var code = $@"//
+
+using System;
+using System.Collections.Generic;
+
+namespace {namespaceName};
+
+partial class {className}
+{{
+ public IEnumerable Report()
+ {{
+{string.Join("\n", methodBody)}
+ }}
+}}
+";
+
+ // Add the source code to the compilation.
+ context.AddSource($"{className}.g.cs", SourceText.From(code, Encoding.UTF8));
+ }
+ }
+}
diff --git a/SourceGenerators1/SourceGenerators1/SampleSourceGenerator.cs b/SourceGenerators1/SourceGenerators1/SampleSourceGenerator.cs
new file mode 100644
index 0000000..d70106f
--- /dev/null
+++ b/SourceGenerators1/SourceGenerators1/SampleSourceGenerator.cs
@@ -0,0 +1,56 @@
+using System.IO;
+using Microsoft.CodeAnalysis;
+
+namespace SourceGenerators1;
+
+///
+/// A sample source generator that creates C# classes based on the text file (in this case, Domain Driven Design ubiquitous language registry).
+/// When using a simple text file as a baseline, we can create a non-incremental source generator.
+///
+[Generator]
+public class SampleSourceGenerator : ISourceGenerator
+{
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ // No initialization required for this generator.
+ }
+
+ public void Execute(GeneratorExecutionContext context)
+ {
+ // If you would like to put some data to non-compilable file (e.g. a .txt file), mark it as an Additional File.
+
+ // Go through all files marked as an Additional File in file properties.
+ foreach (var additionalFile in context.AdditionalFiles)
+ {
+ if (additionalFile == null)
+ continue;
+
+ // Check if the file name is the specific file that we expect.
+ if (Path.GetFileName(additionalFile.Path) != "DDD.UbiquitousLanguageRegistry.txt")
+ continue;
+
+ var text = additionalFile.GetText();
+ if (text == null)
+ continue;
+
+ foreach (var line in text.Lines)
+ {
+ var className = line.ToString().Trim();
+
+ // Build up the source code.
+ string source = $@"//
+
+namespace Entities
+{{
+ public partial class {className}
+ {{
+ }}
+}}
+";
+
+ // Add the source code to the compilation.
+ context.AddSource($"{className}.g.cs", source);
+ }
+ }
+ }
+}
diff --git a/SourceGenerators1/SourceGenerators1/SourceGenerators1.csproj b/SourceGenerators1/SourceGenerators1/SourceGenerators1.csproj
new file mode 100644
index 0000000..99cd737
--- /dev/null
+++ b/SourceGenerators1/SourceGenerators1/SourceGenerators1.csproj
@@ -0,0 +1,31 @@
+
+
+
+ netstandard2.1
+ false
+ enable
+ latest
+
+ true
+ true
+
+ SourceGenerators1
+ SourceGenerators1
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SourceGenerators1/SourceGenerators1/Test.cs b/SourceGenerators1/SourceGenerators1/Test.cs
new file mode 100644
index 0000000..6edc886
--- /dev/null
+++ b/SourceGenerators1/SourceGenerators1/Test.cs
@@ -0,0 +1,232 @@
+using System;
+using System.CodeDom.Compiler;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using JetBrains.Annotations;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+
+namespace SourceGenerators1;
+
+[Generator]
+public class PacketHandlerGenerator : IIncrementalGenerator
+{
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ var attributeProvider = context.SyntaxProvider.CreateSyntaxProvider(
+ predicate: static (node, _) => node is ClassDeclarationSyntax,
+ transform: static (ctx, _) => GetAttributesInheriting(ctx)
+ )
+ .Where(m => m is not null);
+
+ var attribute = attributeProvider.Select((data, _) => data);
+
+ /*
+ var classProvider = context.SyntaxProvider.CreateSyntaxProvider(
+ predicate: static (node, _) => node is ClassDeclarationSyntax,
+ transform: (ctx, _) => GetIncomingPacketTypes(ctx, attribute)
+ )
+ .Where(m => m is not null);
+
+ var test = attributeProvider.Combine(classProvider.Collect());*/
+
+ context.RegisterSourceOutput(context.CompilationProvider.Combine(attributeProvider.Collect()),
+ Execute
+ );
+ }
+
+ private static string? GetIncomingPacketTypes(GeneratorSyntaxContext context, string? desiredAttributeName)
+ {
+ var structDeclarationSyntax = (ClassDeclarationSyntax)context.Node;
+
+ var isValidBaseType = false;
+ foreach (var baseTypeSyntax in structDeclarationSyntax.BaseList?.Types ?? Enumerable.Empty())
+ {
+ var simpleBaseTypeSyntax = baseTypeSyntax as SimpleBaseTypeSyntax;
+ var genericName = simpleBaseTypeSyntax?.Type as GenericNameSyntax;
+ if (genericName?.Identifier.ToString() !=
+ GetAttributeName(typeof(IIncomingPacket)))
+ {
+ continue;
+ }
+
+ isValidBaseType = true;
+ }
+
+ if (!isValidBaseType)
+ {
+ return null;
+ }
+
+ foreach (var attributeList in structDeclarationSyntax.AttributeLists)
+ {
+ foreach (var attributeSyntax in attributeList.Attributes)
+ {
+ if (attributeSyntax.Name.ToString() != desiredAttributeName)
+ continue;
+
+ return structDeclarationSyntax.Identifier.Text;
+ }
+ }
+
+ return null;
+ }
+
+ private static AttributeClassMapping? GetAttributesInheriting(GeneratorSyntaxContext context)
+ {
+ var structDeclarationSyntax = (ClassDeclarationSyntax)context.Node;
+ var targetName = structDeclarationSyntax.Identifier.Text;
+ string? attributeName = null;
+ var isValidAttribute = false;
+ var isValidPacketType = false;
+ foreach (var attributeList in structDeclarationSyntax.AttributeLists)
+ {
+ foreach (var attributeSyntax in attributeList.Attributes)
+ {
+ }
+ }
+ foreach (var baseTypeSyntax in structDeclarationSyntax.BaseList?.Types ?? Enumerable.Empty())
+ {
+ var simpleBaseTypeSyntax = baseTypeSyntax as SimpleBaseTypeSyntax;
+ var genericName = simpleBaseTypeSyntax?.Type as GenericNameSyntax;
+ if (genericName?.Identifier.ToString() ==
+ GetAttributeName(typeof(PacketIdAttribute<>)))
+ {
+ isValidAttribute = true;
+ attributeName = simpleBaseTypeSyntax?.ToFullString();
+ }
+ var interfaceName = simpleBaseTypeSyntax?.Type as IdentifierNameSyntax;
+
+ if (interfaceName?.Identifier.ToString() == nameof(IIncomingPacket))
+ isValidPacketType = true;
+ }
+
+ if (isValidPacketType)
+ return new AttributeClassMapping(targetName, attributeName);
+
+ return null;
+ }
+
+ private struct AttributeClassMapping
+ {
+ public AttributeClassMapping(string? className, string? attributeName)
+ {
+ this.ClassName = className;
+ this.AttributeName = attributeName;
+ }
+
+ public readonly string? ClassName;
+ public readonly string? AttributeName;
+ }
+
+ private static string GetAttributeName(Type type)
+ {
+ return type.IsGenericType ? type.Name.Split('`')[0] : type.Name;
+ }
+
+ private static void Execute(SourceProductionContext context,
+ (Compilation Left, ImmutableArray Right) tuple)
+ {
+ var dependencies = new string[] { "System", "System.Threading.Channels", "SourceGenerators1.Sample" };
+ /*
+ var lambda = CodeGenerator.Lambda>(fun =>
+ {
+ var argPacketData = fun[0];
+ var newPacket = packetsType.Value.New();
+
+ var packetVariable = CodeGenerator.DeclareVariable(packetsType.Value, "packet");
+ CodeGenerator.Assign(packetVariable, newPacket);
+ CodeGenerator.Call(packetVariable, nameof(IIncomingPacket.Deserialize), argPacketData);
+
+ CodeGenerator.Return(packetVariable);
+ }).Compile();
+ */
+ var allparsingFunctions = "";
+ context.ReportDiagnostic(Diagnostic.Create(
+ new DiagnosticDescriptor("fuckyou0001",
+ "FuckYou",
+ "Message: {0}",
+ "FUCK YOU",
+ DiagnosticSeverity.Error,
+ true
+ ),
+ Location.None,
+ string.Join(", ",
+ tuple.Right.Select(x =>
+ {
+ if (!x.HasValue)
+ return string.Empty;
+ return x.Value.AttributeName + ":" + x.Value.ClassName;
+ }
+ )
+ )
+ )
+ );
+ if (tuple.Right.Length == 0)
+ {
+ throw new Exception();
+ }
+
+ foreach (var packetName in tuple.Right)
+ {
+ var parsingFunctionTemplate = $$"""
+ public {{packetName?.ClassName}} Parse(byte[] data) {
+ var packet = new {{packetName?.ClassName}}();
+ packet.Deserialize(data);
+ return packet;
+ }
+ """;
+ allparsingFunctions += parsingFunctionTemplate;
+ }
+
+ var codeTemplate = $$"""
+ //
+ {{string.Join('\n', dependencies.Select(dep => $"using {dep};"))}}
+
+ public class PacketDistributorService{
+
+ {{allparsingFunctions}}
+
+ private async Task InvokePacketHandlerAsync((byte[], TPacketIdEnum, TSession) valueTuple,
+ CancellationToken cancellationToken)
+ {
+ var (packetData, operationCode, session) = valueTuple;
+ if (!_deserializationMap.TryGetValue(operationCode, out var func))
+ {
+ return;
+ }
+
+ var packet = func(packetData);
+
+ await _packetHandlersInstantiation[operationCode]?.TryHandleAsync(packet, session, cancellationToken)!;
+ }
+ }
+ """;
+ context.AddSource("Test.g.cs", SourceText.From(codeTemplate, Encoding.UTF8));
+ }
+}
+
+[AttributeUsage(AttributeTargets.Class, Inherited = false)]
+public abstract class PacketIdAttribute : Attribute where TPacketIdEnum : Enum
+{
+ protected PacketIdAttribute(TPacketIdEnum code)
+ {
+ Code = code;
+ }
+
+ public TPacketIdEnum Code { get; }
+}
+
+[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
+public interface IIncomingPacket : IPacket
+{
+ [UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
+ public void Deserialize(byte[] data);
+}
+
+public interface IPacket;