You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
NewLine() after a partial Write() makes the next line lose its indentation (also breaks expression bodies that start with a blank line) #115
IndentedTextWriter.WriteLineNoTabs does not re-arm the writer's pending-tabs flag. If the writer is part-way through a line when NewLine() is called, that line is ended, but whatever is written next starts at column 0.
The repository already knows about this hazard. TemplateRendering.WriteConstraints (lines 186–189) avoids NewLine() for exactly this reason. The public API has no such guard.
cb.WriteLine("class C");using(newScope(cb)){cb.Write("int x = 1;");cb.NewLine();cb.WriteLine("int y = 2;");}
Observed: class C\n{\n\tint x = 1;\nint y = 2;\n}\n. int y is at column 0.
Expected: \tint y = 2;.
Templates: WriteExpressionBody (Templates/TemplateRendering.cs:274, :287-289) hits the same bug when the expression body's first rendered line is blank:
newPropertyTemplate{Type="int",Name="X",Keywords={"public"},ExpressionBodyFactory= e =>{e.NewLine();e.WriteLine("1 +");e.Write("2");}}
Observed inside a class: \tpublic int X => \n1 +\n\t2;. There is a trailing space after =>, and 1 + is at column 0.
The existing test AMultiLineExpressionBodyKeepsABlankLineWithoutIndentingIt only covers a blank line in the middle of the body, where the writer is already at the start of a line.
Why it matters
Generated code comes out mis-indented. For source generators and formatters built on CodeBlocker, the output no longer matches .editorconfig expectations and makes noisy diffs. Callers cannot see that it will happen, because it depends on whether the previous call was Write or WriteLine.
Suggested fix
Have CodeBlocker track whether it is part-way through a line: set the flag after a non-empty Write, and clear it after WriteLine and NewLine.
In NewLine(), when part-way through a line, call IndentedTextWriter.WriteLine(). That writes no tabs in that state and re-arms the indent. Otherwise keep WriteLineNoTabs, so blank lines stay free of whitespace.
The existing tests NewLineShouldAddEmptyLine and BlankLineUsesTheConfiguredTerminator still pass under this change.
WriteExpressionBody should also skip leading blank lines, as it already skips trailing ones, so " => " isn't left dangling at the end of the declaration line.
Priority: Medium. Callers get mis-indented generated code and can't predict when it will happen. Source generators downstream are affected. It is cosmetic, but it is in the library's core purpose.
Area / suggested owner:CodeBlocker.NewLine and TemplateRendering.WriteExpressionBody (maintainer: matt-edmondson)
Duplicates / in progress: No duplicate found, and no open PR covers it.
Notes: Track whether the writer is part-way through a line: call WriteLine() when it is, and WriteLineNoTabs when it isn't. Also skip leading blank lines in expression bodies. The existing tests should still pass.
Activity
matt-edmondson commentedon Sep 26, 2026
Triage
CodeBlocker.NewLineandTemplateRendering.WriteExpressionBody(maintainer: matt-edmondson)WriteLine()when it is, andWriteLineNoTabswhen it isn't. Also skip leading blank lines in expression bodies. The existing tests should still pass.Generated by Claude Code