diff --git a/CodeBlocker.Test/TemplateTests.cs b/CodeBlocker.Test/TemplateTests.cs index 3fd8237..86b3df6 100644 --- a/CodeBlocker.Test/TemplateTests.cs +++ b/CodeBlocker.Test/TemplateTests.cs @@ -951,6 +951,82 @@ public void ABlankLineInsideABracedBodyIsKept() Assert.AreEqual("public void Run()\n{\n\tFirst();\n\n\tSecond();\n}\n", Render(method)); } + [TestMethod] + public void ABracedBodyDropsTheBlankLinesAroundIt() + { + ClassTemplate type = new() { Name = "C" }; + type.Members.Add(new MethodTemplate + { + Type = "void", + Name = "A", + BodyFactory = codeBlocker => + { + using (new Scope(codeBlocker)) + { + codeBlocker.WriteLine("x();"); + } + + codeBlocker.NewLine(); + }, + }); + type.Members.Add(new MethodTemplate + { + Type = "void", + Name = "B", + BodyFactory = codeBlocker => + { + codeBlocker.NewLine(); + codeBlocker.WriteLine("{"); + codeBlocker.WriteLine("}"); + }, + }); + + // One blank line between the members, and each opening brace directly under its signature. + Assert.AreEqual( + "class C\n{\n\tvoid A()\n\t{\n\t\tx();\n\t}\n\n\tvoid B()\n\t{\n\t}\n}\n", + Render(type)); + } + + [TestMethod] + public void ABracedBodyKeepsItsInteriorBlankLinesWhenItsOuterOnesAreDropped() => + Assert.AreEqual( + "void Run()\n{\n\tFirst();\n\n\tSecond();\n}\n", + Render(new MethodTemplate + { + Type = "void", + Name = "Run", + BodyFactory = codeBlocker => + { + codeBlocker.NewLine(); + codeBlocker.NewLine(); + using (new Scope(codeBlocker)) + { + codeBlocker.WriteLine("First();"); + codeBlocker.NewLine(); + codeBlocker.WriteLine("Second();"); + } + + codeBlocker.NewLine(); + codeBlocker.NewLine(); + }, + })); + + [TestMethod] + public void ACommentOnlyBodyDropsTheBlankLinesAroundIt() => + Assert.AreEqual( + "void Reset()\n{\n\t// nothing to reset\n}\n", + Render(new MethodTemplate + { + Type = "void", + Name = "Reset", + BodyFactory = codeBlocker => + { + codeBlocker.NewLine(); + codeBlocker.WriteLine("// nothing to reset"); + codeBlocker.NewLine(); + }, + })); + [TestMethod] public void AMultiLineBodyIsIndentedToWhereItIsSpliced() { diff --git a/CodeBlocker/Templates/TemplateRendering.cs b/CodeBlocker/Templates/TemplateRendering.cs index f993ece..c95bb2d 100644 --- a/CodeBlocker/Templates/TemplateRendering.cs +++ b/CodeBlocker/Templates/TemplateRendering.cs @@ -102,9 +102,18 @@ internal static string[] SplitLines(CodeBlocker parent, string fragment) /// would land at the current indent and every following line would keep the indentation it had /// in its own buffer, which is why a nested body used to come out flush against the left margin. /// - internal static void SpliceFragment(CodeBlocker parent, string fragment) + internal static void SpliceFragment(CodeBlocker parent, string fragment) => + SpliceLines(parent, SplitLines(parent, fragment)); + + /// + /// Writes already-split lines at the parent's current indent, as does + /// for a whole fragment. + /// + /// The to write the lines to. + /// The lines, as returns them. + private static void SpliceLines(CodeBlocker parent, IEnumerable lines) { - foreach (string line in SplitLines(parent, fragment)) + foreach (string line in lines) { if (line.Length == 0) { @@ -279,7 +288,12 @@ internal static void WriteBody(CodeBlocker codeBlocker, Action? bod } string body = RenderFragment(codeBlocker, bodyFactory); - string[] lines = SplitLines(codeBlocker, body); + + // Blank lines before the first and after the last line of the body are spacing the factory + // left around it, not part of it: spliced, a leading one would separate the declaration from + // its opening brace and a trailing one would add to the blank line between members. Blank + // lines inside the body are kept. + string[] lines = TrimBlankLines(SplitLines(codeBlocker, body)); // Blank lines carry no body of their own, so they are left out when deciding its shape. A // factory that only wrote spacing, such as a NewLine after a loop over an empty collection, @@ -302,7 +316,7 @@ internal static void WriteBody(CodeBlocker codeBlocker, Action? bod codeBlocker.WriteLine(); using (new Scope(codeBlocker)) { - SpliceFragment(codeBlocker, body); + SpliceLines(codeBlocker, lines); } return; @@ -318,7 +332,24 @@ internal static void WriteBody(CodeBlocker codeBlocker, Action? bod // A braced body starts on the line after the declaration. codeBlocker.WriteLine(); - SpliceFragment(codeBlocker, body); + SpliceLines(codeBlocker, lines); + } + + /// + /// Drops the empty lines at the start and end of a body's lines, keeping those between them. + /// + /// The lines, as returns them. + /// The lines from the first non-empty one to the last, or none if every line is empty. + private static string[] TrimBlankLines(string[] lines) + { + int first = Array.FindIndex(lines, line => line.Length != 0); + if (first < 0) + { + return []; + } + + int last = Array.FindLastIndex(lines, line => line.Length != 0); + return [.. lines.Skip(first).Take(last - first + 1)]; } ///