From 5e43c6cc6876fc15fb0a6f7c60ed9da07b766e41 Mon Sep 17 00:00:00 2001 From: progala3 Date: Mon, 14 Oct 2024 19:38:46 +0200 Subject: [PATCH] ## 8.1.3 - 2024-10-14 ### Changed - Fixed regression for default constructors - the build method should appear again - Added generation of methods for properties from private constructors, keeping the Build method removed. --- .../Configuration/Contract/IEntityToBuild.cs | 2 +- Buildenator/Configuration/EntityToBuild.cs | 46 ++++++++++--------- .../BuilderSourceStringGenerator.cs | 20 ++++---- .../Generators/PropertiesStringGenerator.cs | 2 +- CHANGELOG.md | 6 +++ .../EntityWithImplicitConstructor.cs | 2 +- .../EntityWithPrivateConstructorBuilder.cs | 3 +- .../BuildersGeneratorTests.cs | 25 +++++++++- .../PropertiesStringGeneratorTests.cs | 2 + 9 files changed, 68 insertions(+), 40 deletions(-) diff --git a/Buildenator/Configuration/Contract/IEntityToBuild.cs b/Buildenator/Configuration/Contract/IEntityToBuild.cs index 6fd4603..966b414 100644 --- a/Buildenator/Configuration/Contract/IEntityToBuild.cs +++ b/Buildenator/Configuration/Contract/IEntityToBuild.cs @@ -11,7 +11,7 @@ internal interface IEntityToBuild : IAdditionalNamespacesProvider string Name { get; } IReadOnlyList SettableProperties { get; } IReadOnlyList ReadOnlyProperties { get; } - EntityToBuild.Constructor? ConstructorToBuild { get; } + EntityToBuild.Constructor ConstructorToBuild { get; } IReadOnlyList GetAllUniqueReadOnlyPropertiesWithoutConstructorsParametersMatch(); IReadOnlyList GetAllUniqueSettablePropertiesAndParameters(); diff --git a/Buildenator/Configuration/EntityToBuild.cs b/Buildenator/Configuration/EntityToBuild.cs index bf3dbe3..87f0e89 100644 --- a/Buildenator/Configuration/EntityToBuild.cs +++ b/Buildenator/Configuration/EntityToBuild.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Linq; using Buildenator.Abstraction; +using System.Diagnostics; +using System.Reflection.Metadata; namespace Buildenator.Configuration; @@ -13,7 +15,7 @@ internal sealed class EntityToBuild : IEntityToBuild public string Name { get; } public string FullName { get; } public string FullNameWithConstraints { get; } - public Constructor? ConstructorToBuild { get; } + public Constructor ConstructorToBuild { get; } public IReadOnlyList SettableProperties { get; } public IReadOnlyList ReadOnlyProperties { get; } public string[] AdditionalNamespaces { get; } @@ -50,11 +52,6 @@ public EntityToBuild( public IReadOnlyList GetAllUniqueSettablePropertiesAndParameters() { - if (ConstructorToBuild is null) - { - return _uniqueTypedSymbols ??= SettableProperties; - } - return _uniqueTypedSymbols ??= SettableProperties .Where(x => !ConstructorToBuild.ContainsParameter(x.SymbolName)) .Concat(ConstructorToBuild.Parameters).ToList(); @@ -62,11 +59,6 @@ public IReadOnlyList GetAllUniqueSettablePropertiesAndParameters() public IReadOnlyList GetAllUniqueReadOnlyPropertiesWithoutConstructorsParametersMatch() { - if (ConstructorToBuild is null) - { - return _uniqueReadOnlyTypedSymbols ??= ReadOnlyProperties; - } - return _uniqueReadOnlyTypedSymbols ??= ReadOnlyProperties .Where(x => !ConstructorToBuild.ContainsParameter(x.SymbolName)).ToList(); } @@ -86,28 +78,38 @@ private static (TypedSymbol[] Settable, TypedSymbol[] ReadOnly) DivideProperties internal sealed class Constructor { - public static Constructor? CreateConstructorOrDefault( + public bool IsPrivate { get; } + + public static Constructor CreateConstructorOrDefault( INamedTypeSymbol entityToBuildSymbol, IMockingProperties? mockingConfiguration, IFixtureProperties? fixtureConfiguration, NullableStrategy nullableStrategy) { - var onlyPublicOrInternalConstructors = entityToBuildSymbol.Constructors - .Where(m => - !m.IsStatic - && !m.IsImplicitlyDeclared - && (m.DeclaredAccessibility == Accessibility.Public || m.DeclaredAccessibility == Accessibility.Internal)) + var constructors = entityToBuildSymbol.Constructors.Select(a => a).ToArray(); + var onlyPublicConstructors = constructors + .Where(m => m.DeclaredAccessibility == Accessibility.Public || m.DeclaredAccessibility == Accessibility.Internal) .ToList(); - return onlyPublicOrInternalConstructors.Count > 0 - ? new Constructor(onlyPublicOrInternalConstructors.OrderByDescending(x => x.Parameters.Length).First().Parameters - .ToDictionary(x => x.PascalCaseName(), s => new TypedSymbol(s, mockingConfiguration, fixtureConfiguration, nullableStrategy))) - : default; + var isPrivate = onlyPublicConstructors.Count == 0; + + Dictionary parameters = []; + if (!isPrivate) + { + parameters = onlyPublicConstructors + .OrderByDescending(x => x.Parameters.Length) + .First() + .Parameters + .ToDictionary(x => x.PascalCaseName(), s => new TypedSymbol(s, mockingConfiguration, fixtureConfiguration, nullableStrategy)); + } + + return new Constructor(parameters, isPrivate); } - private Constructor(IReadOnlyDictionary constructorParameters) + private Constructor(IReadOnlyDictionary constructorParameters, bool isPrivate) { ConstructorParameters = constructorParameters; + IsPrivate = isPrivate; } public IReadOnlyDictionary ConstructorParameters { get; } diff --git a/Buildenator/Generators/BuilderSourceStringGenerator.cs b/Buildenator/Generators/BuilderSourceStringGenerator.cs index 0593f9f..ea6c583 100644 --- a/Buildenator/Generators/BuilderSourceStringGenerator.cs +++ b/Buildenator/Generators/BuilderSourceStringGenerator.cs @@ -73,10 +73,10 @@ private string GenerateBuilderDefinition() private string GenerateBuildsCode() { - if (_entity.ConstructorToBuild is null) + if (_entity.ConstructorToBuild.IsPrivate) return ""; - var (parameters, properties) = GetParametersAndProperties(_entity.ConstructorToBuild); + var (parameters, properties) = GetParametersAndProperties(); var disableWarning = _builder.NullableStrategy == NullableStrategy.Enabled ? "#pragma warning disable CS8604\n" @@ -106,10 +106,10 @@ private string GenerateBuildManyCode() private string GenerateStaticBuildsCode() { - if (_entity.ConstructorToBuild is null) + if (_entity.ConstructorToBuild.IsPrivate) return ""; - var (parameters, properties) = GetParametersAndProperties(_entity.ConstructorToBuild); + var (parameters, properties) = GetParametersAndProperties(); var moqInit = parameters .Concat(properties) .Where(symbol => symbol.IsMockable()) @@ -145,14 +145,10 @@ private string GenerateImplicitCastCode() return $@" public static implicit operator {_entity.FullName}({_builder.FullName} builder) => builder.Build();"; } - private (IReadOnlyList Parameters, IReadOnlyList Properties) GetParametersAndProperties(EntityToBuild.Constructor constructorToBuild) + private (IReadOnlyList Parameters, IReadOnlyList Properties) GetParametersAndProperties() { - var parameters = constructorToBuild.Parameters; - var properties = _entity.SettableProperties.AsEnumerable(); - if (_entity.ConstructorToBuild is not null) - { - properties = properties.Where(x => !constructorToBuild.ContainsParameter(x.SymbolName)); - } + var parameters = _entity.ConstructorToBuild.Parameters; + var properties = _entity.SettableProperties.Where(x => !_entity.ConstructorToBuild.ContainsParameter(x.SymbolName)); return (parameters.ToList(), properties.ToList()); } @@ -194,7 +190,7 @@ private string GenerateBuildEntityString(IEnumerable parameters, I // ------------------------------------------------------------------------------ // // This code was generated by a source generator named Buildenator (https://github.com/pmrogala/Buildenator) -// Version 8.1.2 +// Version 8.1.3 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. diff --git a/Buildenator/Generators/PropertiesStringGenerator.cs b/Buildenator/Generators/PropertiesStringGenerator.cs index bc8bd71..28034aa 100644 --- a/Buildenator/Generators/PropertiesStringGenerator.cs +++ b/Buildenator/Generators/PropertiesStringGenerator.cs @@ -21,7 +21,7 @@ public string GeneratePropertiesCode() { var properties = _entity.GetAllUniqueSettablePropertiesAndParameters(); - if (_builder.ShouldGenerateMethodsForUnreachableProperties) + if (_builder.ShouldGenerateMethodsForUnreachableProperties || _entity.ConstructorToBuild.IsPrivate) { properties = properties.Concat(_entity.GetAllUniqueReadOnlyPropertiesWithoutConstructorsParametersMatch()).ToList(); } diff --git a/CHANGELOG.md b/CHANGELOG.md index 17c8d3d..b2b7d9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed ### Removed +## 6.1.3 & 8.1.3 - 2024-10-11 + +### Changed + +- Fixed regression for default constructors - the build method should appear again +- Added generation of methods for properties from private constructors, keeping the Build method removed. ## 6.1.2 & 8.1.2 - 2024-10-11 diff --git a/Tests/Buildenator.IntegrationTests.SharedEntities/EntityWithImplicitConstructor.cs b/Tests/Buildenator.IntegrationTests.SharedEntities/EntityWithImplicitConstructor.cs index 95c588c..1c1cc49 100644 --- a/Tests/Buildenator.IntegrationTests.SharedEntities/EntityWithImplicitConstructor.cs +++ b/Tests/Buildenator.IntegrationTests.SharedEntities/EntityWithImplicitConstructor.cs @@ -4,5 +4,5 @@ public class EntityWithImplicitConstructor { public int PropertyIntGetter { get; } = 1; - public string PropertyGetter { get; } = "1"; + public string PropertyGetter { get; set; } } \ No newline at end of file diff --git a/Tests/Buildenator.IntegrationTests.Source/Builders/EntityWithPrivateConstructorBuilder.cs b/Tests/Buildenator.IntegrationTests.Source/Builders/EntityWithPrivateConstructorBuilder.cs index c7223b3..c6bf057 100644 --- a/Tests/Buildenator.IntegrationTests.Source/Builders/EntityWithPrivateConstructorBuilder.cs +++ b/Tests/Buildenator.IntegrationTests.Source/Builders/EntityWithPrivateConstructorBuilder.cs @@ -1,5 +1,6 @@ using Buildenator.Abstraction; using Buildenator.IntegrationTests.SharedEntities; +using System; namespace Buildenator.IntegrationTests.Source.Builders; @@ -8,6 +9,6 @@ public partial class EntityWithPrivateConstructorBuilder { public EntityWithPrivateConstructor Build() { - return default!; + throw new InvalidOperationException("It is a test!"); } } diff --git a/Tests/Buildenator.IntegrationTests/BuildersGeneratorTests.cs b/Tests/Buildenator.IntegrationTests/BuildersGeneratorTests.cs index 325ba76..d390c28 100644 --- a/Tests/Buildenator.IntegrationTests/BuildersGeneratorTests.cs +++ b/Tests/Buildenator.IntegrationTests/BuildersGeneratorTests.cs @@ -3,8 +3,6 @@ using Buildenator.IntegrationTests.Source.Builders; using Buildenator.IntegrationTests.SharedEntities.DifferentNamespace; using FluentAssertions; -using System.Collections.Generic; -using System.Linq; using Xunit; using PostBuildEntityBuilder = Buildenator.IntegrationTests.Source.Builders.PostBuildEntityBuilder; @@ -274,4 +272,27 @@ public void BuildersGenerator_ReadOnlyProperty_ShouldCreateMethodForSettingItsVa _ = builder.WithPrivateField(privateField); _ = builder.Build().PrivateField.Should().BeEquivalentTo(privateField); } + + [Fact] + public void BuildersGenerator_DefaultPublicConstructor_ShouldCreateBuildMethod() + { + _ = new EntityWithDefaultConstructorBuilder().Build().Should().NotBeNull(); + } + + + [Fact] + public void BuildersGenerator_PrivateConstructor_ShouldNotGenerateBuildMethod() + { + var lamda = () => new EntityWithPrivateConstructorBuilder().Build(); + lamda.Should().ThrowExactly().Which.Message.Should().Be("It is a test!"); + } + + [Fact] + public void BuildersGenerator_PrivateConstructor_ShouldCreateWithProperties() + { + var builder = new EntityWithPrivateConstructorBuilder(); + _ = builder.WithPropertyIntGetter(1); + _ = builder.WithPropertyGetter("1"); + _ = builder.WithEntityInDifferentNamespace(null!); + } } \ No newline at end of file diff --git a/Tests/Buildenator.UnitTests/Generators/PropertiesStringGeneratorTests.cs b/Tests/Buildenator.UnitTests/Generators/PropertiesStringGeneratorTests.cs index 22cc35d..c2fbb43 100644 --- a/Tests/Buildenator.UnitTests/Generators/PropertiesStringGeneratorTests.cs +++ b/Tests/Buildenator.UnitTests/Generators/PropertiesStringGeneratorTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using AutoFixture; using AutoFixture.AutoMoq; +using AutoFixture.Xunit2; using Buildenator.CodeAnalysis; using Buildenator.Configuration; using Buildenator.Configuration.Contract; @@ -99,6 +100,7 @@ public void GeneratePropertiesCode_ShouldNotGenerateCodeForAlreadyDeclaredFields _ = result.Should().NotContain(existingMethod); } + [Fact] public void GeneratePropertiesCode_ShouldGenerateMethodDefinitionForMockableTypedSymbols() {