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
@@ -0,0 +1,110 @@
namespace NetEvolve.CodeBuilder.Tests.Integration;

using System;

public partial class CSharpCodeBuilderTests
{
[Test]
public async Task GenerateFromMixedCharSources_Should_ProduceCorrectOutput()
{
var arrayFragment = "field one".ToCharArray();
var memoryFragment = "field two".AsMemory();
var spanFragment = "field three".AsSpan();

var builder = new CSharpCodeBuilder()
.AppendLine("public class MixedSourceFragment")
.Append("{")
.Append("public string A => \"")
.Append(arrayFragment)
.AppendLine("\";")
.Append("public string B => \"")
.Append(arrayFragment, 0, 5)
.AppendLine("\";")
.Append("public string C => \"")
.Append(memoryFragment)
.AppendLine("\";")
.Append("public string D => \"")
.Append(memoryFragment, 0, 5)
.AppendLine("\";")
.Append("public string E => \"")
.Append(spanFragment)
.AppendLine("\";")
.Append("public string F => \"")
.Append(spanFragment, 0, 5)
.AppendLine("\";")
.Append("public string G => \"")
.Append("full string field", 0, 4)
.AppendLine("\";")
.Append("}");

var result = builder.ToString();

_ = await Verify(result).ConfigureAwait(false);
}

[Test]
#pragma warning disable S6640 // Unsafe code is intentional to exercise the pointer-based overloads
public unsafe Task GenerateFromPointerSource_Should_ProduceCorrectOutput()
{
var text = "pointer field";

string result;
fixed (char* pointer = text)
{
var builder = new CSharpCodeBuilder()
.AppendLine("public class PointerSourceFragment")
.Append("{")
.Append("public string Value => \"")
.Append(pointer, text.Length)
.AppendLine("\";")
.Append("}");

result = builder.ToString();
}

return Verify(result);
}
#pragma warning restore S6640

[Test]
public async Task GenerateWithEmptyAndNullSources_Should_BeIgnored()
{
var builder = new CSharpCodeBuilder()
.Append(default(char[]))
.Append(Array.Empty<char>())
.Append(default(char[]), 0, 0)
.Append(ReadOnlyMemory<char>.Empty)
.Append(ReadOnlySpan<char>.Empty)
.Append(default(string))
.Append(string.Empty)
.Append("\0");

_ = await Assert.That(builder.ToString()).IsEqualTo(string.Empty);
}

[Test]
public async Task GenerateNestedBracesFromRawCharsAndStrings_Should_ProduceCorrectOutput()
{
// Exercises the special handling of '{', '}', '[', ']', '\n', '\r' for both the
// char and string overloads of Append, as used when a generator emits raw braces
// received from an external template rather than literal source code.
var builder = new CSharpCodeBuilder()
.AppendLine("public class RawBraceFragment")
.Append('{')
.Append("public int[] Values")
.Append('[')
.Append("public int Value")
.Append('\n')
.Append(']')
.Append('\r')
.Append("[")
.Append("public int Other")
.AppendLine("\r\n")
.Append("]")
.Append('}');

var result = builder.ToString();

_ = await Verify(result).ConfigureAwait(false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
namespace NetEvolve.CodeBuilder.Tests.Integration;

public partial class CSharpCodeBuilderTests
{
[Test]
public async Task GenerateConfigurableClass_AllFlagsEnabled_Should_ProduceCorrectOutput()
{
var result = BuildConfigurableClass(enabled: true);

_ = await Verify(result).ConfigureAwait(false);
}

[Test]
public async Task GenerateConfigurableClass_AllFlagsDisabled_Should_ProduceCorrectOutput()
{
var result = BuildConfigurableClass(enabled: false);

_ = await Verify(result).ConfigureAwait(false);
}

// Exercises every AppendIf/AppendLineIf overload for both the "condition true" and
// "condition false" branches, mirroring how a real source generator toggles optional
// code fragments (feature flags, generated attributes, diagnostics, ...).
private static string BuildConfigurableClass(bool enabled)
{
var repeatChar = '-';
var charArray = "// array-comment".ToCharArray();
var memory = "// memory-comment".AsMemory();
var span = "// span-comment".AsSpan();

var builder = new CSharpCodeBuilder()
.AppendLineIf(enabled, "// generated diagnostics header")
.AppendLineIf(enabled)
.AppendLine("public class ConfigurableService")
.Append("{")
.AppendIf(enabled, repeatChar, 20)
.AppendLineIf(enabled)
.AppendIf(enabled, "public bool IsEnabled")
.AppendLineIf(enabled, " => true;")
.AppendLineIf(!enabled, "public bool IsEnabled => false;")
.AppendIf(enabled, true)
.AppendLineIf(enabled)
.AppendIf(!enabled, false)
.AppendLineIf(!enabled)
.AppendLineIf(enabled, charArray)
.AppendLineIf(enabled, charArray, 3, 5)
.AppendLineIf(enabled, memory)
.AppendLineIf(enabled, memory, 3, 6)
.AppendLineIf(enabled, span)
.AppendLineIf(enabled, span, 3, 6)
.AppendIf(enabled, memory)
.AppendIf(enabled, memory, 3, 6)
.AppendIf(enabled, span)
.AppendIf(enabled, span, 3, 6)
.AppendIf(enabled, charArray)
.AppendIf(enabled, charArray, 3, 5)
.AppendIf(enabled, 'x')
.AppendLineIf(enabled, 'y')
.AppendLineIf(enabled, 'z', 3)
.AppendIf(enabled, "trailing-marker", 0, 8)
.AppendLineIf(enabled, "trailing-marker-line", 0, 13)
.Append("}");

return builder.ToString();
}

[Test]
#pragma warning disable S6640 // Unsafe code is intentional to exercise the pointer-based overloads
public unsafe Task GenerateConfigurableClass_WithPointerFragments_Should_ProduceCorrectOutput()
{
var text = "// pointer-comment";

string result;
fixed (char* pointer = text)
{
var builder = new CSharpCodeBuilder()
.AppendLine("public class PointerBackedFragment")
.Append("{")
.AppendIf(true, pointer, text.Length)
.AppendLineIf(true)
.AppendIf(false, pointer, text.Length)
.AppendLineIf(true, pointer, text.Length)
.AppendLineIf(false, pointer, text.Length)
.Append("}");

result = builder.ToString();
}

return Verify(result);
}
#pragma warning restore S6640
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
namespace NetEvolve.CodeBuilder.Tests.Integration;

using System;
using System.Collections.Generic;
using System.Threading;

public partial class CSharpCodeBuilderTests
{
[Test]
public async Task GenerateFullyDocumentedMethod_Should_ProduceCorrectOutput()
{
var builder = new CSharpCodeBuilder()
.AppendLine("namespace MyApplication.Documentation;")
.AppendLine()
.AppendXmlDoc("A raw single-line documentation remark placed above the type.")
.AppendXmlDocSummary(["Represents a fully documented service.", "Every XML doc tag is exercised."])
.AppendXmlDocTypeParams([("TRequest", "The request type."), ("TResponse", "The response type.")])
.AppendXmlDocRemarks(["This type demonstrates every supported XML documentation helper.", "Line two."])
.AppendXmlDocExample([
"var service = new DocumentedService<Request, Response>();",
"service.Process(request);",
])
.AppendXmlDocSeeAlso("DocumentedServiceBase")
.AppendXmlDocSeeAlso("https://example.invalid/docs", isHref: true)
.AppendLine("public sealed class DocumentedService<TRequest, TResponse>")
.Append("{")
.AppendXmlDocSummary("Processes the specified request.")
.AppendXmlDocParams([
("request", "The request to process."),
("cancellationToken", "A cancellation token."),
])
.AppendXmlDocReturns("The response produced for the request.")
.AppendXmlDocExceptions([
("ArgumentNullException", "Thrown when request is null."),
("OperationCanceledException", "Thrown when the operation is cancelled."),
])
.AppendXmlDocException<InvalidOperationException>("Thrown when the service has not been initialized.")
.AppendXmlDocSee("DocumentedServiceBase.Process")
.AppendXmlDocExample("var response = service.Process(request, default);")
.AppendLine("public TResponse Process(TRequest request, CancellationToken cancellationToken)")
.Append("{")
.AppendLine("throw new NotImplementedException();")
.Append("}")
.AppendLine()
.AppendXmlDocValue("Gets the number of processed requests.")
.AppendLine("public int ProcessedCount { get; }")
.AppendLine()
.AppendXmlDocInheritDoc()
.AppendLine("public override string? ToString() => base.ToString();")
.AppendLine()
.AppendXmlDocInheritDoc("DocumentedServiceBase.Dispose")
.AppendLine("public void Dispose() { }")
.AppendLine()
.AppendXmlDocCustomElement("note", "This is a custom documentation element.", "type=\"important\"")
.AppendXmlDocCustomElement("preliminary")
.Append("}");

var result = builder.ToString();

_ = await Verify(result).ConfigureAwait(false);
}

[Test]
public async Task GenerateDocumentation_WithEmptyOrNullInputs_Should_BeIgnored()
{
var builder = new CSharpCodeBuilder()
.AppendXmlDoc(null)
.AppendXmlDoc(string.Empty)
.AppendXmlDocSummary(default(string))
.AppendXmlDocSummary(default(IEnumerable<string>))
.AppendXmlDocSummary(Array.Empty<string>())
.AppendXmlDocParam(null, "description")
.AppendXmlDocParam("name", null)
.AppendXmlDocParams(null)
.AppendXmlDocReturns(null)
.AppendXmlDocRemarks(default(string))
.AppendXmlDocRemarks(default(IEnumerable<string>))
.AppendXmlDocException(null, "description")
.AppendXmlDocException("Type", null)
.AppendXmlDocExceptions(null)
.AppendXmlDocExample(default(string))
.AppendXmlDocExample(default(IEnumerable<string>))
.AppendXmlDocSee(null)
.AppendXmlDocSeeAlso(null)
.AppendXmlDocValue(null)
.AppendXmlDocTypeParam(null, "description")
.AppendXmlDocTypeParam("name", null)
.AppendXmlDocTypeParams(null)
.AppendXmlDocCustomElement(null);

_ = await Assert.That(builder.ToString()).IsEqualTo(string.Empty);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
namespace NetEvolve.CodeBuilder.Tests.Integration;

using System;
using System.Globalization;

public partial class CSharpCodeBuilderTests
{
[Test]
#pragma warning disable CA1305, MA0011 // Culture is intentionally omitted on some calls to exercise the convenience overloads
public async Task GenerateFormattedMembers_Should_ProduceCorrectOutput()
{
var index = 3;
var typeName = "decimal";
FormattableString formattable = $"public {typeName} FieldFormattable;";
FormattableString lineFormattable = $"public {typeName} LineFieldFormattable;";

var builder = new CSharpCodeBuilder()
.AppendLine("public class FormattedMembersFragment")
.Append("{")
.AppendFormat("public {0} Field0;", typeName)
.AppendLine()
.AppendFormat("public {0} Field{1};", typeName, index)
.AppendLine()
.AppendFormat(CultureInfo.InvariantCulture, "public {0} Field{1}_{2};", typeName, index, index + 1)
.AppendLine()
.AppendFormat(formattable)
.AppendLine()
.AppendLineFormat("public {0} LineField0;", typeName)
.AppendLineFormat("public {0} LineField{1};", typeName, index)
.AppendLineFormat(CultureInfo.InvariantCulture, "public {0} LineField{1}_{2};", typeName, index, index + 1)
.AppendLineFormat(lineFormattable)
.Append("}");

var result = builder.ToString();

_ = await Verify(result).ConfigureAwait(false);
}
#pragma warning restore CA1305, MA0011

[Test]
public async Task GenerateFormattedMembers_WithNullFormattable_Should_OnlyAppendLineTerminator()
{
var builder = new CSharpCodeBuilder()
.AppendFormat(default(FormattableString))
.AppendLine("marker")
.AppendLineFormat(default(FormattableString));

var result = builder.ToString();

_ = await Assert.That(result).Contains("marker");
}

[Test]
public async Task GenerateWithInterpolatedHandler_Should_ProduceCorrectOutput()
{
var className = "InterpolatedFragment";
var propertyName = "Total";
var value = 42;

var builder = new CSharpCodeBuilder();
_ = builder.AppendLineInterpolated($"public class {className}");
_ = builder.Append("{");
_ = builder.AppendInterpolated($"public int {propertyName} => {value, 6:D3};");
_ = builder.AppendLine();
_ = builder.AppendLineInterpolated($"public string Empty => \"{string.Empty}\";");
_ = builder.Append("}");

var result = builder.ToString();

_ = await Verify(result).ConfigureAwait(false);
}
}
Loading