public static void EmitAdjustAll(DraftableRecord r, StringBuilder output)
        {
            if (r.UsedInImmutableCollections.HasFlag(ImmutableCollectionType.ImmutableArray))
            {
                EmitArrayAdjustAll(r, output);
            }

            if (r.UsedInImmutableCollections.HasFlag(ImmutableCollectionType.ImmutableHashSet))
            {
                EmitOneTypeArg("System.Collections.Immutable.ImmutableHashSet", includeIndexed: false, r, output);
            }

            if (r.UsedInImmutableCollections.HasFlag(ImmutableCollectionType.ImmutableList))
            {
                EmitOneTypeArg("System.Collections.Immutable.ImmutableList", includeIndexed: true, r, output);
            }

            if (r.UsedInImmutableCollections.HasFlag(ImmutableCollectionType.ImmutableSortedSet))
            {
                EmitOneTypeArg("System.Collections.Immutable.ImmutableSortedSet", includeIndexed: true, r, output);
            }

            if (r.UsedInImmutableCollections.HasFlag(ImmutableCollectionType.ImmutableDictionary))
            {
                EmitTwoTypeArg("System.Collections.Immutable.ImmutableDictionary", r, output);
            }

            if (r.UsedInImmutableCollections.HasFlag(ImmutableCollectionType.ImmutableSortedDictionary))
            {
                EmitTwoTypeArg("System.Collections.Immutable.ImmutableSortedDictionary", r, output);
            }
        }
 private static void EmitTwoTypeArg(string immutableType, DraftableRecord r, StringBuilder output)
 {
     output.AppendLine($"  public static void AdjustAll<Key>(this {immutableType}<Key, {r.FullyQualifiedRecordName}>.Builder b, System.Action<Key, {r.FullyQualifiedInterfaceName}> f) where Key : notnull");
     output.AppendLine("  {");
     output.AppendLine("    var orig = b.ToImmutable();");
     output.AppendLine("    b.Clear();");
     output.AppendLine("    b.AddRange(System.Linq.Enumerable.Select(orig, x => System.Collections.Generic.KeyValuePair.Create(x.Key, x.Value.Produce(d => f(x.Key, d)))));");
     output.AppendLine("  }");
 }
Exemple #3
0
 public static void Emit(EmitPhase phase, DraftableRecord record, RecordProperty prop, StringBuilder output)
 {
     if (prop.Nullable == Microsoft.CodeAnalysis.NullableAnnotation.NotAnnotated)
     {
         EmitNonNullable(phase, record, prop, output);
     }
     else
     {
         EmitNullable(phase, record, prop, output);
     }
 }
Exemple #4
0
        private static void EmitNullable(EmitPhase phase, DraftableRecord record, RecordProperty prop, StringBuilder output)
        {
            var propRecord      = prop.TypeIsDraftable;
            var createdPropName = Names.PropPrefix + "creat_" + prop.PropertyName;
            var draftPropName   = Names.PropPrefix + "draft_" + prop.PropertyName;
            var originalProp    = $"((({record.FullyQualifiedRecordName}){Names.OriginalProp}).{prop.PropertyName})";

            switch (phase)
            {
            case EmitPhase.Interface:
                output.AppendLine($"  {propRecord.FullyQualifiedInterfaceName}? {prop.PropertyName} {{get;}}");
                output.AppendLine($"  {propRecord.FullyQualifiedInterfaceName}? Set{prop.PropertyName}({prop.FullTypeName}? value);");
                break;

            case EmitPhase.PropImplementation:
                output.AppendLine($"    protected bool {createdPropName} = false;");
                output.AppendLine($"    protected {propRecord.FullyQualifiedDraftInstanceClassName}? {draftPropName} = null;");
                output.AppendLine($"    public {propRecord.FullyQualifiedInterfaceName}? {prop.PropertyName}");
                output.AppendLine("    {");
                output.AppendLine($"      get {{");
                output.AppendLine($"        if (!{createdPropName}) {{");
                output.AppendLine($"          {createdPropName} = true;");
                output.AppendLine($"          var original = {originalProp};");
                output.AppendLine($"          if (original != null) {{");
                output.AppendLine($"            {draftPropName} = new {propRecord.FullyQualifiedDraftInstanceClassName}(original, this);");
                output.AppendLine("          }"); // close if checking original prop not null
                output.AppendLine("        }");   // close if checking not created
                output.AppendLine($"        return {draftPropName};");
                output.AppendLine("      }");     // close get
                output.AppendLine("    }");
                output.AppendLine($"    public {propRecord.FullyQualifiedInterfaceName}? Set{prop.PropertyName}({prop.FullTypeName}? value)");
                output.AppendLine("    {");
                output.AppendLine($"      {Names.SetDirtyMethod}();");
                output.AppendLine($"      {createdPropName} = true;");
                output.AppendLine($"      {draftPropName} = value == null ? null : new {propRecord.FullyQualifiedDraftInstanceClassName}(value, this);");
                output.AppendLine($"      return {draftPropName};");
                output.AppendLine("    }");

                break;

            case EmitPhase.Constructor:
                // nothing needed here, not initialized until the first get
                break;

            case EmitPhase.Finish:
                output.AppendLine($"          {prop.PropertyName} = {createdPropName} ? {draftPropName}?.{Names.FinishMethod}() : {originalProp},");
                break;
            }
        }
        private static void EmitArrayAdjustAll(DraftableRecord r, StringBuilder output)
        {
            output.AppendLine($"  public static void AdjustAll(this System.Collections.Immutable.ImmutableArray<{r.FullyQualifiedRecordName}>.Builder b, System.Action<{r.FullyQualifiedInterfaceName}> f)");
            output.AppendLine("  {");
            output.AppendLine("    for (int i = 0; i < b.Count; i++) {");
            output.AppendLine("      b[i] = b[i].Produce(f);");
            output.AppendLine("    }");
            output.AppendLine("  }");

            output.AppendLine($"  public static void AdjustAll(this System.Collections.Immutable.ImmutableArray<{r.FullyQualifiedRecordName}>.Builder b, System.Action<{r.FullyQualifiedInterfaceName}, int> f)");
            output.AppendLine("  {");
            output.AppendLine("    for (int i = 0; i < b.Count; i++) {");
            output.AppendLine("      b[i] = b[i].Produce(x => f(x, i));");
            output.AppendLine("    }");
            output.AppendLine("  }");
        }
Exemple #6
0
 private void EmitProperties(EmitPhase phase, DraftableRecord rds, StringBuilder output)
 {
     foreach (var prop in rds.Properties)
     {
         if (prop.TypeIsDraftable != null)
         {
             PropDraftable.Emit(phase, rds, prop, output);
         }
         else if (prop.IsImmutableCollection)
         {
             PropImmutableCollection.Emit(phase, prop, output);
         }
         else
         {
             PropNonDraftable.Emit(phase, prop, output);
         }
     }
 }
Exemple #7
0
        public void Execute(GeneratorExecutionContext context)
        {
            var attrReceiver = (AttrSyntaxReceiver)context.SyntaxReceiver;
            var records      = BuildRecords.RecordsToDraft(context.Compilation, attrReceiver.Records);
            var assemblyName = context.Compilation.AssemblyName;

            // Check if Draftable attribute defined in a dependency of this assembly
            if (context.Compilation.GetTypeByMetadataName("Germinate.DraftableAttribute") == null)
            {
                context.AddSource("DraftableBase.g.cs", DraftableBase);
            }

            foreach (var rds in records.Values.Where(r => r.Emit))
            {
                var output = new StringBuilder();
                output.AppendLine("#nullable enable");

                // Interface
                if (!string.IsNullOrEmpty(rds.Namespace))
                {
                    output.AppendLine($"namespace {rds.Namespace} {{");
                }
                output.AppendLine($"public interface {rds.InterfaceName} {(rds.BaseRecord != null ? " : " + rds.BaseRecord.FullyQualifiedInterfaceName : "")} {{");
                EmitProperties(EmitPhase.Interface, rds, output);
                output.AppendLine("}"); // close interface
                if (!string.IsNullOrEmpty(rds.Namespace))
                {
                    output.AppendLine("}");
                }

                output.AppendLine();

                output.AppendLine($"namespace Germinate.Internal{(string.IsNullOrEmpty(rds.Namespace) ? "" : "." + rds.Namespace)} {{");
                output.AppendLine($"  public class {rds.DraftInstanceClassName} : {rds.BaseRecord?.FullyQualifiedDraftInstanceClassName ?? Names.FullyQualifiedDraftableBase}, {rds.FullyQualifiedInterfaceName} {{");

                EmitProperties(EmitPhase.PropImplementation, rds, output);

                // constructor
                output.AppendLine($"    public {rds.DraftInstanceClassName}({rds.FullyQualifiedRecordName} value, {Names.FullyQualifiedDraftableBase}? parent, {Names.FullyQualifiedCheckDirty}? checkDirty = null) : base(value, parent, checkDirty)");
                output.AppendLine("    {");
                EmitProperties(EmitPhase.Constructor, rds, output);
                output.AppendLine("    }"); // close constructor

                // finish
                output.AppendLine($"    public override {rds.FullyQualifiedRecordName} {Names.FinishMethod}()");
                output.AppendLine("    {");
                output.AppendLine($"      if ({Names.IsDirtyProp})");
                output.AppendLine("      {");
                output.AppendLine($"        return new {rds.FullyQualifiedRecordName}() {{");
                {
                    DraftableRecord r = rds;
                    while (r != null)
                    {
                        EmitProperties(EmitPhase.Finish, r, output);
                        r = r.BaseRecord;
                    }
                }
                output.AppendLine("        };"); // close initializer
                output.AppendLine("      } else {");
                output.AppendLine($"        return ({rds.FullyQualifiedRecordName}){Names.OriginalProp};");
                output.AppendLine("      }"); // close else
                output.AppendLine("    }");   // close finish method

                output.AppendLine("  }");     // close class
                output.AppendLine("}");       // close Internal namespace

                // Producer
                output.AppendLine("namespace Germinate {");
                output.AppendLine($"public static partial class Producer_{assemblyName?.Replace(".", "_").Replace("-", "_")} {{");
                output.AppendLine($"  public static {rds.FullyQualifiedRecordName} Produce(this {rds.FullyQualifiedRecordName} value, System.Action<{rds.FullyQualifiedInterfaceName}> f)");
                output.AppendLine("  {");
                output.AppendLine($"    var check = new {Names.FullyQualifiedCheckDirty}() {{ Checks = new System.Collections.Generic.List<System.Action>() }};");
                output.AppendLine($"    var draft = new {rds.FullyQualifiedDraftInstanceClassName}(value, null, check);");
                output.AppendLine("    f(draft);");
                output.AppendLine("    foreach (var a in check.Checks) a();");
                output.AppendLine($"    return draft.{Names.FinishMethod}();");
                output.AppendLine("  }");

                ImmutableAdjustAll.EmitAdjustAll(rds, output);

                output.AppendLine("}}"); // close Producer and namespace

                context.AddSource(rds.RecordName + ".Draftable.g.cs", output.ToString());
            }
        }
Exemple #8
0
        private static DraftableRecord AnalyzeRecord(INamedTypeSymbol recordSymbol, IDictionary <string, DraftableRecord> allRecords)
        {
            var             fullQualName = recordSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
            DraftableRecord record;

            if (allRecords.TryGetValue(fullQualName, out record))
            {
                return(record);
            }

            var nsp        = recordSymbol.ContainingNamespace?.ToDisplayString();
            var recordName = recordSymbol.Name;

            DraftableRecord baseRecord = null;

            if (recordSymbol.BaseType != null && recordSymbol.BaseType.SpecialType == SpecialType.None)
            {
                baseRecord = AnalyzeRecord(recordSymbol.BaseType, allRecords);
            }

            record = new DraftableRecord()
            {
                Emit       = false,
                RecordName = recordName,
                Namespace  = nsp,
                FullyQualifiedRecordName             = fullQualName,
                InterfaceName                        = "I" + recordName + "Draft",
                FullyQualifiedInterfaceName          = "global::" + (string.IsNullOrEmpty(nsp) ? "" : nsp + ".") + "I" + recordName + "Draft",
                DraftInstanceClassName               = recordName + "Draft",
                FullyQualifiedDraftInstanceClassName = "global::Germinate.Internal" + (string.IsNullOrEmpty(nsp) ? "" : "." + nsp) + "." + recordName + "Draft",
                BaseRecord = baseRecord,
                Properties = recordSymbol.GetMembers()
                             .OfType <IPropertySymbol>()
                             .Where(p => p.DeclaredAccessibility == Accessibility.Public &&
                                    p.GetMethod != null &&
                                    p.GetMethod.DeclaredAccessibility == Accessibility.Public &&
                                    p.SetMethod != null &&
                                    p.SetMethod.DeclaredAccessibility == Accessibility.Public
                                    )
                             .Select(p =>
                {
                    DraftableRecord typeIsDraftable = null;
                    if (IsDraftable(p.Type))
                    {
                        typeIsDraftable = AnalyzeRecord(p.Type as INamedTypeSymbol, allRecords);
                    }
                    var fullTypeName = p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
                    var isImmutable  = IsImmutableCollection(fullTypeName, p.Type, allRecords);
                    return(new RecordProperty()
                    {
                        PropertyName = p.Name,
                        Nullable = p.NullableAnnotation,
                        FullTypeName = fullTypeName,
                        IsValueType = p.Type.IsValueType,
                        TypeIsDraftable = typeIsDraftable,
                        IsImmutableCollection = isImmutable
                    });
                })
                             .ToList(),
            };

            allRecords.Add(fullQualName, record);
            return(record);
        }
        private static void EmitOneTypeArg(string immutableType, bool includeIndexed, DraftableRecord r, StringBuilder output)
        {
            output.AppendLine($"  public static void AdjustAll(this {immutableType}<{r.FullyQualifiedRecordName}>.Builder b, System.Action<{r.FullyQualifiedInterfaceName}> f)");
            output.AppendLine("  {");
            output.AppendLine("    var orig = b.ToImmutable();");
            output.AppendLine("    b.Clear();");
            output.AppendLine("    b.AddRange(System.Linq.Enumerable.Select(orig, x => x.Produce(f)));");
            output.AppendLine("  }");

            if (includeIndexed)
            {
                output.AppendLine($"  public static void AdjustAll(this {immutableType}<{r.FullyQualifiedRecordName}>.Builder b, System.Action<{r.FullyQualifiedInterfaceName}, int> f)");
                output.AppendLine("  {");
                output.AppendLine("    var orig = b.ToImmutable();");
                output.AppendLine("    b.Clear();");
                output.AppendLine("    b.AddRange(System.Linq.Enumerable.Select(orig, (x, idx) => x.Produce(d => f(d, idx))));");
                output.AppendLine("  }");
            }
        }