| | | 1 | | using System.Linq; |
| | | 2 | | using System.Text; |
| | | 3 | | |
| | | 4 | | using NexusLabs.Needlr.Generators.Models; |
| | | 5 | | |
| | | 6 | | namespace NexusLabs.Needlr.Generators.CodeGen; |
| | | 7 | | |
| | | 8 | | /// <summary> |
| | | 9 | | /// Emits one public forwarding constructor overload for a positional record model. |
| | | 10 | | /// </summary> |
| | | 11 | | internal static class RecordConstructorOverloadCodeGenerator |
| | | 12 | | { |
| | | 13 | | private const string GeneratedFileSuffix = |
| | | 14 | | ".RecordConstructorOverload.g.cs"; |
| | | 15 | | |
| | | 16 | | /// <summary> |
| | | 17 | | /// Builds the deterministic generated-source hint name. |
| | | 18 | | /// </summary> |
| | | 19 | | internal static string BuildHintName(RecordConstructorOverloadModel model) |
| | | 20 | | { |
| | 16 | 21 | | var baseName = model.ContainingNamespace.Length > 0 |
| | 16 | 22 | | ? $"{model.ContainingNamespace}.{model.ContainingTypeName}" |
| | 16 | 23 | | : model.ContainingTypeName; |
| | 16 | 24 | | return $"{GeneratorHelpers.SanitizeIdentifier(baseName)}_T{model.Arity}{GeneratedFileSuffix}"; |
| | | 25 | | } |
| | | 26 | | |
| | | 27 | | /// <summary> |
| | | 28 | | /// Emits the complete partial record declaration and forwarding constructor. |
| | | 29 | | /// </summary> |
| | | 30 | | internal static string GenerateSource( |
| | | 31 | | RecordConstructorOverloadModel model, |
| | | 32 | | string assemblyName, |
| | | 33 | | BreadcrumbWriter breadcrumbs) |
| | | 34 | | { |
| | 16 | 35 | | var builder = new StringBuilder(); |
| | | 36 | | |
| | 16 | 37 | | breadcrumbs.WriteFileHeader( |
| | 16 | 38 | | builder, |
| | 16 | 39 | | assemblyName, |
| | 16 | 40 | | $"Generated record constructor overload for {model.ContainingTypeName}{model.TypeParameterList}"); |
| | 16 | 41 | | builder.AppendLine("#nullable enable"); |
| | 16 | 42 | | builder.AppendLine(); |
| | | 43 | | |
| | 16 | 44 | | if (model.ContainingNamespace.Length > 0) |
| | | 45 | | { |
| | 16 | 46 | | builder.AppendLine($"namespace {model.ContainingNamespace};"); |
| | 16 | 47 | | builder.AppendLine(); |
| | | 48 | | } |
| | | 49 | | |
| | 16 | 50 | | breadcrumbs.WriteInlineComment( |
| | 16 | 51 | | builder, |
| | 16 | 52 | | string.Empty, |
| | 16 | 53 | | $"Constructor overload generated from {model.PropertyParameters.Length} marked property parameter(s)"); |
| | 16 | 54 | | builder.AppendLine( |
| | 16 | 55 | | $"partial record class {model.EscapedContainingTypeName}{model.TypeParameterList}"); |
| | 16 | 56 | | builder.AppendLine("{"); |
| | 16 | 57 | | builder.AppendLine(" /// <summary>"); |
| | 16 | 58 | | builder.AppendLine( |
| | 16 | 59 | | $" /// Initializes a new instance of the <see cref=\"{model.EscapedContainingTypeName}{ToCrefTypeParamete |
| | 16 | 60 | | builder.AppendLine(" /// </summary>"); |
| | | 61 | | |
| | 84 | 62 | | foreach (var parameter in model.PrimaryParameters) |
| | | 63 | | { |
| | 26 | 64 | | builder.AppendLine( |
| | 26 | 65 | | $" /// <param name=\"{parameter.Name}\">{parameter.Documentation}</param>"); |
| | | 66 | | } |
| | | 67 | | |
| | 72 | 68 | | foreach (var parameter in model.PropertyParameters) |
| | | 69 | | { |
| | 20 | 70 | | builder.AppendLine( |
| | 20 | 71 | | $" /// <param name=\"{parameter.PropertyName}\">{parameter.Documentation}</param>"); |
| | | 72 | | } |
| | | 73 | | |
| | 16 | 74 | | ConstructorGuardCodeGenerator.WriteExceptionDocumentation( |
| | 16 | 75 | | builder, |
| | 16 | 76 | | model.PropertyParameters |
| | 20 | 77 | | .Select(parameter => parameter.PropertyName) |
| | 16 | 78 | | .ToArray(), |
| | 16 | 79 | | model.PropertyParameters |
| | 20 | 80 | | .Select(parameter => parameter.EffectiveGuards) |
| | 16 | 81 | | .ToArray(), |
| | 16 | 82 | | " "); |
| | | 83 | | |
| | 16 | 84 | | var parameters = model.PrimaryParameters |
| | 16 | 85 | | .Select(parameter => |
| | 26 | 86 | | $"{parameter.TypeName} {parameter.EscapedName}") |
| | 16 | 87 | | .Concat(model.PropertyParameters.Select(parameter => |
| | 36 | 88 | | $"{parameter.TypeName} {parameter.EscapedPropertyName}")); |
| | 16 | 89 | | builder.AppendLine( |
| | 16 | 90 | | $" public {model.EscapedContainingTypeName}({string.Join(", ", parameters)})"); |
| | | 91 | | |
| | 16 | 92 | | var forwardedArguments = string.Join( |
| | 16 | 93 | | ", ", |
| | 16 | 94 | | model.PrimaryParameters.Select(parameter => |
| | 42 | 95 | | parameter.EscapedName)); |
| | 16 | 96 | | builder.AppendLine($" : this({forwardedArguments})"); |
| | 16 | 97 | | builder.AppendLine(" {"); |
| | | 98 | | |
| | 16 | 99 | | var emittedGuard = false; |
| | 72 | 100 | | foreach (var parameter in model.PropertyParameters) |
| | | 101 | | { |
| | 54 | 102 | | foreach (var guard in parameter.EffectiveGuards) |
| | | 103 | | { |
| | 7 | 104 | | builder.AppendLine( |
| | 7 | 105 | | $" {ConstructorGuardCodeGenerator.BuildGuardCall(guard, parameter.EscapedPropertyName)}"); |
| | 7 | 106 | | emittedGuard = true; |
| | | 107 | | } |
| | | 108 | | } |
| | | 109 | | |
| | 16 | 110 | | if (emittedGuard) |
| | 5 | 111 | | builder.AppendLine(); |
| | | 112 | | |
| | 72 | 113 | | foreach (var parameter in model.PropertyParameters) |
| | | 114 | | { |
| | 20 | 115 | | builder.AppendLine( |
| | 20 | 116 | | $" this.{parameter.EscapedPropertyName} = {parameter.EscapedPropertyName};"); |
| | | 117 | | } |
| | | 118 | | |
| | 16 | 119 | | builder.AppendLine(" }"); |
| | 16 | 120 | | builder.AppendLine("}"); |
| | | 121 | | |
| | 16 | 122 | | return builder.ToString(); |
| | | 123 | | } |
| | | 124 | | |
| | | 125 | | private static string ToCrefTypeParameterList(string typeParameterList) |
| | | 126 | | { |
| | 16 | 127 | | if (typeParameterList.Length == 0) |
| | 14 | 128 | | return string.Empty; |
| | | 129 | | |
| | 2 | 130 | | return typeParameterList.Replace('<', '{').Replace('>', '}'); |
| | | 131 | | } |
| | | 132 | | } |