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
Original file line number Diff line number Diff line change
Expand Up @@ -436,10 +436,17 @@ private static (bool IsPrimaryConstructor, IReadOnlyList<IPropertyOrParameterSym

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))
var existingIndex = propertiesPublicSettable.FindIndex(p => string.Equals(p.Name, property.Name, StringComparison.OrdinalIgnoreCase));
if (existingIndex < 0)
{
propertiesPublicSettable.Add(property);
}
else if (propertiesPublicSettable[existingIndex].PropertyType == PropertyType.Parameter)
{
// Replace the constructor parameter entry with the property entry so that
// _isSet tracking and property-assignment logic are correctly generated.
propertiesPublicSettable[existingIndex] = property;
}
}

var propertiesPrivateSettable = accessibility != FluentBuilderAccessibility.PublicAndPrivate ? [] :
Expand Down Expand Up @@ -517,8 +524,7 @@ private static string GenerateSeveralMethods(FluentData fluentData, ClassSymbol

if (isPrimaryConstructor)
{
var parameters = propertiesPublicSettable
.Where(p => p.PropertyType == PropertyType.Parameter)
var parameters = publicConstructors[0].Parameters
.Select(p => $"_{p.Name.ToCamelCase()}.Value");

output.AppendLine(20, $"else {{ instance = new {className}({string.Join(", ", parameters)}); }}");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace FluentBuilderGeneratorTests.DTO;

public enum StageType
{
Build,
Test
}

public class ClassWithConstructorParamMatchingProperty
{
public ClassWithConstructorParamMatchingProperty(StageType type)
{
Type = type;
}

public StageType Type { get; set; }

public string Name { get; set; } = string.Empty;
}
Original file line number Diff line number Diff line change
Expand Up @@ -907,4 +907,42 @@ public void GenerateFiles_ForRecordWithPrimaryConstructor_Should_GenerateCorrect

data.Should().BeEquivalentTo(new { Normal = "normal", Data = "t50" });
}

[Fact]
public void GenerateFiles_ForClassWithConstructorParamMatchingProperty_ShouldNotDuplicate()
{
// Arrange (Issue #76: constructor parameter name matching a property name should not create duplicate members)
var path = "./DTO/ClassWithConstructorParamMatchingProperty.cs";
var sourceFile = new SourceFile
{
Path = path,
Text = File.ReadAllText(path),
AttributeToAddToClass = "FluentBuilder.AutoGenerateBuilder"
};

// Act
var result = _sut.Execute(Namespace, [sourceFile]);

// Assert
result.Valid.Should().BeTrue();
result.Files.Should().HaveCount(NumFiles);
result.Files.Should().NotContain(r => r.Path.EndsWith("Error.g.cs"));

var fileResult = result.Files[NumFiles - 1];
var filename = Path.GetFileName(fileResult.Path);

if (Write) File.WriteAllText($"../../../DTO/{filename}", fileResult.Text);

// Verify the generated code does not contain duplicate '_type' fields
var generatedCode = fileResult.Text;
var typeFieldCount = System.Text.RegularExpressions.Regex.Matches(generatedCode, @"private Lazy<.*> _type\b").Count;
typeFieldCount.Should().Be(1, "the '_type' field should only be declared once");

var withTypeMethodCount = System.Text.RegularExpressions.Regex.Matches(generatedCode, @"public .* WithType\(").Count;
withTypeMethodCount.Should().Be(2, "WithType should only have two overloads (value and Func)");

// Verify constructor/property wiring in generated code.
generatedCode.Should().Contain("else { instance = new ClassWithConstructorParamMatchingProperty(_type.Value); }");
generatedCode.Should().Contain("if (_typeIsSet) { Instance.Value.Type = _type.Value; }");
}
}
Loading