From 9fe8a75a1c1d2c5327f8a4509699f957cd31e54e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Boh=C3=A1=C4=8Dek?= Date: Wed, 29 Dec 2021 22:39:52 +0100 Subject: [PATCH] feat: add source generator for basic packet converter --- .../IParameterGenerator.cs | 43 +++ .../PacketIndexAttributeGenerator.cs | 73 ++++ .../Errors/DiagnosticError.cs | 25 ++ .../Errors/IError.cs | 18 + .../Extensions/SyntaxNodeExtensions.cs | 60 +++ .../IsExternalInit.cs | 15 + ...osSmooth.PacketSerializersGenerator.csproj | 22 ++ .../PacketClassReceiver.cs | 50 +++ .../PacketSerializerGenerator.cs | 353 ++++++++++++++++++ .../ParameterInfo.cs | 39 ++ 10 files changed, 698 insertions(+) create mode 100644 Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/IParameterGenerator.cs create mode 100644 Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketIndexAttributeGenerator.cs create mode 100644 Core/NosSmooth.PacketSerializersGenerator/Errors/DiagnosticError.cs create mode 100644 Core/NosSmooth.PacketSerializersGenerator/Errors/IError.cs create mode 100644 Core/NosSmooth.PacketSerializersGenerator/Extensions/SyntaxNodeExtensions.cs create mode 100644 Core/NosSmooth.PacketSerializersGenerator/IsExternalInit.cs create mode 100644 Core/NosSmooth.PacketSerializersGenerator/NosSmooth.PacketSerializersGenerator.csproj create mode 100644 Core/NosSmooth.PacketSerializersGenerator/PacketClassReceiver.cs create mode 100644 Core/NosSmooth.PacketSerializersGenerator/PacketSerializerGenerator.cs create mode 100644 Core/NosSmooth.PacketSerializersGenerator/ParameterInfo.cs diff --git a/Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/IParameterGenerator.cs b/Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/IParameterGenerator.cs new file mode 100644 index 0000000..7fb1048 --- /dev/null +++ b/Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/IParameterGenerator.cs @@ -0,0 +1,43 @@ +// +// IParameterGenerator.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.CodeDom.Compiler; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using NosSmooth.PacketSerializersGenerator.Errors; + +namespace NosSmooth.PacketSerializersGenerator.AttributeGenerators +{ + /// + /// Generate serializer and deserializer method parts for the given constructor parameter. + /// + public interface IParameterGenerator + { + /// + /// Check whether this generator should handle parameter with this attribute. + /// + /// The parameters attribute. + /// Whether to handle this parameter. + public bool ShouldHandle(AttributeSyntax attribute); + + /// + /// Generate part for the Serializer method to serialize the given parameter. + /// + /// The text writer to write the code to. + /// The packet record declaration syntax. + /// The parameter info to generate for. + /// The generated source code. + public IError? GenerateSerializerPart(IndentedTextWriter textWriter, RecordDeclarationSyntax recordDeclarationSyntax, ParameterInfo parameterInfo); + + /// + /// Generate part for the Deserializer method to deserialize the given parameter. + /// + /// The text writer to write the code to. + /// The packet record declaration syntax. + /// The parameter info to generate for. + /// The generated source code. + public IError? GenerateDeserializerPart(IndentedTextWriter textWriter, RecordDeclarationSyntax recordDeclarationSyntax, ParameterInfo parameterInfo); + } +} \ No newline at end of file diff --git a/Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketIndexAttributeGenerator.cs b/Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketIndexAttributeGenerator.cs new file mode 100644 index 0000000..fc1241a --- /dev/null +++ b/Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketIndexAttributeGenerator.cs @@ -0,0 +1,73 @@ +// +// PacketIndexAttributeGenerator.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.CodeDom.Compiler; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using NosSmooth.PacketSerializersGenerator.Errors; + +namespace NosSmooth.PacketSerializersGenerator.AttributeGenerators +{ + /// + public class PacketIndexAttributeGenerator : IParameterGenerator + { + /// + public bool ShouldHandle(AttributeSyntax attribute) + => attribute.Name.NormalizeWhitespace().ToFullString() == "PacketIndex"; + + /// + public IError? GenerateSerializerPart(IndentedTextWriter textWriter, RecordDeclarationSyntax recordDeclarationSyntax, ParameterInfo parameterInfo) + { + textWriter.WriteLine($@" +var {parameterInfo.Name}Result = _typeConverterRepository.Serialize(obj.{parameterInfo.Name}, builder); +if (!{parameterInfo.Name}Result.IsSuccess) +{{ + return Result.FromError(new PacketParameterSerializerError(this, ""{parameterInfo.Name}"", {parameterInfo.Name}Result), {parameterInfo.Name}Result); +}} +"); + + return null; + } + + /// + public IError? GenerateDeserializerPart(IndentedTextWriter textWriter, RecordDeclarationSyntax recordDeclarationSyntax, ParameterInfo parameterInfo) + { + bool pushedLevel = false; + + if (parameterInfo.NamedAttributeArguments.ContainsKey("AfterSeparator") && parameterInfo.NamedAttributeArguments["AfterSeparator"] is not null) + { + textWriter.WriteLine($"stringEnumerator.SetAfterSeparatorOnce('{parameterInfo.NamedAttributeArguments["AfterSeparator"]}');"); + } + + if (parameterInfo.NamedAttributeArguments.ContainsKey("InnerSeparator") && parameterInfo.NamedAttributeArguments["InnerSeparator"] is not null) + { + pushedLevel = true; + textWriter.WriteLine($"stringEnumerator.PushLevel('{parameterInfo.NamedAttributeArguments["InnerSeparator"]}');"); + } + + var semanticModel = parameterInfo.Compilation.GetSemanticModel(recordDeclarationSyntax.SyntaxTree); + var type = semanticModel.GetTypeInfo(parameterInfo.Parameter.Type!).Type; + string last = parameterInfo.IsLast ? "true" : "false"; + textWriter.WriteLine($@" +var {parameterInfo.Name}Result = _typeConverterRepository.Deserialize<{type!.ToString()}>(stringEnumerator); +var {parameterInfo.Name}Error = CheckDeserializationResult({parameterInfo.Name}Result, ""{parameterInfo.Name}"", stringEnumerator, {last}); +if ({parameterInfo.Name}Error is not null) +{{ + return Result<{recordDeclarationSyntax.Identifier.NormalizeWhitespace().ToFullString()}?>.FromError({parameterInfo.Name}Error, {parameterInfo.Name}Result); +}} + +var {parameterInfo.Name} = {parameterInfo.Name}Result.Entity; +"); + if (pushedLevel) + { + textWriter.WriteLine("stringEnumerator.PopLevel();"); + } + + return null; + } + } +} \ No newline at end of file diff --git a/Core/NosSmooth.PacketSerializersGenerator/Errors/DiagnosticError.cs b/Core/NosSmooth.PacketSerializersGenerator/Errors/DiagnosticError.cs new file mode 100644 index 0000000..7366cec --- /dev/null +++ b/Core/NosSmooth.PacketSerializersGenerator/Errors/DiagnosticError.cs @@ -0,0 +1,25 @@ +// +// DiagnosticError.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace NosSmooth.PacketSerializersGenerator.Errors; + +/// +/// The diagnostic error. +/// +/// +/// +/// +/// +/// +/// +public record DiagnosticError(string Id, string Title, string MessageFormat, SyntaxTree Tree, TextSpan Span, List Parameters) : IError +{ + /// + public string Message => Title; +} \ No newline at end of file diff --git a/Core/NosSmooth.PacketSerializersGenerator/Errors/IError.cs b/Core/NosSmooth.PacketSerializersGenerator/Errors/IError.cs new file mode 100644 index 0000000..0ec6911 --- /dev/null +++ b/Core/NosSmooth.PacketSerializersGenerator/Errors/IError.cs @@ -0,0 +1,18 @@ +// +// IError.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace NosSmooth.PacketSerializersGenerator.Errors; + +/// +/// Base error type. +/// +public interface IError +{ + /// + /// Gets the message. + /// + public string Message { get; } +} \ No newline at end of file diff --git a/Core/NosSmooth.PacketSerializersGenerator/Extensions/SyntaxNodeExtensions.cs b/Core/NosSmooth.PacketSerializersGenerator/Extensions/SyntaxNodeExtensions.cs new file mode 100644 index 0000000..9870ede --- /dev/null +++ b/Core/NosSmooth.PacketSerializersGenerator/Extensions/SyntaxNodeExtensions.cs @@ -0,0 +1,60 @@ +// +// SyntaxNodeExtensions.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace NosSmooth.PacketSerializersGenerator.Extensions; + +/// +/// Extensions for . +/// +public static class SyntaxNodeExtensions +{ + /// + /// Gets the prefix of the given member (class or namespace). + /// + /// The member to get prefix of. + /// The full name. + public static string GetPrefix(this SyntaxNode? member) + { + if (member is null) + { + return string.Empty; + } + + StringBuilder sb = new StringBuilder(); + SyntaxNode node = member; + + while (node.Parent != null) + { + node = node.Parent; + + if (node is NamespaceDeclarationSyntax) + { + var namespaceDeclaration = (NamespaceDeclarationSyntax)node; + + sb.Insert(0, "."); + sb.Insert(0, namespaceDeclaration.Name.ToString()); + } + else if (node is FileScopedNamespaceDeclarationSyntax fileScopedNamespaceDeclarationSyntax) + { + sb.Insert(0, "."); + sb.Insert(0, fileScopedNamespaceDeclarationSyntax.Name.ToString()); + } + else if (node is ClassDeclarationSyntax) + { + var classDeclaration = (ClassDeclarationSyntax)node; + + sb.Insert(0, "."); + sb.Insert(0, classDeclaration.Identifier.ToString()); + } + } + + return sb.ToString().Trim('.'); + } +} \ No newline at end of file diff --git a/Core/NosSmooth.PacketSerializersGenerator/IsExternalInit.cs b/Core/NosSmooth.PacketSerializersGenerator/IsExternalInit.cs new file mode 100644 index 0000000..5c2fc46 --- /dev/null +++ b/Core/NosSmooth.PacketSerializersGenerator/IsExternalInit.cs @@ -0,0 +1,15 @@ +// +// IsExternalInit.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Runtime.CompilerServices +{ + /// + /// Dummy. + /// + public class IsExternalInit + { + } +} \ No newline at end of file diff --git a/Core/NosSmooth.PacketSerializersGenerator/NosSmooth.PacketSerializersGenerator.csproj b/Core/NosSmooth.PacketSerializersGenerator/NosSmooth.PacketSerializersGenerator.csproj new file mode 100644 index 0000000..2c152d4 --- /dev/null +++ b/Core/NosSmooth.PacketSerializersGenerator/NosSmooth.PacketSerializersGenerator.csproj @@ -0,0 +1,22 @@ + + + + netstandard2.0 + enable + enable + 10 + true + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + diff --git a/Core/NosSmooth.PacketSerializersGenerator/PacketClassReceiver.cs b/Core/NosSmooth.PacketSerializersGenerator/PacketClassReceiver.cs new file mode 100644 index 0000000..e0d4a11 --- /dev/null +++ b/Core/NosSmooth.PacketSerializersGenerator/PacketClassReceiver.cs @@ -0,0 +1,50 @@ +// +// PacketClassReceiver.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace NosSmooth.PacketSerializersGenerator +{ + /// + /// Syntax receiver of classes with generate attribute. + /// + public class PacketClassReceiver : ISyntaxReceiver + { + private readonly List _packetClasses; + + /// + /// Gets the name of the attribute that indicates the packet should have a serializer generated. + /// + public static string AttributeFullName => "GenerateSerializer"; + + /// + /// Initializes a new instance of the class. + /// + public PacketClassReceiver() + { + _packetClasses = new List(); + } + + /// + /// Gets the classes that should have serializers generated. + /// + public IReadOnlyList PacketClasses => _packetClasses; + + /// + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + { + if (syntaxNode.IsKind(SyntaxKind.RecordDeclaration) && syntaxNode is RecordDeclarationSyntax classDeclarationSyntax) + { + if (classDeclarationSyntax.AttributeLists.Any(x => x.Attributes.Any(x => x.Name.NormalizeWhitespace().ToFullString() == AttributeFullName))) + { + _packetClasses.Add(classDeclarationSyntax); + } + } + } + } +} \ No newline at end of file diff --git a/Core/NosSmooth.PacketSerializersGenerator/PacketSerializerGenerator.cs b/Core/NosSmooth.PacketSerializersGenerator/PacketSerializerGenerator.cs new file mode 100644 index 0000000..05b5740 --- /dev/null +++ b/Core/NosSmooth.PacketSerializersGenerator/PacketSerializerGenerator.cs @@ -0,0 +1,353 @@ +// +// PacketSerializerGenerator.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.CodeDom.Compiler; +using System.Diagnostics; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using NosSmooth.PacketSerializersGenerator.AttributeGenerators; +using NosSmooth.PacketSerializersGenerator.Errors; +using NosSmooth.PacketSerializersGenerator.Extensions; + +namespace NosSmooth.PacketSerializersGenerator; + +/// +/// Generates ITypeGenerator for packets that are marked with NosSmooth.Packets.Attributes.GenerateSerializerAttribute. +/// +/// +/// The packets to create serializer for have to be records that specify PacketIndices in the constructor. +/// +[Generator] +public class PacketSerializerGenerator : ISourceGenerator +{ + /// + /// Initializes a new instance of the class. + /// + public PacketSerializerGenerator() + { + _generators = new List(new[] { new PacketIndexAttributeGenerator() }); + } + + private readonly List _generators; + + /// + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForSyntaxNotifications(() => new PacketClassReceiver()); + } + + /// + public void Execute(GeneratorExecutionContext context) + { + var syntaxReceiver = (PacketClassReceiver)context.SyntaxReceiver!; + + foreach (var packetClass in syntaxReceiver.PacketClasses) + { + if (packetClass is not null) + { + using var stringWriter = new StringWriter(); + var writer = new IndentedTextWriter(stringWriter, " "); + var generatedResult = GeneratePacketSerializer(writer, context.Compilation, packetClass); + if (generatedResult is not null) + { + if (generatedResult is DiagnosticError diagnosticError) + { + context.ReportDiagnostic(Diagnostic.Create + ( + new DiagnosticDescriptor + ( + diagnosticError.Id, + diagnosticError.Title, + diagnosticError.MessageFormat, + "Serialization", + DiagnosticSeverity.Error, + true + ), + Location.Create(diagnosticError.Tree, diagnosticError.Span), + diagnosticError.Parameters.ToArray() + ) + ); + } + else if (generatedResult is not null) + { + throw new Exception(generatedResult.Message); + } + + continue; + } + + context.AddSource($"{packetClass.Identifier.NormalizeWhitespace().ToFullString()}Converter.g.cs", stringWriter.GetStringBuilder().ToString()); + } + } + } + + private IError? GeneratePacketSerializer(IndentedTextWriter textWriter, Compilation compilation, RecordDeclarationSyntax packetClass) + { + var semanticModel = compilation.GetSemanticModel(packetClass.SyntaxTree); + + var name = packetClass.Identifier.NormalizeWhitespace().ToFullString(); + var @namespace = packetClass.GetPrefix(); + + var constructor = (ParameterListSyntax?)packetClass.ChildNodes() + .FirstOrDefault(x => x.IsKind(SyntaxKind.ParameterList)); + + if (constructor is null) + { + return new DiagnosticError( + "SG0001", + "Packet without constructor", + "The packet class {0} does not have any constructors to use for packet serializer.", + packetClass.SyntaxTree, + packetClass.FullSpan, + new List(new[] + { + packetClass.Identifier.NormalizeWhitespace().ToFullString() + }) + ); + } + + var parameters = constructor.Parameters; + var orderedParameters = new List(); + int constructorIndex = 0; + foreach (var parameter in parameters) + { + var attributeLists = parameter.AttributeLists + .Where(x => x.Attributes.Any(x => x.Name.NormalizeWhitespace().ToFullString().StartsWith("Packet"))).ToList(); + var attributes = attributeLists.FirstOrDefault()?.Attributes.Where(x => x.Name.NormalizeWhitespace().ToFullString().StartsWith("Packet")) + .ToList(); + + if (attributeLists.Count > 1 || (attributes is not null && attributes?.Count > 1)) + { + return new DiagnosticError + ( + "SG0002", + "Packet constructor parameter with multiple packet attributes", + "There are multiple PacketIndexAttributes on {0} parameter in class {1}. Only one may be specified.", + parameter.SyntaxTree, + parameter.FullSpan, + new List(new[] + { + parameter.Identifier.NormalizeWhitespace().ToFullString(), + name + }) + ); + } + else if (attributeLists.Count == 0 || attributes is null || attributes.Count < 1) + { + return new DiagnosticError( + "SG0003", + "Packet constructor parameter without packet attribute", + "Could not find PacketIndexAttribute on {0} parameter in class {1}. Parameters without PacketIndexAttribute aren't allowed.", + parameter.SyntaxTree, + parameter.FullSpan, + new List(new[] + { + parameter.Identifier.NormalizeWhitespace().ToFullString(), + name + }) + ); + } + + var attribute = attributes.First(); + var indexArg = attribute.ArgumentList!.Arguments[0]; + var indexExp = indexArg.Expression; + var index = ushort.Parse(semanticModel.GetConstantValue(indexExp).ToString()); + var namedArguments = new Dictionary(); + var arguments = new List(); + + foreach (var argument in attribute.ArgumentList.Arguments) + { + var argumentName = argument.NameEquals?.Name.Identifier.NormalizeWhitespace().ToFullString(); + var expression = argument.Expression; + var value = semanticModel.GetConstantValue(expression).Value; + + if (argumentName is not null) + { + namedArguments.Add(argumentName, value); + } + else + { + arguments.Add(value); + } + } + + orderedParameters.Add(new ParameterInfo + ( + compilation, + parameter, + attribute, + arguments, + namedArguments, + parameter.Identifier.NormalizeWhitespace().ToFullString(), + constructorIndex, + index + ) + ); + constructorIndex++; + } + + orderedParameters = orderedParameters.OrderBy(x => x.PacketIndex).ToList(); + orderedParameters.Last().IsLast = true; + + textWriter.WriteLine(@$"// +#nullable enable +#pragma warning disable 1591 + +using {@namespace}; +using NosSmooth.Packets.Converters; +using NosSmooth.Packets.Errors; +using NosSmooth.Packets; +using Remora.Results; + +namespace {@namespace}.Generated; + +public class {name}Converter : BaseTypeConverter<{name}> +{{"); + textWriter.Indent++; + textWriter.WriteLine($@" +private readonly ITypeConverterRepository _typeConverterRepository; + +public {name}Converter(ITypeConverterRepository typeConverterRepository) +{{ + _typeConverterRepository = typeConverterRepository; +}} + +/// +public override Result Serialize({name}? obj, PacketStringBuilder builder) +{{ + if (obj is null) + {{ + return new ArgumentNullError(nameof(obj)); + }} +"); + textWriter.Indent++; + var serializerError = GenerateSerializer(textWriter, packetClass, orderedParameters); + if (serializerError is not null) + { + return serializerError; + } + + textWriter.Indent--; + textWriter.WriteLine($@" +}} + +/// +public override Result<{name}?> Deserialize(PacketStringEnumerator stringEnumerator) +{{ +"); + + textWriter.Indent++; + var deserializerError = GenerateDeserializer(textWriter, packetClass, orderedParameters); + if (deserializerError is not null) + { + return deserializerError; + } + textWriter.Indent--; + + textWriter.WriteLine($@" + }} + +private IResultError? CheckDeserializationResult(Result result, string property, PacketStringEnumerator stringEnumerator, bool last = false) +{{ + if (!result.IsSuccess) + {{ + return new PacketParameterSerializerError(this, property, result); + }} + + if (!last && (stringEnumerator.IsOnLastToken() ?? false)) + {{ + return new PacketEndNotExpectedError(this, property); + }} + + return null; +}} +}}"); + return null; + } + + private IError? GenerateSerializer(IndentedTextWriter textWriter, RecordDeclarationSyntax packetClass, List parameters) + { + foreach (var parameter in parameters) + { + bool handled = false; + foreach (var generator in _generators) + { + if (generator.ShouldHandle(parameter.Attribute)) + { + var result = generator.GenerateSerializerPart(textWriter, packetClass, parameter); + if (result is not null) + { + return result; + } + + handled = true; + break; + } + } + + if (!handled) + { + throw new InvalidOperationException($"Could not handle {packetClass.Identifier.NormalizeWhitespace().ToFullString()}.{parameter.Name}"); + } + } + + textWriter.WriteLine("return Result.FromSuccess();"); + return null; + } + + private IError? GenerateDeserializer(IndentedTextWriter textWriter, RecordDeclarationSyntax packetClass, List parameters) + { + var lastIndex = parameters.FirstOrDefault()?.PacketIndex ?? 0; + bool skipped = false; + foreach (var parameter in parameters) + { + var skip = parameter.PacketIndex - lastIndex - 1; + if (skip > 0) + { + if (!skipped) + { + textWriter.WriteLine("Result skipResult;"); + textWriter.WriteLine("IResultError? skipError;"); + skipped = true; + } + textWriter.WriteLine($@"skipResult = stringEnumerator.GetNextToken();"); + textWriter.WriteLine($@"skipError = CheckDeserializationResult(result, ""None"", stringEnumerator, false);"); + textWriter.WriteLine($@"if (skipError is not null) {{ + return Result<{packetClass.Identifier.NormalizeWhitespace().ToFullString()}>.FromError(skipError, skipResult); +}}"); + } + + bool handled = false; + foreach (var generator in _generators) + { + if (generator.ShouldHandle(parameter.Attribute)) + { + var result = generator.GenerateDeserializerPart(textWriter, packetClass, parameter); + if (result is not null) + { + return result; + } + + handled = true; + break; + } + } + + if (!handled) + { + throw new InvalidOperationException($"Could not handle {packetClass.Identifier.NormalizeWhitespace().ToFullString()}.{parameter.Name}"); + } + lastIndex = parameter.PacketIndex; + } + + string parametersString = string.Join(", ", parameters.OrderBy(x => x.ConstructorIndex).Select(x => x.Name)); + textWriter.WriteLine($"return new {packetClass.Identifier.NormalizeWhitespace().ToFullString()}({parametersString});"); + + return null; + } +} \ No newline at end of file diff --git a/Core/NosSmooth.PacketSerializersGenerator/ParameterInfo.cs b/Core/NosSmooth.PacketSerializersGenerator/ParameterInfo.cs new file mode 100644 index 0000000..6e93cc8 --- /dev/null +++ b/Core/NosSmooth.PacketSerializersGenerator/ParameterInfo.cs @@ -0,0 +1,39 @@ +// +// ParameterInfo.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace NosSmooth.PacketSerializersGenerator; + +/// +/// Information about a parameter of a packet constructor. +/// +/// +/// +/// +/// +/// +/// +/// +/// +public record ParameterInfo +( + Compilation Compilation, + ParameterSyntax Parameter, + AttributeSyntax Attribute, + IReadOnlyList IndexedAttributeArguments, + IReadOnlyDictionary NamedAttributeArguments, + string Name, + int ConstructorIndex, + int PacketIndex +) +{ + /// + /// Gets or sets if this parameter is the last one. + /// + public bool IsLast { get; set; } +} \ No newline at end of file -- 2.49.0