Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions CodeBlocker.Test/TemplateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
41 changes: 36 additions & 5 deletions CodeBlocker/Templates/TemplateRendering.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </remarks>
internal static void SpliceFragment(CodeBlocker parent, string fragment)
internal static void SpliceFragment(CodeBlocker parent, string fragment) =>
SpliceLines(parent, SplitLines(parent, fragment));

/// <summary>
/// Writes already-split lines at the parent's current indent, as <see cref="SpliceFragment"/> does
/// for a whole fragment.
/// </summary>
/// <param name="parent">The <see cref="CodeBlocker"/> to write the lines to.</param>
/// <param name="lines">The lines, as <see cref="SplitLines"/> returns them.</param>
private static void SpliceLines(CodeBlocker parent, IEnumerable<string> lines)
{
foreach (string line in SplitLines(parent, fragment))
foreach (string line in lines)
{
if (line.Length == 0)
{
Expand Down Expand Up @@ -279,7 +288,12 @@ internal static void WriteBody(CodeBlocker codeBlocker, Action<CodeBlocker>? 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,
Expand All @@ -302,7 +316,7 @@ internal static void WriteBody(CodeBlocker codeBlocker, Action<CodeBlocker>? bod
codeBlocker.WriteLine();
using (new Scope(codeBlocker))
{
SpliceFragment(codeBlocker, body);
SpliceLines(codeBlocker, lines);
}

return;
Expand All @@ -318,7 +332,24 @@ internal static void WriteBody(CodeBlocker codeBlocker, Action<CodeBlocker>? bod

// A braced body starts on the line after the declaration.
codeBlocker.WriteLine();
SpliceFragment(codeBlocker, body);
SpliceLines(codeBlocker, lines);
}

/// <summary>
/// Drops the empty lines at the start and end of a body's lines, keeping those between them.
/// </summary>
/// <param name="lines">The lines, as <see cref="SplitLines"/> returns them.</param>
/// <returns>The lines from the first non-empty one to the last, or none if every line is empty.</returns>
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)];
}

/// <summary>
Expand Down
Loading