just test stuff
This commit is contained in:
parent
b5ac05a853
commit
be0d5c08bf
18 changed files with 821 additions and 8 deletions
3
PacketMediator.Samples/Program.cs
Normal file
3
PacketMediator.Samples/Program.cs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
// Licensed to Timothy Schenk under the Apache 2.0 License.
|
||||||
|
|
||||||
|
Console.WriteLine("Hello, World!");
|
|
@ -15,9 +15,9 @@
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||||
<PackageReference Include="xunit" Version="2.7.0" />
|
<PackageReference Include="xunit" Version="2.9.0" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|
11
PacketMediator.Tests/UnitTest1.cs
Normal file
11
PacketMediator.Tests/UnitTest1.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// Licensed to Timothy Schenk under the Apache 2.0 License.
|
||||||
|
|
||||||
|
namespace PacketMediator.Tests;
|
||||||
|
|
||||||
|
public class UnitTest1
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Test1()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PacketMediator.Samples", "P
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PacketMediator.Tests", "PacketMediator.Tests\PacketMediator.Tests.csproj", "{C0508314-2B9C-4285-9EE5-6DED3DD8C2DA}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PacketMediator.Tests", "PacketMediator.Tests\PacketMediator.Tests.csproj", "{C0508314-2B9C-4285-9EE5-6DED3DD8C2DA}"
|
||||||
EndProject
|
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
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{C0508314-2B9C-4285-9EE5-6DED3DD8C2DA}.Release|Any CPU.Build.0 = 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
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
66
RaiNote.PacketMediator/PacketHandlerGenerator.cs
Normal file
66
RaiNote.PacketMediator/PacketHandlerGenerator.cs
Normal file
|
@ -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<string> GetAttributesInheriting(GeneratorSyntaxContext context)
|
||||||
|
{
|
||||||
|
var arrayOfInheritingTypes = new List<string>();
|
||||||
|
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<ICollection<string>> 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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,20 +20,26 @@
|
||||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
<IncludeSymbols>true</IncludeSymbols>
|
<IncludeSymbols>true</IncludeSymbols>
|
||||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
|
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="../README.md" Pack="true" PackagePath="\"/>
|
<None Include="../README.md" Pack="true" PackagePath="\"/>
|
||||||
<PackageReference Include="DotNext" Version="5.3.1"/>
|
<PackageReference Include="DotNext" Version="5.11.0" />
|
||||||
<PackageReference Include="DotNext.Metaprogramming" Version="5.3.0"/>
|
<PackageReference Include="DotNext.Metaprogramming" Version="5.7.0" />
|
||||||
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0"/>
|
<PackageReference Include="JetBrains.Annotations" Version="2024.2.0" />
|
||||||
<PackageReference Include="Meziantou.Analyzer" Version="2.0.146">
|
<PackageReference Include="Meziantou.Analyzer" Version="2.0.163">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0"/>
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1"/>
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1"/>
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.9.28">
|
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.10.48">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|
35
SourceGenerators1/SourceGenerators1.Sample/Examples.cs
Normal file
35
SourceGenerators1/SourceGenerators1.Sample/Examples.cs
Normal file
|
@ -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<OperationCode>
|
||||||
|
{
|
||||||
|
public GamePacketIdAttribute(OperationCode code) : base(code)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum OperationCode : ushort
|
||||||
|
{
|
||||||
|
LoginRequest = 1
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<RootNamespace>SourceGenerators1.Sample</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\SourceGenerators1\SourceGenerators1.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="true"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="DDD.UbiquitousLanguageRegistry.txt"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="JetBrains.Annotations" Version="2024.2.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -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 = @"// <auto-generated/>
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace TestNamespace;
|
||||||
|
|
||||||
|
partial class Vector3
|
||||||
|
{
|
||||||
|
public IEnumerable<string> 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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
|
||||||
|
<RootNamespace>SourceGenerators1.Tests</RootNamespace>
|
||||||
|
</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>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\SourceGenerators1\SourceGenerators1.csproj"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
</Project>
|
|
@ -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; }
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||||
|
"profiles": {
|
||||||
|
"DebugRoslynSourceGenerator": {
|
||||||
|
"commandName": "DebugRoslynComponent",
|
||||||
|
"targetProject": "../SourceGenerators1.Sample/SourceGenerators1.Sample.csproj"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
SourceGenerators1/SourceGenerators1/Readme.md
Normal file
29
SourceGenerators1/SourceGenerators1/Readme.md
Normal file
|
@ -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).
|
|
@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
[Generator]
|
||||||
|
public class SampleIncrementalSourceGenerator : IIncrementalGenerator
|
||||||
|
{
|
||||||
|
private const string Namespace = "Generators";
|
||||||
|
private const string AttributeName = "ReportAttribute";
|
||||||
|
|
||||||
|
private const string AttributeSourceCode = $@"// <auto-generated/>
|
||||||
|
|
||||||
|
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))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether the Node is annotated with the [Report] attribute and maps syntax context to the specific node type (ClassDeclarationSyntax).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">Syntax context, based on CreateSyntaxProvider predicate</param>
|
||||||
|
/// <returns>The specific cast and whether the attribute was found.</returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate code action.
|
||||||
|
/// It will be executed on specific nodes (ClassDeclarationSyntax annotated with the [Report] attribute) changed by the user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">Source generation context used to add source files.</param>
|
||||||
|
/// <param name="compilation">Compilation used to provide access to the Semantic Model.</param>
|
||||||
|
/// <param name="classDeclarations">Nodes annotated with the [Report] attribute that trigger the generate action.</param>
|
||||||
|
private 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 (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<IPropertySymbol>()
|
||||||
|
.Select(p =>
|
||||||
|
$@" yield return $""{p.Name}:{{this.{p.Name}}}"";"
|
||||||
|
); // e.g. yield return $"Id:{this.Id}";
|
||||||
|
|
||||||
|
// Build up the source code
|
||||||
|
var code = $@"// <auto-generated/>
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace {namespaceName};
|
||||||
|
|
||||||
|
partial class {className}
|
||||||
|
{{
|
||||||
|
public IEnumerable<string> Report()
|
||||||
|
{{
|
||||||
|
{string.Join("\n", methodBody)}
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
";
|
||||||
|
|
||||||
|
// Add the source code to the compilation.
|
||||||
|
context.AddSource($"{className}.g.cs", SourceText.From(code, Encoding.UTF8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
SourceGenerators1/SourceGenerators1/SampleSourceGenerator.cs
Normal file
56
SourceGenerators1/SourceGenerators1/SampleSourceGenerator.cs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace SourceGenerators1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
[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 = $@"// <auto-generated/>
|
||||||
|
|
||||||
|
namespace Entities
|
||||||
|
{{
|
||||||
|
public partial class {className}
|
||||||
|
{{
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
";
|
||||||
|
|
||||||
|
// Add the source code to the compilation.
|
||||||
|
context.AddSource($"{className}.g.cs", source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
SourceGenerators1/SourceGenerators1/SourceGenerators1.csproj
Normal file
31
SourceGenerators1/SourceGenerators1/SourceGenerators1.csproj
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
|
||||||
|
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||||
|
<IsRoslynComponent>true</IsRoslynComponent>
|
||||||
|
|
||||||
|
<RootNamespace>SourceGenerators1</RootNamespace>
|
||||||
|
<PackageId>SourceGenerators1</PackageId>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="JetBrains.Annotations" Version="2024.2.0" />
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.0"/>
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.3.0"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="test.snippet.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
</Project>
|
232
SourceGenerators1/SourceGenerators1/Test.cs
Normal file
232
SourceGenerators1/SourceGenerators1/Test.cs
Normal file
|
@ -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<BaseTypeSyntax>())
|
||||||
|
{
|
||||||
|
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<BaseTypeSyntax>())
|
||||||
|
{
|
||||||
|
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<AttributeClassMapping?> Right) tuple)
|
||||||
|
{
|
||||||
|
var dependencies = new string[] { "System", "System.Threading.Channels", "SourceGenerators1.Sample" };
|
||||||
|
/*
|
||||||
|
var lambda = CodeGenerator.Lambda<Func<byte[], IIncomingPacket>>(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 = $$"""
|
||||||
|
//<auto-generated/>
|
||||||
|
{{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<TPacketIdEnum> : 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;
|
Loading…
Reference in a new issue