Skip to content
Merged
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
30 changes: 30 additions & 0 deletions CodeBlocker.Test/CodeBlockerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,36 @@
Assert.AreEqual(CodeBlocker.DefaultNewLineString, result);
}

[TestMethod]
public void NewLineAfterAPartialWriteKeepsTheNextLineIndented()
{
using CodeBlocker codeBlocker = CodeBlocker.Create();

codeBlocker.WriteLine("class C");
using (new Scope(codeBlocker))
{
codeBlocker.Write("int x = 1;");
codeBlocker.NewLine();
codeBlocker.WriteLine("int y = 2;");
}

Assert.AreEqual("class C\n{\n\tint x = 1;\n\tint y = 2;\n}\n", codeBlocker.ToString());
}

[TestMethod]
public void NewLineAtTheStartOfALineStillWritesNoIndentation()
{
using CodeBlocker codeBlocker = CodeBlocker.Create();

codeBlocker.Indent();
codeBlocker.Write("a");
codeBlocker.NewLine();
codeBlocker.NewLine();
codeBlocker.WriteLine("b");

Assert.AreEqual("\ta\n\n\tb\n", codeBlocker.ToString());
}

[TestMethod]
public void WriteLineWithIndentationShouldRespectIndentLevel()
{
Expand Down Expand Up @@ -112,7 +142,7 @@
}

[TestMethod]
public void DisposeShouldNotThrowException()

Check warning on line 145 in CodeBlocker.Test/CodeBlockerTests.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Add at least one assertion to this test case.

Check warning on line 145 in CodeBlocker.Test/CodeBlockerTests.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Add at least one assertion to this test case.

Check warning on line 145 in CodeBlocker.Test/CodeBlockerTests.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Add at least one assertion to this test case.

Check warning on line 145 in CodeBlocker.Test/CodeBlockerTests.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Add at least one assertion to this test case.
{
// Arrange

Expand All @@ -125,7 +155,7 @@
}

[TestMethod]
public void DisposeMultipleCallsShouldNotThrow()

Check warning on line 158 in CodeBlocker.Test/CodeBlockerTests.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Add at least one assertion to this test case.

Check warning on line 158 in CodeBlocker.Test/CodeBlockerTests.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Add at least one assertion to this test case.

Check warning on line 158 in CodeBlocker.Test/CodeBlockerTests.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Add at least one assertion to this test case.
{
// Arrange

Expand Down Expand Up @@ -395,7 +425,7 @@
}

[TestMethod]
public void DisposeWithStringWriterManagementShouldNotThrowWhenCalledMultipleTimes()

Check warning on line 428 in CodeBlocker.Test/CodeBlockerTests.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Add at least one assertion to this test case.

Check warning on line 428 in CodeBlocker.Test/CodeBlockerTests.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Add at least one assertion to this test case.

Check warning on line 428 in CodeBlocker.Test/CodeBlockerTests.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Add at least one assertion to this test case.
{
// Arrange

Expand Down
29 changes: 29 additions & 0 deletions CodeBlocker.Test/TemplateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,35 @@ public void AMultiLineExpressionBodyKeepsABlankLineWithoutIndentingIt()
Render(type));
}

[TestMethod]
public void AnExpressionBodyStartingWithABlankLineKeepsItsFirstLineOnTheDeclaration()
{
ClassTemplate type = new()
{
Name = "C",
Keywords = { "public" },
Members =
{
new PropertyTemplate
{
Type = "int",
Name = "X",
Keywords = { "public" },
ExpressionBodyFactory = codeBlocker =>
{
codeBlocker.NewLine();
codeBlocker.WriteLine("1 +");
codeBlocker.Write("2");
},
},
},
};

Assert.AreEqual(
"public class C\n{\n\tpublic int X => 1 +\n\t2;\n}\n",
Render(type));
}

[TestMethod]
public void AnEmptyExpressionBodyStillTerminatesTheDeclaration()
{
Expand Down
51 changes: 47 additions & 4 deletions CodeBlocker/CodeBlocker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ public class CodeBlocker : IDisposable
private bool disposedValue;
private bool shouldDisposeWriter;

/// <summary>
/// Whether the current line has been started by <see cref="Write"/> and not yet terminated.
/// </summary>
/// <remarks>
/// <see cref="NewLine"/> needs this: <see cref="IndentedTextWriter.WriteLineNoTabs"/> keeps a
/// blank line free of indentation, but it does not re-arm the writer's pending-tab flag, so using
/// it to end a line that is already under way would leave the next line at column zero.
/// </remarks>
private bool isMidLine;

private IndentedTextWriter IndentedTextWriter { get; }

/// <summary>
Expand Down Expand Up @@ -203,24 +213,57 @@ public static CodeBlocker Create(string indentString, string newLineString)
/// <summary>
/// Write a line of code without indentation.
/// </summary>
public void NewLine() => IndentedTextWriter.WriteLineNoTabs(string.Empty);
/// <remarks>
/// At the start of a line this writes a blank line with no indentation. After a partial
/// <see cref="Write"/> it ends that line instead, and the next line is indented as usual.
/// </remarks>
public void NewLine()
{
if (isMidLine)
{
// Nothing is pending, so WriteLine() writes no tabs here, but it does re-arm them for
// whatever is written next.
IndentedTextWriter.WriteLine();
}
else
{
IndentedTextWriter.WriteLineNoTabs(string.Empty);
}

isMidLine = false;
}

/// <summary>
/// Write a line of code with indentation.
/// </summary>
/// <param name="line">The line of code to write.</param>
public void WriteLine(string line) => IndentedTextWriter.WriteLine(line);
public void WriteLine(string line)
{
IndentedTextWriter.WriteLine(line);
isMidLine = false;
}

/// <summary>
/// Write a line of code with indentation.
/// </summary>
public void WriteLine() => IndentedTextWriter.WriteLine();
public void WriteLine()
{
IndentedTextWriter.WriteLine();
isMidLine = false;
}

/// <summary>
/// Write a line of code with indentation.
/// </summary>
/// <param name="text">The text to write.</param>
public void Write(string text) => IndentedTextWriter.Write(text);
public void Write(string text)
{
IndentedTextWriter.Write(text);
if (!string.IsNullOrEmpty(text))
{
isMidLine = true;
}
}

/// <summary>
/// Increase the indentation level.
Expand Down
14 changes: 10 additions & 4 deletions CodeBlocker/Templates/TemplateRendering.cs
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,7 @@ internal static void WriteConstraints(CodeBlocker codeBlocker, IReadOnlyCollecti
return;
}

// Terminate the declaration line the constraints hang off. WriteLine() rather than
// NewLine(): NewLine() writes through IndentedTextWriter.WriteLineNoTabs, which does not
// re-arm the writer's pending-tab flag, so whatever came next would land at column zero.
// Terminate the declaration line the constraints hang off.
codeBlocker.WriteLine();

using IndentScope indent = new(codeBlocker);
Expand Down Expand Up @@ -271,14 +269,22 @@ internal static void WriteExpressionBody(CodeBlocker codeBlocker, Action<CodeBlo
last--;
}

// Leading blank lines are skipped as trailing ones are, so the first line with content
// stays on the declaration line rather than leaving the arrow dangling at its end.
int first = 0;
while (first < last && lines[first].Length == 0)
{
first++;
}

codeBlocker.Write(" => ");
if (last < 0)
{
codeBlocker.WriteLine(";");
return;
}

for (int i = 0; i <= last; i++)
for (int i = first; i <= last; i++)
{
if (i == last)
{
Expand Down
Loading