Problem
CodeBlocker's only constructor takes a StringWriter:
public class CodeBlocker(StringWriter stringWriter) : IDisposable
IndentedTextWriter itself accepts any TextWriter, so the restriction is self-imposed. It rules out generating straight to a StreamWriter (a file), to a TextWriter supplied by a build task, or to a test double — which matters for a generator emitting many files, where buffering every one in memory as a string is wasteful.
The restriction exists because ToString() reads back from the captured StringWriter:
public override string ToString() => stringWriter.ToString();
Proposal
- Add a
CodeBlocker(TextWriter writer) constructor (plus the indentString overload) and keep the StringWriter ones as the convenient path.
- Keep
Create() / Create(string) returning a StringWriter-backed instance, so ToString() continues to work for the common case and the existing API is unaffected.
- Make
ToString() honest for the general case: return the buffered text when the underlying writer is a StringWriter, otherwise either fall back to base.ToString() or throw InvalidOperationException with a message pointing at Create(). Pick one and document it — silently returning a type name would be worse than either.
- Preserve the existing disposal contract:
CodeBlocker disposes the writer only when it created it (shouldDisposeStringWriter), never a caller-supplied one.
Acceptance criteria
Context
Part of ktsu-dev/Semantics#181 — generalizing the code-generation stack so more projects can use it.
Problem
CodeBlocker's only constructor takes aStringWriter:IndentedTextWriteritself accepts anyTextWriter, so the restriction is self-imposed. It rules out generating straight to aStreamWriter(a file), to aTextWritersupplied by a build task, or to a test double — which matters for a generator emitting many files, where buffering every one in memory as a string is wasteful.The restriction exists because
ToString()reads back from the capturedStringWriter:Proposal
CodeBlocker(TextWriter writer)constructor (plus theindentStringoverload) and keep theStringWriterones as the convenient path.Create()/Create(string)returning aStringWriter-backed instance, soToString()continues to work for the common case and the existing API is unaffected.ToString()honest for the general case: return the buffered text when the underlying writer is aStringWriter, otherwise either fall back tobase.ToString()or throwInvalidOperationExceptionwith a message pointing atCreate(). Pick one and document it — silently returning a type name would be worse than either.CodeBlockerdisposes the writer only when it created it (shouldDisposeStringWriter), never a caller-supplied one.Acceptance criteria
CodeBlockercan be constructed over an arbitraryTextWriterand writes indented output to it.CodeBlocker.Dispose(); aCreate()-owned one still is.ToString()behaviour on a non-StringWriterinstance is specified and covered by a test.StringWriterAPI and behaviour unchanged.Context
Part of ktsu-dev/Semantics#181 — generalizing the code-generation stack so more projects can use it.