From 723d7d23f9fb0b5d43997321fc83b6173ca95fab Mon Sep 17 00:00:00 2001 From: Mel Grubb Date: Sun, 14 Jan 2024 14:21:10 -0500 Subject: [PATCH] Refactored template storage Refactores DTO mechanism Moved license file --- LICENSE.txt | 2 +- README.md | 5 + .../Examples/OutputWithInternals.cs | 21 +- .../Examples/OutputWithoutInternals.cs | 19 +- .../Templates/MyTemplates.cs | 18 ++ src/BuilderGenerator.sln | 1 - src/BuilderGenerator/BuilderGenerator.cs | 102 +++----- src/BuilderGenerator/BuilderGenerator.csproj | 13 +- .../{ => Framework}/BuilderInfo.cs | 16 +- .../{ => Framework}/TemplateParser.cs | 0 src/BuilderGenerator/LICENSE.txt | 21 -- src/BuilderGenerator/PropertyInfo.cs | 17 -- .../Templates/BuildMethod.txt | 19 -- .../Templates/BuildMethodSetter.txt | 1 - .../Templates/BuilderBaseClass.txt | 23 -- .../Templates/BuilderClass.txt | 25 -- .../Templates/BuilderForAttribute.txt | 15 -- src/BuilderGenerator/Templates/CSharp.cs | 230 ++++++++++++++++++ src/BuilderGenerator/Templates/CSharp11.cs | 37 +++ .../Templates/Constructors.txt | 10 - .../Templates/FromTemplateMethod.txt | 8 - .../Templates/FromTemplateMethodSetter.txt | 1 - src/BuilderGenerator/Templates/Property.txt | 1 - src/BuilderGenerator/Templates/README.md | 5 + .../Templates/WithMethods.txt | 17 -- .../Templates/WithObjectMethod.txt | 10 - src/Publish-Local.ps1 | 20 +- 27 files changed, 395 insertions(+), 262 deletions(-) create mode 100644 src/BuilderGenerator.Tests.Unit/Templates/MyTemplates.cs rename src/BuilderGenerator/{ => Framework}/BuilderInfo.cs (76%) rename src/BuilderGenerator/{ => Framework}/TemplateParser.cs (100%) delete mode 100644 src/BuilderGenerator/LICENSE.txt delete mode 100644 src/BuilderGenerator/PropertyInfo.cs delete mode 100644 src/BuilderGenerator/Templates/BuildMethod.txt delete mode 100644 src/BuilderGenerator/Templates/BuildMethodSetter.txt delete mode 100644 src/BuilderGenerator/Templates/BuilderBaseClass.txt delete mode 100644 src/BuilderGenerator/Templates/BuilderClass.txt delete mode 100644 src/BuilderGenerator/Templates/BuilderForAttribute.txt create mode 100644 src/BuilderGenerator/Templates/CSharp.cs create mode 100644 src/BuilderGenerator/Templates/CSharp11.cs delete mode 100644 src/BuilderGenerator/Templates/Constructors.txt delete mode 100644 src/BuilderGenerator/Templates/FromTemplateMethod.txt delete mode 100644 src/BuilderGenerator/Templates/FromTemplateMethodSetter.txt delete mode 100644 src/BuilderGenerator/Templates/Property.txt create mode 100644 src/BuilderGenerator/Templates/README.md delete mode 100644 src/BuilderGenerator/Templates/WithMethods.txt delete mode 100644 src/BuilderGenerator/Templates/WithObjectMethod.txt diff --git a/LICENSE.txt b/LICENSE.txt index 052ffe9..5f71c48 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2023 Mel Grubb +Copyright (c) 2021-2024 Mel Grubb Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 2cbfb28..8194eae 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,11 @@ Install-Package BuilderGenerator After installation, create a partial class to define your builder in. Decorate it with the ```BuilderFor``` attribute, specifying the type of class that the builder is meant to build (e.g. ```[BuilderFor(typeof(Foo))]```. Define any factory and helper methods in this partial class. Meanwhile, another partial class definition will be auto-generated which contains all the "boring" parts such as the backing fields and "with" methods. ## Version History ## +- v2.4.0 + - Test code reorganization + - Moved WithObject from the base class to the generated builder class + - Added WithValuesFrom method to shallow clone an example object. + - v2.3.0 - Major caching and performance improvements - Internal code cleanup diff --git a/src/BuilderGenerator.Tests.Unit/Examples/OutputWithInternals.cs b/src/BuilderGenerator.Tests.Unit/Examples/OutputWithInternals.cs index 249a604..b46d523 100644 --- a/src/BuilderGenerator.Tests.Unit/Examples/OutputWithInternals.cs +++ b/src/BuilderGenerator.Tests.Unit/Examples/OutputWithInternals.cs @@ -1,10 +1,10 @@ #nullable disable -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------- // -// This code was generated by BuilderGenerator at 2024-01-13T22:02:03 in 863.1781ms. +// This code was generated by BuilderGenerator at 2024-01-14T14:02:29 in 1232.3874ms // -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------- using System.CodeDom.Compiler; using BuilderGenerator; @@ -27,13 +27,13 @@ public PersonBuilderWithInternals(BuilderGenerator.UnitTests.Person value = null } } - /// Populates this instance with values from the provided template. - /// This is a shallow clone, and does not traverse the template creating builders for its properties. - public PersonBuilderWithInternals FromTemplate(BuilderGenerator.UnitTests.Person template) + /// Populates this instance with values from the provided example. + /// This is a shallow clone, and does not traverse the example object creating builders for its properties. + public PersonBuilderWithInternals WithValuesFrom(BuilderGenerator.UnitTests.Person example) { - WithFirstName(template.FirstName); - WithInternalString(template.InternalString); - WithLastName(template.LastName); + WithFirstName(example.FirstName); + WithInternalString(example.InternalString); + WithLastName(example.LastName); return this; } @@ -59,13 +59,14 @@ public override BuilderGenerator.UnitTests.Person Build() return Object.Value; } + /// Sets the object to be returned by this instance. /// The object to be returned. /// A reference to this builder instance. public PersonBuilderWithInternals WithObject(BuilderGenerator.UnitTests.Person value) { Object = new System.Lazy(() => value); - FromTemplate(value); + WithValuesFrom(value); return this; } diff --git a/src/BuilderGenerator.Tests.Unit/Examples/OutputWithoutInternals.cs b/src/BuilderGenerator.Tests.Unit/Examples/OutputWithoutInternals.cs index b196afe..775cb6e 100644 --- a/src/BuilderGenerator.Tests.Unit/Examples/OutputWithoutInternals.cs +++ b/src/BuilderGenerator.Tests.Unit/Examples/OutputWithoutInternals.cs @@ -1,10 +1,10 @@ #nullable disable -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------- // -// This code was generated by BuilderGenerator at 2024-01-13T22:03:19 in 3.8837ms. +// This code was generated by BuilderGenerator at 2024-01-14T14:03:02 in 4.9846ms // -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------- using System.CodeDom.Compiler; using BuilderGenerator; @@ -26,12 +26,12 @@ public PersonBuilderWithoutInternals(BuilderGenerator.UnitTests.Person value = n } } - /// Populates this instance with values from the provided template. - /// This is a shallow clone, and does not traverse the template creating builders for its properties. - public PersonBuilderWithoutInternals FromTemplate(BuilderGenerator.UnitTests.Person template) + /// Populates this instance with values from the provided example. + /// This is a shallow clone, and does not traverse the example object creating builders for its properties. + public PersonBuilderWithoutInternals WithValuesFrom(BuilderGenerator.UnitTests.Person example) { - WithFirstName(template.FirstName); - WithLastName(template.LastName); + WithFirstName(example.FirstName); + WithLastName(example.LastName); return this; } @@ -56,13 +56,14 @@ public override BuilderGenerator.UnitTests.Person Build() return Object.Value; } + /// Sets the object to be returned by this instance. /// The object to be returned. /// A reference to this builder instance. public PersonBuilderWithoutInternals WithObject(BuilderGenerator.UnitTests.Person value) { Object = new System.Lazy(() => value); - FromTemplate(value); + WithValuesFrom(value); return this; } diff --git a/src/BuilderGenerator.Tests.Unit/Templates/MyTemplates.cs b/src/BuilderGenerator.Tests.Unit/Templates/MyTemplates.cs new file mode 100644 index 0000000..76990df --- /dev/null +++ b/src/BuilderGenerator.Tests.Unit/Templates/MyTemplates.cs @@ -0,0 +1,18 @@ +using System; +using BuilderGenerator.Templates; + +namespace BuilderGenerator.Tests.Unit.Templates; + +/// Defines custom code generation templates for testing. +/// This is here to test the custom template mechanism itself by overriding individual pieces. +internal class MyTemplates : CSharp11 +{ + // TODO: Override the BuilderClass template, changing the header to remove the timestamp only so that the results match. + public override string BuilderClass + { + get + { + return base.BuilderClass[base.BuilderClass.IndexOf("using", StringComparison.OrdinalIgnoreCase)..]; + } + } +} diff --git a/src/BuilderGenerator.sln b/src/BuilderGenerator.sln index 4e522d5..4315aee 100644 --- a/src/BuilderGenerator.sln +++ b/src/BuilderGenerator.sln @@ -14,7 +14,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\LICENSE.txt = ..\LICENSE.txt Publish-Local.ps1 = Publish-Local.ps1 ..\README.md = ..\README.md - Refresh.ps1 = Refresh.ps1 EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{16BA5C10-6F7A-4CDA-A88E-A4247FFEFC80}" diff --git a/src/BuilderGenerator/BuilderGenerator.cs b/src/BuilderGenerator/BuilderGenerator.cs index 7597516..8ac9452 100644 --- a/src/BuilderGenerator/BuilderGenerator.cs +++ b/src/BuilderGenerator/BuilderGenerator.cs @@ -1,12 +1,11 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Linq; -using System.Reflection; using System.Text; using System.Threading; using BuilderGenerator.Diagnostics; +using BuilderGenerator.Templates; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; @@ -16,55 +15,20 @@ namespace BuilderGenerator; [Generator] internal class BuilderGenerator : IIncrementalGenerator { - private static readonly string BuilderBaseClass; - private static readonly string BuilderClass; - private static readonly string BuilderForAttribute; - private static readonly string BuildMethod; - private static readonly string BuildMethodSetter; - private static readonly string Constructors; - private static readonly string FromTemplateMethod; - private static readonly string FromTemplateMethodSetter; #pragma warning disable RS1035 private static readonly string NewLine = Environment.NewLine; // TODO: Derive this value from the project itself #pragma warning restore RS1035 - private static readonly string Property; - private static readonly string WithMethods; - private static readonly string WithObjectMethod; - static BuilderGenerator() - { - var assembly = typeof(BuilderGenerator).Assembly; - - BuilderBaseClass = GetResourceAsString(assembly, nameof(BuilderBaseClass)); - BuilderClass = GetResourceAsString(assembly, nameof(BuilderClass)); - BuilderForAttribute = GetResourceAsString(assembly, nameof(BuilderForAttribute)); - BuildMethodSetter = GetResourceAsString(assembly, nameof(BuildMethodSetter)); - BuildMethod = GetResourceAsString(assembly, nameof(BuildMethod)); - Constructors = GetResourceAsString(assembly, nameof(Constructors)); - FromTemplateMethod = GetResourceAsString(assembly, nameof(FromTemplateMethod)); - FromTemplateMethodSetter = GetResourceAsString(assembly, nameof(FromTemplateMethodSetter)); - Property = GetResourceAsString(assembly, nameof(Property)); - WithMethods = GetResourceAsString(assembly, nameof(WithMethods)); - WithObjectMethod = GetResourceAsString(assembly, nameof(WithObjectMethod)); - } - - public static string GetResourceAsString(Assembly assembly, string resourceName) - { - resourceName = assembly.GetManifestResourceNames().Single(x => x.Equals($"BuilderGenerator.Templates.{resourceName}.txt", StringComparison.OrdinalIgnoreCase)); - using var stream = assembly.GetManifestResourceStream(resourceName) ?? throw new InvalidOperationException($"Resource '{resourceName}' not found."); - using var reader = new StreamReader(stream); - - return reader.ReadToEnd(); - } + private static readonly CSharp Templates = new CSharp11(); public void Initialize(IncrementalGeneratorInitializationContext context) { - // Register injection of classes that never change. + // Register injection of classes to be injected as-is. context.RegisterPostInitializationOutput( x => { - x.AddSource(nameof(BuilderBaseClass), SourceText.From(BuilderBaseClass, Encoding.UTF8)); - x.AddSource(nameof(BuilderForAttribute), SourceText.From(BuilderForAttribute, Encoding.UTF8)); + x.AddSource(nameof(Templates.BuilderBaseClass), SourceText.From(Templates.BuilderBaseClass, Encoding.UTF8)); + x.AddSource(nameof(Templates.BuilderForAttribute), SourceText.From(Templates.BuilderForAttribute, Encoding.UTF8)); }); // Register generation for classes based on the project contents @@ -91,11 +55,11 @@ private static void Execute(SourceProductionContext context, BuilderInfo? builde templateParser.SetTag("TargetClassFullName", builder.Value.TargetClassFullName); templateParser.SetTag("Constructors", GenerateConstructors(templateParser)); templateParser.SetTag("Properties", GenerateProperties(templateParser, builder.Value.Properties)); - templateParser.SetTag("FromTemplateMethod", GenerateFromTemplateMethod(templateParser, builder.Value.Properties)); + templateParser.SetTag("WithValuesFromMethod", GenerateWithValuesFromMethod(templateParser, builder.Value.Properties)); templateParser.SetTag("BuildMethod", GenerateBuildMethod(templateParser, builder.Value.Properties)); templateParser.SetTag("WithMethods", GenerateWithMethods(templateParser, builder.Value.Properties)); templateParser.SetTag("WithObjectMethod", GenerateWithObjectMethod(templateParser)); - var source = templateParser.ParseString(BuilderClass); + var source = templateParser.ParseString(Templates.BuilderClass); context.AddSource($"{builder.Value.BuilderClassName}.g.cs", SourceText.From(source, Encoding.UTF8)); } catch (Exception e) @@ -104,7 +68,7 @@ private static void Execute(SourceProductionContext context, BuilderInfo? builde } } - private static string GenerateBuildMethod(TemplateParser templateParser, IEnumerable properties) + private static string GenerateBuildMethod(TemplateParser templateParser, IEnumerable properties) { var setters = string.Join( NewLine, @@ -113,39 +77,37 @@ private static string GenerateBuildMethod(TemplateParser templateParser, IEnumer { templateParser.SetTag("PropertyName", p.Name); - return templateParser.ParseString(BuildMethodSetter); + return templateParser.ParseString(Templates.BuildMethodSetter); })); templateParser.SetTag("Setters", setters); - var result = templateParser.ParseString(BuildMethod); + var result = templateParser.ParseString(Templates.BuildMethod); return result; } private static string GenerateConstructors(TemplateParser templateParser) { - return templateParser.ParseString(Constructors); + return templateParser.ParseString(Templates.Constructors); } - private static string GenerateFromTemplateMethod(TemplateParser templateParser, IEnumerable properties) + private static string GenerateProperties(TemplateParser templateParser, IEnumerable properties) { - var setters = string.Join( + var result = string.Join( NewLine, properties.Select( p => { templateParser.SetTag("PropertyName", p.Name); + templateParser.SetTag("PropertyType", p.Type); - return templateParser.ParseString(FromTemplateMethodSetter); + return templateParser.ParseString(Templates.Property); })); - templateParser.SetTag("FromTemplateMethodSetters", setters); - var result = templateParser.ParseString(FromTemplateMethod); - return result; } - private static string GenerateProperties(TemplateParser templateParser, IEnumerable properties) + private static string GenerateWithMethods(TemplateParser templateParser, IEnumerable properties) { var result = string.Join( NewLine, @@ -155,31 +117,33 @@ private static string GenerateProperties(TemplateParser templateParser, IEnumera templateParser.SetTag("PropertyName", p.Name); templateParser.SetTag("PropertyType", p.Type); - return templateParser.ParseString(Property); + return templateParser.ParseString(Templates.WithMethods); })); return result; } - private static string GenerateWithMethods(TemplateParser templateParser, IEnumerable properties) + private static string GenerateWithObjectMethod(TemplateParser templateParser) { - var result = string.Join( + return templateParser.ParseString(Templates.WithObjectMethod); + } + + private static string GenerateWithValuesFromMethod(TemplateParser templateParser, IEnumerable properties) + { + var setters = string.Join( NewLine, properties.Select( p => { templateParser.SetTag("PropertyName", p.Name); - templateParser.SetTag("PropertyType", p.Type); - return templateParser.ParseString(WithMethods); + return templateParser.ParseString(Templates.WithValuesFromSetter); })); - return result; - } + templateParser.SetTag("WithValuesFromSetters", setters); + var result = templateParser.ParseString(Templates.WithValuesFromMethod); - private static string GenerateWithObjectMethod(TemplateParser templateParser) - { - return templateParser.ParseString(WithObjectMethod); + return result; } private static IEnumerable GetPropertySymbols(INamedTypeSymbol namedTypeSymbol, bool includeInternals) @@ -200,10 +164,10 @@ private static IEnumerable GetPropertySymbols(INamedTypeSymbol return symbols; } - /// Performs a first-pass filtering of syntax nodes that might possibly represent a builder class. + /// Performs a first-pass filtering of syntax nodes that could possibly represent a builder class. /// The syntax node being examined. /// A cancellation token (currently unused). - /// A indicating whether or not might possibly represent a builder class. + /// A indicating whether might possibly represent a builder class. private static bool Predicate(SyntaxNode node, CancellationToken _) { return node is TypeDeclarationSyntax { AttributeLists.Count: > 0 }; @@ -226,7 +190,7 @@ private static bool Predicate(SyntaxNode node, CancellationToken _) if (!namedTypeSymbol.GetAttributes().Any(x => x.AttributeClass?.Name == "BuilderForAttribute")) { return null; } - var attributeSymbol = namedTypeSymbol.GetAttributes().SingleOrDefault(x => x.AttributeClass!.Name == nameof(BuilderForAttribute)); + var attributeSymbol = namedTypeSymbol.GetAttributes().SingleOrDefault(x => x.AttributeClass!.Name == nameof(Templates.BuilderForAttribute)); if (attributeSymbol is null) { return null; } @@ -250,7 +214,7 @@ private static bool Predicate(SyntaxNode node, CancellationToken _) TargetClassFullName = targetClassType.Value!.ToString(), BuilderClassUsingBlock = ((CompilationUnitSyntax)typeNode.SyntaxTree.GetRoot()).Usings.ToString(), Properties = targetClassProperties.Select( - x => new PropertyInfo + x => new BuilderInfo.PropertyInfo { Accessibility = x.Accessibility, Name = x.Name, @@ -258,6 +222,8 @@ private static bool Predicate(SyntaxNode node, CancellationToken _) }).ToList(), Location = typeNode.GetLocation(), Identifier = typeNode.Identifier.ToString(), + + // TODO: Retrieve template class instance/type from attribute, and use that to retrieve the strings later on. TimeToGenerate = stopwatch.Elapsed, }; diff --git a/src/BuilderGenerator/BuilderGenerator.csproj b/src/BuilderGenerator/BuilderGenerator.csproj index 9f22cef..7811667 100644 --- a/src/BuilderGenerator/BuilderGenerator.csproj +++ b/src/BuilderGenerator/BuilderGenerator.csproj @@ -22,7 +22,7 @@ v2.4.0 - Test code reorganization - Moved WithObject from the base class to the generated builder class - - Added FromTemplate method to shallow clone an example object. + - Added WithValuesFrom method to shallow clone an example object. v2.3.0 - Major caching and performance improvements @@ -102,6 +102,15 @@ + + + + + + + + + all @@ -120,7 +129,7 @@ True - + diff --git a/src/BuilderGenerator/BuilderInfo.cs b/src/BuilderGenerator/Framework/BuilderInfo.cs similarity index 76% rename from src/BuilderGenerator/BuilderInfo.cs rename to src/BuilderGenerator/Framework/BuilderInfo.cs index c9530b7..f36219a 100644 --- a/src/BuilderGenerator/BuilderInfo.cs +++ b/src/BuilderGenerator/Framework/BuilderInfo.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Collections.Generic; using System.Linq; @@ -58,4 +56,18 @@ public override int GetHashCode() return hash; } } + + internal record struct PropertyInfo + { + /// Gets or sets the accessibility of the target class property. + /// The accessibility of the target class property. + /// Although this isn't used directly by the templates themselves, a change in accessibility could result in a re-generation of the builder, so we need this to be part of the hash code. + public Accessibility Accessibility { get; set; } + + /// Gets or sets the name of the target class property. + /// The name of the target class property. + public string Name { get; set; } + + public string Type { get; set; } + } } diff --git a/src/BuilderGenerator/TemplateParser.cs b/src/BuilderGenerator/Framework/TemplateParser.cs similarity index 100% rename from src/BuilderGenerator/TemplateParser.cs rename to src/BuilderGenerator/Framework/TemplateParser.cs diff --git a/src/BuilderGenerator/LICENSE.txt b/src/BuilderGenerator/LICENSE.txt deleted file mode 100644 index 2a5cfde..0000000 --- a/src/BuilderGenerator/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021 Mel Grubb - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/src/BuilderGenerator/PropertyInfo.cs b/src/BuilderGenerator/PropertyInfo.cs deleted file mode 100644 index a6859a9..0000000 --- a/src/BuilderGenerator/PropertyInfo.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.CodeAnalysis; - -namespace BuilderGenerator; - -internal record struct PropertyInfo -{ - /// Gets or sets the accessibility of the target class property. - /// The accessibility of the target class property. - /// Although this isn't used by the templates themselves, a change in accessibility could result in a re-generation of the builder, so we need this to be part of the hash code. - public Accessibility Accessibility { get; set; } - - /// Gets or sets the name of the target class property. - /// The name of the target class property. - public string Name { get; set; } - - public string Type { get; set; } -} diff --git a/src/BuilderGenerator/Templates/BuildMethod.txt b/src/BuilderGenerator/Templates/BuildMethod.txt deleted file mode 100644 index 95a3448..0000000 --- a/src/BuilderGenerator/Templates/BuildMethod.txt +++ /dev/null @@ -1,19 +0,0 @@ - public override {{TargetClassFullName}} Build() - { - if (Object?.IsValueCreated != true) - { - Object = new System.Lazy<{{TargetClassFullName}}>(() => - { - var result = new {{TargetClassFullName}} - { -{{Setters}} - }; - - return result; - }); - - PostProcess(Object.Value); - } - - return Object.Value; - } \ No newline at end of file diff --git a/src/BuilderGenerator/Templates/BuildMethodSetter.txt b/src/BuilderGenerator/Templates/BuildMethodSetter.txt deleted file mode 100644 index 71ab8ad..0000000 --- a/src/BuilderGenerator/Templates/BuildMethodSetter.txt +++ /dev/null @@ -1 +0,0 @@ - {{PropertyName}} = {{PropertyName}}.Value, \ No newline at end of file diff --git a/src/BuilderGenerator/Templates/BuilderBaseClass.txt b/src/BuilderGenerator/Templates/BuilderBaseClass.txt deleted file mode 100644 index dd2c3d3..0000000 --- a/src/BuilderGenerator/Templates/BuilderBaseClass.txt +++ /dev/null @@ -1,23 +0,0 @@ -#nullable disable - -namespace BuilderGenerator -{ - /// Base class for object builder classes. - /// The type of the objects built by this builder. - public abstract class Builder where T : class - { - /// Gets or sets the object returned by this builder. - /// The constructed object. - #pragma warning disable CA1720 // Identifier contains type name - protected System.Lazy Object { get; set; } - #pragma warning restore CA1720 // Identifier contains type name - - /// Builds the object instance. - /// The constructed object. - public abstract T Build(); - - protected virtual void PostProcess(T value) - { - } - } -} diff --git a/src/BuilderGenerator/Templates/BuilderClass.txt b/src/BuilderGenerator/Templates/BuilderClass.txt deleted file mode 100644 index 86b8f8c..0000000 --- a/src/BuilderGenerator/Templates/BuilderClass.txt +++ /dev/null @@ -1,25 +0,0 @@ -#nullable disable - -//------------------------------------------------------------------------------ -// -// This code was generated by BuilderGenerator at {{GenerationTime:u}} in {{GenerationDuration}}ms. -// -//------------------------------------------------------------------------------ -using System.CodeDom.Compiler; -{{BuilderClassUsingBlock}} - -namespace {{BuilderClassNamespace}} -{ - {{BuilderClassAccessibility}} partial class {{BuilderClassName}} : BuilderGenerator.Builder<{{TargetClassFullName}}> - { -{{Properties}} - -{{Constructors}} - -{{FromTemplateMethod}} - -{{BuildMethod}} -{{WithObjectMethod}} -{{WithMethods}} - } -} diff --git a/src/BuilderGenerator/Templates/BuilderForAttribute.txt b/src/BuilderGenerator/Templates/BuilderForAttribute.txt deleted file mode 100644 index 0d73a06..0000000 --- a/src/BuilderGenerator/Templates/BuilderForAttribute.txt +++ /dev/null @@ -1,15 +0,0 @@ -namespace BuilderGenerator -{ - [System.AttributeUsage(System.AttributeTargets.Class)] - public class BuilderForAttribute : System.Attribute - { - public bool IncludeInternals { get; } - public System.Type Type { get; } - - public BuilderForAttribute(System.Type type, bool includeInternals = false) - { - IncludeInternals = includeInternals; - Type = type; - } - } -} diff --git a/src/BuilderGenerator/Templates/CSharp.cs b/src/BuilderGenerator/Templates/CSharp.cs new file mode 100644 index 0000000..bfd8117 --- /dev/null +++ b/src/BuilderGenerator/Templates/CSharp.cs @@ -0,0 +1,230 @@ +namespace BuilderGenerator.Templates; + +/// Defines code generation templates compatible with C# 10 features. +public class CSharp +{ + public virtual string BuilderBaseClass + { + get + { + return """ + #nullable disable + + namespace BuilderGenerator + { + /// Base class for object builder classes. + /// The type of the objects built by this builder. + public abstract class Builder where T : class + { + /// Gets or sets the object returned by this builder. + /// The constructed object. + #pragma warning disable CA1720 // Identifier contains type name + protected System.Lazy Object { get; set; } + #pragma warning restore CA1720 // Identifier contains type name + + /// Builds the object instance. + /// The constructed object. + public abstract T Build(); + + protected virtual void PostProcess(T value) + { + } + } + } + """; + } + } + + public virtual string BuilderClass + { + get + { + return """ + #nullable disable + + //------------------------------------------------------------------------------------- + // + // This code was generated by BuilderGenerator at {{GenerationTime:u}} in {{GenerationDuration}}ms + // + //------------------------------------------------------------------------------------- + using System.CodeDom.Compiler; + {{BuilderClassUsingBlock}} + + namespace {{BuilderClassNamespace}} + { + {{BuilderClassAccessibility}} partial class {{BuilderClassName}} : BuilderGenerator.Builder<{{TargetClassFullName}}> + { + {{Properties}} + + {{Constructors}} + + {{WithValuesFromMethod}} + + {{BuildMethod}} + + {{WithObjectMethod}} + {{WithMethods}} + } + } + + """; + } + } + + public virtual string BuilderForAttribute + { + get + { + return """ + namespace BuilderGenerator + { + [System.AttributeUsage(System.AttributeTargets.Class)] + public class BuilderForAttribute : System.Attribute + { + public bool IncludeInternals { get; } + public System.Type Type { get; } + + public BuilderForAttribute(System.Type type, bool includeInternals = false) + { + IncludeInternals = includeInternals; + Type = type; + } + } + } + """; + } + } + + public virtual string BuildMethod + { + get + { + return """ + public override {{TargetClassFullName}} Build() + { + if (Object?.IsValueCreated != true) + { + Object = new System.Lazy<{{TargetClassFullName}}>(() => + { + var result = new {{TargetClassFullName}} + { + {{Setters}} + }; + + return result; + }); + + PostProcess(Object.Value); + } + + return Object.Value; + } + """; + } + } + + public virtual string BuildMethodSetter + { + get + { + return " {{PropertyName}} = {{PropertyName}}.Value,"; + } + } + + public virtual string Constructors + { + get + { + return """ + /// Initializes a new instance of the class using the provided for the Object value. + /// The instance to build on. + /// Note: is not simply a template. The actual instance will be remembered and returned. + public {{BuilderClassName}}({{TargetClassFullName}} value = null) + { + if (value != null) + { + WithObject(value); + } + } + """; + } + } + + public virtual string Property + { + get + { + return " public System.Lazy<{{PropertyType}}> {{PropertyName}} = new System.Lazy<{{PropertyType}}>(() => default({{PropertyType}}));"; + } + } + + public virtual string WithMethods + { + get + { + return """ + + public {{BuilderClassName}} With{{PropertyName}}({{PropertyType}} value) + { + return With{{PropertyName}}(() => value); + } + + public {{BuilderClassName}} With{{PropertyName}}(System.Func<{{PropertyType}}> func) + { + {{PropertyName}} = new System.Lazy<{{PropertyType}}>(func); + return this; + } + + public {{BuilderClassName}} Without{{PropertyName}}() + { + {{PropertyName}} = new System.Lazy<{{PropertyType}}>(() => default({{PropertyType}})); + return this; + } + """; + } + } + + public virtual string WithObjectMethod + { + get + { + return """ + /// Sets the object to be returned by this instance. + /// The object to be returned. + /// A reference to this builder instance. + public {{BuilderClassName}} WithObject({{TargetClassFullName}} value) + { + Object = new System.Lazy<{{TargetClassFullName}}>(() => value); + WithValuesFrom(value); + + return this; + } + """; + } + } + + public virtual string WithValuesFromMethod + { + get + { + return """ + /// Populates this instance with values from the provided example. + /// This is a shallow clone, and does not traverse the example object creating builders for its properties. + public {{BuilderClassName}} WithValuesFrom({{TargetClassFullName}} example) + { + {{WithValuesFromSetters}} + + return this; + } + """; + } + } + + public virtual string WithValuesFromSetter + { + get + { + return " With{{PropertyName}}(example.{{PropertyName}});"; + } + } +} diff --git a/src/BuilderGenerator/Templates/CSharp11.cs b/src/BuilderGenerator/Templates/CSharp11.cs new file mode 100644 index 0000000..9403535 --- /dev/null +++ b/src/BuilderGenerator/Templates/CSharp11.cs @@ -0,0 +1,37 @@ +namespace BuilderGenerator.Templates; + +/// Defines code generation templates compatible with C# 11 features. +public class CSharp11 : CSharp +{ + public override string BuilderForAttribute + { + get + { + return """ + namespace BuilderGenerator + { + [System.AttributeUsage(System.AttributeTargets.Class)] + public class BuilderForAttribute : System.Attribute + { + public bool IncludeInternals { get; } + public System.Type Type { get; } + + public BuilderForAttribute(System.Type type, bool includeInternals = false) + { + IncludeInternals = includeInternals; + Type = type; + } + } + + [System.AttributeUsage(System.AttributeTargets.Class)] + public class BuilderForAttribute : BuilderForAttribute + { + public BuilderForAttribute(bool includeInternals = false) : base(typeof(T), includeInternals) + { + } + } + } + """; + } + } +} diff --git a/src/BuilderGenerator/Templates/Constructors.txt b/src/BuilderGenerator/Templates/Constructors.txt deleted file mode 100644 index 3ce80dd..0000000 --- a/src/BuilderGenerator/Templates/Constructors.txt +++ /dev/null @@ -1,10 +0,0 @@ - /// Initializes a new instance of the class using the provided for the Object value. - /// The instance to build on. - /// Note: is not simply a template. The actual instance will be remembered and returned. - public {{BuilderClassName}}({{TargetClassFullName}} value = null) - { - if (value != null) - { - WithObject(value); - } - } \ No newline at end of file diff --git a/src/BuilderGenerator/Templates/FromTemplateMethod.txt b/src/BuilderGenerator/Templates/FromTemplateMethod.txt deleted file mode 100644 index 7d54010..0000000 --- a/src/BuilderGenerator/Templates/FromTemplateMethod.txt +++ /dev/null @@ -1,8 +0,0 @@ - /// Populates this instance with values from the provided template. - /// This is a shallow clone, and does not traverse the template creating builders for its properties. - public {{BuilderClassName}} FromTemplate({{TargetClassFullName}} template) - { -{{FromTemplateMethodSetters}} - - return this; - } \ No newline at end of file diff --git a/src/BuilderGenerator/Templates/FromTemplateMethodSetter.txt b/src/BuilderGenerator/Templates/FromTemplateMethodSetter.txt deleted file mode 100644 index 53c2bc1..0000000 --- a/src/BuilderGenerator/Templates/FromTemplateMethodSetter.txt +++ /dev/null @@ -1 +0,0 @@ - With{{PropertyName}}(template.{{PropertyName}}); \ No newline at end of file diff --git a/src/BuilderGenerator/Templates/Property.txt b/src/BuilderGenerator/Templates/Property.txt deleted file mode 100644 index bc993e1..0000000 --- a/src/BuilderGenerator/Templates/Property.txt +++ /dev/null @@ -1 +0,0 @@ - public System.Lazy<{{PropertyType}}> {{PropertyName}} = new System.Lazy<{{PropertyType}}>(() => default({{PropertyType}})); \ No newline at end of file diff --git a/src/BuilderGenerator/Templates/README.md b/src/BuilderGenerator/Templates/README.md new file mode 100644 index 0000000..9b34cae --- /dev/null +++ b/src/BuilderGenerator/Templates/README.md @@ -0,0 +1,5 @@ +# Templates + +This is still a work in progress, but the idea is to store the templates as properties on an class. These classes can then inherit from previous versions to change only the things they have to. For instance, C# 11 introduced the ability to create attributes with generic parameters. The CSharp11 class inherits the CSharp10 class and overrides the template for the attribute, adding the generic version of the attribute. + +Specifying the set of templates to use is still under development, but this is a placeholder to work out what the mechanism might look like when it's finished. \ No newline at end of file diff --git a/src/BuilderGenerator/Templates/WithMethods.txt b/src/BuilderGenerator/Templates/WithMethods.txt deleted file mode 100644 index e1b81ed..0000000 --- a/src/BuilderGenerator/Templates/WithMethods.txt +++ /dev/null @@ -1,17 +0,0 @@ - - public {{BuilderClassName}} With{{PropertyName}}({{PropertyType}} value) - { - return With{{PropertyName}}(() => value); - } - - public {{BuilderClassName}} With{{PropertyName}}(System.Func<{{PropertyType}}> func) - { - {{PropertyName}} = new System.Lazy<{{PropertyType}}>(func); - return this; - } - - public {{BuilderClassName}} Without{{PropertyName}}() - { - {{PropertyName}} = new System.Lazy<{{PropertyType}}>(() => default({{PropertyType}})); - return this; - } \ No newline at end of file diff --git a/src/BuilderGenerator/Templates/WithObjectMethod.txt b/src/BuilderGenerator/Templates/WithObjectMethod.txt deleted file mode 100644 index feb3712..0000000 --- a/src/BuilderGenerator/Templates/WithObjectMethod.txt +++ /dev/null @@ -1,10 +0,0 @@ - /// Sets the object to be returned by this instance. - /// The object to be returned. - /// A reference to this builder instance. - public {{BuilderClassName}} WithObject({{TargetClassFullName}} value) - { - Object = new System.Lazy<{{TargetClassFullName}}>(() => value); - FromTemplate(value); - - return this; - } \ No newline at end of file diff --git a/src/Publish-Local.ps1 b/src/Publish-Local.ps1 index e3f8ea5..23080e7 100644 --- a/src/Publish-Local.ps1 +++ b/src/Publish-Local.ps1 @@ -1,7 +1,25 @@ [xml]$buildProps = Get-Content BuilderGenerator\BuilderGenerator.csproj $version = $buildProps.Project.PropertyGroup.Version -$version = "$version".Trim() + +Write-Output "Uninstalling package" +Uninstall-Package -Id BuilderGenerator -ProjectName BuilderGenerator.Sample.NuGet + +Write-Output "Deleting local package cache version $version" +if (test-path %userprofile%\.nuget\packages\buildergenerator\$version) +{ + rm %userprofile%\.nuget\packages\buildergenerator\$version -recurse +} + +$path = "C:\Projects\NuGet Local Repo\BuilderGenerator.$version.nupkg" +Write-Output "Deleting local NuGet package '$path'" +if (test-path $path) +{ + rm $path +} Write-Output "Publishing package version '$version' to local NuGet repo" $path = '.\BuilderGenerator\bin\Debug\BuilderGenerator.' + $version + '.nupkg' dotnet nuget push $path --source 'C:\NuGet\BuilderGenerator' + +Write-Output "Installing new package version" +Install-Package -Id BuilderGenerator -ProjectName BuilderGenerator.Sample.NuGet \ No newline at end of file