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(" }"); }
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); } }
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(" }"); }
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); } } }
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()); } }
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(" }"); } }