From b79589aaf4730ec70e8df4d31e61f46bf024c1c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20St=C3=BChmer?= Date: Wed, 9 Jul 2025 23:40:59 +0200 Subject: [PATCH 1/8] feat!: Initial CSharpCodeBuilder --- .editorconfig | 4 + .github/workflows/cicd.yml | 17 +- CodeBuilder.sln | 39 -- CodeBuilder.slnx | 24 + Directory.Build.props | 17 +- Directory.Packages.props | 12 +- README.md | 358 ++++++++++++- .../CSharpCodeBuilder.Append.cs | 224 ++++++++ .../CSharpCodeBuilder.AppendFormat.cs | 52 ++ .../CSharpCodeBuilder.AppendIf.cs | 132 +++++ .../CSharpCodeBuilder.AppendLine.cs | 113 ++++ .../CSharpCodeBuilder.AppendLineIf.cs | 127 +++++ .../CSharpCodeBuilder.Clear.cs | 20 + .../CSharpCodeBuilder.EnsureCapacity.cs | 18 + .../CSharpCodeBuilder.EnsureIndented.cs | 53 ++ .../CSharpCodeBuilder.ToString.cs | 13 + .../CSharpCodeBuilder._EMPTY_.cs | 3 + .../CSharpCodeBuilder.cs | 42 ++ .../NetEvolve.CodeBuilder.csproj | 9 + tests/.editorconfig | 282 ++++++++++ .../CSharpCodeBuilderTests.Append.cs | 247 +++++++++ .../CSharpCodeBuilderTests.AppendFormat.cs | 484 +++++++++++++++++ .../CSharpCodeBuilderTests.AppendIf.cs | 462 ++++++++++++++++ .../CSharpCodeBuilderTests.AppendLine.cs | 265 +++++++++ .../CSharpCodeBuilderTests.AppendLineIf.cs | 501 ++++++++++++++++++ .../CSharpCodeBuilderTests.Clear.cs | 96 ++++ .../CSharpCodeBuilderTests.Constructor.cs | 47 ++ .../CSharpCodeBuilderTests.EnsureCapacity.cs | 103 ++++ .../CSharpCodeBuilderTests.Indentation.cs | 167 ++++++ .../CSharpCodeBuilderTests.Properties.cs | 160 ++++++ .../CSharpCodeBuilderTests.ToString.cs | 88 +++ .../CSharpCodeBuilderTests.cs | 3 + .../NetEvolve.CodeBuilder.Tests.Unit.csproj | 19 + 33 files changed, 4141 insertions(+), 60 deletions(-) delete mode 100644 CodeBuilder.sln create mode 100644 CodeBuilder.slnx create mode 100644 src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Append.cs create mode 100644 src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendFormat.cs create mode 100644 src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendIf.cs create mode 100644 src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendLine.cs create mode 100644 src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendLineIf.cs create mode 100644 src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Clear.cs create mode 100644 src/NetEvolve.CodeBuilder/CSharpCodeBuilder.EnsureCapacity.cs create mode 100644 src/NetEvolve.CodeBuilder/CSharpCodeBuilder.EnsureIndented.cs create mode 100644 src/NetEvolve.CodeBuilder/CSharpCodeBuilder.ToString.cs create mode 100644 src/NetEvolve.CodeBuilder/CSharpCodeBuilder._EMPTY_.cs create mode 100644 src/NetEvolve.CodeBuilder/CSharpCodeBuilder.cs create mode 100644 src/NetEvolve.CodeBuilder/NetEvolve.CodeBuilder.csproj create mode 100644 tests/.editorconfig create mode 100644 tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Append.cs create mode 100644 tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.AppendFormat.cs create mode 100644 tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.AppendIf.cs create mode 100644 tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.AppendLine.cs create mode 100644 tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.AppendLineIf.cs create mode 100644 tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Clear.cs create mode 100644 tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Constructor.cs create mode 100644 tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.EnsureCapacity.cs create mode 100644 tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Indentation.cs create mode 100644 tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Properties.cs create mode 100644 tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.ToString.cs create mode 100644 tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.cs create mode 100644 tests/NetEvolve.CodeBuilder.Tests.Unit/NetEvolve.CodeBuilder.Tests.Unit.csproj diff --git a/.editorconfig b/.editorconfig index 5339d29..daa435f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -276,3 +276,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..2d8f37c 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>$(_DefaultTargetFrameworks) + false - - net8.0 + net9.0 - diff --git a/Directory.Packages.props b/Directory.Packages.props index feb65be..4fef13f 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..39d68e6 --- /dev/null +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Append.cs @@ -0,0 +1,224 @@ +namespace NetEvolve.CodeBuilder; + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Text; + +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..e00979e --- /dev/null +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendFormat.cs @@ -0,0 +1,52 @@ +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..43f9a0a --- /dev/null +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendIf.cs @@ -0,0 +1,132 @@ +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..70ed11e --- /dev/null +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendLine.cs @@ -0,0 +1,113 @@ +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..bceec78 --- /dev/null +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendLineIf.cs @@ -0,0 +1,127 @@ +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.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.EnsureIndented.cs b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.EnsureIndented.cs new file mode 100644 index 0000000..76081ee --- /dev/null +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.EnsureIndented.cs @@ -0,0 +1,53 @@ +namespace NetEvolve.CodeBuilder; + +using System.Runtime.CompilerServices; + +public partial record CSharpCodeBuilder +{ + /// + /// 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 void EnsureIndented(bool deactivate = false) + { + if (!_isNewline || deactivate) + { + return; + } + + var indentCount = Math.Max(0, _indentLevel * (UseTabs ? 1 : 4)); + _ = _builder.Append(UseTabs ? '\t' : ' ', indentCount); + + _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/CSharpCodeBuilder.ToString.cs b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.ToString.cs new file mode 100644 index 0000000..4489028 --- /dev/null +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.ToString.cs @@ -0,0 +1,13 @@ +namespace NetEvolve.CodeBuilder; + +public partial record CSharpCodeBuilder +{ + /// + /// 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 string ToString() => _builder.ToString(); +} 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..553736c --- /dev/null +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.cs @@ -0,0 +1,42 @@ +namespace NetEvolve.CodeBuilder; + +using System.Text; + +/// +/// Provides functionality for building C# code strings with proper indentation. +/// +public partial record CSharpCodeBuilder +{ + private readonly StringBuilder _builder; + private int _indentLevel; + private bool _isNewline = true; + + /// + /// Initializes a new instance of the class. + /// + public CSharpCodeBuilder() => _builder = new StringBuilder(); + + /// + /// Initializes a new instance of the class with the specified initial capacity. + /// + /// The initial capacity of the internal . + public CSharpCodeBuilder(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/.editorconfig b/tests/.editorconfig new file mode 100644 index 0000000..daa435f --- /dev/null +++ b/tests/.editorconfig @@ -0,0 +1,282 @@ +# EditorConfig is awesome: https://EditorConfig.org +# top-most EditorConfig file +root = true + +# DO NOT CHANGE SETTINGS IN THIS FILE. PLEASE CREATE PULL REQUEST IN REPOSITORY `dotnet-engineering`. + +# Don't use tabs for indentation. +[*] +insert_final_newline = true +indent_style = space +trim_trailing_whitespace = true +charset = utf-8 +end_of_line = lf + +# Verify settings +# https://github.com/VerifyTests/Verify?tab=readme-ov-file#text-file-settings +[*.{received,verified}.{txt,xml,json}] +charset = utf-8-bom +end_of_line = lf +indent_size = unset +indent_style = unset +insert_final_newline = false +tab_width = unset +trim_trailing_whitespace = false + +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 4 +insert_final_newline = true +charset = utf-8-bom + +# Razor and cshtml files +# UTF-8-BOM is set as default, as all official template files use 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 + +# Generated code +[*{_AssemblyInfo.cs,.notsupported.cs,.generated.cs}] +generated_code = true + +# XML project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj,nativeproj,locproj}] +indent_size = 2 + +# Xml build files +[*.builds] +indent_size = 2 + +# Xml files +[*.{xml,stylecop,resx,ruleset}] +indent_size = 2 + +# XML config files +[*.{props,targets,ruleset,config,nuspec,vsixmanifest,vsct}] +indent_size = 2 + +# JSON files +[*.json] +indent_size = 2 + +# YAML files +[*.{yml,yaml}] +indent_size = 2 + +# Powershell files +[*.ps1] +indent_size = 2 + +# Shell scripts +[*.sh] +indent_size = 2 + +# Commandline scripts +[*.{cmd,bat}] +end_of_line = crlf +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false +insert_final_newline = false + +# Visual Studio Solution Files +[*.sln] +indent_style = tab + +[*.{received,verified}.txt] +insert_final_newline = false +trim_trailing_whitespace = false + +[*.{cs,csx,vb,vbx}] +# .NET Code Style Settings +# See https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference +dotnet_sort_system_directives_first = true +dotnet_separate_import_directive_groups = false + +# Don't use 'this.'/'Me.' prefix for anything +dotnet_style_qualification_for_field = false : error +dotnet_style_qualification_for_property = false : error +dotnet_style_qualification_for_method = false : error +dotnet_style_qualification_for_event = false : error + +# Use language keywords over framework type names for type references +# i.e. prefer 'string' over 'String' +dotnet_style_predefined_type_for_locals_parameters_members = true : error +dotnet_style_predefined_type_for_member_access = true : error + +# Prefer object/collection initializers +# This is a suggestion because there are cases where this is necessary +dotnet_style_object_initializer = true : warning +dotnet_style_collection_initializer = true : warning + +# C# 7: Prefer using named tuple names over '.Item1', '.Item2', etc. +dotnet_style_explicit_tuple_names = true : error + +# Prefer using 'foo ?? bar' over 'foo is not null ? foo : bar' +dotnet_style_coalesce_expression = true : error + +# Prefer using '?.' over ternary null checking where possible +dotnet_style_null_propagation = true : error + +# 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_readonly_field = true : warning + +# Required Styles +dotnet_naming_style.all_const.capitalization = pascal_case +dotnet_naming_symbols.all_const.applicable_kinds = field +dotnet_naming_symbols.all_const.required_modifiers = const +dotnet_naming_rule.all_const.severity = error +dotnet_naming_rule.all_const.style = all_elements +dotnet_naming_rule.all_const.symbols = all_const + +dotnet_naming_style.all_fields.required_prefix = _ +dotnet_naming_style.all_fields.capitalization = camel_case +dotnet_naming_symbols.all_fields.applicable_kinds = field +dotnet_naming_rule.all_fields.severity = error +dotnet_naming_rule.all_fields.style = all_fields +dotnet_naming_rule.all_fields.symbols = all_fields + +dotnet_naming_style.all_interfaces.required_prefix = I +dotnet_naming_style.all_interfaces.capitalization = pascal_case +dotnet_naming_symbols.all_interfaces.applicable_kinds = interface +dotnet_naming_rule.all_interfaces.severity = error +dotnet_naming_rule.all_interfaces.style = all_interfaces +dotnet_naming_rule.all_interfaces.symbols = all_interfaces + +dotnet_naming_style.all_type_parameter.required_prefix = T +dotnet_naming_style.all_type_parameter.capitalization = pascal_case +dotnet_naming_symbols.all_type_parameter.applicable_kinds = type_parameter +dotnet_naming_rule.all_type_parameter.severity = error +dotnet_naming_rule.all_type_parameter.style = all_type_parameter +dotnet_naming_rule.all_type_parameter.symbols = all_type_parameter + +dotnet_naming_style.abstract_class.required_suffix = Base +dotnet_naming_style.abstract_class.capitalization = pascal_case +dotnet_naming_symbols.abstract_class.applicable_kinds = class +dotnet_naming_symbols.abstract_class.required_modifiers = abstract +dotnet_naming_rule.abstract_class.severity = warning +dotnet_naming_rule.abstract_class.style = abstract_class +dotnet_naming_rule.abstract_class.symbols = abstract_class + +dotnet_naming_style.method_async.required_suffix = Async +dotnet_naming_style.method_async.capitalization = pascal_case +dotnet_naming_symbols.method_async.applicable_kinds = method +dotnet_naming_symbols.method_async.required_modifiers = async +dotnet_naming_rule.method_async.severity = warning +dotnet_naming_rule.method_async.style = method_async +dotnet_naming_rule.method_async.symbols = method_async + +dotnet_naming_style.all_elements.capitalization = pascal_case +dotnet_naming_symbols.all_elements.applicable_kinds = namespace,class,struct,enum,property,method,event,delegate,local_function +dotnet_naming_rule.all_elements.severity = error +dotnet_naming_rule.all_elements.style = all_elements +dotnet_naming_rule.all_elements.symbols = all_elements + +dotnet_naming_style.all_parameters.capitalization = camel_case +dotnet_naming_symbols.all_parameters.applicable_kinds = parameter,local +dotnet_naming_rule.all_parameters.severity = error +dotnet_naming_rule.all_parameters.style = all_parameters +dotnet_naming_rule.all_parameters.symbols = all_parameters + +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_is_null_check_over_reference_equality_method = true : suggestion +dotnet_style_prefer_auto_properties = true : silent + +# Placement for using directives +csharp_using_directive_placement = inside_namespace : warning + +# Use 'var' in all cases where it can be used +csharp_style_var_for_built_in_types = true : error +csharp_style_var_when_type_is_apparent = true : error +csharp_style_var_elsewhere = true : error + +# Unused value preferences +csharp_style_unused_value_expression_statement_preference = discard_variable : warning +csharp_style_unused_value_assignment_preference = discard_variable : warning + +# C# 7: Prefer using pattern matching over "if(x is T) { var t = (T)x; }" and "var t = x as T; if(t is not null) { ... }" +csharp_style_pattern_matching_over_is_with_cast_check = true : warning +csharp_style_pattern_matching_over_as_with_null_check = true : warning + +# C# 7: Prefer using 'out var' where possible +csharp_style_inlined_variable_declaration = true : error + +# C# 7: Use throw expressions when null-checking +csharp_style_throw_expression = false : error + +# Prefer using "func?.Invoke(args)" over "if(func is not null) { func(args); }" +csharp_style_conditional_delegate_call = true : error + +# Newline settings +csharp_indent_braces = false +csharp_open_brace_on_new_line = all +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true + +# Prefer expression-bodied methods, constructors, operators, etc. +csharp_style_expression_bodied_methods = true : warning +csharp_style_expression_bodied_constructors = true : warning +csharp_style_expression_bodied_operators = true : warning +csharp_style_expression_bodied_properties = true : warning +csharp_style_expression_bodied_indexers = true : warning +csharp_style_expression_bodied_accessors = true : warning + +# Prefer Braces even for one line of code, because of +csharp_prefer_braces = true : error +csharp_type_declaration_braces = next_line +csharp_invocable_declaration_braces = next_line +csharp_anonymous_method_declaration_braces = next_line +csharp_accessor_owner_declaration_braces = next_line +csharp_accessor_declaration_braces = next_line +csharp_case_block_braces = next_line +csharp_initializer_braces = next_line +csharp_other_braces = next_line + +# Tuple Preferences +csharp_style_deconstructed_variable_declaration = true : warning + +# Simplify new expression (IDE0090) +csharp_style_implicit_object_creation_when_type_is_apparent = false +csharp_style_namespace_declarations = file_scoped : warning +csharp_prefer_simple_using_statement = false : suggestion +csharp_indent_labels = one_less_than_current +csharp_style_expression_bodied_lambdas = true : silent +csharp_style_expression_bodied_local_functions = false : silent + +# Use Compound assignment +dotnet_style_prefer_compound_assignment = false + +# Prefer if-else statement +dotnet_style_prefer_conditional_expression_over_return = false +dotnet_diagnostic.IDE0046.severity = suggestion + +# Prefer standard constructors +csharp_style_prefer_primary_constructors = false +dotnet_diagnostic.IDE0290.severity = suggestion + +# [CSharpier] Incompatible rules deactivated +# https://csharpier.com/docs/IntegratingWithLinters#code-analysis-rules +dotnet_diagnostic.IDE0055.severity = none +dotnet_diagnostic.SA1000.severity = none +dotnet_diagnostic.SA1009.severity = none +dotnet_diagnostic.SA1111.severity = none +dotnet_diagnostic.SA1118.severity = none +dotnet_diagnostic.SA1137.severity = none +dotnet_diagnostic.SA1413.severity = none +dotnet_diagnostic.SA1500.severity = none +dotnet_diagnostic.SA1501.severity = none +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/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.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..1329819 --- /dev/null +++ b/tests/NetEvolve.CodeBuilder.Tests.Unit/NetEvolve.CodeBuilder.Tests.Unit.csproj @@ -0,0 +1,19 @@ + + + $(_TestTargetFrameworks) + Exe + true + $(NoWarn);CA2000;S2930 + true + + + + + + + + + + + + From b6a5ea2c41576bd3de601ea1c6834c09fe42c4be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20St=C3=BChmer?= Date: Wed, 9 Jul 2025 23:56:44 +0200 Subject: [PATCH 2/8] chore: Abstract Base class --- .../CSharpCodeBuilder.Append.cs | 2 - .../CSharpCodeBuilder.cs | 30 +++---------- ...d.cs => CodeBuilderBase.EnsureIndented.cs} | 7 ++- ...oString.cs => CodeBuilderBase.ToString.cs} | 4 +- src/NetEvolve.CodeBuilder/CodeBuilderBase.cs | 43 +++++++++++++++++++ 5 files changed, 53 insertions(+), 33 deletions(-) rename src/NetEvolve.CodeBuilder/{CSharpCodeBuilder.EnsureIndented.cs => CodeBuilderBase.EnsureIndented.cs} (87%) rename src/NetEvolve.CodeBuilder/{CSharpCodeBuilder.ToString.cs => CodeBuilderBase.ToString.cs} (77%) create mode 100644 src/NetEvolve.CodeBuilder/CodeBuilderBase.cs diff --git a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Append.cs b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Append.cs index 39d68e6..b0c1117 100644 --- a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Append.cs +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Append.cs @@ -1,8 +1,6 @@ namespace NetEvolve.CodeBuilder; using System; -using System.Diagnostics.CodeAnalysis; -using System.Text; public partial record CSharpCodeBuilder { diff --git a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.cs b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.cs index 553736c..bd4d747 100644 --- a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.cs +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.cs @@ -5,38 +5,18 @@ /// /// Provides functionality for building C# code strings with proper indentation. /// -public partial record CSharpCodeBuilder +public partial record CSharpCodeBuilder : CodeBuilderBase { - private readonly StringBuilder _builder; - private int _indentLevel; - private bool _isNewline = true; - /// /// Initializes a new instance of the class. /// - public CSharpCodeBuilder() => _builder = new StringBuilder(); + 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) => _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; } + public CSharpCodeBuilder(int initialCapacity) + : base(initialCapacity) { } } diff --git a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.EnsureIndented.cs b/src/NetEvolve.CodeBuilder/CodeBuilderBase.EnsureIndented.cs similarity index 87% rename from src/NetEvolve.CodeBuilder/CSharpCodeBuilder.EnsureIndented.cs rename to src/NetEvolve.CodeBuilder/CodeBuilderBase.EnsureIndented.cs index 76081ee..898b688 100644 --- a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.EnsureIndented.cs +++ b/src/NetEvolve.CodeBuilder/CodeBuilderBase.EnsureIndented.cs @@ -2,7 +2,7 @@ using System.Runtime.CompilerServices; -public partial record CSharpCodeBuilder +public partial record CodeBuilderBase { /// /// Ensures that indentation is applied if we are at the start of a new line. @@ -12,15 +12,14 @@ public partial record CSharpCodeBuilder /// 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 void EnsureIndented(bool deactivate = false) + private protected void EnsureIndented(bool deactivate = false) { if (!_isNewline || deactivate) { return; } - var indentCount = Math.Max(0, _indentLevel * (UseTabs ? 1 : 4)); - _ = _builder.Append(UseTabs ? '\t' : ' ', indentCount); + _ = _builder.Append(UseTabs ? '\t' : ' ', _indentLevel * (UseTabs ? 1 : 4)); _isNewline = false; } diff --git a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.ToString.cs b/src/NetEvolve.CodeBuilder/CodeBuilderBase.ToString.cs similarity index 77% rename from src/NetEvolve.CodeBuilder/CSharpCodeBuilder.ToString.cs rename to src/NetEvolve.CodeBuilder/CodeBuilderBase.ToString.cs index 4489028..b480359 100644 --- a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.ToString.cs +++ b/src/NetEvolve.CodeBuilder/CodeBuilderBase.ToString.cs @@ -1,6 +1,6 @@ namespace NetEvolve.CodeBuilder; -public partial record CSharpCodeBuilder +public partial record CodeBuilderBase { /// /// Returns the string that has been built by this . @@ -9,5 +9,5 @@ public partial record CSharpCodeBuilder /// /// This method returns the content of the internal . /// - public override string ToString() => _builder.ToString(); + 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..f19ec78 --- /dev/null +++ b/src/NetEvolve.CodeBuilder/CodeBuilderBase.cs @@ -0,0 +1,43 @@ +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; } +} From 5b35cec8c312b4df92621f1208f259cd93772478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20St=C3=BChmer?= Date: Thu, 10 Jul 2025 00:09:43 +0200 Subject: [PATCH 3/8] chore: Additional methods for XMLDocs --- .../CSharpCodeBuilder.Documentation.cs | 403 +++++++++++++++ .../CSharpCodeBuilderTests.Documentation.cs | 474 ++++++++++++++++++ 2 files changed, 877 insertions(+) create mode 100644 src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Documentation.cs create mode 100644 tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Documentation.cs diff --git a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Documentation.cs b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Documentation.cs new file mode 100644 index 0000000..5689e75 --- /dev/null +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Documentation.cs @@ -0,0 +1,403 @@ +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) + { + return 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/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); + } +} From a9947fea7ad1d0de269249f18256e3e5ec1b351e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20St=C3=BChmer?= Date: Thu, 10 Jul 2025 00:18:25 +0200 Subject: [PATCH 4/8] fix: Use Expression body --- src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Documentation.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Documentation.cs b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Documentation.cs index 5689e75..9c14731 100644 --- a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Documentation.cs +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Documentation.cs @@ -353,12 +353,9 @@ public CSharpCodeBuilder AppendXmlDocTypeParams(IEnumerable<(string, string)>? t /// 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) - { - return string.IsNullOrEmpty(cref) + public CSharpCodeBuilder AppendXmlDocInheritDoc(string? cref = null) => string.IsNullOrEmpty(cref) ? EnsureNewLineForXmlDoc().AppendLine("/// ") : EnsureNewLineForXmlDoc().AppendLine($"/// "); - } /// /// Appends a custom XML documentation element. From 34174646428a4799d1b18a7d454d57eb2ce0c706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20St=C3=BChmer?= Date: Thu, 10 Jul 2025 00:20:19 +0200 Subject: [PATCH 5/8] style: Reformatted code --- Directory.Build.props | 2 +- src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Documentation.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 2d8f37c..3c5fd1e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -9,7 +9,7 @@ 2024 <_DefaultTargetFrameworks>net8.0;net9.0;net10.0 <_ProjectTargetFrameworks>netstandard2.0;netstandard2.1;$(_DefaultTargetFrameworks) - <_TestTargetFrameworks>$(_DefaultTargetFrameworks) + <_TestTargetFrameworks>net6.0;net7.0;$(_DefaultTargetFrameworks) false diff --git a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Documentation.cs b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Documentation.cs index 9c14731..56bdaae 100644 --- a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Documentation.cs +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Documentation.cs @@ -353,7 +353,8 @@ public CSharpCodeBuilder AppendXmlDocTypeParams(IEnumerable<(string, string)>? t /// 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) + public CSharpCodeBuilder AppendXmlDocInheritDoc(string? cref = null) => + string.IsNullOrEmpty(cref) ? EnsureNewLineForXmlDoc().AppendLine("/// ") : EnsureNewLineForXmlDoc().AppendLine($"/// "); From a28e58f3ac37285a48d93efe01e0547d1faca3b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20St=C3=BChmer?= Date: Thu, 10 Jul 2025 00:25:43 +0200 Subject: [PATCH 6/8] chore(deps): Updated nuget packages --- .editorconfig | 6 ++++-- Directory.Packages.props | 12 ++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.editorconfig b/.editorconfig index daa435f..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 diff --git a/Directory.Packages.props b/Directory.Packages.props index 4fef13f..bd93ac1 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,14 +4,14 @@ true - - - + + + - - - + + + From 72dee0bf5323d2374c469cba3b1d61607bd9a628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20St=C3=BChmer?= Date: Thu, 10 Jul 2025 00:26:08 +0200 Subject: [PATCH 7/8] style: Reformatted code --- .../CSharpCodeBuilder.AppendFormat.cs | 6 +--- .../CSharpCodeBuilder.AppendIf.cs | 31 ++++++------------- .../CSharpCodeBuilder.AppendLine.cs | 6 ++-- .../CSharpCodeBuilder.AppendLineIf.cs | 28 +++++------------ .../CSharpCodeBuilder.Documentation.cs | 21 ++++--------- src/NetEvolve.CodeBuilder/CodeBuilderBase.cs | 3 +- .../NetEvolve.CodeBuilder.Tests.Unit.csproj | 4 +-- 7 files changed, 29 insertions(+), 70 deletions(-) diff --git a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendFormat.cs b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendFormat.cs index e00979e..5cb118d 100644 --- a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendFormat.cs +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendFormat.cs @@ -39,11 +39,7 @@ public CSharpCodeBuilder AppendFormat(string format, params object?[] args) => /// 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 - ) + public CSharpCodeBuilder AppendFormat(IFormatProvider? provider, string format, params object?[] args) { EnsureIndented(); _ = _builder.AppendFormat(provider, format, args); diff --git a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendIf.cs b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendIf.cs index 43f9a0a..1feb663 100644 --- a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendIf.cs +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendIf.cs @@ -13,8 +13,7 @@ public partial record CSharpCodeBuilder /// 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; + 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. @@ -35,8 +34,7 @@ public CSharpCodeBuilder AppendIf(bool condition, char value, int repeatCount) = /// 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; + 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. @@ -48,12 +46,8 @@ public CSharpCodeBuilder AppendIf(bool condition, char value) => /// 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; + 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. @@ -63,8 +57,7 @@ int charCount /// 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; + 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. @@ -86,8 +79,7 @@ public unsafe CSharpCodeBuilder AppendIf(bool condition, char* value, int length /// 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; + 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. @@ -99,12 +91,8 @@ public CSharpCodeBuilder AppendIf(bool condition, ReadOnlyMemory value) => /// 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; + 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. @@ -127,6 +115,5 @@ public CSharpCodeBuilder AppendIf(bool condition, string? value, int startIndex, /// 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; + 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 index 70ed11e..b612b70 100644 --- a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendLine.cs +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendLine.cs @@ -80,8 +80,7 @@ public CSharpCodeBuilder AppendLine(char[]? value, int startIndex, int charCount /// 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(); + public unsafe CSharpCodeBuilder AppendLine(char* value, int length) => Append(value, length).AppendLine(); /// /// Appends a character followed by a line terminator to the current builder. @@ -99,8 +98,7 @@ public unsafe CSharpCodeBuilder AppendLine(char* value, int length) => /// 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(); + public CSharpCodeBuilder AppendLine(char value, int repeatCount) => Append(value, repeatCount).AppendLine(); /// /// Appends a boolean value followed by a line terminator to the current builder. diff --git a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendLineIf.cs b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendLineIf.cs index bceec78..18e6c6b 100644 --- a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendLineIf.cs +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendLineIf.cs @@ -21,8 +21,7 @@ public partial record CSharpCodeBuilder /// 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; + 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. @@ -45,12 +44,8 @@ public CSharpCodeBuilder AppendLineIf(bool condition, ReadOnlyMemory value /// 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; + 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. @@ -60,8 +55,7 @@ int count /// 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; + 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. @@ -73,12 +67,8 @@ public CSharpCodeBuilder AppendLineIf(bool condition, char[]? value) => /// 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; + 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. @@ -99,8 +89,7 @@ public unsafe CSharpCodeBuilder AppendLineIf(bool condition, char* value, int le /// 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; + 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. @@ -122,6 +111,5 @@ public CSharpCodeBuilder AppendLineIf(bool condition, char value, int repeatCoun /// 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; + public CSharpCodeBuilder AppendLineIf(bool condition, bool value) => condition ? AppendLine(value) : this; } diff --git a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Documentation.cs b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Documentation.cs index 56bdaae..92a8627 100644 --- a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Documentation.cs +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Documentation.cs @@ -14,9 +14,7 @@ public partial record CSharpCodeBuilder /// 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}"); + string.IsNullOrEmpty(content) ? this : EnsureNewLineForXmlDoc().AppendLine($"/// {content}"); /// /// Appends an XML documentation summary element. @@ -79,8 +77,7 @@ public CSharpCodeBuilder AppendXmlDocParam(string? paramName, string? descriptio return this; } - return EnsureNewLineForXmlDoc() - .AppendLine($"/// {description}"); + return EnsureNewLineForXmlDoc().AppendLine($"/// {description}"); } /// @@ -89,9 +86,7 @@ public CSharpCodeBuilder AppendXmlDocParam(string? paramName, string? descriptio /// 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 - ) + public CSharpCodeBuilder AppendXmlDocParams(IEnumerable<(string Name, string Description)>? parameters) { if (parameters is null) { @@ -194,9 +189,7 @@ public CSharpCodeBuilder AppendXmlDocException(string? exceptionType, string? de /// 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 - ) + public CSharpCodeBuilder AppendXmlDocExceptions(IEnumerable<(string Type, string Description)>? exceptions) { if (exceptions is null) { @@ -321,8 +314,7 @@ public CSharpCodeBuilder AppendXmlDocTypeParam(string? paramName, string? descri return this; } - return EnsureNewLineForXmlDoc() - .AppendLine($"/// {description}"); + return EnsureNewLineForXmlDoc().AppendLine($"/// {description}"); } /// @@ -384,8 +376,7 @@ public CSharpCodeBuilder AppendXmlDocCustomElement( return EnsureNewLineForXmlDoc().AppendLine($"/// <{elementName}{attributesPart} />"); } - return EnsureNewLineForXmlDoc() - .AppendLine($"/// <{elementName}{attributesPart}>{content}"); + return EnsureNewLineForXmlDoc().AppendLine($"/// <{elementName}{attributesPart}>{content}"); } /// diff --git a/src/NetEvolve.CodeBuilder/CodeBuilderBase.cs b/src/NetEvolve.CodeBuilder/CodeBuilderBase.cs index f19ec78..6397834 100644 --- a/src/NetEvolve.CodeBuilder/CodeBuilderBase.cs +++ b/src/NetEvolve.CodeBuilder/CodeBuilderBase.cs @@ -20,8 +20,7 @@ public abstract partial record CodeBuilderBase /// 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); + private protected CodeBuilderBase(int initialCapacity) => _builder = new StringBuilder(initialCapacity); /// /// Gets the current capacity of the internal . diff --git a/tests/NetEvolve.CodeBuilder.Tests.Unit/NetEvolve.CodeBuilder.Tests.Unit.csproj b/tests/NetEvolve.CodeBuilder.Tests.Unit/NetEvolve.CodeBuilder.Tests.Unit.csproj index 1329819..077ef45 100644 --- a/tests/NetEvolve.CodeBuilder.Tests.Unit/NetEvolve.CodeBuilder.Tests.Unit.csproj +++ b/tests/NetEvolve.CodeBuilder.Tests.Unit/NetEvolve.CodeBuilder.Tests.Unit.csproj @@ -7,13 +7,13 @@ true - - + + From 5ece6593578e2772f8838078d6c3f759b711ca67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20St=C3=BChmer?= Date: Thu, 10 Jul 2025 00:29:05 +0200 Subject: [PATCH 8/8] chore: Removed additional editorconfig --- tests/.editorconfig | 282 -------------------------------------------- 1 file changed, 282 deletions(-) delete mode 100644 tests/.editorconfig diff --git a/tests/.editorconfig b/tests/.editorconfig deleted file mode 100644 index daa435f..0000000 --- a/tests/.editorconfig +++ /dev/null @@ -1,282 +0,0 @@ -# EditorConfig is awesome: https://EditorConfig.org -# top-most EditorConfig file -root = true - -# DO NOT CHANGE SETTINGS IN THIS FILE. PLEASE CREATE PULL REQUEST IN REPOSITORY `dotnet-engineering`. - -# Don't use tabs for indentation. -[*] -insert_final_newline = true -indent_style = space -trim_trailing_whitespace = true -charset = utf-8 -end_of_line = lf - -# Verify settings -# https://github.com/VerifyTests/Verify?tab=readme-ov-file#text-file-settings -[*.{received,verified}.{txt,xml,json}] -charset = utf-8-bom -end_of_line = lf -indent_size = unset -indent_style = unset -insert_final_newline = false -tab_width = unset -trim_trailing_whitespace = false - -# Code files -[*.{cs,csx,vb,vbx}] -indent_size = 4 -insert_final_newline = true -charset = utf-8-bom - -# Razor and cshtml files -# UTF-8-BOM is set as default, as all official template files use 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 - -# Generated code -[*{_AssemblyInfo.cs,.notsupported.cs,.generated.cs}] -generated_code = true - -# XML project files -[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj,nativeproj,locproj}] -indent_size = 2 - -# Xml build files -[*.builds] -indent_size = 2 - -# Xml files -[*.{xml,stylecop,resx,ruleset}] -indent_size = 2 - -# XML config files -[*.{props,targets,ruleset,config,nuspec,vsixmanifest,vsct}] -indent_size = 2 - -# JSON files -[*.json] -indent_size = 2 - -# YAML files -[*.{yml,yaml}] -indent_size = 2 - -# Powershell files -[*.ps1] -indent_size = 2 - -# Shell scripts -[*.sh] -indent_size = 2 - -# Commandline scripts -[*.{cmd,bat}] -end_of_line = crlf -indent_size = 2 - -[*.md] -trim_trailing_whitespace = false -insert_final_newline = false - -# Visual Studio Solution Files -[*.sln] -indent_style = tab - -[*.{received,verified}.txt] -insert_final_newline = false -trim_trailing_whitespace = false - -[*.{cs,csx,vb,vbx}] -# .NET Code Style Settings -# See https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference -dotnet_sort_system_directives_first = true -dotnet_separate_import_directive_groups = false - -# Don't use 'this.'/'Me.' prefix for anything -dotnet_style_qualification_for_field = false : error -dotnet_style_qualification_for_property = false : error -dotnet_style_qualification_for_method = false : error -dotnet_style_qualification_for_event = false : error - -# Use language keywords over framework type names for type references -# i.e. prefer 'string' over 'String' -dotnet_style_predefined_type_for_locals_parameters_members = true : error -dotnet_style_predefined_type_for_member_access = true : error - -# Prefer object/collection initializers -# This is a suggestion because there are cases where this is necessary -dotnet_style_object_initializer = true : warning -dotnet_style_collection_initializer = true : warning - -# C# 7: Prefer using named tuple names over '.Item1', '.Item2', etc. -dotnet_style_explicit_tuple_names = true : error - -# Prefer using 'foo ?? bar' over 'foo is not null ? foo : bar' -dotnet_style_coalesce_expression = true : error - -# Prefer using '?.' over ternary null checking where possible -dotnet_style_null_propagation = true : error - -# 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_readonly_field = true : warning - -# Required Styles -dotnet_naming_style.all_const.capitalization = pascal_case -dotnet_naming_symbols.all_const.applicable_kinds = field -dotnet_naming_symbols.all_const.required_modifiers = const -dotnet_naming_rule.all_const.severity = error -dotnet_naming_rule.all_const.style = all_elements -dotnet_naming_rule.all_const.symbols = all_const - -dotnet_naming_style.all_fields.required_prefix = _ -dotnet_naming_style.all_fields.capitalization = camel_case -dotnet_naming_symbols.all_fields.applicable_kinds = field -dotnet_naming_rule.all_fields.severity = error -dotnet_naming_rule.all_fields.style = all_fields -dotnet_naming_rule.all_fields.symbols = all_fields - -dotnet_naming_style.all_interfaces.required_prefix = I -dotnet_naming_style.all_interfaces.capitalization = pascal_case -dotnet_naming_symbols.all_interfaces.applicable_kinds = interface -dotnet_naming_rule.all_interfaces.severity = error -dotnet_naming_rule.all_interfaces.style = all_interfaces -dotnet_naming_rule.all_interfaces.symbols = all_interfaces - -dotnet_naming_style.all_type_parameter.required_prefix = T -dotnet_naming_style.all_type_parameter.capitalization = pascal_case -dotnet_naming_symbols.all_type_parameter.applicable_kinds = type_parameter -dotnet_naming_rule.all_type_parameter.severity = error -dotnet_naming_rule.all_type_parameter.style = all_type_parameter -dotnet_naming_rule.all_type_parameter.symbols = all_type_parameter - -dotnet_naming_style.abstract_class.required_suffix = Base -dotnet_naming_style.abstract_class.capitalization = pascal_case -dotnet_naming_symbols.abstract_class.applicable_kinds = class -dotnet_naming_symbols.abstract_class.required_modifiers = abstract -dotnet_naming_rule.abstract_class.severity = warning -dotnet_naming_rule.abstract_class.style = abstract_class -dotnet_naming_rule.abstract_class.symbols = abstract_class - -dotnet_naming_style.method_async.required_suffix = Async -dotnet_naming_style.method_async.capitalization = pascal_case -dotnet_naming_symbols.method_async.applicable_kinds = method -dotnet_naming_symbols.method_async.required_modifiers = async -dotnet_naming_rule.method_async.severity = warning -dotnet_naming_rule.method_async.style = method_async -dotnet_naming_rule.method_async.symbols = method_async - -dotnet_naming_style.all_elements.capitalization = pascal_case -dotnet_naming_symbols.all_elements.applicable_kinds = namespace,class,struct,enum,property,method,event,delegate,local_function -dotnet_naming_rule.all_elements.severity = error -dotnet_naming_rule.all_elements.style = all_elements -dotnet_naming_rule.all_elements.symbols = all_elements - -dotnet_naming_style.all_parameters.capitalization = camel_case -dotnet_naming_symbols.all_parameters.applicable_kinds = parameter,local -dotnet_naming_rule.all_parameters.severity = error -dotnet_naming_rule.all_parameters.style = all_parameters -dotnet_naming_rule.all_parameters.symbols = all_parameters - -dotnet_style_operator_placement_when_wrapping = beginning_of_line -dotnet_style_prefer_is_null_check_over_reference_equality_method = true : suggestion -dotnet_style_prefer_auto_properties = true : silent - -# Placement for using directives -csharp_using_directive_placement = inside_namespace : warning - -# Use 'var' in all cases where it can be used -csharp_style_var_for_built_in_types = true : error -csharp_style_var_when_type_is_apparent = true : error -csharp_style_var_elsewhere = true : error - -# Unused value preferences -csharp_style_unused_value_expression_statement_preference = discard_variable : warning -csharp_style_unused_value_assignment_preference = discard_variable : warning - -# C# 7: Prefer using pattern matching over "if(x is T) { var t = (T)x; }" and "var t = x as T; if(t is not null) { ... }" -csharp_style_pattern_matching_over_is_with_cast_check = true : warning -csharp_style_pattern_matching_over_as_with_null_check = true : warning - -# C# 7: Prefer using 'out var' where possible -csharp_style_inlined_variable_declaration = true : error - -# C# 7: Use throw expressions when null-checking -csharp_style_throw_expression = false : error - -# Prefer using "func?.Invoke(args)" over "if(func is not null) { func(args); }" -csharp_style_conditional_delegate_call = true : error - -# Newline settings -csharp_indent_braces = false -csharp_open_brace_on_new_line = all -csharp_new_line_before_open_brace = all -csharp_new_line_before_else = true -csharp_new_line_before_catch = true -csharp_new_line_before_finally = true -csharp_new_line_before_members_in_object_initializers = true -csharp_new_line_before_members_in_anonymous_types = true - -# Prefer expression-bodied methods, constructors, operators, etc. -csharp_style_expression_bodied_methods = true : warning -csharp_style_expression_bodied_constructors = true : warning -csharp_style_expression_bodied_operators = true : warning -csharp_style_expression_bodied_properties = true : warning -csharp_style_expression_bodied_indexers = true : warning -csharp_style_expression_bodied_accessors = true : warning - -# Prefer Braces even for one line of code, because of -csharp_prefer_braces = true : error -csharp_type_declaration_braces = next_line -csharp_invocable_declaration_braces = next_line -csharp_anonymous_method_declaration_braces = next_line -csharp_accessor_owner_declaration_braces = next_line -csharp_accessor_declaration_braces = next_line -csharp_case_block_braces = next_line -csharp_initializer_braces = next_line -csharp_other_braces = next_line - -# Tuple Preferences -csharp_style_deconstructed_variable_declaration = true : warning - -# Simplify new expression (IDE0090) -csharp_style_implicit_object_creation_when_type_is_apparent = false -csharp_style_namespace_declarations = file_scoped : warning -csharp_prefer_simple_using_statement = false : suggestion -csharp_indent_labels = one_less_than_current -csharp_style_expression_bodied_lambdas = true : silent -csharp_style_expression_bodied_local_functions = false : silent - -# Use Compound assignment -dotnet_style_prefer_compound_assignment = false - -# Prefer if-else statement -dotnet_style_prefer_conditional_expression_over_return = false -dotnet_diagnostic.IDE0046.severity = suggestion - -# Prefer standard constructors -csharp_style_prefer_primary_constructors = false -dotnet_diagnostic.IDE0290.severity = suggestion - -# [CSharpier] Incompatible rules deactivated -# https://csharpier.com/docs/IntegratingWithLinters#code-analysis-rules -dotnet_diagnostic.IDE0055.severity = none -dotnet_diagnostic.SA1000.severity = none -dotnet_diagnostic.SA1009.severity = none -dotnet_diagnostic.SA1111.severity = none -dotnet_diagnostic.SA1118.severity = none -dotnet_diagnostic.SA1137.severity = none -dotnet_diagnostic.SA1413.severity = none -dotnet_diagnostic.SA1500.severity = none -dotnet_diagnostic.SA1501.severity = none -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)