Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src-examples/BuilderConsumer/ClassWithInitProperties.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using FluentBuilder;

namespace BuilderConsumer
{
[AutoGenerateBuilder]
public class ClassWithInitProperties
{
public string Normal { get; set; }

public int SiteId { get; init; }

public string ProductName { get; init; }

public string PrivateProductName { get; private init; }

public required string RequiredTest { get; set; }

public required string RequiredTestInit { get; init; }

public required ClassWithInitProperties2 X { get; init; }
}

public class ClassWithInitProperties2
{
public required string X { get; set; }
}
}
25 changes: 25 additions & 0 deletions src/FluentBuilderGenerator/Extensions/PropertySymbolExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using FluentBuilderGenerator.Interfaces;
using FluentBuilderGenerator.Helpers;
using FluentBuilderGenerator.Types;
using Microsoft.CodeAnalysis;

Expand All @@ -16,6 +17,30 @@ internal static class PropertySymbolExtensions
FluentTypeKind.IReadOnlyList
];

internal static IReadOnlyList<string> GetRequiredPropertiesAsAssignments(this IEnumerable<IPropertySymbol> properties)
{
var requiredValues = new List<string>();
foreach (var p in properties.Where(p => p.IsRequired))
{
var (defaultValue, _) = DefaultValueHelper.GetDefaultValue(p, p.Type);
requiredValues.Add($"{p.Name} = {defaultValue}");
}

return requiredValues;
}

internal static IReadOnlyList<string> GetRequiredPropertiesAsAssignments(this IEnumerable<IPropertyOrParameterSymbol> properties)
{
var requiredValues = new List<string>();
foreach (var p in properties.Where(p => p.Required))
{
var (defaultValue, _) = DefaultValueHelper.GetDefaultValue(p.Symbol, p.Type);
requiredValues.Add($"{p.Name} = {defaultValue}");
}

return requiredValues;
}

internal static bool IsInitOnly(this IPropertySymbol property)
{
return property.SetMethod is { IsInitOnly: true };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ private string CreateClassBuilderCode(FluentData fluentData, ClassSymbol classSy
throw new NotSupportedException($"Unable to generate a FluentBuilder for the class '{classSymbol.NamedTypeSymbol}' because no public constructor is defined.");
}

var constructorCode = GenerateUsingConstructorCode(classSymbol, publicConstructors);
var constructorCode = GenerateUsingConstructorCode(fluentData, classSymbol, publicConstructors);

var propertiesCode = GenerateWithPropertyCode(fluentData, classSymbol, allClassSymbols);

Expand Down Expand Up @@ -129,14 +129,16 @@ public static partial class {classSymbol.BuilderClassName.ToSafeClassName()}Exte
}

private static (StringBuilder StringBuilder, IReadOnlyList<string> ExtraUsings) GenerateUsingConstructorCode(
FluentData fluentData,
ClassSymbol classSymbol,
IReadOnlyList<IMethodSymbol> publicConstructors
)
{
var builderClassName = classSymbol.BuilderClassName;

var extraUsings = new List<string>();

var builderClassName = classSymbol.BuilderClassName;
var (_, propertiesPublicSettable, _) = GetProperties(classSymbol, fluentData.HandleBaseClasses, fluentData.Accessibility);

var sb = new StringBuilder();
foreach (var publicConstructor in publicConstructors)
{
Expand All @@ -159,7 +161,13 @@ IReadOnlyList<IMethodSymbol> publicConstructors
defaultValues.Add(defaultValue);
}

sb.AppendLine(8, $"private Lazy<{classSymbol.NamedTypeSymbol}> _Constructor{constructorHashCode} = new Lazy<{classSymbol.NamedTypeSymbol}>(() => new {classSymbol.NamedTypeSymbol}({string.Join(", ", defaultValues)}));");
var requiredProperties = propertiesPublicSettable.GetRequiredPropertiesAsAssignments();

sb.AppendLine(8, $"private Lazy<{classSymbol.NamedTypeSymbol}> _Constructor{constructorHashCode} = new Lazy<{classSymbol.NamedTypeSymbol}>(() => new {classSymbol.NamedTypeSymbol}({string.Join(",", defaultValues)})");
sb.AppendLine(8, "{");
sb.AppendLines(12, requiredProperties, ",");
sb.AppendLine(8, "});");


sb.AppendLine(8, $"public {builderClassName} UsingConstructor({constructorParametersAsString})");
sb.AppendLine(8, @"{");
Expand All @@ -170,7 +178,10 @@ IReadOnlyList<IMethodSymbol> publicConstructors
sb.AppendLine(8, $" return new {classSymbol.NamedTypeSymbol}");
sb.AppendLine(8, @" (");
sb.AppendLines(20, constructorParameters.Select(x => x.Symbol.Name), ", ");
sb.AppendLine(8, @" );");
sb.AppendLine(8, @" )");
sb.AppendLine(8, @" {");
sb.AppendLines(20, requiredProperties, ",");
sb.AppendLine(8, @" };");

sb.AppendLine(8, @" });");

Expand Down Expand Up @@ -397,7 +408,7 @@ private static (bool IsPrimaryConstructor, IReadOnlyList<IPropertyOrParameterSym
{
isPrimaryConstructor = true;

propertiesPublicSettable.AddRange(publicConstructors[0].Parameters.Select(p => new PropertyOrParameterSymbol(p, p.Type, true)));
propertiesPublicSettable.AddRange(publicConstructors[0].Parameters.Select(p => new PropertyOrParameterSymbol(p, p.Type, true, false)));
}

var properties = classSymbol.NamedTypeSymbol.GetMembers().OfType<IPropertySymbol>()
Expand All @@ -423,7 +434,7 @@ private static (bool IsPrimaryConstructor, IReadOnlyList<IPropertyOrParameterSym
}
}

foreach (var property in properties.Where(p => p.IsPublicSettable()).Select(p => new PropertyOrParameterSymbol(p, p.Type, p.IsInitOnly())))
foreach (var property in properties.Where(p => p.IsPublicSettable()).Select(p => new PropertyOrParameterSymbol(p, p.Type, p.IsInitOnly(), p.IsRequired)))
{
if (propertiesPublicSettable.All(p => p.Name != property.Name))
{
Expand All @@ -432,7 +443,7 @@ private static (bool IsPrimaryConstructor, IReadOnlyList<IPropertyOrParameterSym
}

var propertiesPrivateSettable = accessibility != FluentBuilderAccessibility.PublicAndPrivate ? [] :
properties.Where(p => p.IsPrivateSettable()).Select(p => new PropertyOrParameterSymbol(p, p.Type, p.IsInitOnly())).ToArray();
properties.Where(p => p.IsPrivateSettable()).Select(p => new PropertyOrParameterSymbol(p, p.Type, p.IsInitOnly(), p.IsRequired)).ToArray();

return (isPrimaryConstructor, propertiesPublicSettable, propertiesPrivateSettable);
}
Expand Down Expand Up @@ -541,7 +552,11 @@ private static string GenerateSeveralMethods(FluentData fluentData, ClassSymbol
var (defaultValue, _) = DefaultValueHelper.GetDefaultValue(p.Symbol, p.Symbol.Type);
defaultValues.Add(defaultValue);
}
output.AppendLine(8, $"public static {className} Default() => new {className}({string.Join(", ", defaultValues)});");
output.AppendLine(8, $"public static {className} Default() => new {className}({string.Join(", ", defaultValues)})");
output.AppendLine(8, "{");
var requiredProperties = propertiesPublicSettable.GetRequiredPropertiesAsAssignments();
output.AppendLines(12, requiredProperties, ",");
output.AppendLine(8, "};");

return output.ToString();
}
Expand Down
8 changes: 7 additions & 1 deletion src/FluentBuilderGenerator/Helpers/DefaultValueHelper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using FluentBuilderGenerator.Extensions;
using FluentBuilderGenerator.Models;
using FluentBuilderGenerator.Types;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
Expand Down Expand Up @@ -134,6 +135,11 @@ private static string GetNewConstructor(ITypeSymbol typeSymbol)

var constructorParameters = bestMatchingConstructor.Parameters.Select(parameter => GetDefault(parameter.Type));

return $"new {typeSymbol}({string.Join(", ", constructorParameters)})";
var publicRequiredSetProperties = typeSymbol.GetMembers().OfType<IPropertySymbol>()
.Where(p => p.IsRequired)
.Where(x => x.SetMethod is not null)
.Where(x => x.CanBeReferencedByName);

return $"new {typeSymbol}({string.Join(", ", constructorParameters)}) {{ {string.Join(", ", publicRequiredSetProperties.GetRequiredPropertiesAsAssignments())} }}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ internal interface IPropertyOrParameterSymbol
ITypeSymbol Type { get; }

bool ExcludeFromIsSetLogic { get; }

bool Required { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace FluentBuilderGenerator.Models;

internal record PropertyOrParameterSymbol(ISymbol Symbol, ITypeSymbol Type, bool ExcludeFromIsSetLogic) : IPropertyOrParameterSymbol
internal record PropertyOrParameterSymbol(ISymbol Symbol, ITypeSymbol Type, bool ExcludeFromIsSetLogic, bool Required) : IPropertyOrParameterSymbol
{
public PropertyType PropertyType => Symbol is IPropertySymbol ? PropertyType.Property : PropertyType.Parameter;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,21 @@ public ClassOnOtherNamespaceBuilder WithId(Func<int> func)
}

private bool _Constructor1204632294_IsSet;
private Lazy<AbcTest.OtherNamespace.ClassOnOtherNamespace> _Constructor1204632294 = new Lazy<AbcTest.OtherNamespace.ClassOnOtherNamespace>(() => new AbcTest.OtherNamespace.ClassOnOtherNamespace());
private Lazy<AbcTest.OtherNamespace.ClassOnOtherNamespace> _Constructor1204632294 = new Lazy<AbcTest.OtherNamespace.ClassOnOtherNamespace>(() => new AbcTest.OtherNamespace.ClassOnOtherNamespace()
{

});
public ClassOnOtherNamespaceBuilder UsingConstructor()
{
_Constructor1204632294 = new Lazy<AbcTest.OtherNamespace.ClassOnOtherNamespace>(() =>
{
return new AbcTest.OtherNamespace.ClassOnOtherNamespace
(

);
)
{

};
});
_Constructor1204632294_IsSet = true;

Expand Down Expand Up @@ -95,7 +101,10 @@ public override ClassOnOtherNamespace Build(bool useObjectInitializer)
return Instance.Value;
}

public static ClassOnOtherNamespace Default() => new ClassOnOtherNamespace();
public static ClassOnOtherNamespace Default() => new ClassOnOtherNamespace()
{

};

}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public AddressBuilder WithArray2(Action<FluentBuilderGeneratorTests.DTO.ArrayAdd
return builder.Build(useObjectInitializer);
});
private bool _thingUsingConstructorWithItselfIsSet;
private Lazy<FluentBuilderGeneratorTests.DTO.ThingUsingConstructorWithItself> _thingUsingConstructorWithItself = new Lazy<FluentBuilderGeneratorTests.DTO.ThingUsingConstructorWithItself>(() => new FluentBuilderGeneratorTests.DTO.ThingUsingConstructorWithItself(string.Empty, string.Empty));
private Lazy<FluentBuilderGeneratorTests.DTO.ThingUsingConstructorWithItself> _thingUsingConstructorWithItself = new Lazy<FluentBuilderGeneratorTests.DTO.ThingUsingConstructorWithItself>(() => new FluentBuilderGeneratorTests.DTO.ThingUsingConstructorWithItself(string.Empty, string.Empty) { });
public AddressBuilder WithThingUsingConstructorWithItself(FluentBuilderGeneratorTests.DTO.ThingUsingConstructorWithItself value) => WithThingUsingConstructorWithItself(() => value);
public AddressBuilder WithThingUsingConstructorWithItself(Func<FluentBuilderGeneratorTests.DTO.ThingUsingConstructorWithItself> func)
{
Expand All @@ -85,7 +85,7 @@ public AddressBuilder WithThingUsingConstructorWithItself(Func<FluentBuilderGene
return this;
}
private bool _thingUsingConstructorWith2ParametersIsSet;
private Lazy<FluentBuilderGeneratorTests.DTO.ThingUsingConstructorWith2Parameters> _thingUsingConstructorWith2Parameters = new Lazy<FluentBuilderGeneratorTests.DTO.ThingUsingConstructorWith2Parameters>(() => new FluentBuilderGeneratorTests.DTO.ThingUsingConstructorWith2Parameters(default(int), default(int)));
private Lazy<FluentBuilderGeneratorTests.DTO.ThingUsingConstructorWith2Parameters> _thingUsingConstructorWith2Parameters = new Lazy<FluentBuilderGeneratorTests.DTO.ThingUsingConstructorWith2Parameters>(() => new FluentBuilderGeneratorTests.DTO.ThingUsingConstructorWith2Parameters(default(int), default(int)) { });
public AddressBuilder WithThingUsingConstructorWith2Parameters(FluentBuilderGeneratorTests.DTO.ThingUsingConstructorWith2Parameters value) => WithThingUsingConstructorWith2Parameters(() => value);
public AddressBuilder WithThingUsingConstructorWith2Parameters(Func<FluentBuilderGeneratorTests.DTO.ThingUsingConstructorWith2Parameters> func)
{
Expand All @@ -94,7 +94,7 @@ public AddressBuilder WithThingUsingConstructorWith2Parameters(Func<FluentBuilde
return this;
}
private bool _thingWithoutDefaultConstructorIsSet;
private Lazy<FluentBuilderGeneratorTests.DTO.ThingWithoutDefaultConstructor> _thingWithoutDefaultConstructor = new Lazy<FluentBuilderGeneratorTests.DTO.ThingWithoutDefaultConstructor>(() => new FluentBuilderGeneratorTests.DTO.ThingWithoutDefaultConstructor(default(int)));
private Lazy<FluentBuilderGeneratorTests.DTO.ThingWithoutDefaultConstructor> _thingWithoutDefaultConstructor = new Lazy<FluentBuilderGeneratorTests.DTO.ThingWithoutDefaultConstructor>(() => new FluentBuilderGeneratorTests.DTO.ThingWithoutDefaultConstructor(default(int)) { });
public AddressBuilder WithThingWithoutDefaultConstructor(FluentBuilderGeneratorTests.DTO.ThingWithoutDefaultConstructor value) => WithThingWithoutDefaultConstructor(() => value);
public AddressBuilder WithThingWithoutDefaultConstructor(Func<FluentBuilderGeneratorTests.DTO.ThingWithoutDefaultConstructor> func)
{
Expand All @@ -112,7 +112,7 @@ public AddressBuilder WithThingWithPrivateConstructor(Func<FluentBuilderGenerato
return this;
}
private bool _thingIsSet;
private Lazy<FluentBuilderGeneratorTests.DTO.Thing> _thing = new Lazy<FluentBuilderGeneratorTests.DTO.Thing>(() => new FluentBuilderGeneratorTests.DTO.Thing());
private Lazy<FluentBuilderGeneratorTests.DTO.Thing> _thing = new Lazy<FluentBuilderGeneratorTests.DTO.Thing>(() => new FluentBuilderGeneratorTests.DTO.Thing() { });
public AddressBuilder WithThing(FluentBuilderGeneratorTests.DTO.Thing value) => WithThing(() => value);
public AddressBuilder WithThing(Func<FluentBuilderGeneratorTests.DTO.Thing> func)
{
Expand Down Expand Up @@ -344,15 +344,21 @@ public AddressBuilder WithDictionary2(Action<FluentBuilderGeneratorTests.FluentB
});

private bool _Constructor_1362952513_IsSet;
private Lazy<FluentBuilderGeneratorTests.DTO.Address> _Constructor_1362952513 = new Lazy<FluentBuilderGeneratorTests.DTO.Address>(() => new FluentBuilderGeneratorTests.DTO.Address());
private Lazy<FluentBuilderGeneratorTests.DTO.Address> _Constructor_1362952513 = new Lazy<FluentBuilderGeneratorTests.DTO.Address>(() => new FluentBuilderGeneratorTests.DTO.Address()
{

});
public AddressBuilder UsingConstructor()
{
_Constructor_1362952513 = new Lazy<FluentBuilderGeneratorTests.DTO.Address>(() =>
{
return new FluentBuilderGeneratorTests.DTO.Address
(

);
)
{

};
});
_Constructor_1362952513_IsSet = true;

Expand Down Expand Up @@ -449,7 +455,10 @@ public override Address Build(bool useObjectInitializer)
return Instance.Value;
}

public static Address Default() => new Address();
public static Address Default() => new Address()
{

};

}
}
Expand Down
11 changes: 11 additions & 0 deletions tests/FluentBuilderGeneratorTests/DTO/ClassWithInitProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,16 @@ public class ClassWithInitProperties
public string ProductName { get; init; }

public string PrivateProductName { get; private init; }

public required string RequiredTest { get; set; }

public required string RequiredTestInit { get; init; }

public required ClassWithInitProperties2 X { get; init; }
}

public class ClassWithInitProperties2
{
public required string X { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,21 @@ public AddressBuilder<T> WithStreet(Func<T> func)
}

private bool _Constructor478882805_IsSet;
private Lazy<FluentBuilderGeneratorTests.DTO.Address<T>> _Constructor478882805 = new Lazy<FluentBuilderGeneratorTests.DTO.Address<T>>(() => new FluentBuilderGeneratorTests.DTO.Address<T>());
private Lazy<FluentBuilderGeneratorTests.DTO.Address<T>> _Constructor478882805 = new Lazy<FluentBuilderGeneratorTests.DTO.Address<T>>(() => new FluentBuilderGeneratorTests.DTO.Address<T>()
{

});
public AddressBuilder<T> UsingConstructor()
{
_Constructor478882805 = new Lazy<FluentBuilderGeneratorTests.DTO.Address<T>>(() =>
{
return new FluentBuilderGeneratorTests.DTO.Address<T>
(

);
)
{

};
});
_Constructor478882805_IsSet = true;

Expand Down Expand Up @@ -95,7 +101,10 @@ public override Address<T> Build(bool useObjectInitializer)
return Instance.Value;
}

public static Address<T> Default() => new Address<T>();
public static Address<T> Default() => new Address<T>()
{

};

}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,21 @@ public AddressTTBuilder<T1,T2> WithTestValue2(Func<T2?> func)
}

private bool _Constructor_758958168_IsSet;
private Lazy<FluentBuilderGeneratorTests.DTO.AddressTT<T1, T2>> _Constructor_758958168 = new Lazy<FluentBuilderGeneratorTests.DTO.AddressTT<T1, T2>>(() => new FluentBuilderGeneratorTests.DTO.AddressTT<T1, T2>());
private Lazy<FluentBuilderGeneratorTests.DTO.AddressTT<T1, T2>> _Constructor_758958168 = new Lazy<FluentBuilderGeneratorTests.DTO.AddressTT<T1, T2>>(() => new FluentBuilderGeneratorTests.DTO.AddressTT<T1, T2>()
{

});
public AddressTTBuilder<T1,T2> UsingConstructor()
{
_Constructor_758958168 = new Lazy<FluentBuilderGeneratorTests.DTO.AddressTT<T1, T2>>(() =>
{
return new FluentBuilderGeneratorTests.DTO.AddressTT<T1, T2>
(

);
)
{

};
});
_Constructor_758958168_IsSet = true;

Expand Down Expand Up @@ -106,7 +112,10 @@ public override AddressTT<T1, T2> Build(bool useObjectInitializer)
return Instance.Value;
}

public static AddressTT<T1, T2> Default() => new AddressTT<T1, T2>();
public static AddressTT<T1, T2> Default() => new AddressTT<T1, T2>()
{

};

}
}
Expand Down
Loading
Loading