Problem
CodeBlocker writes through System.CodeDom.Compiler.IndentedTextWriter, which terminates lines with Environment.NewLine. The same input therefore produces CRLF on Windows and LF on Linux.
That is fine for throwaway output, but it breaks the main use case for a code generator: committed generated source. ktsu.Semantics commits its generator output and verifies it in CI, so it had to add a post-processing pass purely to undo this (GeneratedSource.cs):
internal static string NormalizeLineEndings(string source) =>
source.Replace("\r\n", "\n").Replace("\n", "\r\n");
Every consumer that cares about reproducible output has to rediscover and re-implement that workaround.
The dependency also leaks into consumers in a second, less obvious way: Semantics' template model splits rendered bodies on Environment.NewLine to decide whether a method body is single- or multi-line, so a body assembled on one OS is laid out differently on another.
Proposal
Give CodeBlocker an explicit newline, defaulting to something stable rather than to the host's:
- Add a
NewLineString option to the constructors and Create factories, alongside the existing indentString.
- Route all line termination through it (set
TextWriter.CoreNewLine, or wrap the writer) instead of leaving IndentedTextWriter on Environment.NewLine.
- Expose it as a read-only property (mirroring
IndentString) so nested/child CodeBlocker instances can inherit it.
- Decide and document the default.
"\n" is the reproducible choice; Environment.NewLine is the back-compatible one. If the default changes, call it out in CHANGELOG.md as a behavioural break.
Acceptance criteria
Context
Part of ktsu-dev/Semantics#181 — extracting the code-generation stack out of Semantics.SourceGenerators so it can be shared. Once this lands, Semantics can delete GeneratedSource.NormalizeLineEndings.
Problem
CodeBlockerwrites throughSystem.CodeDom.Compiler.IndentedTextWriter, which terminates lines withEnvironment.NewLine. The same input therefore produces CRLF on Windows and LF on Linux.That is fine for throwaway output, but it breaks the main use case for a code generator: committed generated source.
ktsu.Semanticscommits its generator output and verifies it in CI, so it had to add a post-processing pass purely to undo this (GeneratedSource.cs):Every consumer that cares about reproducible output has to rediscover and re-implement that workaround.
The dependency also leaks into consumers in a second, less obvious way:
Semantics' template model splits rendered bodies onEnvironment.NewLineto decide whether a method body is single- or multi-line, so a body assembled on one OS is laid out differently on another.Proposal
Give
CodeBlockeran explicit newline, defaulting to something stable rather than to the host's:NewLineStringoption to the constructors andCreatefactories, alongside the existingindentString.TextWriter.CoreNewLine, or wrap the writer) instead of leavingIndentedTextWriteronEnvironment.NewLine.IndentString) so nested/childCodeBlockerinstances can inherit it."\n"is the reproducible choice;Environment.NewLineis the back-compatible one. If the default changes, call it out inCHANGELOG.mdas a behavioural break.Acceptance criteria
Write/WriteLine/Scopecalls produces byte-identical output on Windows and Linux.ScopeandNewLine().Environment.NewLine-relative comparisons) for at least one nested-scope fixture.README.mddocuments the option and the default.Context
Part of ktsu-dev/Semantics#181 — extracting the code-generation stack out of
Semantics.SourceGeneratorsso it can be shared. Once this lands,Semanticscan deleteGeneratedSource.NormalizeLineEndings.