diff --git a/.editorconfig b/.editorconfig index 5339d29..8738748 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,6 +11,7 @@ indent_style = space trim_trailing_whitespace = true charset = utf-8 end_of_line = lf +max_line_length = 120 # Verify settings # https://github.com/VerifyTests/Verify?tab=readme-ov-file#text-file-settings @@ -24,7 +25,7 @@ tab_width = unset trim_trailing_whitespace = false # Code files -[*.{cs,csx,vb,vbx}] +[*.{cs,csx,vb,vbx,razor,razor.cs}] indent_size = 4 insert_final_newline = true charset = utf-8-bom @@ -34,6 +35,7 @@ charset = utf-8-bom # See https://github.com/dotnet/aspnetcore/pull/23502 and https://github.com/dotnet/aspnetcore/issues/22753 [*.{razor,cshtml}] charset = utf-8-bom +indent_size = 4 # Generated code [*{_AssemblyInfo.cs,.notsupported.cs,.generated.cs}] @@ -121,7 +123,7 @@ dotnet_style_null_propagation = true : # Modifier preferences # See https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#normalize-modifiers -dotnet_style_require_accessibility_modifiers = always : error +dotnet_style_require_accessibility_modifiers = for_non_interface_members : error dotnet_style_readonly_field = true : warning # Required Styles @@ -276,3 +278,7 @@ dotnet_diagnostic.SA1502.severity = none dotnet_diagnostic.SA1504.severity = none dotnet_diagnostic.SA1515.severity = none dotnet_diagnostic.SA1516.severity = none + +# Support for NetEvolve.Arguments Methods +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1062#null-check-validation-methods +dotnet_code_quality.CA1062.null_check_validation_methods = M:NetEvolve.Arguments.Argument.ThrowIfNull(System.Object,System.String)|M:NetEvolve.Arguments.Argument.ThrowIfNull(System.Void*,System.String)|M:NetEvolve.Arguments.Argument.ThrowIfNullOrEmpty(System.String,System.String)|M:NetEvolve.Arguments.Argument.ThrowIfNullOrEmpty``1(System.Collections.Generic.IEnumerable{``0},System.String)|M:NetEvolve.Arguments.Argument.ThrowIfNullOrWhiteSpace(System.String,System.String) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 7266720..585b7b1 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -18,15 +18,18 @@ on: - detailed - diagnostic +permissions: + actions: read + contents: read + pull-requests: write + security-events: write + jobs: all: if: github.run_id != 1 - name: Build & Tests - uses: dailydevops/pipelines/.github/workflows/cicd-dotnet.yml@0.14.110 + uses: dailydevops/pipelines/.github/workflows/build-dotnet-single.yml@1.0.8 with: - disablePublish: true - dotnet-logging: ${{ inputs.dotnet-logging }} - dotnet-version: ${{ vars.NE_DOTNET_TARGETFRAMEWORKS }} - enableSonarQube: ${{ vars.NE_DOTNET_SONAR }} - solution: ###SOLUTION### + dotnetVersion: ${{ vars.NE_DOTNET_TARGETFRAMEWORKS }} + dotnetQuality: ${{ vars.NE_DOTNET_QUALITY }} + solution: ./HealthChecks.slnx secrets: inherit diff --git a/CodeBuilder.sln b/CodeBuilder.sln deleted file mode 100644 index f46e582..0000000 --- a/CodeBuilder.sln +++ /dev/null @@ -1,39 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{089100B1-113F-4E66-888A-E83F3999EAFD}" - ProjectSection(SolutionItems) = preProject - .commitlintrc = .commitlintrc - .editorconfig = .editorconfig - .filenesting.json = .filenesting.json - .gitattributes = .gitattributes - .gitignore = .gitignore - Directory.Build.props = Directory.Build.props - Directory.Packages.props = Directory.Packages.props - Directory.Solution.props = Directory.Solution.props - GitVersion.yml = GitVersion.yml - LICENSE = LICENSE - logo.png = logo.png - nuget.config = nuget.config - README.md = README.md - testEnvironments.json = testEnvironments.json - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {03ABAC60-42A8-4CED-A77B-6D7CA2680D0C} - EndGlobalSection -EndGlobal diff --git a/CodeBuilder.slnx b/CodeBuilder.slnx new file mode 100644 index 0000000..14a4a5d --- /dev/null +++ b/CodeBuilder.slnx @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Directory.Build.props b/Directory.Build.props index bb10fb7..3c5fd1e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,18 +1,19 @@ - $(MSBuildProjectName) - - - + Provides a high-performance, memory-efficient builder for creating C# code. + https://github.com/dailydevops/codebuilder.git + https://github.com/dailydevops/codebuilder $(RepositoryUrl)/releases - + $(PackageTags);codegeneration;sourcegenrator 2024 + <_DefaultTargetFrameworks>net8.0;net9.0;net10.0 + <_ProjectTargetFrameworks>netstandard2.0;netstandard2.1;$(_DefaultTargetFrameworks) + <_TestTargetFrameworks>net6.0;net7.0;$(_DefaultTargetFrameworks) + false - - net8.0 + net9.0 - diff --git a/Directory.Packages.props b/Directory.Packages.props index feb65be..bd93ac1 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,19 +1,21 @@ - true true - - - - + + + - - - + + + + + + + + - diff --git a/README.md b/README.md index 0e844c8..3742067 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,358 @@ # template-dotnet -.NET template for repositories +.NET template for repositories# NetEvolve.CodeBuilder + +[![NuGet](https://img.shields.io/nuget/v/NetEvolve.CodeBuilder.svg)](https://www.nuget.org/packages/NetEvolve.CodeBuilder/) +[![Build Status](https://github.com/dailydevops/codebuilder/workflows/CI/badge.svg)](https://github.com/dailydevops/codebuilder/actions) + +A high-performance, memory-efficient builder for creating C# code with proper indentation and formatting. Designed specifically for code generation scenarios, it provides an intuitive API with automatic indentation management and thread-safe operations. + +## Features + +- **Automatic Indentation**: Intelligent handling of code blocks with `{` and `}` characters +- **High Performance**: Built on top of `StringBuilder` with optimized memory usage +- **Flexible Formatting**: Support for both spaces and tabs indentation +- **Thread-Safe**: Safe indentation operations for multi-threaded scenarios +- **Conditional Appending**: `AppendIf` and `AppendLineIf` methods for conditional code generation +- **Format Support**: `AppendFormat` methods with culture-specific formatting +- **Memory Efficient**: Support for `ReadOnlyMemory` and unsafe pointer operations +- **Multi-Target Framework**: Supports .NET Standard 2.0, 2.1, .NET 8, 9, and 10 + +## Installation + +### Package Manager Console +```powershell +Install-Package NetEvolve.CodeBuilder +``` + +### .NET CLI +```bash +dotnet add package NetEvolve.CodeBuilder +``` + +### PackageReference +```xml + +``` + +## Quick Start + +```csharp +using NetEvolve.CodeBuilder; + +var builder = new CSharpCodeBuilder(); + +builder.AppendLine("public class HelloWorld") + .Append("{") + .AppendLine("public void SayHello()") + .Append("{") + .AppendLine("Console.WriteLine(\"Hello, World!\");") + .Append("}") + .Append("}"); + +Console.WriteLine(builder.ToString()); +``` + +**Output:** +```csharp +public class HelloWorld +{ + public void SayHello() + { + Console.WriteLine("Hello, World!"); + } +} +``` + +## Basic Usage + +### Creating a Builder + +```csharp +// Default constructor +var builder = new CSharpCodeBuilder(); + +// With initial capacity for better performance +var builder = new CSharpCodeBuilder(1024); +``` + +### Appending Content + +```csharp +var builder = new CSharpCodeBuilder(); + +// Append strings +builder.Append("public class ") + .Append("MyClass"); + +// Append with automatic line breaks +builder.AppendLine("public class MyClass"); + +// Append characters +builder.Append('{'); // Automatically increments indentation +builder.Append('}'); // Automatically decrements indentation +``` + +### Working with Indentation + +```csharp +var builder = new CSharpCodeBuilder(); + +// Manual indentation control +builder.IncrementIndent(); +builder.AppendLine("// This line is indented"); +builder.DecrementIndent(); + +// Automatic indentation with braces +builder.AppendLine("if (condition)") + .Append("{") // Automatically increments indent + .AppendLine("DoSomething();") + .Append("}"); // Automatically decrements indent +``` + +### Using Tabs Instead of Spaces + +```csharp +var builder = new CSharpCodeBuilder { UseTabs = true }; + +builder.AppendLine("public class MyClass") + .Append("{") + .AppendLine("// This uses tab indentation") + .Append("}"); +``` + +## Advanced Features + +### Conditional Appending + +```csharp +var builder = new CSharpCodeBuilder(); +bool includeComments = true; +bool isPublic = true; + +builder.AppendLineIf(includeComments, "// This is a comment") + .AppendIf(isPublic, "public ") + .AppendLine("class MyClass") + .Append("{") + .Append("}"); +``` + +### Format Support + +```csharp +var builder = new CSharpCodeBuilder(); + +// Basic formatting +builder.AppendFormat(CultureInfo.InvariantCulture, "public {0} {1}", "class", "MyClass"); + +// Multiple arguments +builder.AppendFormat(CultureInfo.InvariantCulture, + "public {0} {1}({2} {3})", + "void", "SetValue", "string", "value"); + +// With culture-specific formatting +var culture = new CultureInfo("de-DE"); +builder.AppendFormat(culture, "// Price: {0:C}", 123.45m); +``` + +### Memory-Efficient Operations + +```csharp +var builder = new CSharpCodeBuilder(); + +// Using ReadOnlyMemory +ReadOnlyMemory codeFragment = "public void Method()".AsMemory(); +builder.Append(codeFragment); + +// Using character arrays +char[] chars = ['p', 'u', 'b', 'l', 'i', 'c']; +builder.Append(chars, 0, 6); + +// Unsafe operations for maximum performance +unsafe +{ + fixed (char* ptr = "unsafe code") + { + builder.Append(ptr, 11); + } +} +``` + +### Capacity Management + +```csharp +var builder = new CSharpCodeBuilder(); + +// Check current capacity and length +Console.WriteLine($"Capacity: {builder.Capacity}"); +Console.WriteLine($"Length: {builder.Length}"); + +// Ensure sufficient capacity for better performance +builder.EnsureCapacity(2048); + +// Clear content but keep capacity +builder.Clear(); +``` + +## Real-World Examples + +### Class Generation + +```csharp +public string GenerateClass(string className, List properties) +{ + var builder = new CSharpCodeBuilder(); + + builder.AppendLine("using System;") + .AppendLine() + .AppendFormat(CultureInfo.InvariantCulture, "public class {0}", className) + .AppendLine() + .Append("{"); + + foreach (var property in properties) + { + builder.AppendFormat(CultureInfo.InvariantCulture, + "public {0} {1} {{ get; set; }}", + property.Type, property.Name) + .AppendLine(); + } + + builder.Append("}"); + + return builder.ToString(); +} +``` + +### Method Generation with Conditions + +```csharp +public string GenerateMethod(string methodName, bool isAsync, bool isPublic, List parameters) +{ + var builder = new CSharpCodeBuilder(); + + builder.AppendIf(isPublic, "public ") + .AppendIf(isAsync, "async ") + .AppendIf(isAsync, "Task", "void") + .Append(" ") + .Append(methodName) + .Append("("); + + for (int i = 0; i < parameters.Count; i++) + { + if (i > 0) builder.Append(", "); + builder.AppendFormat(CultureInfo.InvariantCulture, + "{0} {1}", parameters[i].Type, parameters[i].Name); + } + + builder.AppendLine(")") + .Append("{") + .AppendLineIf(isAsync, "await Task.CompletedTask;") + .Append("}"); + + return builder.ToString(); +} +``` + +### Interface Generation + +```csharp +public string GenerateInterface(string interfaceName, List methods) +{ + var builder = new CSharpCodeBuilder(); + + builder.AppendFormat(CultureInfo.InvariantCulture, "public interface {0}", interfaceName) + .AppendLine() + .Append("{"); + + foreach (var method in methods) + { + builder.AppendFormat(CultureInfo.InvariantCulture, + "{0} {1}({2});", + method.ReturnType, + method.Name, + string.Join(", ", method.Parameters.Select(p => $"{p.Type} {p.Name}"))) + .AppendLine(); + } + + builder.Append("}"); + + return builder.ToString(); +} +``` + +## API Reference + +### Core Methods + +| Method | Description | +|--------|-------------| +| `Append(string)` | Appends a string to the builder | +| `AppendLine()` | Appends a line terminator | +| `AppendLine(string)` | Appends a string followed by a line terminator | +| `AppendIf(bool, string)` | Conditionally appends a string | +| `AppendLineIf(bool, string)` | Conditionally appends a string with line terminator | +| `AppendFormat(IFormatProvider, string, ...)` | Appends formatted string | +| `Clear()` | Removes all content from the builder | +| `EnsureCapacity(int)` | Ensures the builder has at least the specified capacity | +| `ToString()` | Returns the built string | + +### Properties + +| Property | Type | Description | +|----------|------|-------------| +| `Capacity` | `int` | Gets the current capacity of the internal StringBuilder | +| `Length` | `int` | Gets the current length of the content | +| `UseTabs` | `bool` | Gets or sets whether to use tabs instead of spaces for indentation | + +### Indentation Methods + +| Method | Description | +|--------|-------------| +| `IncrementIndent()` | Increases indentation level by one | +| `DecrementIndent()` | Decreases indentation level by one | + +## Performance Considerations + +- **Initial Capacity**: Specify an appropriate initial capacity to minimize memory allocations +- **Memory Types**: Use `ReadOnlyMemory` overloads for better memory efficiency +- **Unsafe Operations**: Use unsafe pointer methods for maximum performance in critical scenarios +- **Capacity Management**: Use `EnsureCapacity()` when you know the approximate final size + +```csharp +// Good: Pre-allocate capacity +var builder = new CSharpCodeBuilder(4096); + +// Good: Ensure capacity before large operations +builder.EnsureCapacity(estimatedSize); + +// Good: Use memory-efficient overloads +builder.Append(text.AsMemory()); +``` + +## Thread Safety + +The `CSharpCodeBuilder` uses thread-safe operations for indentation management through `Interlocked` operations. However, the underlying `StringBuilder` operations are not thread-safe, so avoid concurrent access to the same instance from multiple threads. + +## Target Frameworks + +- .NET Standard 2.0 +- .NET Standard 2.1 +- .NET 8.0 +- .NET 9.0 +- .NET 10.0 + +## Dependencies + +- **.NET Standard 2.0/2.1**: `System.Memory` package for `ReadOnlyMemory` support +- **.NET 8+**: No additional dependencies + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change. + +## License + +This project is licensed under the MIT License - see the LICENSE file for details. + +## Changelog + +See [RELEASES](https://github.com/dailydevops/codebuilder/releases) for a detailed changelog. diff --git a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Append.cs b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Append.cs new file mode 100644 index 0000000..b0c1117 --- /dev/null +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Append.cs @@ -0,0 +1,222 @@ +namespace NetEvolve.CodeBuilder; + +using System; + +public partial record CSharpCodeBuilder +{ + /// + /// Appends a boolean value to the current builder. + /// + /// The boolean value to append. + /// The current instance to allow for method chaining. + /// Appends either "True" or "False" based on the value. + public CSharpCodeBuilder Append(bool value) + { + EnsureIndented(); + _ = _builder.Append(value ? "true" : "false"); + return this; + } + + /// + /// Appends a character repeated a specified number of times to the current builder. + /// + /// The character to append. + /// The number of times to repeat the character. + /// The current instance to allow for method chaining. + /// Thrown when is less than zero. + public CSharpCodeBuilder Append(char value, int repeatCount) + { + EnsureIndented(); + _ = _builder.Append(value, repeatCount); + return this; + } + + /// + /// Appends a character to the current builder. + /// + /// The character to append. + /// The current instance to allow for method chaining. + public CSharpCodeBuilder Append(char value) + { + if (value is '\0') + { + return this; // No need to append null character + } + + if (value is '\n' or '\r') + { + return AppendLine(); // Handle new line characters + } + + if (value is '}' or ']') + { + DecrementIndent(); + } + + EnsureIndented(); + _ = _builder.Append(value); + + if (value is '{' or '[') + { + IncrementIndent(); + return AppendLine(); + } + else if (value is '}' or ']') + { + return AppendLine(); + } + + return this; + } + + /// + /// Appends a subset of an array of characters to the current builder. + /// + /// The character array to append. + /// The starting position in the character array. + /// The number of characters to append. + /// The current instance to allow for method chaining. + /// If the array is null or empty, the method returns without appending anything. + public CSharpCodeBuilder Append(char[]? value, int startIndex, int charCount) + { + if (value is null || value.Length == 0) + { + return this; + } + + EnsureIndented(); + _ = _builder.Append(value, startIndex, charCount); + return this; + } + + /// + /// Appends an array of characters to the current builder. + /// + /// The character array to append. + /// The current instance to allow for method chaining. + /// If the array is null or empty, the method returns without appending anything. + public CSharpCodeBuilder Append(char[]? value) + { + if (value is null || value.Length == 0) + { + return this; + } + + EnsureIndented(); + _ = _builder.Append(value); + return this; + } + + /// + /// Appends characters from a pointer to the current builder. + /// + /// A pointer to a character array. + /// The number of characters to append. + /// The current instance to allow for method chaining. + /// If the pointer is null or length is negative, the method returns without appending anything. + public unsafe CSharpCodeBuilder Append(char* value, int length) + { + if (value == null || length < 0) + { + return this; + } + + EnsureIndented(); + _ = _builder.Append(value, length); + return this; + } + + /// + /// Appends a read-only memory of characters to the current builder. + /// + /// The read-only memory containing the characters to append. + /// The current instance to allow for method chaining. + /// If the memory is empty, the method returns without appending anything. + public CSharpCodeBuilder Append(ReadOnlyMemory value) + { + if (value.IsEmpty) + { + return this; + } + + EnsureIndented(); + _ = _builder.Append(value); + return this; + } + + /// + /// Appends a subset of a read-only memory of characters to the current builder. + /// + /// The read-only memory containing the characters to append. + /// The starting position in the memory. + /// The number of characters to append. + /// The current instance to allow for method chaining. + /// If the memory is empty, the method returns without appending anything. + public CSharpCodeBuilder Append(ReadOnlyMemory value, int startIndex, int count) + { + if (value.IsEmpty) + { + return this; + } + + EnsureIndented(); + _ = _builder.Append(value.Slice(startIndex, count)); + return this; + } + + /// + /// Appends a subset of a string to the current builder. + /// + /// The string to append. + /// The starting position in the string. + /// The number of characters to append. + /// The current instance to allow for method chaining. + /// If the string is null or empty, the method returns without appending anything. + public CSharpCodeBuilder Append(string? value, int startIndex, int count) + { + if (string.IsNullOrEmpty(value)) + { + return this; + } + + EnsureIndented(); + _ = _builder.Append(value, startIndex, count); + return this; + } + + /// + /// Appends a string to the current builder. + /// + /// The string to append. + /// The current instance to allow for method chaining. + /// If the string is null or empty, the method returns without appending anything. + public CSharpCodeBuilder Append(string? value) + { + if (string.IsNullOrEmpty(value) || value is "\0") + { + return this; + } + + if (value is "\n" or "\r") + { + return AppendLine(); // Handle new line characters + } + + if (value is "}" or "]") + { + DecrementIndent(); + _ = AppendLine(); + } + + EnsureIndented(); + _ = _builder.Append(value); + + if (value is "{" or "[") + { + IncrementIndent(); + _ = AppendLine(); + } + + return this; + } +} diff --git a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendFormat.cs b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendFormat.cs new file mode 100644 index 0000000..5cb118d --- /dev/null +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendFormat.cs @@ -0,0 +1,48 @@ +namespace NetEvolve.CodeBuilder; + +using System; +using System.Globalization; +using System.Runtime.CompilerServices; + +public partial record CSharpCodeBuilder +{ + /// + /// Appends a formatted string to the current builder using invariant culture. + /// + /// A composite format string. + /// The object to format. + /// The current instance to allow for method chaining. + /// Thrown when is . + /// Thrown when is invalid or the index of a format item is greater than zero. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendFormat(string format, object? arg0) => + AppendFormat(CultureInfo.InvariantCulture, format, arg0); + + /// + /// Appends a formatted string to the current builder using invariant culture. + /// + /// A composite format string. + /// An array of objects to format. + /// The current instance to allow for method chaining. + /// Thrown when is . + /// Thrown when is invalid or the index of a format item is greater than the number of elements in minus 1. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendFormat(string format, params object?[] args) => + AppendFormat(CultureInfo.InvariantCulture, format, args); + + /// + /// Appends a formatted string to the current builder using the specified format provider. + /// + /// An object that supplies culture-specific formatting information. + /// A composite format string. + /// An array of objects to format. + /// The current instance to allow for method chaining. + /// Thrown when is . + /// Thrown when is invalid or the index of a format item is greater than the number of elements in minus 1. + public CSharpCodeBuilder AppendFormat(IFormatProvider? provider, string format, params object?[] args) + { + EnsureIndented(); + _ = _builder.AppendFormat(provider, format, args); + return this; + } +} diff --git a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendIf.cs b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendIf.cs new file mode 100644 index 0000000..1feb663 --- /dev/null +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendIf.cs @@ -0,0 +1,119 @@ +namespace NetEvolve.CodeBuilder; + +using System; +using System.Runtime.CompilerServices; + +public partial record CSharpCodeBuilder +{ + /// + /// Appends a boolean value to the current builder if the specified condition is true. + /// + /// The condition that determines whether to append the value. + /// The boolean value to append. + /// The current instance to allow for method chaining. + /// Appends either "true" or "false" based on the value if the condition is true. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendIf(bool condition, bool value) => condition ? Append(value) : this; + + /// + /// Appends a character repeated a specified number of times to the current builder if the specified condition is true. + /// + /// The condition that determines whether to append the value. + /// The character to append. + /// The number of times to repeat the character. + /// The current instance to allow for method chaining. + /// Thrown when is less than zero. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendIf(bool condition, char value, int repeatCount) => + condition ? Append(value, repeatCount) : this; + + /// + /// Appends a character to the current builder if the specified condition is true. + /// + /// The condition that determines whether to append the value. + /// The character to append. + /// The current instance to allow for method chaining. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendIf(bool condition, char value) => condition ? Append(value) : this; + + /// + /// Appends a subset of an array of characters to the current builder if the specified condition is true. + /// + /// The condition that determines whether to append the value. + /// The character array to append. + /// The starting position in the character array. + /// The number of characters to append. + /// The current instance to allow for method chaining. + /// If the array is null or empty, the method returns without appending anything. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendIf(bool condition, char[]? value, int startIndex, int charCount) => + condition ? Append(value, startIndex, charCount) : this; + + /// + /// Appends an array of characters to the current builder if the specified condition is true. + /// + /// The condition that determines whether to append the value. + /// The character array to append. + /// The current instance to allow for method chaining. + /// If the array is null or empty, the method returns without appending anything. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendIf(bool condition, char[]? value) => condition ? Append(value) : this; + + /// + /// Appends characters from a pointer to the current builder if the specified condition is true. + /// + /// The condition that determines whether to append the value. + /// A pointer to a character array. + /// The number of characters to append. + /// The current instance to allow for method chaining. + /// If the pointer is null or length is negative, the method returns without appending anything. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe CSharpCodeBuilder AppendIf(bool condition, char* value, int length) => + condition ? Append(value, length) : this; + + /// + /// Appends a read-only memory of characters to the current builder if the specified condition is true. + /// + /// The condition that determines whether to append the value. + /// The read-only memory containing the characters to append. + /// The current instance to allow for method chaining. + /// If the memory is empty, the method returns without appending anything. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendIf(bool condition, ReadOnlyMemory value) => condition ? Append(value) : this; + + /// + /// Appends a subset of a read-only memory of characters to the current builder if the specified condition is true. + /// + /// The condition that determines whether to append the value. + /// The read-only memory containing the characters to append. + /// The starting position in the memory. + /// The number of characters to append. + /// The current instance to allow for method chaining. + /// If the memory is empty, the method returns without appending anything. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendIf(bool condition, ReadOnlyMemory value, int startIndex, int count) => + condition ? Append(value, startIndex, count) : this; + + /// + /// Appends a subset of a string to the current builder if the specified condition is true. + /// + /// The condition that determines whether to append the value. + /// The string to append. + /// The starting position in the string. + /// The number of characters to append. + /// The current instance to allow for method chaining. + /// If the string is null or empty, the method returns without appending anything. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendIf(bool condition, string? value, int startIndex, int count) => + condition ? Append(value, startIndex, count) : this; + + /// + /// Appends a string to the current builder if the specified condition is true. + /// + /// The condition that determines whether to append the value. + /// The string to append. + /// The current instance to allow for method chaining. + /// If the string is null or empty, the method returns without appending anything. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendIf(bool condition, string? value) => condition ? Append(value) : this; +} diff --git a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendLine.cs b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendLine.cs new file mode 100644 index 0000000..b612b70 --- /dev/null +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendLine.cs @@ -0,0 +1,111 @@ +namespace NetEvolve.CodeBuilder; + +using System; +using System.Runtime.CompilerServices; + +public partial record CSharpCodeBuilder +{ + /// + /// Appends a line terminator to the current builder. + /// + /// The current instance to allow for method chaining. + /// + /// This method appends the default line terminator for the current environment and sets the internal state + /// to indicate that the next character will be at the beginning of a new line. + /// + public CSharpCodeBuilder AppendLine() + { + EnsureIndented(true); + _ = _builder.AppendLine(); + _isNewline = true; + return this; + } + + /// + /// Appends a string followed by a line terminator to the current builder. + /// + /// The string to append. + /// The current instance to allow for method chaining. + /// If the string is null or empty, only the line terminator is appended. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendLine(string? value) => Append(value).AppendLine(); + + /// + /// Appends a read-only memory of characters followed by a line terminator to the current builder. + /// + /// The read-only memory containing the characters to append. + /// The current instance to allow for method chaining. + /// If the memory is empty, only the line terminator is appended. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendLine(ReadOnlyMemory value) => Append(value).AppendLine(); + + /// + /// Appends a subset of a read-only memory of characters followed by a line terminator to the current builder. + /// + /// The read-only memory containing the characters to append. + /// The starting position in the memory. + /// The number of characters to append. + /// The current instance to allow for method chaining. + /// If the memory is empty, only the line terminator is appended. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendLine(ReadOnlyMemory value, int startIndex, int count) => + Append(value, startIndex, count).AppendLine(); + + /// + /// Appends an array of characters followed by a line terminator to the current builder. + /// + /// The character array to append. + /// The current instance to allow for method chaining. + /// If the array is null or empty, only the line terminator is appended. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendLine(char[]? value) => Append(value).AppendLine(); + + /// + /// Appends a subset of an array of characters followed by a line terminator to the current builder. + /// + /// The character array to append. + /// The starting position in the character array. + /// The number of characters to append. + /// The current instance to allow for method chaining. + /// If the array is null or empty, only the line terminator is appended. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendLine(char[]? value, int startIndex, int charCount) => + Append(value, startIndex, charCount).AppendLine(); + + /// + /// Appends characters from a pointer followed by a line terminator to the current builder. + /// + /// A pointer to a character array. + /// The number of characters to append. + /// The current instance to allow for method chaining. + /// If the pointer is null or length is negative, only the line terminator is appended. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe CSharpCodeBuilder AppendLine(char* value, int length) => Append(value, length).AppendLine(); + + /// + /// Appends a character followed by a line terminator to the current builder. + /// + /// The character to append. + /// The current instance to allow for method chaining. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendLine(char value) => Append(value).AppendLine(); + + /// + /// Appends a character repeated a specified number of times followed by a line terminator to the current builder. + /// + /// The character to append. + /// The number of times to repeat the character. + /// The current instance to allow for method chaining. + /// Thrown when is less than zero. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendLine(char value, int repeatCount) => Append(value, repeatCount).AppendLine(); + + /// + /// Appends a boolean value followed by a line terminator to the current builder. + /// + /// The boolean value to append. + /// The current instance to allow for method chaining. + /// Appends either "true" or "false" based on the value, followed by a line terminator. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendLine(bool value) => Append(value).AppendLine(); +} diff --git a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendLineIf.cs b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendLineIf.cs new file mode 100644 index 0000000..18e6c6b --- /dev/null +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendLineIf.cs @@ -0,0 +1,115 @@ +namespace NetEvolve.CodeBuilder; + +using System; +using System.Runtime.CompilerServices; + +public partial record CSharpCodeBuilder +{ + /// + /// Appends a line terminator to the current builder if the specified condition is true. + /// + /// The condition that determines whether to append the line terminator. + /// The current instance to allow for method chaining. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendLineIf(bool condition) => condition ? AppendLine() : this; + + /// + /// Appends a string followed by a line terminator to the current builder if the specified condition is true. + /// + /// The condition that determines whether to append the value. + /// The string to append. + /// The current instance to allow for method chaining. + /// If the string is null or empty, the method returns without appending anything. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendLineIf(bool condition, string? value) => condition ? AppendLine(value) : this; + + /// + /// Appends a read-only memory of characters followed by a line terminator to the current builder if the specified condition is true. + /// + /// The condition that determines whether to append the value. + /// The read-only memory containing the characters to append. + /// The current instance to allow for method chaining. + /// If the memory is empty, the method returns without appending anything. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendLineIf(bool condition, ReadOnlyMemory value) => + condition ? AppendLine(value) : this; + + /// + /// Appends a subset of a read-only memory of characters followed by a line terminator to the current builder if the specified condition is true. + /// + /// The condition that determines whether to append the value. + /// The read-only memory containing the characters to append. + /// The starting position in the memory. + /// The number of characters to append. + /// The current instance to allow for method chaining. + /// If the memory is empty, the method returns without appending anything. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendLineIf(bool condition, ReadOnlyMemory value, int startIndex, int count) => + condition ? AppendLine(value, startIndex, count) : this; + + /// + /// Appends an array of characters followed by a line terminator to the current builder if the specified condition is true. + /// + /// The condition that determines whether to append the value. + /// The character array to append. + /// The current instance to allow for method chaining. + /// If the array is null or empty, the method returns without appending anything. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendLineIf(bool condition, char[]? value) => condition ? AppendLine(value) : this; + + /// + /// Appends a subset of an array of characters followed by a line terminator to the current builder if the specified condition is true. + /// + /// The condition that determines whether to append the value. + /// The character array to append. + /// The starting position in the character array. + /// The number of characters to append. + /// The current instance to allow for method chaining. + /// If the array is null or empty, the method returns without appending anything. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendLineIf(bool condition, char[]? value, int startIndex, int charCount) => + condition ? AppendLine(value, startIndex, charCount) : this; + + /// + /// Appends characters from a pointer followed by a line terminator to the current builder if the specified condition is true. + /// + /// The condition that determines whether to append the value. + /// A pointer to a character array. + /// The number of characters to append. + /// The current instance to allow for method chaining. + /// If the pointer is null or length is negative, the method returns without appending anything. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe CSharpCodeBuilder AppendLineIf(bool condition, char* value, int length) => + condition ? AppendLine(value, length) : this; + + /// + /// Appends a character followed by a line terminator to the current builder if the specified condition is true. + /// + /// The condition that determines whether to append the value. + /// The character to append. + /// The current instance to allow for method chaining. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendLineIf(bool condition, char value) => condition ? AppendLine(value) : this; + + /// + /// Appends a character repeated a specified number of times followed by a line terminator to the current builder if the specified condition is true. + /// + /// The condition that determines whether to append the value. + /// The character to append. + /// The number of times to repeat the character. + /// The current instance to allow for method chaining. + /// Thrown when is less than zero. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendLineIf(bool condition, char value, int repeatCount) => + condition ? AppendLine(value, repeatCount) : this; + + /// + /// Appends a boolean value followed by a line terminator to the current builder if the specified condition is true. + /// + /// The condition that determines whether to append the value. + /// The boolean value to append. + /// The current instance to allow for method chaining. + /// Appends either "true" or "false" based on the value if the condition is true. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendLineIf(bool condition, bool value) => condition ? AppendLine(value) : this; +} diff --git a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Clear.cs b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Clear.cs new file mode 100644 index 0000000..db25aef --- /dev/null +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Clear.cs @@ -0,0 +1,20 @@ +namespace NetEvolve.CodeBuilder; + +public partial record CSharpCodeBuilder +{ + /// + /// Clears the content of the current builder and resets the indentation level to zero. + /// + /// The current instance to allow for method chaining. + /// + /// This method removes all characters from the internal and + /// resets the indentation level to zero, effectively providing a clean slate for building new content. + /// + public CSharpCodeBuilder Clear() + { + _ = _builder.Clear(); + _ = Interlocked.Exchange(ref _indentLevel, 0); + + return this; + } +} diff --git a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Documentation.cs b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Documentation.cs new file mode 100644 index 0000000..92a8627 --- /dev/null +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Documentation.cs @@ -0,0 +1,392 @@ +namespace NetEvolve.CodeBuilder; + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +public partial record CSharpCodeBuilder +{ + /// + /// Appends a single-line XML documentation comment. + /// + /// The content for the documentation comment. + /// The current instance to allow for method chaining. + /// If the content is null or empty, the method returns without appending anything. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CSharpCodeBuilder AppendXmlDoc(string? content) => + string.IsNullOrEmpty(content) ? this : EnsureNewLineForXmlDoc().AppendLine($"/// {content}"); + + /// + /// Appends an XML documentation summary element. + /// + /// The summary text to include in the documentation. + /// The current instance to allow for method chaining. + /// If the summary is null or empty, the method returns without appending anything. + public CSharpCodeBuilder AppendXmlDocSummary(string? summary) + { + if (string.IsNullOrEmpty(summary)) + { + return this; + } + + return EnsureNewLineForXmlDoc() + .AppendLine("/// ") + .AppendLine($"/// {summary}") + .AppendLine("/// "); + } + + /// + /// Appends an XML documentation summary element with multiple lines. + /// + /// The summary lines to include in the documentation. + /// The current instance to allow for method chaining. + /// If the summary lines collection is null or empty, the method returns without appending anything. + public CSharpCodeBuilder AppendXmlDocSummary(IEnumerable? summaryLines) + { + if (summaryLines is null) + { + return this; + } + + var hasContent = false; + var builder = EnsureNewLineForXmlDoc().AppendLine("/// "); + + foreach (var line in summaryLines) + { + if (!string.IsNullOrEmpty(line)) + { + builder = builder.AppendLine($"/// {line}"); + hasContent = true; + } + } + + return hasContent ? builder.AppendLine("/// ") : this; + } + + /// + /// Appends an XML documentation param element. + /// + /// The name of the parameter. + /// The description of the parameter. + /// The current instance to allow for method chaining. + /// If the parameter name or description is null or empty, the method returns without appending anything. + public CSharpCodeBuilder AppendXmlDocParam(string? paramName, string? description) + { + if (string.IsNullOrEmpty(paramName) || string.IsNullOrEmpty(description)) + { + return this; + } + + return EnsureNewLineForXmlDoc().AppendLine($"/// {description}"); + } + + /// + /// Appends multiple XML documentation param elements. + /// + /// A collection of parameter name and description pairs. + /// The current instance to allow for method chaining. + /// If the parameters collection is null or empty, the method returns without appending anything. + public CSharpCodeBuilder AppendXmlDocParams(IEnumerable<(string Name, string Description)>? parameters) + { + if (parameters is null) + { + return this; + } + + var builder = this; + foreach (var (name, description) in parameters) + { + builder = builder.AppendXmlDocParam(name, description); + } + + return builder; + } + + /// + /// Appends an XML documentation returns element. + /// + /// The description of the return value. + /// The current instance to allow for method chaining. + /// If the description is null or empty, the method returns without appending anything. + public CSharpCodeBuilder AppendXmlDocReturns(string? description) + { + if (string.IsNullOrEmpty(description)) + { + return this; + } + + return EnsureNewLineForXmlDoc().AppendLine($"/// {description}"); + } + + /// + /// Appends an XML documentation remarks element. + /// + /// The remarks text to include in the documentation. + /// The current instance to allow for method chaining. + /// If the remarks text is null or empty, the method returns without appending anything. + public CSharpCodeBuilder AppendXmlDocRemarks(string? remarks) + { + if (string.IsNullOrEmpty(remarks)) + { + return this; + } + + return EnsureNewLineForXmlDoc() + .AppendLine("/// ") + .AppendLine($"/// {remarks}") + .AppendLine("/// "); + } + + /// + /// Appends an XML documentation remarks element with multiple lines. + /// + /// The remarks lines to include in the documentation. + /// The current instance to allow for method chaining. + /// If the remarks lines collection is null or empty, the method returns without appending anything. + public CSharpCodeBuilder AppendXmlDocRemarks(IEnumerable? remarksLines) + { + if (remarksLines is null) + { + return this; + } + + var hasContent = false; + var builder = EnsureNewLineForXmlDoc().AppendLine("/// "); + + foreach (var line in remarksLines) + { + if (!string.IsNullOrEmpty(line)) + { + builder = builder.AppendLine($"/// {line}"); + hasContent = true; + } + } + + return hasContent ? builder.AppendLine("/// ") : this; + } + + /// + /// Appends an XML documentation exception element. + /// + /// The type of exception that can be thrown. + /// The description of when the exception is thrown. + /// The current instance to allow for method chaining. + /// If the exception type or description is null or empty, the method returns without appending anything. + public CSharpCodeBuilder AppendXmlDocException(string? exceptionType, string? description) + { + if (string.IsNullOrEmpty(exceptionType) || string.IsNullOrEmpty(description)) + { + return this; + } + + return EnsureNewLineForXmlDoc() + .AppendLine($"/// {description}"); + } + + /// + /// Appends multiple XML documentation exception elements. + /// + /// A collection of exception type and description pairs. + /// The current instance to allow for method chaining. + /// If the exceptions collection is null or empty, the method returns without appending anything. + public CSharpCodeBuilder AppendXmlDocExceptions(IEnumerable<(string Type, string Description)>? exceptions) + { + if (exceptions is null) + { + return this; + } + + var builder = this; + foreach (var (type, description) in exceptions) + { + builder = builder.AppendXmlDocException(type, description); + } + + return builder; + } + + /// + /// Appends an XML documentation example element. + /// + /// The example text or code to include in the documentation. + /// The current instance to allow for method chaining. + /// If the example is null or empty, the method returns without appending anything. + public CSharpCodeBuilder AppendXmlDocExample(string? example) + { + if (string.IsNullOrEmpty(example)) + { + return this; + } + + return EnsureNewLineForXmlDoc() + .AppendLine("/// ") + .AppendLine($"/// {example}") + .AppendLine("/// "); + } + + /// + /// Appends an XML documentation example element with multiple lines. + /// + /// The example lines to include in the documentation. + /// The current instance to allow for method chaining. + /// If the example lines collection is null or empty, the method returns without appending anything. + public CSharpCodeBuilder AppendXmlDocExample(IEnumerable? exampleLines) + { + if (exampleLines is null) + { + return this; + } + + var hasContent = false; + var builder = EnsureNewLineForXmlDoc().AppendLine("/// "); + + foreach (var line in exampleLines) + { + if (!string.IsNullOrEmpty(line)) + { + builder = builder.AppendLine($"/// {line}"); + hasContent = true; + } + } + + return hasContent ? builder.AppendLine("/// ") : this; + } + + /// + /// Appends an XML documentation see element for cross-references. + /// + /// The cross-reference to another member or type. + /// The current instance to allow for method chaining. + /// If the cref is null or empty, the method returns without appending anything. + public CSharpCodeBuilder AppendXmlDocSee(string? cref) + { + if (string.IsNullOrEmpty(cref)) + { + return this; + } + + return EnsureNewLineForXmlDoc().AppendLine($"/// "); + } + + /// + /// Appends an XML documentation seealso element for see-also references. + /// + /// The cross-reference to another member or type. + /// The current instance to allow for method chaining. + /// If the cref is null or empty, the method returns without appending anything. + public CSharpCodeBuilder AppendXmlDocSeeAlso(string? cref) + { + if (string.IsNullOrEmpty(cref)) + { + return this; + } + + return EnsureNewLineForXmlDoc().AppendLine($"/// "); + } + + /// + /// Appends an XML documentation value element for property documentation. + /// + /// The description of the property value. + /// The current instance to allow for method chaining. + /// If the description is null or empty, the method returns without appending anything. + public CSharpCodeBuilder AppendXmlDocValue(string? description) + { + if (string.IsNullOrEmpty(description)) + { + return this; + } + + return EnsureNewLineForXmlDoc().AppendLine($"/// {description}"); + } + + /// + /// Appends an XML documentation typeparam element for generic type parameters. + /// + /// The name of the type parameter. + /// The description of the type parameter. + /// The current instance to allow for method chaining. + /// If the parameter name or description is null or empty, the method returns without appending anything. + public CSharpCodeBuilder AppendXmlDocTypeParam(string? paramName, string? description) + { + if (string.IsNullOrEmpty(paramName) || string.IsNullOrEmpty(description)) + { + return this; + } + + return EnsureNewLineForXmlDoc().AppendLine($"/// {description}"); + } + + /// + /// Appends multiple XML documentation typeparam elements. + /// + /// A collection of type parameter name and description pairs. + /// The current instance to allow for method chaining. + /// If the type parameters collection is null or empty, the method returns without appending anything. + public CSharpCodeBuilder AppendXmlDocTypeParams(IEnumerable<(string, string)>? typeParameters) + { + if (typeParameters is null) + { + return this; + } + + var builder = this; + foreach (var (name, description) in typeParameters) + { + builder = builder.AppendXmlDocTypeParam(name, description); + } + + return builder; + } + + /// + /// Appends an inheritdoc XML documentation element. + /// + /// Optional cross-reference to specify which member to inherit documentation from. + /// The current instance to allow for method chaining. + /// If no cref is provided, inherits documentation from the base or interface member. + public CSharpCodeBuilder AppendXmlDocInheritDoc(string? cref = null) => + string.IsNullOrEmpty(cref) + ? EnsureNewLineForXmlDoc().AppendLine("/// ") + : EnsureNewLineForXmlDoc().AppendLine($"/// "); + + /// + /// Appends a custom XML documentation element. + /// + /// The name of the XML element. + /// The content of the XML element. + /// Optional attributes for the XML element. + /// The current instance to allow for method chaining. + /// If the element name is null or empty, the method returns without appending anything. + public CSharpCodeBuilder AppendXmlDocCustomElement( + string? elementName, + string? content = null, + string? attributes = null + ) + { + if (string.IsNullOrEmpty(elementName)) + { + return this; + } + + var attributesPart = string.IsNullOrEmpty(attributes) ? string.Empty : $" {attributes}"; + + if (string.IsNullOrEmpty(content)) + { + return EnsureNewLineForXmlDoc().AppendLine($"/// <{elementName}{attributesPart} />"); + } + + return EnsureNewLineForXmlDoc().AppendLine($"/// <{elementName}{attributesPart}>{content}"); + } + + /// + /// Ensures that XML documentation comments start on a new line if there's already content in the builder. + /// + /// The current instance to allow for method chaining. + /// + /// This method checks if the builder already has content and if we're not already on a new line, + /// it appends a line break to ensure XML documentation starts on a fresh line. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private CSharpCodeBuilder EnsureNewLineForXmlDoc() => AppendLineIf(Length > 0 && !_isNewline); +} diff --git a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.EnsureCapacity.cs b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.EnsureCapacity.cs new file mode 100644 index 0000000..39c4423 --- /dev/null +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.EnsureCapacity.cs @@ -0,0 +1,18 @@ +namespace NetEvolve.CodeBuilder; + +public partial record CSharpCodeBuilder +{ + /// + /// Ensures that the capacity of the internal is at least the specified value. + /// + /// The minimum capacity to ensure. + /// + /// If the current capacity is less than the value specified, the capacity is increased to the specified value. + /// + public CSharpCodeBuilder EnsureCapacity(int capacity) + { + _ = _builder.EnsureCapacity(capacity); + + return this; + } +} diff --git a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder._EMPTY_.cs b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder._EMPTY_.cs new file mode 100644 index 0000000..7682b2f --- /dev/null +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder._EMPTY_.cs @@ -0,0 +1,3 @@ +namespace NetEvolve.CodeBuilder; + +public partial record CSharpCodeBuilder { } diff --git a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.cs b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.cs new file mode 100644 index 0000000..bd4d747 --- /dev/null +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.cs @@ -0,0 +1,22 @@ +namespace NetEvolve.CodeBuilder; + +using System.Text; + +/// +/// Provides functionality for building C# code strings with proper indentation. +/// +public partial record CSharpCodeBuilder : CodeBuilderBase +{ + /// + /// Initializes a new instance of the class. + /// + public CSharpCodeBuilder() + : base() { } + + /// + /// Initializes a new instance of the class with the specified initial capacity. + /// + /// The initial capacity of the internal . + public CSharpCodeBuilder(int initialCapacity) + : base(initialCapacity) { } +} diff --git a/src/NetEvolve.CodeBuilder/CodeBuilderBase.EnsureIndented.cs b/src/NetEvolve.CodeBuilder/CodeBuilderBase.EnsureIndented.cs new file mode 100644 index 0000000..898b688 --- /dev/null +++ b/src/NetEvolve.CodeBuilder/CodeBuilderBase.EnsureIndented.cs @@ -0,0 +1,52 @@ +namespace NetEvolve.CodeBuilder; + +using System.Runtime.CompilerServices; + +public partial record CodeBuilderBase +{ + /// + /// Ensures that indentation is applied if we are at the start of a new line. + /// + /// + /// This method adds the appropriate indentation (tabs or spaces) at the beginning of a line + /// based on the current indentation level. It only applies indentation if the current position is + /// at the start of a new line (when is ). + /// + private protected void EnsureIndented(bool deactivate = false) + { + if (!_isNewline || deactivate) + { + return; + } + + _ = _builder.Append(UseTabs ? '\t' : ' ', _indentLevel * (UseTabs ? 1 : 4)); + + _isNewline = false; + } + + /// + /// Increments the indentation level by one. + /// + /// + /// This method increases the current indentation level, which affects subsequent lines + /// that are appended to the builder. The operation is thread-safe. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void IncrementIndent() => Interlocked.Increment(ref _indentLevel); + + /// + /// Decrements the indentation level by one. + /// + /// + /// This method decreases the current indentation level, which affects subsequent lines + /// that are appended to the builder. The operation is thread-safe. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void DecrementIndent() + { + if (Interlocked.Decrement(ref _indentLevel) < 0) + { + _ = Interlocked.Exchange(ref _indentLevel, 0); + } + } +} diff --git a/src/NetEvolve.CodeBuilder/CodeBuilderBase.ToString.cs b/src/NetEvolve.CodeBuilder/CodeBuilderBase.ToString.cs new file mode 100644 index 0000000..b480359 --- /dev/null +++ b/src/NetEvolve.CodeBuilder/CodeBuilderBase.ToString.cs @@ -0,0 +1,13 @@ +namespace NetEvolve.CodeBuilder; + +public partial record CodeBuilderBase +{ + /// + /// Returns the string that has been built by this . + /// + /// The string representation of the current instance. + /// + /// This method returns the content of the internal . + /// + public override sealed string ToString() => _builder.ToString(); +} diff --git a/src/NetEvolve.CodeBuilder/CodeBuilderBase.cs b/src/NetEvolve.CodeBuilder/CodeBuilderBase.cs new file mode 100644 index 0000000..6397834 --- /dev/null +++ b/src/NetEvolve.CodeBuilder/CodeBuilderBase.cs @@ -0,0 +1,42 @@ +namespace NetEvolve.CodeBuilder; + +using System.Text; + +/// +/// Provides the base functionality for building code strings with proper indentation and formatting. +/// +public abstract partial record CodeBuilderBase +{ + private protected readonly StringBuilder _builder; + private protected int _indentLevel; + private protected bool _isNewline = true; + + /// + /// Initializes a new instance of the class. + /// + private protected CodeBuilderBase() => _builder = new StringBuilder(); + + /// + /// Initializes a new instance of the class with the specified initial capacity. + /// + /// The initial capacity of the internal . + private protected CodeBuilderBase(int initialCapacity) => _builder = new StringBuilder(initialCapacity); + + /// + /// Gets the current capacity of the internal . + /// + /// The capacity of the internal string builder. + public int Capacity => _builder.Capacity; + + /// + /// Gets the current length of the internal . + /// + /// The length of the content that has been built so far. + public int Length => _builder.Length; + + /// + /// Gets or sets a value indicating whether to use tabs instead of spaces for indentation. + /// + /// true to use tabs for indentation; false to use spaces. + public bool UseTabs { get; set; } +} diff --git a/src/NetEvolve.CodeBuilder/NetEvolve.CodeBuilder.csproj b/src/NetEvolve.CodeBuilder/NetEvolve.CodeBuilder.csproj new file mode 100644 index 0000000..6d086c3 --- /dev/null +++ b/src/NetEvolve.CodeBuilder/NetEvolve.CodeBuilder.csproj @@ -0,0 +1,9 @@ + + + $(_ProjectTargetFrameworks) + true + + + + + diff --git a/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Append.cs b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Append.cs new file mode 100644 index 0000000..ce48611 --- /dev/null +++ b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Append.cs @@ -0,0 +1,247 @@ +namespace NetEvolve.CodeBuilder.Tests.Unit; + +using System; + +public partial class CSharpCodeBuilderTests +{ + [Test] + public async Task Append_Boolean_True_Should_Append_TrueString() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.Append(true); + + _ = await Assert.That(builder.ToString()).IsEqualTo("true"); + } + + [Test] + public async Task Append_Boolean_False_Should_Append_FalseString() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.Append(false); + + _ = await Assert.That(builder.ToString()).IsEqualTo("false"); + } + + [Test] + public async Task Append_Char_Should_Append_Character() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.Append('x'); + + _ = await Assert.That(builder.ToString()).IsEqualTo("x"); + } + + [Test] + public async Task Append_Char_Multiple_Should_Append_All_Characters() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.Append("a").Append('b').Append('c'); + + _ = await Assert.That(builder.ToString()).IsEqualTo("abc"); + } + + [Test] + public async Task Append_Multiple_Should_Same() + { + var builder = new CSharpCodeBuilder(10); + + var builder2 = builder.Append('a').Append('b').Append('c'); + + _ = await Assert.That(builder).IsEqualTo(builder2); + } + + [Test] + public async Task Append_Char_With_RepeatCount_Should_Append_Character_Repeated_Times() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.Append('x', 5); + + _ = await Assert.That(builder.ToString()).IsEqualTo("xxxxx"); + } + + [Test] + public async Task Append_Char_With_RepeatCount_Zero_Should_Not_Append_Anything() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.Append('x', 0); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task Append_Char_With_RepeatCount_Negative_Should_Throw_ArgumentOutOfRangeException() + { + var exception = Assert.Throws(() => + { + var builder = new CSharpCodeBuilder(10); + _ = builder.Append('x', -1); + }); + + _ = await Assert.That(exception.ParamName).IsEqualTo("repeatCount"); + } + + [Test] + public async Task Append_CharArray_Should_Append_All_Characters() + { + var builder = new CSharpCodeBuilder(10); + var chars = new[] { 'a', 'b', 'c' }; + + _ = builder.Append(chars); + + _ = await Assert.That(builder.ToString()).IsEqualTo("abc"); + } + + [Test] + public async Task Append_CharArray_Null_Should_Not_Change_Builder() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.Append((char[]?)null); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task Append_CharArray_Empty_Should_Not_Change_Builder() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.Append([]); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task Append_CharArray_With_StartIndex_And_Count_Should_Append_Specified_Characters() + { + var builder = new CSharpCodeBuilder(10); + var chars = new[] { 'a', 'b', 'c', 'd', 'e' }; + + _ = builder.Append(chars, 1, 3); + + _ = await Assert.That(builder.ToString()).IsEqualTo("bcd"); + } + + [Test] + public async Task Append_CharArray_With_StartIndex_And_Count_Null_Should_Not_Change_Builder() + { + var builder = new CSharpCodeBuilder(10); + char[]? chars = null; + + _ = builder.Append(chars, 0, 0); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task Append_ReadOnlyMemory_Should_Append_Characters() + { + var builder = new CSharpCodeBuilder(10); + var memory = "abc".AsMemory(); + + _ = builder.Append(memory); + + _ = await Assert.That(builder.ToString()).IsEqualTo("abc"); + } + + [Test] + public async Task Append_ReadOnlyMemory_Empty_Should_Not_Change_Builder() + { + var builder = new CSharpCodeBuilder(10); + var memory = ReadOnlyMemory.Empty; + + _ = builder.Append(memory); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task Append_ReadOnlyMemory_With_StartIndex_And_Count_Should_Append_Specified_Characters() + { + var builder = new CSharpCodeBuilder(10); + var memory = "abcde".AsMemory(); + + _ = builder.Append(memory, 1, 3); + + _ = await Assert.That(builder.ToString()).IsEqualTo("bcd"); + } + + [Test] + public async Task Append_String_Should_Append_Characters() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.Append("abc"); + + _ = await Assert.That(builder.ToString()).IsEqualTo("abc"); + } + + [Test] + public async Task Append_String_Null_Should_Not_Change_Builder() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.Append((string?)null); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task Append_String_Empty_Should_Not_Change_Builder() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.Append(string.Empty); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task Append_String_With_StartIndex_And_Count_Should_Append_Specified_Characters() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.Append("abcdef", 1, 3); + + _ = await Assert.That(builder.ToString()).IsEqualTo("bcd"); + } + + [Test] + public async Task Append_String_With_StartIndex_And_Count_Null_Should_Not_Change_Builder() + { + var builder = new CSharpCodeBuilder(10); + string? text = null; + + _ = builder.Append(text, 0, 0); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task Append_Multiple_Calls_Should_Append_In_Sequence() + { + var builder = new CSharpCodeBuilder(20); + + _ = builder.Append("Hello").Append(' ').Append("World").Append('!'); + + _ = await Assert.That(builder.ToString()).IsEqualTo("Hello World!"); + } + + [Test] + public async Task Append_With_Capacity_Expansion_Should_Work_Correctly() + { + // Start with small capacity to force expansion + var builder = new CSharpCodeBuilder(5); + var longString = new string('x', 100); + + _ = builder.Append(longString); + + _ = await Assert.That(builder.ToString()).IsEqualTo(longString); + } +} diff --git a/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.AppendFormat.cs b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.AppendFormat.cs new file mode 100644 index 0000000..51b128f --- /dev/null +++ b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.AppendFormat.cs @@ -0,0 +1,484 @@ +namespace NetEvolve.CodeBuilder.Tests.Unit; + +using System; +using System.Globalization; + +public partial class CSharpCodeBuilderTests +{ + [Test] + public async Task AppendFormat_OneArgument_Should_Format_Correctly() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendFormat(CultureInfo.InvariantCulture, "Value: {0}", 42); + + _ = await Assert.That(builder.ToString()).IsEqualTo("Value: 42"); + } + + [Test] + public async Task AppendFormat_OneArgument_Should_Use_InvariantCulture() + { + var builder = new CSharpCodeBuilder(10); + var decimalValue = 1234.56m; + + _ = builder.AppendFormat(CultureInfo.InvariantCulture, "Value: {0:N2}", decimalValue); + + // InvariantCulture uses period as decimal separator + _ = await Assert.That(builder.ToString()).IsEqualTo("Value: 1,234.56"); + } + + [Test] + public async Task AppendFormat_OneArgument_Should_Apply_Indentation() + { + var builder = new CSharpCodeBuilder(10); + builder.IncrementIndent(); + + // First append a newline, then format text to see indentation + _ = builder.AppendLine().AppendFormat(CultureInfo.InvariantCulture, "Value: {0}", 42); + + var expected = Environment.NewLine + " Value: 42"; + _ = await Assert.That(builder.ToString()).IsEqualTo(expected); + } + + [Test] + public async Task AppendFormat_OneArgument_Should_Return_Same_Instance() + { + var builder = new CSharpCodeBuilder(10); + + var result = builder.AppendFormat(CultureInfo.InvariantCulture, "Value: {0}", 42); + + _ = await Assert.That(result).IsEqualTo(builder); + } + + [Test] + public async Task AppendFormat_OneArgument_Null_Format_Should_Throw_ArgumentNullException() + { + var exception = Assert.Throws(() => + { + var builder = new CSharpCodeBuilder(10); + _ = builder.AppendFormat(CultureInfo.InvariantCulture, null!, 42); + }); + + _ = await Assert.That(exception.ParamName).IsEqualTo("format"); + } + + [Test] + public async Task AppendFormat_OneArgument_Invalid_Format_Should_Throw_FormatException() => + await Assert.ThrowsAsync(() => + { + var builder = new CSharpCodeBuilder(10); + _ = builder.AppendFormat(CultureInfo.InvariantCulture, "Value: {1}", 42); + return Task.CompletedTask; + }); + + [Test] + public async Task AppendFormat_TwoArguments_Should_Format_Correctly() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendFormat(CultureInfo.InvariantCulture, "Values: {0}, {1}", 42, "test"); + + _ = await Assert.That(builder.ToString()).IsEqualTo("Values: 42, test"); + } + + [Test] + public async Task AppendFormat_TwoArguments_Should_Use_InvariantCulture() + { + var builder = new CSharpCodeBuilder(10); + var decimalValue = 1234.56m; + + _ = builder.AppendFormat( + CultureInfo.InvariantCulture, + "Values: {0:N2}, {1}", + decimalValue, + "test" + ); + + var result = builder.ToString(); + + _ = await Assert.That(result).IsEqualTo("Values: 1,234.56, test"); + } + + [Test] + public async Task AppendFormat_TwoArguments_Should_Apply_Indentation() + { + var builder = new CSharpCodeBuilder(10); + builder.IncrementIndent(); + + // First append a newline, then format text to see indentation + var result = builder + .AppendLine() + .AppendFormat(CultureInfo.InvariantCulture, "Values: {0}, {1}", 42, "test") + .ToString(); + + var expected = Environment.NewLine + " Values: 42, test"; + _ = await Assert.That(result).IsEqualTo(expected); + } + + [Test] + public async Task AppendFormat_TwoArguments_Should_Return_Same_Instance() + { + var builder = new CSharpCodeBuilder(10); + + var result = builder.AppendFormat( + CultureInfo.InvariantCulture, + "Values: {0}, {1}", + 42, + "test" + ); + + _ = await Assert.That(result).IsEqualTo(builder); + } + + [Test] + public async Task AppendFormat_TwoArguments_Null_Format_Should_Throw_ArgumentNullException() + { + var exception = Assert.Throws(() => + { + var builder = new CSharpCodeBuilder(10); + _ = builder.AppendFormat(CultureInfo.InvariantCulture, null!, 42, "test"); + }); + + _ = await Assert.That(exception.ParamName).IsEqualTo("format"); + } + + [Test] + public async Task AppendFormat_TwoArguments_Invalid_Format_Should_Throw_FormatException() => + await Assert.ThrowsAsync(() => + { + var builder = new CSharpCodeBuilder(10); + _ = builder.AppendFormat(CultureInfo.InvariantCulture, "Values: {0}, {2}", 42, "test"); + return Task.CompletedTask; + }); + + [Test] + public async Task AppendFormat_ThreeArguments_Should_Format_Correctly() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendFormat( + CultureInfo.InvariantCulture, + "Values: {0}, {1}, {2}", + 42, + "test", + true + ); + + _ = await Assert.That(builder.ToString()).IsEqualTo("Values: 42, test, True"); + } + + [Test] + public async Task AppendFormat_ThreeArguments_Should_Use_InvariantCulture() + { + var builder = new CSharpCodeBuilder(10); + var decimalValue = 1234.56m; + + _ = builder.AppendFormat( + CultureInfo.InvariantCulture, + "Values: {0:N2}, {1}, {2}", + decimalValue, + "test", + true + ); + + _ = await Assert.That(builder.ToString()).IsEqualTo("Values: 1,234.56, test, True"); + } + + [Test] + public async Task AppendFormat_ThreeArguments_Should_Apply_Indentation() + { + var builder = new CSharpCodeBuilder(10); + builder.IncrementIndent(); + + // First append a newline, then format text to see indentation + _ = builder + .AppendLine() + .AppendFormat(CultureInfo.InvariantCulture, "Values: {0}, {1}, {2}", 42, "test", true); + + var expected = Environment.NewLine + " Values: 42, test, True"; + _ = await Assert.That(builder.ToString()).IsEqualTo(expected); + } + + [Test] + public async Task AppendFormat_ThreeArguments_Should_Return_Same_Instance() + { + var builder = new CSharpCodeBuilder(10); + + var result = builder.AppendFormat( + CultureInfo.InvariantCulture, + "Values: {0}, {1}, {2}", + 42, + "test", + true + ); + + _ = await Assert.That(result).IsEqualTo(builder); + } + + [Test] + public async Task AppendFormat_ThreeArguments_Null_Format_Should_Throw_ArgumentNullException() + { + var exception = Assert.Throws(() => + { + var builder = new CSharpCodeBuilder(10); + _ = builder.AppendFormat(CultureInfo.InvariantCulture, null!, 42, "test", true); + }); + + _ = await Assert.That(exception.ParamName).IsEqualTo("format"); + } + + [Test] + public async Task AppendFormat_ThreeArguments_Invalid_Format_Should_Throw_FormatException() => + await Assert.ThrowsAsync(() => + { + var builder = new CSharpCodeBuilder(10); + _ = builder.AppendFormat( + CultureInfo.InvariantCulture, + "Values: {0}, {1}, {3}", + 42, + "test", + true + ); + return Task.CompletedTask; + }); + + [Test] + public async Task AppendFormat_ParamsArray_Should_Format_Correctly() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendFormat( + CultureInfo.InvariantCulture, + "Values: {0}, {1}, {2}, {3}", + 42, + "test", + true, + 3.14 + ); + + _ = await Assert.That(builder.ToString()).IsEqualTo("Values: 42, test, True, 3.14"); + } + + [Test] + public async Task AppendFormat_ParamsArray_Should_Use_InvariantCulture() + { + var builder = new CSharpCodeBuilder(10); + var decimalValue = 1234.56m; + + _ = builder.AppendFormat( + CultureInfo.InvariantCulture, + "Values: {0:N2}, {1}, {2}, {3:F2}", + decimalValue, + "test", + true, + 3.14 + ); + + _ = await Assert.That(builder.ToString()).IsEqualTo("Values: 1,234.56, test, True, 3.14"); + } + + [Test] + public async Task AppendFormat_ParamsArray_Should_Apply_Indentation() + { + var builder = new CSharpCodeBuilder(10); + builder.IncrementIndent(); + + // First append a newline, then format text to see indentation + _ = builder + .AppendLine() + .AppendFormat( + CultureInfo.InvariantCulture, + "Values: {0}, {1}, {2}, {3}", + 42, + "test", + true, + 3.14 + ); + + var expected = Environment.NewLine + " Values: 42, test, True, 3.14"; + _ = await Assert.That(builder.ToString()).IsEqualTo(expected); + } + + [Test] + public async Task AppendFormat_ParamsArray_Should_Return_Same_Instance() + { + var builder = new CSharpCodeBuilder(10); + + var result = builder.AppendFormat( + CultureInfo.InvariantCulture, + "Values: {0}, {1}, {2}, {3}", + 42, + "test", + true, + 3.14 + ); + + _ = await Assert.That(result).IsEqualTo(builder); + } + + [Test] + public async Task AppendFormat_ParamsArray_Null_Format_Should_Throw_ArgumentNullException() + { + var exception = Assert.Throws(() => + { + var builder = new CSharpCodeBuilder(10); + _ = builder.AppendFormat(CultureInfo.InvariantCulture, null!, 42, "test", true, 3.14); + }); + + _ = await Assert.That(exception.ParamName).IsEqualTo("format"); + } + + [Test] + public async Task AppendFormat_ParamsArray_Invalid_Format_Should_Throw_FormatException() => + await Assert.ThrowsAsync(() => + { + var builder = new CSharpCodeBuilder(10); + _ = builder.AppendFormat( + CultureInfo.InvariantCulture, + "Values: {0}, {1}, {2}, {4}", + 42, + "test", + true, + 3.14 + ); + return Task.CompletedTask; + }); + + [Test] + public void AppendFormat_EmptyArray_ThrowsFormatException() + { + var builder = new CSharpCodeBuilder(10); + + _ = Assert.Throws(() => + builder.AppendFormat(CultureInfo.InvariantCulture, "No params: {0}", []) + ); + } + + [Test] + public async Task AppendFormat_WithProvider_OneArgument_Should_Format_Correctly() + { + var builder = new CSharpCodeBuilder(10); + var cultureInfo = new CultureInfo("de-DE"); + + _ = builder.AppendFormat(cultureInfo, "Value: {0:N2}", 1234.56); + + // French culture uses comma as decimal separator + _ = await Assert.That(builder.ToString()).IsEqualTo("Value: 1.234,56"); + } + + [Test] + public async Task AppendFormat_WithProvider_TwoArguments_Should_Format_Correctly() + { + var builder = new CSharpCodeBuilder(10); + var cultureInfo = new CultureInfo("de-DE"); + + _ = builder.AppendFormat(cultureInfo, "Values: {0:N2}, {1}", 1234.56, "test"); + + _ = await Assert.That(builder.ToString()).IsEqualTo("Values: 1.234,56, test"); + } + + [Test] + public async Task AppendFormat_WithProvider_ThreeArguments_Should_Format_Correctly() + { + var builder = new CSharpCodeBuilder(10); + var cultureInfo = new CultureInfo("de-DE"); + + _ = builder.AppendFormat(cultureInfo, "Values: {0:N2}, {1}, {2}", 1234.56, "test", true); + + _ = await Assert.That(builder.ToString()).IsEqualTo("Values: 1.234,56, test, True"); + } + + [Test] + public async Task AppendFormat_WithProvider_ParamsArray_Should_Format_Correctly() + { + var builder = new CSharpCodeBuilder(10); + var cultureInfo = new CultureInfo("de-DE"); + + _ = builder.AppendFormat( + cultureInfo, + "Values: {0:N2}, {1}, {2}, {3:F2}", + 1234.56, + "test", + true, + 3.14 + ); + + _ = await Assert.That(builder.ToString()).IsEqualTo("Values: 1.234,56, test, True, 3,14"); + } + + [Test] + public async Task AppendFormat_WithProvider_NullProvider_Should_Use_CurrentCulture() + { + var builder = new CSharpCodeBuilder(10); + var originalCulture = CultureInfo.CurrentCulture; + + try + { + CultureInfo.CurrentCulture = new CultureInfo("de-DE"); +#pragma warning disable S3220 // Method calls should not resolve ambiguously to overloads with "params" + _ = builder.AppendFormat(null, "Value: {0:N2}", 1234.56); +#pragma warning restore S3220 // Method calls should not resolve ambiguously to overloads with "params" + + _ = await Assert.That(builder.ToString()).IsEqualTo("Value: 1.234,56"); + } + finally + { + CultureInfo.CurrentCulture = originalCulture; + } + } + + [Test] + public async Task AppendFormat_Multiple_Calls_Should_Append_Sequentially() + { + var builder = new CSharpCodeBuilder(20); + + _ = builder + .AppendFormat(CultureInfo.InvariantCulture, "First: {0}", 1) + .AppendFormat(CultureInfo.InvariantCulture, " Second: {0}", 2) + .AppendFormat(CultureInfo.InvariantCulture, " Third: {0}", 3); + + _ = await Assert.That(builder.ToString()).IsEqualTo("First: 1 Second: 2 Third: 3"); + } + + [Test] + public async Task AppendFormat_With_NonSequentialFormatItems_Should_Format_Correctly() + { + var builder = new CSharpCodeBuilder(20); + + _ = builder.AppendFormat( + CultureInfo.InvariantCulture, + "Values: {1}, {0}, {2}", + "A", + "B", + "C" + ); + + _ = await Assert.That(builder.ToString()).IsEqualTo("Values: B, A, C"); + } + + [Test] + public async Task AppendFormat_With_RepeatedFormatItems_Should_Format_Correctly() + { + var builder = new CSharpCodeBuilder(20); + + _ = builder.AppendFormat(CultureInfo.InvariantCulture, "Value: {0}, repeat: {0}", "test"); + + _ = await Assert.That(builder.ToString()).IsEqualTo("Value: test, repeat: test"); + } + + [Test] + public async Task AppendFormat_WithComplexFormatting_Should_Format_Correctly() + { + var builder = new CSharpCodeBuilder(30); + var date = new DateTime(2023, 1, 15, 0, 0, 0, DateTimeKind.Utc); + + _ = builder.AppendFormat( + CultureInfo.InvariantCulture, + "Date: {0:yyyy-MM-dd}, Value: {1:X8}", + date, + 255 + ); + + _ = await Assert.That(builder.ToString()).IsEqualTo("Date: 2023-01-15, Value: 000000FF"); + } +} diff --git a/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.AppendIf.cs b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.AppendIf.cs new file mode 100644 index 0000000..9cd9c69 --- /dev/null +++ b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.AppendIf.cs @@ -0,0 +1,462 @@ +namespace NetEvolve.CodeBuilder.Tests.Unit; + +using System; + +public partial class CSharpCodeBuilderTests +{ + [Test] + public async Task AppendIf_Boolean_True_Condition_True_Should_Append_TrueString() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendIf(true, true); + + _ = await Assert.That(builder.ToString()).IsEqualTo("true"); + } + + [Test] + public async Task AppendIf_Boolean_False_Condition_True_Should_Append_FalseString() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendIf(true, false); + + _ = await Assert.That(builder.ToString()).IsEqualTo("false"); + } + + [Test] + public async Task AppendIf_Boolean_Condition_False_Should_Not_Append() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendIf(false, true); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task AppendIf_Boolean_Should_Return_Same_Instance() + { + var builder = new CSharpCodeBuilder(10); + + var result = builder.AppendIf(true, true); + + _ = await Assert.That(result).IsEqualTo(builder); + } + + [Test] + public async Task AppendIf_Char_Condition_True_Should_Append_Character() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendIf(true, 'x'); + + _ = await Assert.That(builder.ToString()).IsEqualTo("x"); + } + + [Test] + public async Task AppendIf_Char_Condition_False_Should_Not_Append() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendIf(false, 'x'); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task AppendIf_Char_Should_Handle_Special_Characters_When_Condition_True() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendIf(true, '{'); + + _ = await Assert.That(builder.ToString()).IsEqualTo("{" + Environment.NewLine); + } + + [Test] + public async Task AppendIf_Char_Should_Not_Handle_Special_Characters_When_Condition_False() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendIf(false, '{'); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task AppendIf_Char_Should_Return_Same_Instance() + { + var builder = new CSharpCodeBuilder(10); + + var result = builder.AppendIf(true, 'x'); + + _ = await Assert.That(result).IsEqualTo(builder); + } + + [Test] + public async Task AppendIf_Char_With_RepeatCount_Condition_True_Should_Append_Repeated_Character() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendIf(true, 'x', 5); + + _ = await Assert.That(builder.ToString()).IsEqualTo("xxxxx"); + } + + [Test] + public async Task AppendIf_Char_With_RepeatCount_Condition_False_Should_Not_Append() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendIf(false, 'x', 5); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task AppendIf_Char_With_RepeatCount_Should_Return_Same_Instance() + { + var builder = new CSharpCodeBuilder(10); + + var result = builder.AppendIf(true, 'x', 3); + + _ = await Assert.That(result).IsEqualTo(builder); + } + + [Test] + public async Task AppendIf_CharArray_Condition_True_Should_Append_All_Characters() + { + var builder = new CSharpCodeBuilder(10); + var chars = new[] { 'a', 'b', 'c' }; + + _ = builder.AppendIf(true, chars); + + _ = await Assert.That(builder.ToString()).IsEqualTo("abc"); + } + + [Test] + public async Task AppendIf_CharArray_Condition_False_Should_Not_Append() + { + var builder = new CSharpCodeBuilder(10); + var chars = new[] { 'a', 'b', 'c' }; + + _ = builder.AppendIf(false, chars); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task AppendIf_CharArray_Null_Condition_True_Should_Not_Append() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendIf(true, (char[]?)null); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task AppendIf_CharArray_Should_Return_Same_Instance() + { + var builder = new CSharpCodeBuilder(10); + var chars = new[] { 'a', 'b', 'c' }; + + var result = builder.AppendIf(true, chars); + + _ = await Assert.That(result).IsEqualTo(builder); + } + + [Test] + public async Task AppendIf_CharArray_With_StartIndex_And_Count_Condition_True_Should_Append_Specified_Characters() + { + var builder = new CSharpCodeBuilder(10); + var chars = new[] { 'a', 'b', 'c', 'd', 'e' }; + + _ = builder.AppendIf(true, chars, 1, 3); + + _ = await Assert.That(builder.ToString()).IsEqualTo("bcd"); + } + + [Test] + public async Task AppendIf_CharArray_With_StartIndex_And_Count_Condition_False_Should_Not_Append() + { + var builder = new CSharpCodeBuilder(10); + var chars = new[] { 'a', 'b', 'c', 'd', 'e' }; + + _ = builder.AppendIf(false, chars, 1, 3); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task AppendIf_CharArray_With_StartIndex_And_Count_Should_Return_Same_Instance() + { + var builder = new CSharpCodeBuilder(10); + var chars = new[] { 'a', 'b', 'c', 'd', 'e' }; + + var result = builder.AppendIf(true, chars, 1, 3); + + _ = await Assert.That(result).IsEqualTo(builder); + } + + [Test] + public async Task AppendIf_CharPointer_Condition_True_Should_Append_Characters() + { + var builder = new CSharpCodeBuilder(10); + var chars = "abc".ToCharArray(); + + unsafe + { + fixed (char* ptr = chars) + { + _ = builder.AppendIf(true, ptr, chars.Length); + } + } + + _ = await Assert.That(builder.ToString()).IsEqualTo("abc"); + } + + [Test] + public async Task AppendIf_CharPointer_Condition_False_Should_Not_Append() + { + var builder = new CSharpCodeBuilder(10); + var chars = "abc".ToCharArray(); + + unsafe + { + fixed (char* ptr = chars) + { + _ = builder.AppendIf(false, ptr, chars.Length); + } + } + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task AppendIf_CharPointer_Should_Return_Same_Instance() + { + var builder = new CSharpCodeBuilder(10); + var chars = "abc".ToCharArray(); + CSharpCodeBuilder result; + + unsafe + { + fixed (char* ptr = chars) + { + result = builder.AppendIf(true, ptr, chars.Length); + } + } + + _ = await Assert.That(result).IsEqualTo(builder); + } + + [Test] + public async Task AppendIf_ReadOnlyMemory_Condition_True_Should_Append_Characters() + { + var builder = new CSharpCodeBuilder(10); + var memory = "abc".AsMemory(); + + _ = builder.AppendIf(true, memory); + + _ = await Assert.That(builder.ToString()).IsEqualTo("abc"); + } + + [Test] + public async Task AppendIf_ReadOnlyMemory_Condition_False_Should_Not_Append() + { + var builder = new CSharpCodeBuilder(10); + var memory = "abc".AsMemory(); + + _ = builder.AppendIf(false, memory); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task AppendIf_ReadOnlyMemory_Empty_Condition_True_Should_Not_Append() + { + var builder = new CSharpCodeBuilder(10); + var memory = ReadOnlyMemory.Empty; + + _ = builder.AppendIf(true, memory); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task AppendIf_ReadOnlyMemory_Should_Return_Same_Instance() + { + var builder = new CSharpCodeBuilder(10); + var memory = "abc".AsMemory(); + + var result = builder.AppendIf(true, memory); + + _ = await Assert.That(result).IsEqualTo(builder); + } + + [Test] + public async Task AppendIf_ReadOnlyMemory_With_StartIndex_And_Count_Condition_True_Should_Append_Specified_Characters() + { + var builder = new CSharpCodeBuilder(10); + var memory = "abcde".AsMemory(); + + _ = builder.AppendIf(true, memory, 1, 3); + + _ = await Assert.That(builder.ToString()).IsEqualTo("bcd"); + } + + [Test] + public async Task AppendIf_ReadOnlyMemory_With_StartIndex_And_Count_Condition_False_Should_Not_Append() + { + var builder = new CSharpCodeBuilder(10); + var memory = "abcde".AsMemory(); + + _ = builder.AppendIf(false, memory, 1, 3); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task AppendIf_ReadOnlyMemory_With_StartIndex_And_Count_Should_Return_Same_Instance() + { + var builder = new CSharpCodeBuilder(10); + var memory = "abcde".AsMemory(); + + var result = builder.AppendIf(true, memory, 1, 3); + + _ = await Assert.That(result).IsEqualTo(builder); + } + + [Test] + public async Task AppendIf_String_Condition_True_Should_Append_Characters() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendIf(true, "abc"); + + _ = await Assert.That(builder.ToString()).IsEqualTo("abc"); + } + + [Test] + public async Task AppendIf_String_Condition_False_Should_Not_Append() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendIf(false, "abc"); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task AppendIf_String_Null_Condition_True_Should_Not_Append() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendIf(true, (string?)null); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task AppendIf_String_Empty_Condition_True_Should_Not_Append() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendIf(true, string.Empty); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task AppendIf_String_Should_Return_Same_Instance() + { + var builder = new CSharpCodeBuilder(10); + + var result = builder.AppendIf(true, "abc"); + + _ = await Assert.That(result).IsEqualTo(builder); + } + + [Test] + public async Task AppendIf_String_With_StartIndex_And_Count_Condition_True_Should_Append_Specified_Characters() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendIf(true, "abcdef", 1, 3); + + _ = await Assert.That(builder.ToString()).IsEqualTo("bcd"); + } + + [Test] + public async Task AppendIf_String_With_StartIndex_And_Count_Condition_False_Should_Not_Append() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendIf(false, "abcdef", 1, 3); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task AppendIf_String_With_StartIndex_And_Count_Should_Return_Same_Instance() + { + var builder = new CSharpCodeBuilder(10); + + var result = builder.AppendIf(true, "abcdef", 1, 3); + + _ = await Assert.That(result).IsEqualTo(builder); + } + + [Test] + public async Task AppendIf_Multiple_Calls_Should_Respect_Conditions() + { + var builder = new CSharpCodeBuilder(20); + + _ = builder + .AppendIf(true, "Hello") + .AppendIf(false, " Skipped") + .AppendIf(true, " World") + .AppendIf(true, '!'); + + _ = await Assert.That(builder.ToString()).IsEqualTo("Hello World!"); + } + + [Test] + public async Task AppendIf_Should_Apply_Indentation_When_Condition_True() + { + var builder = new CSharpCodeBuilder(10); + builder.IncrementIndent(); + _ = builder.AppendLine(); + + _ = builder.AppendIf(true, "test"); + + var expected = Environment.NewLine + " test"; + _ = await Assert.That(builder.ToString()).IsEqualTo(expected); + } + + [Test] + public async Task AppendIf_Should_Not_Apply_Indentation_When_Condition_False() + { + var builder = new CSharpCodeBuilder(10); + builder.IncrementIndent(); + _ = builder.AppendLine(); + + _ = builder.AppendIf(false, "test"); + + var expected = Environment.NewLine; + _ = await Assert.That(builder.ToString()).IsEqualTo(expected); + } + + [Test] + public async Task AppendIf_Chaining_Should_Work_Correctly() + { + var builder = new CSharpCodeBuilder(30); + + var result = builder.AppendIf(true, "a").AppendIf(false, "b").AppendIf(true, "c"); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo("ac"); + } +} diff --git a/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.AppendLine.cs b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.AppendLine.cs new file mode 100644 index 0000000..f49d893 --- /dev/null +++ b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.AppendLine.cs @@ -0,0 +1,265 @@ +namespace NetEvolve.CodeBuilder.Tests.Unit; + +using System; + +public partial class CSharpCodeBuilderTests +{ + [Test] + public async Task AppendLine_Empty_Should_Append_Newline() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendLine(); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo(Environment.NewLine); + } + + [Test] + public async Task AppendLine_String_Should_Append_String_With_Newline() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendLine("Hello World"); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo("Hello World" + Environment.NewLine); + } + + [Test] + public async Task AppendLine_String_Null_Should_Append_Only_Newline() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendLine((string?)null); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo(Environment.NewLine); + } + + [Test] + public async Task AppendLine_String_Empty_Should_Append_Only_Newline() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendLine(string.Empty); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo(Environment.NewLine); + } + + [Test] + public async Task AppendLine_ReadOnlyMemory_Should_Append_Characters_With_Newline() + { + var builder = new CSharpCodeBuilder(); + var memory = "Hello World".AsMemory(); + + var result = builder.AppendLine(memory); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo("Hello World" + Environment.NewLine); + } + + [Test] + public async Task AppendLine_ReadOnlyMemory_Empty_Should_Append_Only_Newline() + { + var builder = new CSharpCodeBuilder(); + var memory = ReadOnlyMemory.Empty; + + var result = builder.AppendLine(memory); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo(Environment.NewLine); + } + + [Test] + public async Task AppendLine_ReadOnlyMemory_With_StartIndex_And_Count_Should_Append_Subset_With_Newline() + { + var builder = new CSharpCodeBuilder(); + var memory = "Hello World".AsMemory(); + + var result = builder.AppendLine(memory, 6, 5); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo("World" + Environment.NewLine); + } + + [Test] + public async Task AppendLine_CharArray_Should_Append_Characters_With_Newline() + { + var builder = new CSharpCodeBuilder(); + var chars = "Hello".ToCharArray(); + + var result = builder.AppendLine(chars); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo("Hello" + Environment.NewLine); + } + + [Test] + public async Task AppendLine_CharArray_Null_Should_Append_Only_Newline() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendLine((char[]?)null); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo(Environment.NewLine); + } + + [Test] + public async Task AppendLine_CharArray_Empty_Should_Append_Only_Newline() + { + var builder = new CSharpCodeBuilder(); + var chars = Array.Empty(); + + var result = builder.AppendLine(chars); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo(Environment.NewLine); + } + + [Test] + public async Task AppendLine_CharArray_With_StartIndex_And_Count_Should_Append_Subset_With_Newline() + { + var builder = new CSharpCodeBuilder(); + var chars = "Hello World".ToCharArray(); + + var result = builder.AppendLine(chars, 6, 5); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo("World" + Environment.NewLine); + } + + [Test] + public async Task AppendLine_Char_Should_Append_Character_With_Newline() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendLine('X'); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo("X" + Environment.NewLine); + } + + [Test] + public async Task AppendLine_Char_With_RepeatCount_Should_Append_Repeated_Character_With_Newline() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendLine('X', 3); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo("XXX" + Environment.NewLine); + } + + [Test] + public async Task AppendLine_Bool_True_Should_Append_True_With_Newline() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendLine(true); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo("true" + Environment.NewLine); + } + + [Test] + public async Task AppendLine_Bool_False_Should_Append_False_With_Newline() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendLine(false); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo("false" + Environment.NewLine); + } + + [Test] + public async Task AppendLine_Multiple_Should_Create_Multiple_Lines() + { + var builder = new CSharpCodeBuilder(); + + _ = builder.AppendLine("First").AppendLine("Second").AppendLine("Third"); + + var expected = + "First" + + Environment.NewLine + + "Second" + + Environment.NewLine + + "Third" + + Environment.NewLine; + _ = await Assert.That(builder.ToString()).IsEqualTo(expected); + } + + [Test] + public async Task AppendLine_With_Indentation_Should_Apply_Indentation() + { + var builder = new CSharpCodeBuilder(); + builder.IncrementIndent(); + + _ = builder.AppendLine().AppendLine("Hello"); + + var expected = Environment.NewLine + " Hello" + Environment.NewLine; + _ = await Assert.That(builder.ToString()).IsEqualTo(expected); + } + + [Test] + public async Task AppendLine_Unsafe_CharPointer_Should_Append_Characters_With_Newline() + { + var builder = new CSharpCodeBuilder(); + var text = "Hello"; + CSharpCodeBuilder result; + string builderResult; + + unsafe + { + fixed (char* ptr = text) + { + result = builder.AppendLine(ptr, text.Length); + } + } + + builderResult = builder.ToString(); + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builderResult).IsEqualTo("Hello" + Environment.NewLine); + } + + [Test] + public async Task AppendLine_Unsafe_CharPointer_Null_Should_Append_Only_Newline() + { + var builder = new CSharpCodeBuilder(); + CSharpCodeBuilder result; + string builderResult; + + unsafe + { + result = builder.AppendLine(null, 0); + } + + builderResult = builder.ToString(); + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builderResult).IsEqualTo(Environment.NewLine); + } + + [Test] + public async Task AppendLine_Unsafe_CharPointer_Negative_Length_Should_Append_Only_Newline() + { + var builder = new CSharpCodeBuilder(); + var text = "Hello"; + CSharpCodeBuilder result; + string builderResult; + + unsafe + { + fixed (char* ptr = text) + { + result = builder.AppendLine(ptr, -1); + } + } + + builderResult = builder.ToString(); + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builderResult).IsEqualTo(Environment.NewLine); + } +} diff --git a/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.AppendLineIf.cs b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.AppendLineIf.cs new file mode 100644 index 0000000..ec28c47 --- /dev/null +++ b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.AppendLineIf.cs @@ -0,0 +1,501 @@ +namespace NetEvolve.CodeBuilder.Tests.Unit; + +using System; + +public partial class CSharpCodeBuilderTests +{ + [Test] + public async Task AppendLineIf_NoParameters_Condition_True_Should_Append_Newline() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendLineIf(true); + + _ = await Assert.That(builder.ToString()).IsEqualTo(Environment.NewLine); + } + + [Test] + public async Task AppendLineIf_NoParameters_Condition_False_Should_Not_Append() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendLineIf(false); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task AppendLineIf_NoParameters_Should_Return_Same_Instance() + { + var builder = new CSharpCodeBuilder(10); + + var result = builder.AppendLineIf(true); + + _ = await Assert.That(result).IsEqualTo(builder); + } + + [Test] + public async Task AppendLineIf_String_Condition_True_Should_Append_String_With_Newline() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendLineIf(true, "test"); + + _ = await Assert.That(builder.ToString()).IsEqualTo("test" + Environment.NewLine); + } + + [Test] + public async Task AppendLineIf_String_Condition_False_Should_Not_Append() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendLineIf(false, "test"); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task AppendLineIf_String_Null_Condition_True_Should_Append_Only_Newline() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendLineIf(true, (string?)null); + + _ = await Assert.That(builder.ToString()).IsEqualTo(Environment.NewLine); + } + + [Test] + public async Task AppendLineIf_String_Empty_Condition_True_Should_Append_Only_Newline() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendLineIf(true, string.Empty); + + _ = await Assert.That(builder.ToString()).IsEqualTo(Environment.NewLine); + } + + [Test] + public async Task AppendLineIf_String_Should_Return_Same_Instance() + { + var builder = new CSharpCodeBuilder(10); + + var result = builder.AppendLineIf(true, "test"); + + _ = await Assert.That(result).IsEqualTo(builder); + } + + [Test] + public async Task AppendLineIf_ReadOnlyMemory_Condition_True_Should_Append_Characters_With_Newline() + { + var builder = new CSharpCodeBuilder(10); + var memory = "abc".AsMemory(); + + _ = builder.AppendLineIf(true, memory); + + _ = await Assert.That(builder.ToString()).IsEqualTo("abc" + Environment.NewLine); + } + + [Test] + public async Task AppendLineIf_ReadOnlyMemory_Condition_False_Should_Not_Append() + { + var builder = new CSharpCodeBuilder(10); + var memory = "abc".AsMemory(); + + _ = builder.AppendLineIf(false, memory); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task AppendLineIf_ReadOnlyMemory_Empty_Condition_True_Should_Append_Only_Newline() + { + var builder = new CSharpCodeBuilder(10); + var memory = ReadOnlyMemory.Empty; + + _ = builder.AppendLineIf(true, memory); + + _ = await Assert.That(builder.ToString()).IsEqualTo(Environment.NewLine); + } + + [Test] + public async Task AppendLineIf_ReadOnlyMemory_Should_Return_Same_Instance() + { + var builder = new CSharpCodeBuilder(10); + var memory = "abc".AsMemory(); + + var result = builder.AppendLineIf(true, memory); + + _ = await Assert.That(result).IsEqualTo(builder); + } + + [Test] + public async Task AppendLineIf_ReadOnlyMemory_With_StartIndex_And_Count_Condition_True_Should_Append_Specified_Characters_With_Newline() + { + var builder = new CSharpCodeBuilder(10); + var memory = "abcde".AsMemory(); + + _ = builder.AppendLineIf(true, memory, 1, 3); + + _ = await Assert.That(builder.ToString()).IsEqualTo("bcd" + Environment.NewLine); + } + + [Test] + public async Task AppendLineIf_ReadOnlyMemory_With_StartIndex_And_Count_Condition_False_Should_Not_Append() + { + var builder = new CSharpCodeBuilder(10); + var memory = "abcde".AsMemory(); + + _ = builder.AppendLineIf(false, memory, 1, 3); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task AppendLineIf_ReadOnlyMemory_With_StartIndex_And_Count_Should_Return_Same_Instance() + { + var builder = new CSharpCodeBuilder(10); + var memory = "abcde".AsMemory(); + + var result = builder.AppendLineIf(true, memory, 1, 3); + + _ = await Assert.That(result).IsEqualTo(builder); + } + + [Test] + public async Task AppendLineIf_CharArray_Condition_True_Should_Append_All_Characters_With_Newline() + { + var builder = new CSharpCodeBuilder(10); + var chars = new[] { 'a', 'b', 'c' }; + + _ = builder.AppendLineIf(true, chars); + + _ = await Assert.That(builder.ToString()).IsEqualTo("abc" + Environment.NewLine); + } + + [Test] + public async Task AppendLineIf_CharArray_Condition_False_Should_Not_Append() + { + var builder = new CSharpCodeBuilder(10); + var chars = new[] { 'a', 'b', 'c' }; + + _ = builder.AppendLineIf(false, chars); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task AppendLineIf_CharArray_Null_Condition_True_Should_Append_Only_Newline() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendLineIf(true, (char[]?)null); + + _ = await Assert.That(builder.ToString()).IsEqualTo(Environment.NewLine); + } + + [Test] + public async Task AppendLineIf_CharArray_Empty_Condition_True_Should_Append_Only_Newline() + { + var builder = new CSharpCodeBuilder(10); + var chars = Array.Empty(); + + _ = builder.AppendLineIf(true, chars); + + _ = await Assert.That(builder.ToString()).IsEqualTo(Environment.NewLine); + } + + [Test] + public async Task AppendLineIf_CharArray_Should_Return_Same_Instance() + { + var builder = new CSharpCodeBuilder(10); + var chars = new[] { 'a', 'b', 'c' }; + + var result = builder.AppendLineIf(true, chars); + + _ = await Assert.That(result).IsEqualTo(builder); + } + + [Test] + public async Task AppendLineIf_CharArray_With_StartIndex_And_Count_Condition_True_Should_Append_Specified_Characters_With_Newline() + { + var builder = new CSharpCodeBuilder(10); + var chars = new[] { 'a', 'b', 'c', 'd', 'e' }; + + _ = builder.AppendLineIf(true, chars, 1, 3); + + _ = await Assert.That(builder.ToString()).IsEqualTo("bcd" + Environment.NewLine); + } + + [Test] + public async Task AppendLineIf_CharArray_With_StartIndex_And_Count_Condition_False_Should_Not_Append() + { + var builder = new CSharpCodeBuilder(10); + var chars = new[] { 'a', 'b', 'c', 'd', 'e' }; + + _ = builder.AppendLineIf(false, chars, 1, 3); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task AppendLineIf_CharArray_With_StartIndex_And_Count_Should_Return_Same_Instance() + { + var builder = new CSharpCodeBuilder(10); + var chars = new[] { 'a', 'b', 'c', 'd', 'e' }; + + var result = builder.AppendLineIf(true, chars, 1, 3); + + _ = await Assert.That(result).IsEqualTo(builder); + } + + [Test] + public async Task AppendLineIf_CharPointer_Condition_True_Should_Append_Characters_With_Newline() + { + var builder = new CSharpCodeBuilder(10); + var chars = "abc".ToCharArray(); + + unsafe + { + fixed (char* ptr = chars) + { + _ = builder.AppendLineIf(true, ptr, chars.Length); + } + } + + _ = await Assert.That(builder.ToString()).IsEqualTo("abc" + Environment.NewLine); + } + + [Test] + public async Task AppendLineIf_CharPointer_Condition_False_Should_Not_Append() + { + var builder = new CSharpCodeBuilder(10); + var chars = "abc".ToCharArray(); + + unsafe + { + fixed (char* ptr = chars) + { + _ = builder.AppendLineIf(false, ptr, chars.Length); + } + } + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task AppendLineIf_CharPointer_Should_Return_Same_Instance() + { + var builder = new CSharpCodeBuilder(10); + var chars = "abc".ToCharArray(); + CSharpCodeBuilder result; + + unsafe + { + fixed (char* ptr = chars) + { + result = builder.AppendLineIf(true, ptr, chars.Length); + } + } + + _ = await Assert.That(result).IsEqualTo(builder); + } + + [Test] + public async Task AppendLineIf_Char_Condition_True_Should_Append_Character_With_Newline() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendLineIf(true, 'x'); + + _ = await Assert.That(builder.ToString()).IsEqualTo("x" + Environment.NewLine); + } + + [Test] + public async Task AppendLineIf_Char_Condition_False_Should_Not_Append() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendLineIf(false, 'x'); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task AppendLineIf_Char_Should_Handle_Special_Characters_When_Condition_True() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendLineIf(true, '{'); + + _ = await Assert + .That(builder.ToString()) + .IsEqualTo("{" + Environment.NewLine + Environment.NewLine); + } + + [Test] + public async Task AppendLineIf_Char_Should_Not_Handle_Special_Characters_When_Condition_False() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendLineIf(false, '{'); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task AppendLineIf_Char_Should_Return_Same_Instance() + { + var builder = new CSharpCodeBuilder(10); + + var result = builder.AppendLineIf(true, 'x'); + + _ = await Assert.That(result).IsEqualTo(builder); + } + + [Test] + public async Task AppendLineIf_Char_With_RepeatCount_Condition_True_Should_Append_Repeated_Character_With_Newline() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendLineIf(true, 'x', 5); + + _ = await Assert.That(builder.ToString()).IsEqualTo("xxxxx" + Environment.NewLine); + } + + [Test] + public async Task AppendLineIf_Char_With_RepeatCount_Condition_False_Should_Not_Append() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendLineIf(false, 'x', 5); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task AppendLineIf_Char_With_RepeatCount_Should_Return_Same_Instance() + { + var builder = new CSharpCodeBuilder(10); + + var result = builder.AppendLineIf(true, 'x', 3); + + _ = await Assert.That(result).IsEqualTo(builder); + } + + [Test] + public async Task AppendLineIf_Boolean_True_Condition_True_Should_Append_TrueString_With_Newline() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendLineIf(true, true); + + _ = await Assert.That(builder.ToString()).IsEqualTo("true" + Environment.NewLine); + } + + [Test] + public async Task AppendLineIf_Boolean_False_Condition_True_Should_Append_FalseString_With_Newline() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendLineIf(true, false); + + _ = await Assert.That(builder.ToString()).IsEqualTo("false" + Environment.NewLine); + } + + [Test] + public async Task AppendLineIf_Boolean_Condition_False_Should_Not_Append() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.AppendLineIf(false, true); + + _ = await Assert.That(builder.ToString()).IsEqualTo(""); + } + + [Test] + public async Task AppendLineIf_Boolean_Should_Return_Same_Instance() + { + var builder = new CSharpCodeBuilder(10); + + var result = builder.AppendLineIf(true, true); + + _ = await Assert.That(result).IsEqualTo(builder); + } + + [Test] + public async Task AppendLineIf_Multiple_Calls_Should_Respect_Conditions() + { + var builder = new CSharpCodeBuilder(50); + + _ = builder + .AppendLineIf(true, "Line 1") + .AppendLineIf(false, "Skipped Line") + .AppendLineIf(true, "Line 2") + .AppendLineIf(true); + + var expected = + "Line 1" + Environment.NewLine + "Line 2" + Environment.NewLine + Environment.NewLine; + + _ = await Assert.That(builder.ToString()).IsEqualTo(expected); + } + + [Test] + public async Task AppendLineIf_Should_Apply_Indentation_When_Condition_True() + { + var builder = new CSharpCodeBuilder(10); + builder.IncrementIndent(); + _ = builder.AppendLine(); + + _ = builder.AppendLineIf(true, "test"); + + var expected = Environment.NewLine + " test" + Environment.NewLine; + _ = await Assert.That(builder.ToString()).IsEqualTo(expected); + } + + [Test] + public async Task AppendLineIf_Should_Not_Apply_Indentation_When_Condition_False() + { + var builder = new CSharpCodeBuilder(10); + builder.IncrementIndent(); + _ = builder.AppendLine(); + + _ = builder.AppendLineIf(false, "test"); + + var expected = Environment.NewLine; + _ = await Assert.That(builder.ToString()).IsEqualTo(expected); + } + + [Test] + public async Task AppendLineIf_Chaining_Should_Work_Correctly() + { + var builder = new CSharpCodeBuilder(50); + + var result = builder + .AppendLineIf(true, "a") + .AppendLineIf(false, "b") + .AppendLineIf(true, "c"); + + _ = await Assert.That(result).IsEqualTo(builder); + + var expected = "a" + Environment.NewLine + "c" + Environment.NewLine; + _ = await Assert.That(builder.ToString()).IsEqualTo(expected); + } + + [Test] + public async Task AppendLineIf_Mixed_With_AppendIf_Should_Work_Correctly() + { + var builder = new CSharpCodeBuilder(50); + + _ = builder + .AppendIf(true, "Start: ") + .AppendLineIf(true, "First Line") + .AppendIf(false, "Skipped") + .AppendLineIf(true, "Second Line") + .AppendIf(true, "End"); + + var expected = + "Start: First Line" + Environment.NewLine + "Second Line" + Environment.NewLine + "End"; + + _ = await Assert.That(builder.ToString()).IsEqualTo(expected); + } +} diff --git a/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Clear.cs b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Clear.cs new file mode 100644 index 0000000..2e76e80 --- /dev/null +++ b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Clear.cs @@ -0,0 +1,96 @@ +namespace NetEvolve.CodeBuilder.Tests.Unit; + +using System; + +public partial class CSharpCodeBuilderTests +{ + [Test] + public async Task Clear_Empty_Builder_Should_Return_Same_Instance() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.Clear(); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.Length).IsEqualTo(0); + _ = await Assert.That(builder.ToString()).IsEqualTo(string.Empty); + } + + [Test] + public async Task Clear_Builder_With_Content_Should_Remove_All_Content() + { + var builder = new CSharpCodeBuilder(); + _ = builder.Append("Hello World"); + + var result = builder.Clear(); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.Length).IsEqualTo(0); + _ = await Assert.That(builder.ToString()).IsEqualTo(string.Empty); + } + + [Test] + public async Task Clear_Should_Reset_Indentation_Level() + { + var builder = new CSharpCodeBuilder(); + builder.IncrementIndent(); + builder.IncrementIndent(); + _ = builder.AppendLine().Append("Hello"); + + _ = builder.Clear(); + _ = builder.AppendLine().Append("World"); + + var result = builder.ToString(); + _ = await Assert.That(result).IsEqualTo(Environment.NewLine + "World"); + } + + [Test] + public async Task Clear_Should_Allow_Method_Chaining() + { + var builder = new CSharpCodeBuilder(); + _ = builder.Append("Hello"); + + var result = builder.Clear().Append("World"); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo("World"); + } + + [Test] + public async Task Clear_Multiple_Times_Should_Work() + { + var builder = new CSharpCodeBuilder(); + _ = builder.Append("First"); + _ = builder.Clear(); + _ = builder.Append("Second"); + _ = builder.Clear(); + _ = builder.Append("Third"); + + var result = builder.ToString(); + + _ = await Assert.That(result).IsEqualTo("Third"); + } + + [Test] + public async Task Clear_Should_Preserve_UseTabs_Setting() + { + var builder = new CSharpCodeBuilder { UseTabs = true }; + _ = builder.Append("Hello"); + + _ = builder.Clear(); + + _ = await Assert.That(builder.UseTabs).IsEqualTo(true); + } + + [Test] + public async Task Clear_Should_Preserve_Capacity() + { + var builder = new CSharpCodeBuilder(100); + var originalCapacity = builder.Capacity; + _ = builder.Append("Hello World"); + + _ = builder.Clear(); + + _ = await Assert.That(builder.Capacity).IsEqualTo(originalCapacity); + } +} diff --git a/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Constructor.cs b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Constructor.cs new file mode 100644 index 0000000..5706a3f --- /dev/null +++ b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Constructor.cs @@ -0,0 +1,47 @@ +namespace NetEvolve.CodeBuilder.Tests.Unit; + +using System; + +public partial class CSharpCodeBuilderTests +{ + [Test] + public async Task Constructor_Default_Should_Create_Instance() + { + var builder = new CSharpCodeBuilder(); + + _ = await Assert.That(builder).IsNotNull(); + _ = await Assert.That(builder.Length).IsEqualTo(0); + _ = await Assert.That(builder.Capacity).IsGreaterThan(0); + _ = await Assert.That(builder.UseTabs).IsEqualTo(false); + } + + [Test] + public async Task Constructor_WithCapacity_Should_Create_Instance_With_Specified_Capacity() + { + var initialCapacity = 100; + var builder = new CSharpCodeBuilder(initialCapacity); + + _ = await Assert.That(builder).IsNotNull(); + _ = await Assert.That(builder.Length).IsEqualTo(0); + _ = await Assert.That(builder.Capacity).IsGreaterThanOrEqualTo(initialCapacity); + _ = await Assert.That(builder.UseTabs).IsEqualTo(false); + } + + [Test] + public async Task Constructor_WithZeroCapacity_Should_Create_Instance() + { + var builder = new CSharpCodeBuilder(0); + + _ = await Assert.That(builder).IsNotNull(); + _ = await Assert.That(builder.Length).IsEqualTo(0); + _ = await Assert.That(builder.UseTabs).IsEqualTo(false); + } + + [Test] + public async Task Constructor_WithNegativeCapacity_Should_Throw_ArgumentOutOfRangeException() => + _ = await Assert.ThrowsAsync(() => + { + _ = new CSharpCodeBuilder(-1); + return Task.CompletedTask; + }); +} diff --git a/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Documentation.cs b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Documentation.cs new file mode 100644 index 0000000..ef49ba0 --- /dev/null +++ b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Documentation.cs @@ -0,0 +1,474 @@ +namespace NetEvolve.CodeBuilder.Tests.Unit; + +using System; +using System.Collections.Generic; +using System.Linq; + +public partial class CSharpCodeBuilderTests +{ + [Test] + public async Task AppendXmlDoc_WithContent_Should_AppendSingleLineComment() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendXmlDoc("This is a comment"); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert + .That(builder.ToString()) + .IsEqualTo("/// This is a comment" + Environment.NewLine); + } + + [Test] + public async Task AppendXmlDoc_WithNullContent_Should_NotAppendAnything() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendXmlDoc(null); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo(string.Empty); + } + + [Test] + public async Task AppendXmlDoc_WithEmptyContent_Should_NotAppendAnything() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendXmlDoc(""); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo(string.Empty); + } + + [Test] + public async Task AppendXmlDocSummary_WithSingleLine_Should_AppendSummaryElement() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendXmlDocSummary("This is a summary"); + + var expected = + "/// " + + Environment.NewLine + + "/// This is a summary" + + Environment.NewLine + + "/// " + + Environment.NewLine; + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo(expected); + } + + [Test] + public async Task AppendXmlDocSummary_WithMultipleLines_Should_AppendSummaryElementWithAllLines() + { + var builder = new CSharpCodeBuilder(); + var summaryLines = new[] { "First line", "Second line", "Third line" }; + + var result = builder.AppendXmlDocSummary(summaryLines); + + var expected = + "/// " + + Environment.NewLine + + "/// First line" + + Environment.NewLine + + "/// Second line" + + Environment.NewLine + + "/// Third line" + + Environment.NewLine + + "/// " + + Environment.NewLine; + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo(expected); + } + + [Test] + public async Task AppendXmlDocSummary_WithEmptyAndNullLines_Should_SkipEmptyLines() + { + var builder = new CSharpCodeBuilder(); + var summaryLines = new List { "First line", "", null, "Last line" }; + + var result = builder.AppendXmlDocSummary(summaryLines.Where(x => x is not null)!); + + var expected = + "/// " + + Environment.NewLine + + "/// First line" + + Environment.NewLine + + "/// Last line" + + Environment.NewLine + + "/// " + + Environment.NewLine; + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo(expected); + } + + [Test] + public async Task AppendXmlDocParam_WithValidParameters_Should_AppendParamElement() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendXmlDocParam("paramName", "Parameter description"); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert + .That(builder.ToString()) + .IsEqualTo( + "/// Parameter description" + Environment.NewLine + ); + } + + [Test] + public async Task AppendXmlDocParam_WithNullName_Should_NotAppendAnything() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendXmlDocParam(null, "Parameter description"); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo(string.Empty); + } + + [Test] + public async Task AppendXmlDocParams_WithMultipleParameters_Should_AppendAllParamElements() + { + var builder = new CSharpCodeBuilder(); + var parameters = new List<(string Name, string Description)> + { + ("param1", "First parameter"), + ("param2", "Second parameter"), + }; + + var result = builder.AppendXmlDocParams(parameters); + + var expected = + "/// First parameter" + + Environment.NewLine + + "/// Second parameter" + + Environment.NewLine; + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo(expected); + } + + [Test] + public async Task AppendXmlDocReturns_WithDescription_Should_AppendReturnsElement() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendXmlDocReturns("The result value"); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert + .That(builder.ToString()) + .IsEqualTo("/// The result value" + Environment.NewLine); + } + + [Test] + public async Task AppendXmlDocRemarks_WithSingleLine_Should_AppendRemarksElement() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendXmlDocRemarks("This is a remark"); + + var expected = + "/// " + + Environment.NewLine + + "/// This is a remark" + + Environment.NewLine + + "/// " + + Environment.NewLine; + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo(expected); + } + + [Test] + public async Task AppendXmlDocException_WithValidParameters_Should_AppendExceptionElement() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendXmlDocException( + "ArgumentNullException", + "Thrown when argument is null" + ); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert + .That(builder.ToString()) + .IsEqualTo( + "/// Thrown when argument is null" + + Environment.NewLine + ); + } + + [Test] + public async Task AppendXmlDocExample_WithContent_Should_AppendExampleElement() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendXmlDocExample("var example = new Example();"); + + var expected = + "/// " + + Environment.NewLine + + "/// var example = new Example();" + + Environment.NewLine + + "/// " + + Environment.NewLine; + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo(expected); + } + + [Test] + public async Task AppendXmlDocSee_WithCref_Should_AppendSeeElement() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendXmlDocSee("System.String"); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert + .That(builder.ToString()) + .IsEqualTo("/// " + Environment.NewLine); + } + + [Test] + public async Task AppendXmlDocSeeAlso_WithCref_Should_AppendSeeAlsoElement() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendXmlDocSeeAlso("System.Object"); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert + .That(builder.ToString()) + .IsEqualTo("/// " + Environment.NewLine); + } + + [Test] + public async Task AppendXmlDocValue_WithDescription_Should_AppendValueElement() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendXmlDocValue("The value of the property"); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert + .That(builder.ToString()) + .IsEqualTo("/// The value of the property" + Environment.NewLine); + } + + [Test] + public async Task AppendXmlDocTypeParam_WithValidParameters_Should_AppendTypeParamElement() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendXmlDocTypeParam("T", "The generic type parameter"); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert + .That(builder.ToString()) + .IsEqualTo( + "/// The generic type parameter" + + Environment.NewLine + ); + } + + [Test] + public async Task AppendXmlDocInheritDoc_WithoutCref_Should_AppendInheritDocElement() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendXmlDocInheritDoc(); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert + .That(builder.ToString()) + .IsEqualTo("/// " + Environment.NewLine); + } + + [Test] + public async Task AppendXmlDocInheritDoc_WithCref_Should_AppendInheritDocElementWithCref() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendXmlDocInheritDoc("BaseClass.Method"); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert + .That(builder.ToString()) + .IsEqualTo("/// " + Environment.NewLine); + } + + [Test] + public async Task AppendXmlDocCustomElement_WithContentAndAttributes_Should_AppendCustomElement() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendXmlDocCustomElement( + "custom", + "Custom content", + "attr=\"value\"" + ); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert + .That(builder.ToString()) + .IsEqualTo("/// Custom content" + Environment.NewLine); + } + + [Test] + public async Task AppendXmlDocCustomElement_WithoutContent_Should_AppendSelfClosingElement() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.AppendXmlDocCustomElement("custom", null, "attr=\"value\""); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert + .That(builder.ToString()) + .IsEqualTo("/// " + Environment.NewLine); + } + + [Test] + public async Task XmlDocumentationMethods_Should_SupportMethodChaining() + { + var builder = new CSharpCodeBuilder(); + + var result = builder + .AppendXmlDocSummary("Method summary") + .AppendXmlDocParam("param1", "First parameter") + .AppendXmlDocParam("param2", "Second parameter") + .AppendXmlDocReturns("Return value description") + .AppendXmlDocRemarks("Additional remarks"); + + var expected = + "/// " + + Environment.NewLine + + "/// Method summary" + + Environment.NewLine + + "/// " + + Environment.NewLine + + "/// First parameter" + + Environment.NewLine + + "/// Second parameter" + + Environment.NewLine + + "/// Return value description" + + Environment.NewLine + + "/// " + + Environment.NewLine + + "/// Additional remarks" + + Environment.NewLine + + "/// " + + Environment.NewLine; + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo(expected); + } + + [Test] + public async Task XmlDocumentationMethods_WithIndentation_Should_RespectIndentation() + { + var builder = new CSharpCodeBuilder(); + builder.IncrementIndent(); + + var result = builder + .AppendXmlDocSummary("Indented summary") + .AppendXmlDocParam("param", "Parameter description"); + + var expected = + " /// " + + Environment.NewLine + + " /// Indented summary" + + Environment.NewLine + + " /// " + + Environment.NewLine + + " /// Parameter description" + + Environment.NewLine; + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo(expected); + } + + [Test] + public async Task XmlDocumentationMethods_AfterContent_Should_StartOnNewLine() + { + var builder = new CSharpCodeBuilder(); + + var result = builder + .Append("public class MyClass") + .AppendXmlDocSummary("Method summary") + .AppendXmlDocParam("param", "Parameter description") + .Append("public void MyMethod(string param) { }"); + + var expected = + "public class MyClass" + + Environment.NewLine + + "/// " + + Environment.NewLine + + "/// Method summary" + + Environment.NewLine + + "/// " + + Environment.NewLine + + "/// Parameter description" + + Environment.NewLine + + "public void MyMethod(string param) { }"; + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo(expected); + } + + [Test] + public async Task XmlDocumentationMethods_AtStartOfBuilder_Should_NotAddExtraNewLine() + { + var builder = new CSharpCodeBuilder(); + + var result = builder + .AppendXmlDocSummary("Method summary") + .AppendXmlDocParam("param", "Parameter description"); + + var expected = + "/// " + + Environment.NewLine + + "/// Method summary" + + Environment.NewLine + + "/// " + + Environment.NewLine + + "/// Parameter description" + + Environment.NewLine; + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo(expected); + } + + [Test] + public async Task XmlDocumentationMethods_AfterNewLine_Should_NotAddExtraNewLine() + { + var builder = new CSharpCodeBuilder(); + + var result = builder + .AppendLine("public class MyClass") + .AppendXmlDocSummary("Method summary") + .AppendXmlDocParam("param", "Parameter description"); + + var expected = + "public class MyClass" + + Environment.NewLine + + "/// " + + Environment.NewLine + + "/// Method summary" + + Environment.NewLine + + "/// " + + Environment.NewLine + + "/// Parameter description" + + Environment.NewLine; + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo(expected); + } +} diff --git a/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.EnsureCapacity.cs b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.EnsureCapacity.cs new file mode 100644 index 0000000..1d507dd --- /dev/null +++ b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.EnsureCapacity.cs @@ -0,0 +1,103 @@ +namespace NetEvolve.CodeBuilder.Tests.Unit; + +using System; + +public partial class CSharpCodeBuilderTests +{ + [Test] + public async Task EnsureCapacity_Smaller_Than_Current_Should_Not_Change_Capacity() + { + var builder = new CSharpCodeBuilder(100); + var originalCapacity = builder.Capacity; + + var result = builder.EnsureCapacity(50); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.Capacity).IsEqualTo(originalCapacity); + } + + [Test] + public async Task EnsureCapacity_Larger_Than_Current_Should_Increase_Capacity() + { + var builder = new CSharpCodeBuilder(10); + var originalCapacity = builder.Capacity; + + var result = builder.EnsureCapacity(200); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.Capacity).IsGreaterThanOrEqualTo(200); + _ = await Assert.That(builder.Capacity).IsGreaterThan(originalCapacity); + } + + [Test] + public async Task EnsureCapacity_Equal_To_Current_Should_Not_Change_Capacity() + { + var builder = new CSharpCodeBuilder(100); + var originalCapacity = builder.Capacity; + + var result = builder.EnsureCapacity(originalCapacity); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.Capacity).IsEqualTo(originalCapacity); + } + + [Test] + public async Task EnsureCapacity_Zero_Should_Not_Change_Capacity() + { + var builder = new CSharpCodeBuilder(50); + var originalCapacity = builder.Capacity; + + var result = builder.EnsureCapacity(0); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.Capacity).IsEqualTo(originalCapacity); + } + + [Test] + public async Task EnsureCapacity_Should_Not_Affect_Content() + { + var builder = new CSharpCodeBuilder(); + _ = builder.Append("Hello World"); + var originalContent = builder.ToString(); + + _ = builder.EnsureCapacity(200); + + _ = await Assert.That(builder.ToString()).IsEqualTo(originalContent); + _ = await Assert.That(builder.Length).IsEqualTo(originalContent.Length); + } + + [Test] + public async Task EnsureCapacity_Should_Allow_Method_Chaining() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.EnsureCapacity(100).Append("Hello"); + + _ = await Assert.That(result).IsEqualTo(builder); + _ = await Assert.That(builder.ToString()).IsEqualTo("Hello"); + } + + [Test] + public async Task EnsureCapacity_Multiple_Calls_Should_Work() + { + var builder = new CSharpCodeBuilder(10); + + _ = builder.EnsureCapacity(50); + var capacity50 = builder.Capacity; + + _ = builder.EnsureCapacity(100); + var capacity100 = builder.Capacity; + + _ = await Assert.That(capacity100).IsGreaterThanOrEqualTo(100); + _ = await Assert.That(capacity100).IsGreaterThanOrEqualTo(capacity50); + } + + [Test] + public async Task EnsureCapacity_Negative_Value_Should_Throw_ArgumentOutOfRangeException() => + _ = await Assert.ThrowsAsync(() => + { + var builder = new CSharpCodeBuilder(); + _ = builder.EnsureCapacity(-1); + return Task.CompletedTask; + }); +} diff --git a/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Indentation.cs b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Indentation.cs new file mode 100644 index 0000000..793af39 --- /dev/null +++ b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Indentation.cs @@ -0,0 +1,167 @@ +namespace NetEvolve.CodeBuilder.Tests.Unit; + +using System; + +public partial class CSharpCodeBuilderTests +{ + [Test] + public async Task IncrementIndent_Should_Increase_Indentation_Level() + { + var builder = new CSharpCodeBuilder(); + + builder.IncrementIndent(); + _ = builder.AppendLine().Append("Hello"); + + var result = builder.ToString(); + _ = await Assert.That(result).IsEqualTo(Environment.NewLine + " Hello"); + } + + [Test] + public async Task IncrementIndent_Multiple_Times_Should_Increase_Indentation_Level() + { + var builder = new CSharpCodeBuilder(); + + builder.IncrementIndent(); + builder.IncrementIndent(); + _ = builder.AppendLine().Append("Hello"); + + var result = builder.ToString(); + _ = await Assert.That(result).IsEqualTo(Environment.NewLine + " Hello"); // 8 spaces (2 * 4) + } + + [Test] + public async Task IncrementIndent_With_Tabs_Should_Use_Tab_Characters() + { + var builder = new CSharpCodeBuilder { UseTabs = true }; + + builder.IncrementIndent(); + _ = builder.AppendLine().Append("Hello"); + + var result = builder.ToString(); + _ = await Assert.That(result).IsEqualTo(Environment.NewLine + "\tHello"); + } + + [Test] + public async Task IncrementIndent_Multiple_With_Tabs_Should_Use_Multiple_Tab_Characters() + { + var builder = new CSharpCodeBuilder { UseTabs = true }; + + builder.IncrementIndent(); + builder.IncrementIndent(); + builder.IncrementIndent(); + _ = builder.AppendLine().Append("Hello"); + + var result = builder.ToString(); + _ = await Assert.That(result).IsEqualTo(Environment.NewLine + "\t\t\tHello"); + } + + [Test] + public async Task DecrementIndent_Should_Decrease_Indentation_Level() + { + var builder = new CSharpCodeBuilder(); + builder.IncrementIndent(); + builder.IncrementIndent(); + + builder.DecrementIndent(); + _ = builder.AppendLine().Append("Hello"); + + var result = builder.ToString(); + _ = await Assert.That(result).IsEqualTo(Environment.NewLine + " Hello"); // 4 spaces (1 * 4) + } + + [Test] + public async Task DecrementIndent_To_Zero_Should_Remove_All_Indentation() + { + var builder = new CSharpCodeBuilder(); + builder.IncrementIndent(); + + builder.DecrementIndent(); + _ = builder.AppendLine().Append("Hello"); + + var result = builder.ToString(); + _ = await Assert.That(result).IsEqualTo(Environment.NewLine + "Hello"); + } + + [Test] + public async Task DecrementIndent_Below_Zero_Should_Handle_Gracefully() + { + var builder = new CSharpCodeBuilder(); + + builder.DecrementIndent(); + _ = builder.AppendLine().Append("Hello"); + + var result = builder.ToString(); + _ = await Assert.That(result).IsEqualTo(Environment.NewLine + "Hello"); + } + + [Test] + public async Task IncrementIndent_And_DecrementIndent_Should_Balance() + { + var builder = new CSharpCodeBuilder(); + + builder.IncrementIndent(); + builder.IncrementIndent(); + builder.DecrementIndent(); + _ = builder.AppendLine().Append("Hello"); + + var result = builder.ToString(); + _ = await Assert.That(result).IsEqualTo(Environment.NewLine + " Hello"); // 4 spaces (1 * 4) + } + + [Test] + public async Task IncrementIndent_Should_Not_Affect_Existing_Content() + { + var builder = new CSharpCodeBuilder(); + _ = builder.Append("Hello"); + + builder.IncrementIndent(); + _ = builder.AppendLine().Append("World"); + + var result = builder.ToString(); + _ = await Assert.That(result).IsEqualTo("Hello" + Environment.NewLine + " World"); + } + + [Test] + public async Task DecrementIndent_Should_Not_Affect_Existing_Content() + { + var builder = new CSharpCodeBuilder(); + builder.IncrementIndent(); + _ = builder.AppendLine().Append("Hello"); + + builder.DecrementIndent(); + _ = builder.AppendLine().Append("World"); + + var result = builder.ToString(); + _ = await Assert + .That(result) + .IsEqualTo(Environment.NewLine + " Hello" + Environment.NewLine + "World"); + } + + [Test] + public async Task IncrementIndent_And_DecrementIndent_Multiple_Operations_Should_Work() + { + var builder = new CSharpCodeBuilder(); + + // Simulate nested code blocks - the braces automatically handle indentation + _ = builder.Append("class MyClass"); + _ = builder.Append("{"); // This automatically increments indent and adds newline + _ = builder.Append("public void Method()"); + _ = builder.Append("{"); // This automatically increments indent and adds newline + _ = builder.Append("Console.WriteLine(\"Hello\");"); + _ = builder.Append("}"); // This automatically decrements indent but doesn't add newline + _ = builder.Append("}"); // This automatically decrements indent but doesn't add newline + + var result = builder.ToString().Replace("\r\n", "\n", StringComparison.Ordinal); + + // The actual output based on the CSharpCodeBuilder's behavior + var expected = """ + class MyClass{ + public void Method(){ + Console.WriteLine("Hello"); + } + } + """; + + _ = await Assert.That(result).IsEqualTo(expected); + } +} diff --git a/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Properties.cs b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Properties.cs new file mode 100644 index 0000000..d5b7a08 --- /dev/null +++ b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Properties.cs @@ -0,0 +1,160 @@ +namespace NetEvolve.CodeBuilder.Tests.Unit; + +using System; + +public partial class CSharpCodeBuilderTests +{ + [Test] + public async Task Capacity_Get_Should_Return_Current_Capacity() + { + var builder = new CSharpCodeBuilder(100); + + var capacity = builder.Capacity; + + _ = await Assert.That(capacity).IsGreaterThanOrEqualTo(100); + } + + [Test] + public async Task Capacity_After_EnsureCapacity_Should_Be_Updated() + { + var builder = new CSharpCodeBuilder(10); + var originalCapacity = builder.Capacity; + + _ = builder.EnsureCapacity(200); + + _ = await Assert.That(builder.Capacity).IsGreaterThan(originalCapacity); + _ = await Assert.That(builder.Capacity).IsGreaterThanOrEqualTo(200); + } + + [Test] + public async Task Length_Empty_Builder_Should_Return_Zero() + { + var builder = new CSharpCodeBuilder(); + + var length = builder.Length; + + _ = await Assert.That(length).IsEqualTo(0); + } + + [Test] + public async Task Length_After_Append_Should_Return_Correct_Length() + { + var builder = new CSharpCodeBuilder(); + var text = "Hello World"; + + _ = builder.Append(text); + + _ = await Assert.That(builder.Length).IsEqualTo(text.Length); + } + + [Test] + public async Task Length_After_Multiple_Appends_Should_Return_Total_Length() + { + var builder = new CSharpCodeBuilder(); + + _ = builder.Append("Hello").Append(" ").Append("World"); + + _ = await Assert.That(builder.Length).IsEqualTo(11); + } + + [Test] + public async Task Length_After_Clear_Should_Return_Zero() + { + var builder = new CSharpCodeBuilder(); + _ = builder.Append("Hello World"); + + _ = builder.Clear(); + + _ = await Assert.That(builder.Length).IsEqualTo(0); + } + + [Test] + public async Task Length_After_AppendLine_Should_Include_Newline_Characters() + { + var builder = new CSharpCodeBuilder(); + + _ = builder.AppendLine("Hello"); + + _ = await Assert.That(builder.Length).IsEqualTo(5 + Environment.NewLine.Length); + } + + [Test] + public async Task UseTabs_Default_Should_Be_False() + { + var builder = new CSharpCodeBuilder(); + + var useTabs = builder.UseTabs; + + _ = await Assert.That(useTabs).IsEqualTo(false); + } + + [Test] + public async Task UseTabs_Set_To_True_Should_Update_Property() + { + var builder = new CSharpCodeBuilder { UseTabs = true }; + + _ = await Assert.That(builder.UseTabs).IsEqualTo(true); + } + + [Test] + public async Task UseTabs_Set_To_False_Should_Update_Property() + { + var builder = new CSharpCodeBuilder { UseTabs = true }; + + builder.UseTabs = false; + + _ = await Assert.That(builder.UseTabs).IsEqualTo(false); + } + + [Test] + public async Task UseTabs_True_Should_Use_Tab_Character_For_Indentation() + { + var builder = new CSharpCodeBuilder { UseTabs = true }; + builder.IncrementIndent(); + + _ = builder.AppendLine().Append("Hello"); + + var result = builder.ToString(); + _ = await Assert.That(result).IsEqualTo(Environment.NewLine + "\tHello"); + } + + [Test] + public async Task UseTabs_False_Should_Use_Spaces_For_Indentation() + { + var builder = new CSharpCodeBuilder { UseTabs = false }; + builder.IncrementIndent(); + + _ = builder.AppendLine().Append("Hello"); + + var result = builder.ToString(); + _ = await Assert.That(result).IsEqualTo(Environment.NewLine + " Hello"); + } + + [Test] + public async Task UseTabs_Multiple_Indent_Levels_With_Tabs_Should_Use_Multiple_Tabs() + { + var builder = new CSharpCodeBuilder { UseTabs = true }; + builder.IncrementIndent(); + builder.IncrementIndent(); + builder.IncrementIndent(); + + _ = builder.AppendLine().Append("Hello"); + + var result = builder.ToString(); + _ = await Assert.That(result).IsEqualTo(Environment.NewLine + "\t\t\tHello"); + } + + [Test] + public async Task UseTabs_Multiple_Indent_Levels_With_Spaces_Should_Use_Multiple_Space_Groups() + { + var builder = new CSharpCodeBuilder { UseTabs = false }; + builder.IncrementIndent(); + builder.IncrementIndent(); + builder.IncrementIndent(); + + _ = builder.AppendLine().Append("Hello"); + + var result = builder.ToString(); + _ = await Assert.That(result).IsEqualTo(Environment.NewLine + " Hello"); // 12 spaces (3 * 4) + } +} diff --git a/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.ToString.cs b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.ToString.cs new file mode 100644 index 0000000..a2c8d1d --- /dev/null +++ b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.ToString.cs @@ -0,0 +1,88 @@ +namespace NetEvolve.CodeBuilder.Tests.Unit; + +using System; + +public partial class CSharpCodeBuilderTests +{ + [Test] + public async Task ToString_Empty_Should_Return_Empty_String() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.ToString(); + + _ = await Assert.That(result).IsEqualTo(string.Empty); + } + + [Test] + public async Task ToString_WithContent_Should_Return_Content() + { + var builder = new CSharpCodeBuilder(); + _ = builder.Append("Hello World"); + + var result = builder.ToString(); + + _ = await Assert.That(result).IsEqualTo("Hello World"); + } + + [Test] + public async Task ToString_WithMultipleAppends_Should_Return_All_Content() + { + var builder = new CSharpCodeBuilder(); + _ = builder.Append("Hello").Append(" ").Append("World"); + + var result = builder.ToString(); + + _ = await Assert.That(result).IsEqualTo("Hello World"); + } + + [Test] + public async Task ToString_WithIndentation_Should_Return_Content_With_Indentation() + { + var builder = new CSharpCodeBuilder(); + builder.IncrementIndent(); + _ = builder.AppendLine().Append("Hello"); + + var result = builder.ToString(); + + _ = await Assert.That(result).IsEqualTo(Environment.NewLine + " Hello"); + } + + [Test] + public async Task ToString_WithTabs_Should_Return_Content_With_Tab_Indentation() + { + var builder = new CSharpCodeBuilder { UseTabs = true }; + builder.IncrementIndent(); + _ = builder.AppendLine().Append("Hello"); + + var result = builder.ToString(); + + _ = await Assert.That(result).IsEqualTo(Environment.NewLine + "\tHello"); + } + + [Test] + public async Task ToString_AfterClear_Should_Return_Empty_String() + { + var builder = new CSharpCodeBuilder(); + _ = builder.Append("Hello World"); + _ = builder.Clear(); + + var result = builder.ToString(); + + _ = await Assert.That(result).IsEqualTo(string.Empty); + } + + [Test] + public async Task ToString_Multiple_Calls_Should_Return_Same_Result() + { + var builder = new CSharpCodeBuilder(); + _ = builder.Append("Hello World"); + + var result1 = builder.ToString(); + var result2 = builder.ToString(); + + _ = await Assert.That(result1).IsEqualTo("Hello World"); + _ = await Assert.That(result2).IsEqualTo("Hello World"); + _ = await Assert.That(result1).IsEqualTo(result2); + } +} diff --git a/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.cs b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.cs new file mode 100644 index 0000000..cc9e660 --- /dev/null +++ b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.cs @@ -0,0 +1,3 @@ +namespace NetEvolve.CodeBuilder.Tests.Unit; + +public partial class CSharpCodeBuilderTests { } diff --git a/tests/NetEvolve.CodeBuilder.Tests.Unit/NetEvolve.CodeBuilder.Tests.Unit.csproj b/tests/NetEvolve.CodeBuilder.Tests.Unit/NetEvolve.CodeBuilder.Tests.Unit.csproj new file mode 100644 index 0000000..077ef45 --- /dev/null +++ b/tests/NetEvolve.CodeBuilder.Tests.Unit/NetEvolve.CodeBuilder.Tests.Unit.csproj @@ -0,0 +1,19 @@ + + + $(_TestTargetFrameworks) + Exe + true + $(NoWarn);CA2000;S2930 + true + + + + + + + + + + + +