diff --git a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Clear.cs b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Clear.cs index db25aef..036afea 100644 --- a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Clear.cs +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Clear.cs @@ -17,4 +17,22 @@ public CSharpCodeBuilder Clear() return this; } + + /// + /// Appends a single indentation unit to the current builder without affecting the indentation level. + /// + /// The current instance to allow for method chaining. + /// + /// This method adds one indentation unit directly to the builder based on the setting. + /// If is , a tab character is appended. + /// If is , four space characters are appended. + /// Unlike automatic indentation that occurs at the start of new lines, this method provides manual control + /// over indentation placement and does not modify the current indentation level. + /// + public CSharpCodeBuilder Intend() + { + _ = _builder.Append(UseTabs ? '\t' : ' ', UseTabs ? 1 : 4); + + return this; + } } diff --git a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Scope.cs b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Scope.cs index 06cde62..7a74544 100644 --- a/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Scope.cs +++ b/src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Scope.cs @@ -1,4 +1,4 @@ -namespace NetEvolve.CodeBuilder; +namespace NetEvolve.CodeBuilder; using System; @@ -23,6 +23,32 @@ public partial record CSharpCodeBuilder /// public IDisposable Scope() => new ScopeHandler(this); + /// + /// Appends a line of text and creates a scope that automatically manages indentation levels with braces. + /// + /// The string value to append before creating the scope. Can be . + /// A that appends an opening brace, increments indentation on creation, and appends a closing brace with decremented indentation on disposal. + /// + /// This method combines with functionality. + /// The returned scope handler implements and is designed for use with the using statement. + /// When the scope is created, an opening brace is appended and indentation is incremented by one level. + /// When the scope is disposed, indentation is decremented and a closing brace is appended. + /// + /// + /// + /// var builder = new CSharpCodeBuilder(); + /// using (builder.ScopeLine("public class MyClass")) + /// { + /// builder.AppendLine("public string Name { get; set; }"); + /// } + /// + /// + public IDisposable ScopeLine(string? value) + { + _ = AppendLine(value); + return new ScopeHandler(this); + } + /// /// A disposable struct that manages indentation scope for a . /// diff --git a/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Clear.cs b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Clear.cs index 2e76e80..e070f99 100644 --- a/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Clear.cs +++ b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Clear.cs @@ -93,4 +93,196 @@ public async Task Clear_Should_Preserve_Capacity() _ = await Assert.That(builder.Capacity).IsEqualTo(originalCapacity); } + + [Test] + public async Task Intend_Should_Append_Single_Indentation() + { + var builder = new CSharpCodeBuilder(); + + _ = builder.Intend().Append("Hello"); + + var result = builder.ToString(); + _ = await Assert.That(result).IsEqualTo(" Hello"); + } + + [Test] + public async Task Intend_Should_Not_Affect_Indentation_Level() + { + var builder = new CSharpCodeBuilder(); + + _ = builder.Intend().AppendLine("First"); + _ = builder.Append("Second"); + + var result = builder.ToString(); + // Second line should not be indented because Intend() doesn't change the level + _ = await Assert.That(result).IsEqualTo(" First" + Environment.NewLine + "Second"); + } + + [Test] + public async Task Intend_With_Tabs_Should_Append_Tab_Character() + { + var builder = new CSharpCodeBuilder { UseTabs = true }; + + _ = builder.Intend().Append("Hello"); + + var result = builder.ToString(); + _ = await Assert.That(result).IsEqualTo("\tHello"); + } + + [Test] + public async Task Intend_With_Spaces_Should_Append_Four_Spaces() + { + var builder = new CSharpCodeBuilder { UseTabs = false }; + + _ = builder.Intend().Append("Hello"); + + var result = builder.ToString(); + _ = await Assert.That(result).IsEqualTo(" Hello"); + } + + [Test] + public async Task Intend_Multiple_Calls_Should_Append_Multiple_Indentations() + { + var builder = new CSharpCodeBuilder(); + + _ = builder.Intend().Intend().Intend().Append("Hello"); + + var result = builder.ToString(); + _ = await Assert.That(result).IsEqualTo(" Hello"); // 12 spaces (3 * 4) + } + + [Test] + public async Task Intend_Multiple_With_Tabs_Should_Append_Multiple_Tabs() + { + var builder = new CSharpCodeBuilder { UseTabs = true }; + + _ = builder.Intend().Intend().Intend().Append("Hello"); + + var result = builder.ToString(); + _ = await Assert.That(result).IsEqualTo("\t\t\tHello"); + } + + [Test] + public async Task Intend_Should_Return_Builder_For_Chaining() + { + var builder = new CSharpCodeBuilder(); + + var result = builder.Intend(); + + _ = await Assert.That(result).IsEqualTo(builder); + } + + [Test] + public async Task Intend_In_Middle_Of_Line_Should_Append_Indentation() + { + var builder = new CSharpCodeBuilder(); + + _ = builder.Append("Hello").Intend().Append("World"); + + var result = builder.ToString(); + _ = await Assert.That(result).IsEqualTo("Hello World"); + } + + [Test] + public async Task Intend_After_NewLine_Should_Add_Manual_Indentation() + { + var builder = new CSharpCodeBuilder(); + + _ = builder.AppendLine("First"); + _ = builder.Intend().Append("Second"); + + var result = builder.ToString(); + _ = await Assert.That(result).IsEqualTo("First" + Environment.NewLine + " Second"); + } + + [Test] + public async Task Intend_With_Automatic_Indentation_Should_Stack() + { + var builder = new CSharpCodeBuilder(); + builder.IncrementIndent(); // Set automatic indentation level to 1 + + _ = builder.AppendLine().Intend().Append("Hello"); + + var result = builder.ToString(); + // Should have both automatic (4 spaces) and manual (4 spaces) indentation + _ = await Assert.That(result).IsEqualTo(Environment.NewLine + " Hello"); + } + + [Test] + public async Task Intend_Multiple_Mixed_With_Content_Should_Work() + { + var builder = new CSharpCodeBuilder(); + + _ = builder + .Intend() + .Append("Level 1") + .AppendLine() + .Intend() + .Intend() + .Append("Level 2") + .AppendLine() + .Intend() + .Intend() + .Intend() + .Append("Level 3"); + + var result = builder.ToString(); + _ = await Assert.That(result).Contains(" Level 1"); + _ = await Assert.That(result).Contains(" Level 2"); + _ = await Assert.That(result).Contains(" Level 3"); + } + + [Test] + public async Task Intend_Should_Work_With_Empty_Builder() + { + var builder = new CSharpCodeBuilder(); + + _ = builder.Intend(); + + var result = builder.ToString(); + _ = await Assert.That(result).IsEqualTo(" "); + } + + [Test] + public async Task Intend_Should_Work_After_Clear() + { + var builder = new CSharpCodeBuilder(); + _ = builder.Append("Hello"); + _ = builder.Clear(); + + _ = builder.Intend().Append("World"); + + var result = builder.ToString(); + _ = await Assert.That(result).IsEqualTo(" World"); + } + + [Test] + public async Task Intend_Combined_With_Scope_Should_Add_Extra_Indentation() + { + var builder = new CSharpCodeBuilder(); + + using (builder.Scope()) + { + _ = builder.Intend().Append("Extra indented"); + } + + var result = builder.ToString(); + // Should have both scope indentation (4 spaces) and manual indentation (4 spaces) + _ = await Assert.That(result).Contains(" Extra indented"); // 8 spaces + } + + [Test] + public async Task Intend_Combined_With_ScopeLine_Should_Add_Extra_Indentation() + { + var builder = new CSharpCodeBuilder(); + + using (builder.ScopeLine("public class MyClass")) + { + _ = builder.Intend().Append("// Extra indented comment"); + } + + var result = builder.ToString(); + // Should have both scope indentation and manual indentation + _ = await Assert.That(result).Contains(" // Extra indented comment"); // 8 spaces + } } diff --git a/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Scope.cs b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Scope.cs new file mode 100644 index 0000000..e730e8f --- /dev/null +++ b/tests/NetEvolve.CodeBuilder.Tests.Unit/CSharpCodeBuilderTests.Scope.cs @@ -0,0 +1,330 @@ +namespace NetEvolve.CodeBuilder.Tests.Unit; + +using System; + +public partial class CSharpCodeBuilderTests +{ + #region Scope Tests + + [Test] + public async Task Scope_Should_Create_Disposable_Handler() + { + var builder = new CSharpCodeBuilder(); + + var scope = builder.Scope(); + + _ = await Assert.That(scope).IsNotNull(); + } + + [Test] + public async Task Scope_Should_Append_Opening_Brace() + { + var builder = new CSharpCodeBuilder(); + + using (builder.Scope()) + { + // Empty scope + } + + var result = builder.ToString(); + _ = await Assert.That(result).Contains("{"); + } + + [Test] + public async Task Scope_Should_Append_Closing_Brace_On_Dispose() + { + var builder = new CSharpCodeBuilder(); + + using (builder.Scope()) + { + // Empty scope + } + + var result = builder.ToString(); + _ = await Assert.That(result).Contains("}"); + } + + [Test] + public async Task Scope_Should_Contain_Both_Braces() + { + var builder = new CSharpCodeBuilder(); + + using (builder.Scope()) + { + // Empty scope + } + + var result = builder.ToString(); + _ = await Assert.That(result).Contains("{"); + _ = await Assert.That(result).Contains("}"); + } + + [Test] + public async Task Scope_Nested_Should_Create_Multiple_Indentation_Levels() + { + var builder = new CSharpCodeBuilder(); + + using (builder.Scope()) + { + _ = builder.Append("Level 1"); + using (builder.Scope()) + { + _ = builder.Append("Level 2"); + } + } + + var result = builder.ToString(); + _ = await Assert.That(result).Contains(" Level 1"); // 4 spaces + _ = await Assert.That(result).Contains(" Level 2"); // 8 spaces + } + + [Test] + public async Task Scope_Multiple_Sequential_Should_Reset_Indentation() + { + var builder = new CSharpCodeBuilder(); + + using (builder.Scope()) + { + _ = builder.Append("First"); + } + + using (builder.Scope()) + { + _ = builder.Append("Second"); + } + + var result = builder.ToString(); + // Both should be at the same indentation level (top level) + _ = await Assert.That(result).Contains("{"); + _ = await Assert.That(result).Contains("}"); + } + + [Test] + public async Task Scope_With_Tabs_Should_Use_Tab_Indentation() + { + var builder = new CSharpCodeBuilder { UseTabs = true }; + + using (builder.Scope()) + { + _ = builder.Append("Hello"); + } + + var result = builder.ToString(); + _ = await Assert.That(result).Contains("\tHello"); + } + + #endregion + + #region ScopeLine Tests + + [Test] + public async Task ScopeLine_Should_Append_Line_Before_Scope() + { + var builder = new CSharpCodeBuilder(); + + using (builder.ScopeLine("public class MyClass")) + { + // Empty scope + } + + var result = builder.ToString(); + _ = await Assert.That(result).Contains("public class MyClass"); + } + + [Test] + public async Task ScopeLine_Should_Include_Opening_Brace() + { + var builder = new CSharpCodeBuilder(); + + using (builder.ScopeLine("public class MyClass")) + { + // Empty scope + } + + var result = builder.ToString(); + _ = await Assert.That(result).Contains("{"); + } + + [Test] + public async Task ScopeLine_Should_Include_Closing_Brace_On_Dispose() + { + var builder = new CSharpCodeBuilder(); + + using (builder.ScopeLine("public class MyClass")) + { + // Empty scope + } + + var result = builder.ToString(); + _ = await Assert.That(result).Contains("}"); + } + + [Test] + public async Task ScopeLine_With_Null_Value_Should_Append_Only_Braces() + { + var builder = new CSharpCodeBuilder(); + + using (builder.ScopeLine(null)) + { + // Empty scope + } + + var result = builder.ToString(); + _ = await Assert.That(result).Contains("{"); + _ = await Assert.That(result).Contains("}"); + } + + [Test] + public async Task ScopeLine_With_Empty_String_Should_Append_Only_Braces() + { + var builder = new CSharpCodeBuilder(); + + using (builder.ScopeLine(string.Empty)) + { + // Empty scope + } + + var result = builder.ToString(); + _ = await Assert.That(result).Contains("{"); + _ = await Assert.That(result).Contains("}"); + } + + [Test] + public async Task ScopeLine_With_Content_Should_Format_Class_Structure() + { + var builder = new CSharpCodeBuilder(); + + using (builder.ScopeLine("public class MyClass")) + { + _ = builder.Append("private int _field;"); + } + + var result = builder.ToString(); + _ = await Assert.That(result).Contains("public class MyClass"); + _ = await Assert.That(result).Contains("private int _field;"); + } + + [Test] + public async Task ScopeLine_Nested_Should_Create_Class_Hierarchy() + { + var builder = new CSharpCodeBuilder(); + + using (builder.ScopeLine("namespace MyNamespace")) + { + using (builder.ScopeLine("public class OuterClass")) + { + _ = builder.Append("public void Method() { }"); + } + } + + var result = builder.ToString(); + _ = await Assert.That(result).Contains("namespace MyNamespace"); + _ = await Assert.That(result).Contains("public class OuterClass"); + _ = await Assert.That(result).Contains("public void Method() { }"); + } + + [Test] + public async Task ScopeLine_Should_Apply_Proper_Indentation_To_Content() + { + var builder = new CSharpCodeBuilder(); + + using (builder.ScopeLine("public class MyClass")) + { + _ = builder.Append("public string Name { get; set; }"); + } + + var result = builder.ToString(); + _ = await Assert.That(result).Contains(" public string Name { get; set; }"); + } + + [Test] + public async Task ScopeLine_Multiple_Sequential_Should_Create_Multiple_Classes() + { + var builder = new CSharpCodeBuilder(); + + using (builder.ScopeLine("public class FirstClass")) + { + _ = builder.Append("public int Id { get; set; }"); + } + + _ = builder.AppendLine(); // Add spacing between classes + + using (builder.ScopeLine("public class SecondClass")) + { + _ = builder.Append("public string Name { get; set; }"); + } + + var result = builder.ToString(); + _ = await Assert.That(result).Contains("public class FirstClass"); + _ = await Assert.That(result).Contains("public class SecondClass"); + } + + [Test] + public async Task ScopeLine_With_Tabs_Should_Use_Tab_Indentation() + { + var builder = new CSharpCodeBuilder { UseTabs = true }; + + using (builder.ScopeLine("public class MyClass")) + { + _ = builder.Append("public void Method() { }"); + } + + var result = builder.ToString(); + _ = await Assert.That(result).Contains("\tpublic void Method() { }"); + } + + [Test] + public async Task ScopeLine_Complex_Nested_Structure_Should_Format_Correctly() + { + var builder = new CSharpCodeBuilder(); + + using (builder.ScopeLine("namespace MyApplication")) + { + using (builder.ScopeLine("public class MyClass")) + { + using (builder.ScopeLine("public void MyMethod()")) + { + _ = builder.Append("var x = 10;"); + } + } + } + + var result = builder.ToString(); + _ = await Assert.That(result).Contains("namespace MyApplication"); + _ = await Assert.That(result).Contains(" public class MyClass"); + _ = await Assert.That(result).Contains(" public void MyMethod()"); + _ = await Assert.That(result).Contains(" var x = 10;"); + } + + [Test] + public async Task ScopeLine_Should_Return_Disposable() + { + var builder = new CSharpCodeBuilder(); + + var scope = builder.ScopeLine("public class MyClass"); + + _ = await Assert.That(scope).IsNotNull(); + scope.Dispose(); + } + + [Test] + public async Task ScopeLine_With_Documentation_Should_Format_Complete_Class() + { + var builder = new CSharpCodeBuilder(); + + _ = builder.AppendXmlDocSummary("Represents a person entity."); + using (builder.ScopeLine("public class Person")) + { + _ = builder.AppendXmlDocSummary("Gets or sets the person's name."); + _ = builder.Append("public string Name { get; set; }"); + } + + var result = builder.ToString(); + _ = await Assert.That(result).Contains("/// "); + _ = await Assert.That(result).Contains("/// Represents a person entity."); + _ = await Assert.That(result).Contains("public class Person"); + _ = await Assert.That(result).Contains("public string Name { get; set; }"); + } + + #endregion +}