From 2bf13f6835645f5af759c63431428974a1839c91 Mon Sep 17 00:00:00 2001 From: Bohan Feng <45181245+fengb3@users.noreply.github.com> Date: Tue, 9 Jun 2026 16:21:14 +0800 Subject: [PATCH] Enable nested control flow blocks by adding CodeOption extensions Add If, Else, ElseIf, For, While, DoWhile, Foreach, Switch, and Try extension methods on CodeOption (base class) so control flow constructs can be nested inside any block (e.g. Switch inside For, If inside Case). The existing MethodOption-specific extensions are preserved for backward compatibility. Includes tests for nesting patterns. Fixes #11 Co-Authored-By: Claude Opus 4.8 --- .../Csharp/ControlFlowTests.cs | 758 +++++++++++------- EasyCodeBuilder/Csharp/Code.cs | 485 ++++++----- 2 files changed, 759 insertions(+), 484 deletions(-) diff --git a/EasyCodeBuilder.Test/Csharp/ControlFlowTests.cs b/EasyCodeBuilder.Test/Csharp/ControlFlowTests.cs index 899de2f..42023c8 100644 --- a/EasyCodeBuilder.Test/Csharp/ControlFlowTests.cs +++ b/EasyCodeBuilder.Test/Csharp/ControlFlowTests.cs @@ -1,282 +1,476 @@ -using Fengb3.EasyCodeBuilder.Csharp; -using Xunit.Abstractions; - -namespace EasyCodeBuilder.Test.Csharp; - -public class ControlFlowTests(ITestOutputHelper output) -{ - private static string Norm(string text) => text.Replace("\r\n", "\n").Trim(); - - [Fact] - public void IfStatement() - { - var code = new MethodOption() - .WithKeyword("public") - .WithName("Check") - .WithReturnType("void") - .If(@if => - { - @if.Condition = "x > 0"; - @if.AppendLine("Console.WriteLine(x);"); - }) - .Build(); - - var expected = """ - public void Check() - { - if (x > 0) - { - Console.WriteLine(x); - } - } - """; - - Assert.Equal(Norm(expected), Norm(code)); - output.WriteLine(code); - } - - [Fact] - public void IfElseStatement() - { - var code = new MethodOption() - .WithKeyword("public") - .WithName("Check") - .WithReturnType("void") - .If(@if => - { - @if.Condition = "x > 0"; - @if.AppendLine("Console.WriteLine(\"positive\");"); - }) - .Else(@else => - { - @else.AppendLine("Console.WriteLine(\"non-positive\");"); - }) - .Build(); - - var expected = """ - public void Check() - { - if (x > 0) - { - Console.WriteLine("positive"); - } - else - { - Console.WriteLine("non-positive"); - } - } - """; - - Assert.Equal(Norm(expected), Norm(code)); - output.WriteLine(code); - } - - [Fact] - public void IfElseIfElseStatement() - { - var code = new MethodOption() - .WithKeyword("public") - .WithName("Classify") - .WithReturnType("void") - .If(@if => - { - @if.Condition = "x > 0"; - @if.AppendLine("Console.WriteLine(\"positive\");"); - }) - .ElseIf(@elseIf => - { - @elseIf.Condition = "x < 0"; - @elseIf.AppendLine("Console.WriteLine(\"negative\");"); - }) - .Else(@else => - { - @else.AppendLine("Console.WriteLine(\"zero\");"); - }) - .Build(); - - // Verify structural output contains all branches - Assert.Contains("if (x > 0)", code); - Assert.Contains("else if (x < 0)", code); - Assert.Contains("else", code); - Assert.Contains("Console.WriteLine(\"positive\");", code); - Assert.Contains("Console.WriteLine(\"negative\");", code); - Assert.Contains("Console.WriteLine(\"zero\");", code); - output.WriteLine(code); - } - - [Fact] - public void NestedIfStatement() - { - var code = new MethodOption() - .WithKeyword("public") - .WithName("Check") - .WithReturnType("void") - .If(outer => - { - outer.Condition = "x > 0"; - outer.AddChild(inner => - { - inner.Condition = "x > 100"; - inner.AppendLine("Console.WriteLine(\"large\");"); - }); - }) - .Build(); - - Assert.Contains("if (x > 0)", code); - Assert.Contains("if (x > 100)", code); - Assert.Contains("Console.WriteLine(\"large\");", code); - // Verify nesting: inner if should be indented more - Assert.Contains(" if (x > 100)", code.Replace("\r\n", "\n")); - output.WriteLine(code); - } - - [Fact] - public void WhileLoop() - { - var code = new MethodOption() - .WithKeyword("public") - .WithName("Loop") - .WithReturnType("void") - .While(@while => - { - @while.Condition = "running"; - @while.AppendLine("DoWork();"); - }) - .Build(); - - var expected = """ - public void Loop() - { - while (running) - { - DoWork(); - } - } - """; - - Assert.Equal(Norm(expected), Norm(code)); - output.WriteLine(code); - } - - [Fact] - public void DoWhileLoop() - { - var code = new MethodOption() - .WithKeyword("public") - .WithName("Loop") - .WithReturnType("void") - .DoWhile(@do => - { - @do.Condition = "running"; - @do.AppendLine("DoWork();"); - }) - .Build(); - - var expected = """ - public void Loop() - { - do - { - DoWork(); - }while (running); - } - """; - - Assert.Equal(Norm(expected), Norm(code)); - output.WriteLine(code); - } - - [Fact] - public void ForeachLoop() - { - var code = new MethodOption() - .WithKeyword("public") - .WithName("Iterate") - .WithReturnType("void") - .Foreach(@foreach => - { - @foreach.WithVariable("var", "item", "items"); - @foreach.AppendLine("Console.WriteLine(item);"); - }) - .Build(); - - var expected = """ - public void Iterate() - { - foreach (var item in items) - { - Console.WriteLine(item); - } - } - """; - - Assert.Equal(Norm(expected), Norm(code)); - output.WriteLine(code); - } - - [Fact] - public void ForLoopStandalone() - { - var code = new MethodOption() - .WithKeyword("private") - .WithName("Count") - .WithReturnType("void") - .For(@for => - { - @for.WithInitializer("int i = 0"); - @for.WithCondition("i < 5"); - @for.WithIterator("i++"); - @for.AppendLine("sum += i;"); - }) - .Build(); - - var expected = """ - private void Count() - { - for (int i = 0; i < 5; i++) - { - sum += i; - } - } - """; - - Assert.Equal(Norm(expected), Norm(code)); - output.WriteLine(code); - } - - [Fact] - public void SwitchWithExpressionBody() - { - var method = new MethodOption() - .WithKeyword("public") - .WithName("GetLabel") - .WithReturnType("string") - .WithParameters("int value"); - - method.Switch(@switch => - { - @switch.Expression = "value"; - @switch.Case(@case => - { - @case.Value = "1"; - @case.AppendLine("return \"One\";"); - }); - @switch.Case(@case => - { - @case.Value = "2"; - @case.AppendLine("return \"Two\";"); - }); - @switch.Default(@default => @default.AppendLine("return \"Unknown\";")); - }); - - var code = method.Build(); - - Assert.Contains("switch (value)", code); - Assert.Contains("case 1:", code); - Assert.Contains("case 2:", code); - Assert.Contains("default:", code); - Assert.Contains("return \"One\";", code); - Assert.Contains("return \"Unknown\";", code); - output.WriteLine(code); - } -} +using Fengb3.EasyCodeBuilder.Csharp; +using Xunit.Abstractions; + +namespace EasyCodeBuilder.Test.Csharp; + +public class ControlFlowTests(ITestOutputHelper output) +{ + private static string Norm(string text) => text.Replace("\r\n", "\n").Trim(); + + [Fact] + public void IfStatement() + { + var code = new MethodOption() + .WithKeyword("public") + .WithName("Check") + .WithReturnType("void") + .If(@if => + { + @if.Condition = "x > 0"; + @if.AppendLine("Console.WriteLine(x);"); + }) + .Build(); + + var expected = """ + public void Check() + { + if (x > 0) + { + Console.WriteLine(x); + } + } + """; + + Assert.Equal(Norm(expected), Norm(code)); + output.WriteLine(code); + } + + [Fact] + public void IfElseStatement() + { + var code = new MethodOption() + .WithKeyword("public") + .WithName("Check") + .WithReturnType("void") + .If(@if => + { + @if.Condition = "x > 0"; + @if.AppendLine("Console.WriteLine(\"positive\");"); + }) + .Else(@else => + { + @else.AppendLine("Console.WriteLine(\"non-positive\");"); + }) + .Build(); + + var expected = """ + public void Check() + { + if (x > 0) + { + Console.WriteLine("positive"); + } + else + { + Console.WriteLine("non-positive"); + } + } + """; + + Assert.Equal(Norm(expected), Norm(code)); + output.WriteLine(code); + } + + [Fact] + public void IfElseIfElseStatement() + { + var code = new MethodOption() + .WithKeyword("public") + .WithName("Classify") + .WithReturnType("void") + .If(@if => + { + @if.Condition = "x > 0"; + @if.AppendLine("Console.WriteLine(\"positive\");"); + }) + .ElseIf(@elseIf => + { + @elseIf.Condition = "x < 0"; + @elseIf.AppendLine("Console.WriteLine(\"negative\");"); + }) + .Else(@else => + { + @else.AppendLine("Console.WriteLine(\"zero\");"); + }) + .Build(); + + // Verify structural output contains all branches + Assert.Contains("if (x > 0)", code); + Assert.Contains("else if (x < 0)", code); + Assert.Contains("else", code); + Assert.Contains("Console.WriteLine(\"positive\");", code); + Assert.Contains("Console.WriteLine(\"negative\");", code); + Assert.Contains("Console.WriteLine(\"zero\");", code); + output.WriteLine(code); + } + + [Fact] + public void NestedIfStatement() + { + var code = new MethodOption() + .WithKeyword("public") + .WithName("Check") + .WithReturnType("void") + .If(outer => + { + outer.Condition = "x > 0"; + outer.AddChild(inner => + { + inner.Condition = "x > 100"; + inner.AppendLine("Console.WriteLine(\"large\");"); + }); + }) + .Build(); + + Assert.Contains("if (x > 0)", code); + Assert.Contains("if (x > 100)", code); + Assert.Contains("Console.WriteLine(\"large\");", code); + // Verify nesting: inner if should be indented more + Assert.Contains(" if (x > 100)", code.Replace("\r\n", "\n")); + output.WriteLine(code); + } + + [Fact] + public void WhileLoop() + { + var code = new MethodOption() + .WithKeyword("public") + .WithName("Loop") + .WithReturnType("void") + .While(@while => + { + @while.Condition = "running"; + @while.AppendLine("DoWork();"); + }) + .Build(); + + var expected = """ + public void Loop() + { + while (running) + { + DoWork(); + } + } + """; + + Assert.Equal(Norm(expected), Norm(code)); + output.WriteLine(code); + } + + [Fact] + public void DoWhileLoop() + { + var code = new MethodOption() + .WithKeyword("public") + .WithName("Loop") + .WithReturnType("void") + .DoWhile(@do => + { + @do.Condition = "running"; + @do.AppendLine("DoWork();"); + }) + .Build(); + + var expected = """ + public void Loop() + { + do + { + DoWork(); + }while (running); + } + """; + + Assert.Equal(Norm(expected), Norm(code)); + output.WriteLine(code); + } + + [Fact] + public void ForeachLoop() + { + var code = new MethodOption() + .WithKeyword("public") + .WithName("Iterate") + .WithReturnType("void") + .Foreach(@foreach => + { + @foreach.WithVariable("var", "item", "items"); + @foreach.AppendLine("Console.WriteLine(item);"); + }) + .Build(); + + var expected = """ + public void Iterate() + { + foreach (var item in items) + { + Console.WriteLine(item); + } + } + """; + + Assert.Equal(Norm(expected), Norm(code)); + output.WriteLine(code); + } + + [Fact] + public void ForLoopStandalone() + { + var code = new MethodOption() + .WithKeyword("private") + .WithName("Count") + .WithReturnType("void") + .For(@for => + { + @for.WithInitializer("int i = 0"); + @for.WithCondition("i < 5"); + @for.WithIterator("i++"); + @for.AppendLine("sum += i;"); + }) + .Build(); + + var expected = """ + private void Count() + { + for (int i = 0; i < 5; i++) + { + sum += i; + } + } + """; + + Assert.Equal(Norm(expected), Norm(code)); + output.WriteLine(code); + } + + [Fact] + public void SwitchWithExpressionBody() + { + var method = new MethodOption() + .WithKeyword("public") + .WithName("GetLabel") + .WithReturnType("string") + .WithParameters("int value"); + + method.Switch(@switch => + { + @switch.Expression = "value"; + @switch.Case(@case => + { + @case.Value = "1"; + @case.AppendLine("return \"One\";"); + }); + @switch.Case(@case => + { + @case.Value = "2"; + @case.AppendLine("return \"Two\";"); + }); + @switch.Default(@default => @default.AppendLine("return \"Unknown\";")); + }); + + var code = method.Build(); + + Assert.Contains("switch (value)", code); + Assert.Contains("case 1:", code); + Assert.Contains("case 2:", code); + Assert.Contains("default:", code); + Assert.Contains("return \"One\";", code); + Assert.Contains("return \"Unknown\";", code); + output.WriteLine(code); + } + + [Fact] + public void ForContainingSwitch_Issue11() + { + var code = new MethodOption() + .WithKeyword("public") + .WithName("Parse") + .WithReturnType("void") + .WithParameters("string[] args") + .For(f => + { + f.WithInitializer("int i = 0") + .WithCondition("i < args.Length") + .WithIterator("i++"); + + f.Switch(s => + { + s.WithExpression("args[i]"); + s.Case(c => + { + c.Value = "\"--x\""; + c.AppendLine("x = int.Parse(args[++i]);"); + c.AppendLine("break;"); + }); + s.Default(d => + { + d.AppendLine("break;"); + }); + }); + }) + .Build(); + + var expected = """ + public void Parse(string[] args) + { + for (int i = 0; i < args.Length; i++) + { + switch (args[i]) + { + case "--x": + { + x = int.Parse(args[++i]); + break; + } + default: + { + break; + } + } + } + } + """; + + Assert.Equal(Norm(expected), Norm(code)); + output.WriteLine(code); + } + + [Fact] + public void DeepNesting_ForSwitchCaseIf() + { + var code = new MethodOption() + .WithKeyword("public") + .WithName("Deep") + .WithReturnType("void") + .For(f => + { + f.WithInitializer("int i = 0") + .WithCondition("i < items.Count") + .WithIterator("i++"); + + f.Switch(s => + { + s.WithExpression("items[i].Type"); + s.Case(c => + { + c.Value = "\"A\""; + c.If(@if => + { + @if.Condition = "items[i].Value > 0"; + @if.AppendLine("Process(items[i]);"); + }); + }); + }); + }) + .Build(); + + Assert.Contains("for (int i = 0; i < items.Count; i++)", code); + Assert.Contains("switch (items[i].Type)", code); + Assert.Contains("case \"A\":", code); + Assert.Contains("if (items[i].Value > 0)", code); + Assert.Contains("Process(items[i]);", code); + output.WriteLine(code); + } + + [Fact] + public void WhileContainingFor() + { + var code = new MethodOption() + .WithKeyword("public") + .WithName("Retry") + .WithReturnType("void") + .While(w => + { + w.Condition = "retry"; + w.For(f => + { + f.WithInitializer("int i = 0") + .WithCondition("i < retries") + .WithIterator("i++"); + f.AppendLine("Attempt(i);"); + }); + }) + .Build(); + + Assert.Contains("while (retry)", code); + Assert.Contains("for (int i = 0; i < retries; i++)", code); + Assert.Contains("Attempt(i);", code); + output.WriteLine(code); + } + + [Fact] + public void TryInsideFor() + { + var code = new MethodOption() + .WithKeyword("public") + .WithName("SafeIterate") + .WithReturnType("void") + .WithParameters("List items") + .For(f => + { + f.WithInitializer("int i = 0") + .WithCondition("i < items.Count") + .WithIterator("i++"); + + f.Try(t => + { + t.AppendLine("Process(items[i]);"); + t.Catch(c => + { + c.WithExceptionType("Exception") + .WithVariableName("ex"); + c.AppendLine("Log(ex);"); + }); + }); + }) + .Build(); + + Assert.Contains("for (int i = 0; i < items.Count; i++)", code); + Assert.Contains("try", code); + Assert.Contains("Process(items[i]);", code); + Assert.Contains("catch (Exception ex)", code); + Assert.Contains("Log(ex);", code); + output.WriteLine(code); + } + + [Fact] + public void IfElseIfElseInsideFor() + { + var code = new MethodOption() + .WithKeyword("public") + .WithName("Classify") + .WithReturnType("void") + .For(f => + { + f.WithInitializer("int i = 0") + .WithCondition("i < values.Length") + .WithIterator("i++"); + + f.If(@if => + { + @if.Condition = "values[i] > 0"; + @if.AppendLine("Console.WriteLine(\"positive\");"); + }) + .ElseIf(elseIf => + { + elseIf.Condition = "values[i] < 0"; + elseIf.AppendLine("Console.WriteLine(\"negative\");"); + }) + .Else(@else => + { + @else.AppendLine("Console.WriteLine(\"zero\");"); + }); + }) + .Build(); + + Assert.Contains("for (int i = 0; i < values.Length; i++)", code); + Assert.Contains("if (values[i] > 0)", code); + Assert.Contains("else if (values[i] < 0)", code); + Assert.Contains("else", code); + Assert.Contains("Console.WriteLine(\"positive\");", code); + Assert.Contains("Console.WriteLine(\"negative\");", code); + Assert.Contains("Console.WriteLine(\"zero\");", code); + output.WriteLine(code); + } +} diff --git a/EasyCodeBuilder/Csharp/Code.cs b/EasyCodeBuilder/Csharp/Code.cs index e432128..dfa1741 100644 --- a/EasyCodeBuilder/Csharp/Code.cs +++ b/EasyCodeBuilder/Csharp/Code.cs @@ -1,203 +1,284 @@ -using System; -using System.Linq; - -namespace Fengb3.EasyCodeBuilder.Csharp; - -/// -/// Provides the entry point and extension methods for building C# code using a configuration-based API. -/// -public static partial class Code -{ - /// - /// Creates a new root to begin building code. - /// - /// A new instance. - public static CodeOption Create() - { - return new CodeOption(); - } - - /// - /// Backward-compatible API: add using directives by namespace names. - /// - public static CodeOption Using(this CodeOption option, params string[] usings) - { - foreach (var u in usings) - { - option.AddChild(uo => { uo.Name = u; }); - } - - return option; - } - - /// - /// Add a single using directive with full configuration. - /// - public static CodeOption Using(this CodeOption option, Action configure) - => option.AddChild(configure); - - /// - /// using static {typeOrNamespace}; - /// - public static CodeOption UsingStatic(this CodeOption option, string typeOrNamespace) - => option.AddChild(uo => - { - uo.Name = typeOrNamespace; - uo.IsStatic = true; - }); - - /// - /// using {alias} = {typeOrNamespace}; - /// - public static CodeOption UsingAlias(this CodeOption option, string alias, string typeOrNamespace) - => option.AddChild(uo => - { - uo.Alias = alias; - uo.Name = typeOrNamespace; - }); - - /// - /// global using {typeOrNamespace}; - /// - public static CodeOption GlobalUsing(this CodeOption option, string typeOrNamespace) - => option.AddChild(uo => - { - uo.Keywords.Add("global"); - uo.Name = typeOrNamespace; - }); - - /// - /// global using static {typeOrNamespace}; - /// - public static CodeOption GlobalUsingStatic(this CodeOption option, string typeOrNamespace) - => option.AddChild(uo => - { - uo.Keywords.Add("global"); - uo.Name = typeOrNamespace; - uo.IsStatic = true; - }); - - /// - /// Creates a new child option, configures it via , - /// and attaches its to the parent's . - /// - /// The parent code option. - /// Action to configure the new child. - /// Parent option type. - /// Child option type. - /// The parent option, for fluent chaining. - public static TParent AddChild(this TParent parent, Action configureChild) - where TParent : CodeOption where TChild : CodeOption, new() - { - var child = new TChild(); - configureChild?.Invoke(child); - return parent.AddChild(child); - } - - /// - /// Attaches an existing child option's to the parent's . - /// - /// The parent code option. - /// The child code option to attach. - /// Parent option type. - /// Child option type. - /// The parent option, for fluent chaining. - public static TParent AddChild(this TParent parent, TChild child) - where TParent : CodeOption where TChild : CodeOption - { - parent.OnChildren += child.Build; - return parent; - } - - /// - /// 添加一行或多行代码 - /// - /// 代码选项 - /// 代码行 - /// 代码选项 - public static CodeOption AppendLine(this CodeOption option, params string[] lines) - { - option.OnChildren += cb => cb.AppendLines(lines); - return option; - } - - /// - /// Add a namespace to the code. - /// - /// The root code option. - /// Action to configure the namespace. - /// The root code option, for fluent chaining. - public static CodeOption Namespace(this CodeOption option, Action configure) - => option.AddChild(configure); - - /// - /// Add a class to the code. - /// - /// The root code option. - /// Action to configure the class. - /// The root code option, for fluent chaining. - public static CodeOption Class(this CodeOption option, Action configure) - => option.AddChild(configure); - - - /// - /// add attribute to an element - /// - /// The code option to add attributes to. - /// Attribute declarations (e.g. "Serializable", "Obsolete"). - /// The option type. - /// The option, for fluent chaining. - public static T WithAttributes(this T option, params string[] attributes) where T : CodeOption - { - option.BeforeChildren += cb => cb.AppendLines(attributes.Select(attr => $"[{attr}]")); - return option; - } - - /// - /// add XML doc to an element - /// - /// The code option to add XML documentation to. - /// Action to configure the XML doc. - /// The option type. - /// The option, for fluent chaining. - public static T WithXmlDoc(this T option, Action configure) where T : CodeOption - { - option.BeforeChildren += cb => - { - var xmlDocOption = new XmlDocOption(); - configure(xmlDocOption); - xmlDocOption.Build(cb); - return cb; - }; - return option; - } - - - /// - /// Add single or multiple lines to a code block - /// - /// The code option. - /// The lines of code to add. - /// The option type. - /// The option, for fluent chaining. - public static T AppendLines(this T option, params string[] lines) where T : CodeOption - { - foreach (var line in lines) - option.AppendLine(line); - - return option; - } - - /// - /// 构建代码 - /// - /// 根选项 - /// 代码构建器 - /// 生成的代码字符串 - public static string Build(this CodeOption root, CodeBuilder? cb = null) - { - cb ??= new CodeBuilder(' ', 2, "\n{", "}", 1024); - root.Build(cb); - return cb.ToString(); - } +using System; +using System.Linq; + +namespace Fengb3.EasyCodeBuilder.Csharp; + +/// +/// Provides the entry point and extension methods for building C# code using a configuration-based API. +/// +public static partial class Code +{ + /// + /// Creates a new root to begin building code. + /// + /// A new instance. + public static CodeOption Create() + { + return new CodeOption(); + } + + /// + /// Backward-compatible API: add using directives by namespace names. + /// + public static CodeOption Using(this CodeOption option, params string[] usings) + { + foreach (var u in usings) + { + option.AddChild(uo => { uo.Name = u; }); + } + + return option; + } + + /// + /// Add a single using directive with full configuration. + /// + public static CodeOption Using(this CodeOption option, Action configure) + => option.AddChild(configure); + + /// + /// using static {typeOrNamespace}; + /// + public static CodeOption UsingStatic(this CodeOption option, string typeOrNamespace) + => option.AddChild(uo => + { + uo.Name = typeOrNamespace; + uo.IsStatic = true; + }); + + /// + /// using {alias} = {typeOrNamespace}; + /// + public static CodeOption UsingAlias(this CodeOption option, string alias, string typeOrNamespace) + => option.AddChild(uo => + { + uo.Alias = alias; + uo.Name = typeOrNamespace; + }); + + /// + /// global using {typeOrNamespace}; + /// + public static CodeOption GlobalUsing(this CodeOption option, string typeOrNamespace) + => option.AddChild(uo => + { + uo.Keywords.Add("global"); + uo.Name = typeOrNamespace; + }); + + /// + /// global using static {typeOrNamespace}; + /// + public static CodeOption GlobalUsingStatic(this CodeOption option, string typeOrNamespace) + => option.AddChild(uo => + { + uo.Keywords.Add("global"); + uo.Name = typeOrNamespace; + uo.IsStatic = true; + }); + + /// + /// Creates a new child option, configures it via , + /// and attaches its to the parent's . + /// + /// The parent code option. + /// Action to configure the new child. + /// Parent option type. + /// Child option type. + /// The parent option, for fluent chaining. + public static TParent AddChild(this TParent parent, Action configureChild) + where TParent : CodeOption where TChild : CodeOption, new() + { + var child = new TChild(); + configureChild?.Invoke(child); + return parent.AddChild(child); + } + + /// + /// Attaches an existing child option's to the parent's . + /// + /// The parent code option. + /// The child code option to attach. + /// Parent option type. + /// Child option type. + /// The parent option, for fluent chaining. + public static TParent AddChild(this TParent parent, TChild child) + where TParent : CodeOption where TChild : CodeOption + { + parent.OnChildren += child.Build; + return parent; + } + + /// + /// 添加一行或多行代码 + /// + /// 代码选项 + /// 代码行 + /// 代码选项 + public static CodeOption AppendLine(this CodeOption option, params string[] lines) + { + option.OnChildren += cb => cb.AppendLines(lines); + return option; + } + + /// + /// Add an if statement to the code block. + /// + /// The code option. + /// Action to configure the if statement. + /// The code option, for fluent chaining. + public static CodeOption If(this CodeOption option, Action configure) + => option.AddChild(configure); + + /// + /// Add an else statement to the code block. + /// + /// The code option. + /// Action to configure the else statement. + /// The code option, for fluent chaining. + public static CodeOption Else(this CodeOption option, Action configure) + => option.AddChild(configure); + + /// + /// Add an else-if statement to the code block. + /// + /// The code option. + /// Action to configure the else-if statement. + /// The code option, for fluent chaining. + public static CodeOption ElseIf(this CodeOption option, Action configure) + => option.AddChild(configure); + + /// + /// Add a for loop to the code block. + /// + /// The code option. + /// Action to configure the for loop. + /// The code option, for fluent chaining. + public static CodeOption For(this CodeOption option, Action configure) + => option.AddChild(configure); + + /// + /// Add a while loop to the code block. + /// + /// The code option. + /// Action to configure the while loop. + /// The code option, for fluent chaining. + public static CodeOption While(this CodeOption option, Action configure) + => option.AddChild(configure); + + /// + /// Add a do-while loop to the code block. + /// + /// The code option. + /// Action to configure the do-while loop. + /// The code option, for fluent chaining. + public static CodeOption DoWhile(this CodeOption option, Action configure) + => option.AddChild(configure); + + /// + /// Add a foreach loop to the code block. + /// + /// The code option. + /// Action to configure the foreach loop. + /// The code option, for fluent chaining. + public static CodeOption Foreach(this CodeOption option, Action configure) + => option.AddChild(configure); + + /// + /// Add a switch statement to the code block. + /// + /// The code option. + /// Action to configure the switch statement. + /// The code option, for fluent chaining. + public static CodeOption Switch(this CodeOption option, Action configure) + => option.AddChild(configure); + + /// + /// Add a try-catch-finally statement to the code block. + /// + /// The code option. + /// Action to configure the try statement. + /// The code option, for fluent chaining. + public static CodeOption Try(this CodeOption option, Action configure) + => option.AddChild(configure); + + /// + /// Add a namespace to the code. + /// + /// The root code option. + /// Action to configure the namespace. + /// The root code option, for fluent chaining. + public static CodeOption Namespace(this CodeOption option, Action configure) + => option.AddChild(configure); + + /// + /// Add a class to the code. + /// + /// The root code option. + /// Action to configure the class. + /// The root code option, for fluent chaining. + public static CodeOption Class(this CodeOption option, Action configure) + => option.AddChild(configure); + + + /// + /// add attribute to an element + /// + /// The code option to add attributes to. + /// Attribute declarations (e.g. "Serializable", "Obsolete"). + /// The option type. + /// The option, for fluent chaining. + public static T WithAttributes(this T option, params string[] attributes) where T : CodeOption + { + option.BeforeChildren += cb => cb.AppendLines(attributes.Select(attr => $"[{attr}]")); + return option; + } + + /// + /// add XML doc to an element + /// + /// The code option to add XML documentation to. + /// Action to configure the XML doc. + /// The option type. + /// The option, for fluent chaining. + public static T WithXmlDoc(this T option, Action configure) where T : CodeOption + { + option.BeforeChildren += cb => + { + var xmlDocOption = new XmlDocOption(); + configure(xmlDocOption); + xmlDocOption.Build(cb); + return cb; + }; + return option; + } + + + /// + /// Add single or multiple lines to a code block + /// + /// The code option. + /// The lines of code to add. + /// The option type. + /// The option, for fluent chaining. + public static T AppendLines(this T option, params string[] lines) where T : CodeOption + { + foreach (var line in lines) + option.AppendLine(line); + + return option; + } + + /// + /// 构建代码 + /// + /// 根选项 + /// 代码构建器 + /// 生成的代码字符串 + public static string Build(this CodeOption root, CodeBuilder? cb = null) + { + cb ??= new CodeBuilder(' ', 2, "\n{", "}", 1024); + root.Build(cb); + return cb.ToString(); + } } \ No newline at end of file