diff options
Diffstat (limited to 'src/MessagePack.GeneratorCore/CodeGenerator.cs')
-rw-r--r-- | src/MessagePack.GeneratorCore/CodeGenerator.cs | 317 |
1 files changed, 193 insertions, 124 deletions
diff --git a/src/MessagePack.GeneratorCore/CodeGenerator.cs b/src/MessagePack.GeneratorCore/CodeGenerator.cs index f4dd3d35..1f48973e 100644 --- a/src/MessagePack.GeneratorCore/CodeGenerator.cs +++ b/src/MessagePack.GeneratorCore/CodeGenerator.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; @@ -16,15 +17,15 @@ namespace MessagePackCompiler { public class CodeGenerator { + private static readonly HashSet<char> InvalidFileCharSet = new(Path.GetInvalidFileNameChars()); + private static readonly Encoding NoBomUtf8 = new UTF8Encoding(false); - private Action<string> logger; - private CancellationToken cancellationToken; + private readonly Action<string> logger; public CodeGenerator(Action<string> logger, CancellationToken cancellationToken) { this.logger = logger; - this.cancellationToken = cancellationToken; } /// <summary> @@ -42,21 +43,21 @@ namespace MessagePackCompiler Compilation compilation, string output, string resolverName, - string @namespace, + string? @namespace, bool useMapMode, - string multipleIfDirectiveOutputSymbols, - string[] externalIgnoreTypeNames) + string? multipleIfDirectiveOutputSymbols, + string[]? externalIgnoreTypeNames) { var namespaceDot = string.IsNullOrWhiteSpace(@namespace) ? string.Empty : @namespace + "."; var multipleOutputSymbols = multipleIfDirectiveOutputSymbols?.Split(',') ?? Array.Empty<string>(); var sw = Stopwatch.StartNew(); - foreach (var multioutSymbol in multipleOutputSymbols.Length == 0 ? new[] { string.Empty } : multipleOutputSymbols) + foreach (var multiOutputSymbol in multipleOutputSymbols.Length == 0 ? new[] { string.Empty } : multipleOutputSymbols) { logger("Project Compilation Start:" + compilation.AssemblyName); - var collector = new TypeCollector(compilation, true, useMapMode, externalIgnoreTypeNames, x => Console.WriteLine(x)); + var collector = new TypeCollector(compilation, true, useMapMode, externalIgnoreTypeNames, Console.WriteLine); logger("Project Compilation Complete:" + sw.Elapsed.ToString()); @@ -73,152 +74,220 @@ namespace MessagePackCompiler if (Path.GetExtension(output) == ".cs") { // SingleFile Output - var objectFormatterTemplates = objectInfo - .GroupBy(x => (x.Namespace, x.IsStringKey)) - .Select(x => - { - var (nameSpace, isStringKey) = x.Key; - var objectSerializationInfos = x.ToArray(); - var template = isStringKey ? new StringKeyFormatterTemplate() : (IFormatterTemplate)new FormatterTemplate(); + var fullGeneratedProgramText = GenerateSingleFileSync(resolverName, namespaceDot, objectInfo, enumInfo, unionInfo, genericInfo); + if (multiOutputSymbol == string.Empty) + { + await OutputAsync(output, fullGeneratedProgramText); + } + else + { + var fname = Path.GetFileNameWithoutExtension(output) + "." + MultiSymbolToSafeFilePath(multiOutputSymbol) + ".cs"; + var text = $"#if {multiOutputSymbol}" + Environment.NewLine + fullGeneratedProgramText + Environment.NewLine + "#endif"; + await OutputAsync(Path.Combine(Path.GetDirectoryName(output) ?? string.Empty, fname), text); + } + } + else + { + // Multiple File output + await GenerateMultipleFileAsync(output, resolverName, objectInfo, enumInfo, unionInfo, namespaceDot, multiOutputSymbol, genericInfo); + } - template.Namespace = namespaceDot + "Formatters" + (nameSpace is null ? string.Empty : "." + nameSpace); - template.ObjectSerializationInfos = objectSerializationInfos; + if (objectInfo.Length == 0 && enumInfo.Length == 0 && genericInfo.Length == 0 && unionInfo.Length == 0) + { + logger("Generated result is empty, unexpected result?"); + } + } - return template; - }) - .ToArray(); + logger("Output Generation Complete:" + sw.Elapsed.ToString()); + } - var enumFormatterTemplates = enumInfo - .GroupBy(x => x.Namespace) - .Select(x => new EnumTemplate() - { - Namespace = namespaceDot + "Formatters" + ((x.Key == null) ? string.Empty : "." + x.Key), - EnumSerializationInfos = x.ToArray(), - }) - .ToArray(); - - var unionFormatterTemplates = unionInfo - .GroupBy(x => x.Namespace) - .Select(x => new UnionTemplate() - { - Namespace = namespaceDot + "Formatters" + ((x.Key == null) ? string.Empty : "." + x.Key), - UnionSerializationInfos = x.ToArray(), - }) - .ToArray(); + /// <summary> + /// Generates the specialized resolver and formatters for the types that require serialization in a given compilation. + /// </summary> + /// <param name="resolverName">The resolver name.</param> + /// <param name="namespaceDot">The namespace for the generated type to be created in.</param> + /// <param name="objectInfo">The ObjectSerializationInfo array which TypeCollector.Collect returns.</param> + /// <param name="enumInfo">The EnumSerializationInfo array which TypeCollector.Collect returns.</param> + /// <param name="unionInfo">The UnionSerializationInfo array which TypeCollector.Collect returns.</param> + /// <param name="genericInfo">The GenericSerializationInfo array which TypeCollector.Collect returns.</param> + public static string GenerateSingleFileSync(string resolverName, string namespaceDot, ObjectSerializationInfo[] objectInfo, EnumSerializationInfo[] enumInfo, UnionSerializationInfo[] unionInfo, GenericSerializationInfo[] genericInfo) + { + var objectFormatterTemplates = objectInfo + .GroupBy(x => (x.Namespace, x.IsStringKey)) + .Select(x => + { + var (nameSpace, isStringKey) = x.Key; + var objectSerializationInfos = x.ToArray(); + var ns = namespaceDot + "Formatters" + (nameSpace is null ? string.Empty : "." + nameSpace); + var template = isStringKey ? new StringKeyFormatterTemplate(ns, objectSerializationInfos) : (IFormatterTemplate)new FormatterTemplate(ns, objectSerializationInfos); + return template; + }) + .ToArray(); + + string GetNamespace<T>(IGrouping<string?, T> x) + { + if (x.Key == null) + { + return namespaceDot + "Formatters"; + } - var resolverTemplate = new ResolverTemplate() - { - Namespace = namespaceDot + "Resolvers", - FormatterNamespace = namespaceDot + "Formatters", - ResolverName = resolverName, - RegisterInfos = genericInfo.Where(x => !x.IsOpenGenericType).Cast<IResolverRegisterInfo>().Concat(enumInfo).Concat(unionInfo).Concat(objectInfo.Where(x => !x.IsOpenGenericType)).ToArray(), - }; - - var sb = new StringBuilder(); - sb.AppendLine(resolverTemplate.TransformText()); - sb.AppendLine(); - foreach (var item in enumFormatterTemplates) - { - var text = item.TransformText(); - sb.AppendLine(text); - } + return namespaceDot + "Formatters." + x.Key; + } - sb.AppendLine(); - foreach (var item in unionFormatterTemplates) - { - var text = item.TransformText(); - sb.AppendLine(text); - } + var enumFormatterTemplates = enumInfo + .GroupBy(x => x.Namespace) + .Select(x => new EnumTemplate(GetNamespace(x), x.ToArray())) + .ToArray(); - sb.AppendLine(); - foreach (var item in objectFormatterTemplates) - { - var text = item.TransformText(); - sb.AppendLine(text); - } + var unionFormatterTemplates = unionInfo + .GroupBy(x => x.Namespace) + .Select(x => new UnionTemplate(GetNamespace(x), x.ToArray())) + .ToArray(); - if (multioutSymbol == string.Empty) - { - await OutputAsync(output, sb.ToString(), cancellationToken); - } - else + var resolverTemplate = new ResolverTemplate(namespaceDot + "Resolvers", namespaceDot + "Formatters", resolverName, genericInfo.Where(x => !x.IsOpenGenericType).Cast<IResolverRegisterInfo>().Concat(enumInfo).Concat(unionInfo).Concat(objectInfo.Where(x => !x.IsOpenGenericType)).ToArray()); + + var sb = new StringBuilder(); + sb.AppendLine(resolverTemplate.TransformText()); + sb.AppendLine(); + foreach (var item in enumFormatterTemplates) + { + var text = item.TransformText(); + sb.AppendLine(text); + } + + sb.AppendLine(); + foreach (var item in unionFormatterTemplates) + { + var text = item.TransformText(); + sb.AppendLine(text); + } + + sb.AppendLine(); + foreach (var item in objectFormatterTemplates) + { + var text = item.TransformText(); + sb.AppendLine(text); + } + + return sb.ToString(); + } + + private Task GenerateMultipleFileAsync(string output, string resolverName, ObjectSerializationInfo[] objectInfo, EnumSerializationInfo[] enumInfo, UnionSerializationInfo[] unionInfo, string namespaceDot, string multioutSymbol, GenericSerializationInfo[] genericInfo) + { + string GetNamespace(INamespaceInfo x) + { + if (x.Namespace == null) + { + return namespaceDot + "Formatters"; + } + + return namespaceDot + "Formatters." + x.Namespace; + } + + var waitingTasks = new Task[objectInfo.Length + enumInfo.Length + unionInfo.Length + 1]; + var waitingIndex = 0; + foreach (var x in objectInfo) + { + var ns = namespaceDot + "Formatters" + (x.Namespace is null ? string.Empty : "." + x.Namespace); + var template = x.IsStringKey ? new StringKeyFormatterTemplate(ns, new[] { x }) : (IFormatterTemplate)new FormatterTemplate(ns, new[] { x }); + var text = template.TransformText(); + waitingTasks[waitingIndex++] = OutputToDirAsync(output, template.Namespace, x.Name + "Formatter", multioutSymbol, text); + } + + foreach (var x in enumInfo) + { + var template = new EnumTemplate(GetNamespace(x), new[] { x }); + var text = template.TransformText(); + waitingTasks[waitingIndex++] = OutputToDirAsync(output, template.Namespace, x.Name + "Formatter", multioutSymbol, text); + } + + foreach (var x in unionInfo) + { + var template = new UnionTemplate(GetNamespace(x), new[] { x }); + var text = template.TransformText(); + waitingTasks[waitingIndex++] = OutputToDirAsync(output, template.Namespace, x.Name + "Formatter", multioutSymbol, text); + } + + var resolverTemplate = new ResolverTemplate(namespaceDot + "Resolvers", namespaceDot + "Formatters", resolverName, genericInfo.Where(x => !x.IsOpenGenericType).Cast<IResolverRegisterInfo>().Concat(enumInfo).Concat(unionInfo).Concat(objectInfo.Where(x => !x.IsOpenGenericType)).ToArray()); + waitingTasks[waitingIndex] = OutputToDirAsync(output, resolverTemplate.Namespace, resolverTemplate.ResolverName, multioutSymbol, resolverTemplate.TransformText()); + return Task.WhenAll(waitingTasks); + } + + private Task OutputToDirAsync(string dir, string ns, string name, string multipleOutSymbol, string text) + { + var builder = new StringBuilder(); + void AppendDir(string dir) + { + if (dir.Length != 0) + { + builder.Append(dir); + if (dir[dir.Length - 1] != Path.DirectorySeparatorChar && dir[dir.Length - 1] != Path.AltDirectorySeparatorChar) { - var fname = Path.GetFileNameWithoutExtension(output) + "." + MultiSymbolToSafeFilePath(multioutSymbol) + ".cs"; - var text = $"#if {multioutSymbol}" + Environment.NewLine + sb.ToString() + Environment.NewLine + "#endif"; - await OutputAsync(Path.Combine(Path.GetDirectoryName(output), fname), text, cancellationToken); + builder.Append(Path.DirectorySeparatorChar); } } + } + + void AppendChar(char c) + { + if (c == '.' || InvalidFileCharSet.Contains(c)) + { + builder.Append('_'); + } else { - // Multiple File output - foreach (var x in objectInfo) - { - var template = x.IsStringKey ? new StringKeyFormatterTemplate() : (IFormatterTemplate)new FormatterTemplate(); - template.Namespace = namespaceDot + "Formatters" + (x.Namespace is null ? string.Empty : "." + x.Namespace); - template.ObjectSerializationInfos = new[] { x }; - - var text = template.TransformText(); - await OutputToDirAsync(output, template.Namespace, x.Name + "Formatter", multioutSymbol, text, cancellationToken); - } + builder.Append(c); + } + } - foreach (var x in enumInfo) + void Append(string text) + { + var span = text.AsSpan(); + while (!span.IsEmpty) + { + var index = span.IndexOf("global::".AsSpan()); + if (index == -1) { - var template = new EnumTemplate() + foreach (var c in span) { - Namespace = namespaceDot + "Formatters" + ((x.Namespace == null) ? string.Empty : "." + x.Namespace), - EnumSerializationInfos = new[] { x }, - }; + AppendChar(c); + } - var text = template.TransformText(); - await OutputToDirAsync(output, template.Namespace, x.Name + "Formatter", multioutSymbol, text, cancellationToken); + break; } - foreach (var x in unionInfo) + if (index == 0) { - var template = new UnionTemplate() - { - Namespace = namespaceDot + "Formatters" + ((x.Namespace == null) ? string.Empty : "." + x.Namespace), - UnionSerializationInfos = new[] { x }, - }; - - var text = template.TransformText(); - await OutputToDirAsync(output, template.Namespace, x.Name + "Formatter", multioutSymbol, text, cancellationToken); + span = span.Slice("global::".Length); + continue; } - var resolverTemplate = new ResolverTemplate() + foreach (var c in span.Slice(0, index)) { - Namespace = namespaceDot + "Resolvers", - FormatterNamespace = namespaceDot + "Formatters", - ResolverName = resolverName, - RegisterInfos = genericInfo.Where(x => !x.IsOpenGenericType).Cast<IResolverRegisterInfo>().Concat(enumInfo).Concat(unionInfo).Concat(objectInfo.Where(x => !x.IsOpenGenericType)).ToArray(), - }; - - await OutputToDirAsync(output, resolverTemplate.Namespace, resolverTemplate.ResolverName, multioutSymbol, resolverTemplate.TransformText(), cancellationToken); - } + AppendChar(c); + } - if (objectInfo.Length == 0 && enumInfo.Length == 0 && genericInfo.Length == 0 && unionInfo.Length == 0) - { - logger("Generated result is empty, unexpected result?"); + span = span.Slice(index + "global::".Length); } } - logger("Output Generation Complete:" + sw.Elapsed.ToString()); - } + AppendDir(dir); - private Task OutputToDirAsync(string dir, string ns, string name, string multipleOutSymbol, string text, CancellationToken cancellationToken) - { - if (multipleOutSymbol == string.Empty) - { - return OutputAsync(Path.Combine(dir, $"{ns}_{name}".Replace(".", "_").Replace("global::", string.Empty) + ".cs"), text, cancellationToken); - } - else + if (!string.IsNullOrWhiteSpace(multipleOutSymbol)) { text = $"#if {multipleOutSymbol}" + Environment.NewLine + text + Environment.NewLine + "#endif"; - return OutputAsync(Path.Combine(dir, MultiSymbolToSafeFilePath(multipleOutSymbol), $"{ns}_{name}".Replace(".", "_").Replace("global::", string.Empty) + ".cs"), text, cancellationToken); + AppendDir(MultiSymbolToSafeFilePath(multipleOutSymbol)); } + + Append(ns); + builder.Append('_'); + Append(name); + builder.Append(".cs"); + + return OutputAsync(builder.ToString(), text); } - private Task OutputAsync(string path, string text, CancellationToken cancellationToken) + private Task OutputAsync(string path, string text) { path = path.Replace("global::", string.Empty); @@ -226,12 +295,12 @@ namespace MessagePackCompiler logger(prefix + path); var fi = new FileInfo(path); - if (!fi.Directory.Exists) + if (fi.Directory != null && !fi.Directory.Exists) { fi.Directory.Create(); } - System.IO.File.WriteAllText(path, NormalizeNewLines(text), NoBomUtf8); + File.WriteAllText(path, NormalizeNewLines(text), NoBomUtf8); return Task.CompletedTask; } |