//
// SourceGenerator.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.RegularExpressions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using NosSmooth.PacketSerializersGenerator.AttributeGenerators;
using NosSmooth.PacketSerializersGenerator.Data;
using NosSmooth.PacketSerializersGenerator.Errors;
using NosSmooth.PacketSerializersGenerator.Extensions;
using NosSmooth.PacketSerializersGenerator.InlineConverterGenerators;
namespace NosSmooth.PacketSerializersGenerator;
///
/// Generates IInlineGenerator 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 SourceGenerator : ISourceGenerator
{
///
/// Initializes a new instance of the class.
///
public SourceGenerator()
{
_typeConverterGenerator = new List
(
new IInlineConverterGenerator[]
{
new StringInlineConverterGenerator(),
new BasicInlineConverterGenerator(),
new EnumInlineConverterGenerator(),
new BoolInlineConverterGenerator(),
}
);
var inlineTypeConverter = new InlineTypeConverterGenerator(_typeConverterGenerator);
_typeConverterGenerator.Add(new ListInlineConverterGenerator(inlineTypeConverter));
_generators = new List
(
new IParameterGenerator[]
{
new PacketIndexAttributeGenerator(inlineTypeConverter),
new PacketGreedyIndexAttributeGenerator(inlineTypeConverter),
new PacketListIndexAttributeGenerator(inlineTypeConverter),
new PacketContextListAttributeGenerator(inlineTypeConverter),
new PacketConditionalIndexAttributeGenerator(inlineTypeConverter),
}
);
}
private readonly List _generators;
private readonly List _typeConverterGenerator;
///
public void Initialize(GeneratorInitializationContext context)
{
// SpinWait.SpinUntil(() => Debugger.IsAttached);
}
private IEnumerable GetPacketRecords(Compilation compilation, SyntaxTree tree)
{
var semanticModel = compilation.GetSemanticModel(tree);
var root = tree.GetRoot();
return root
.DescendantNodes()
.OfType()
.Where
(
x => x.AttributeLists.Any
(y => y.ContainsAttribute(semanticModel, Constants.GenerateSourceAttributeClass))
);
}
///
public void Execute(GeneratorExecutionContext context)
{
var packetRecords = context.Compilation.SyntaxTrees
.SelectMany(x => GetPacketRecords(context.Compilation, x));
foreach (var packetRecord in packetRecords)
{
if (packetRecord is not null)
{
using var stringWriter = new StringWriter();
using var writer = new IndentedTextWriter(stringWriter, " ");
var generatedResult = GeneratePacketSerializer(writer, context.Compilation, packetRecord);
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
(
$"{packetRecord.GetPrefix()}.{packetRecord.Identifier.NormalizeWhitespace().ToFullString()}Converter.g.cs",
stringWriter.GetStringBuilder().ToString()
);
File.WriteAllText
(
Path.Combine(Path.GetTempPath(), $"{packetRecord.GetPrefix()}.{packetRecord.Identifier.NormalizeWhitespace().ToFullString()}Converter.g.cs"),
stringWriter.GetStringBuilder().ToString()
);
}
}
var helperClass = GenerateHelperMethods();
context.AddSource
(
$"HelperClass.g.cs",
helperClass
);
}
private string GenerateHelperMethods()
{
using var stringWriter = new StringWriter();
using var writer = new IndentedTextWriter(stringWriter, " ");
writer.WriteMultiline(@"//
#nullable enable
#pragma warning disable 1591
using System.Collections;
using System.Collections.Generic;
using NosSmooth.PacketSerializer.Abstractions;
using NosSmooth.PacketSerializer.Abstractions.Errors;
using Remora.Results;
namespace NosSmooth.Generated;
internal static class HelperClass
{
");
foreach (var inlineHelperGenerator in _typeConverterGenerator)
{
inlineHelperGenerator.GenerateHelperMethods(writer);
}
writer.WriteLine("}");
return 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
(
"SGConst",
"Packet without constructor",
"The packet class {0} does not have any constructors to use for packet serializer.",
packetClass.SyntaxTree,
packetClass.FullSpan,
new List