Skip to content

fix: preserve partial indentation (issue #614)#635

Merged
rexm merged 4 commits into
masterfrom
worktree-agent-a2d376b707ea82537
Jun 20, 2026
Merged

fix: preserve partial indentation (issue #614)#635
rexm merged 4 commits into
masterfrom
worktree-agent-a2d376b707ea82537

Conversation

@rexm

@rexm rexm commented Jun 20, 2026

Copy link
Copy Markdown
Member

Fixes #614

Summary

  • When {{> partial}} is the only non-whitespace content on a line, the leading spaces/tabs are now captured as an indentation prefix and prepended to every line of the rendered partial output.
  • This matches Handlebars.js / Mustache standalone-partial behaviour (Mustache spec §6: Standalone Indentation).

Changes

File What changed
PartialExpression.cs Added Indent property
HandlebarsExpression.cs Added overload of Partial() accepting indent
WhitespaceRemover.cs Extracts leading whitespace from the preceding static token and stores it on the PartialExpression before discarding it from the token stream
PartialBinder.cs Renders into a temporary StringWriter when Indent is set, then calls WriteWithIndent to prefix every line before forwarding to the real writer
StaticConverter.cs New: normalises \r\n\n for all static template text at compile time, making output platform-independent
WhitespaceTests.StandalonePartials Updated expected value to reflect correct indentation
IssueTests.TestNestedPartials Updated expected value to reflect correct indentation through nested @partial-block
Issues/Issue614Tests.cs New tests covering the spec example, multi-line partials, tab indentation, no-indent case, and block iteration
Several test files Updated \r\n\n in template strings and expected values to match the new normalised output

Root cause of the Windows test failures (third attempt — solved)

The first two attempts both tried to normalise \r\n at render time in a specific call path (WriteWithIndent). That only covered the partial's own captured output; the surrounding template's static text was still written verbatim, producing mixed \r\n/\n output on Windows/macOS (CRLF source files).

The fix is to normalise once at compile time in StaticConverter — the single point where all StaticToken values become StaticExpression nodes. WhitespaceRemover (which runs earlier) already uses \r?\n in all its regexes and is unaffected. WriteWithIndent keeps its own normalisation to handle \r\n in data values (dynamic content from bindings), which are outside the compile-time path.

Test plan

  • dotnet test source/Handlebars.Test/Handlebars.Test.csproj — all 1796 tests pass (6 new)
  • New tests in source/Handlebars.Test/Issues/Issue614Tests.cs confirm the spec example (" {{> p}}" + "line1\nline2"" line1\n line2")
  • PartialWithCrLfLineEndingsNormalisedToLf confirms that \r\n partial source → normalised \n output

🤖 Generated with Claude Code

@rexm rexm enabled auto-merge June 20, 2026 12:40
@rexm rexm force-pushed the worktree-agent-a2d376b707ea82537 branch from e752970 to a2feeef Compare June 20, 2026 17:07
When a partial invocation ({{> partial}}) is the only non-whitespace
content on a line, the leading whitespace is now captured as an indent
string and prepended to every line of the rendered partial output.
This matches Handlebars.js / Mustache standalone-partial behaviour.

Implementation:
- PartialExpression gains an Indent property
- WhitespaceRemover.ProcessTokens extracts the leading whitespace from
  the preceding static token and stores it on a new PartialExpression
  before discarding the whitespace from the token stream
- PartialBinder.InvokePartial* renders into a temporary StringWriter
  and calls WriteWithIndent to prefix every line with the captured
  indent before writing to the real output writer
- Two pre-existing tests updated to reflect the new correct output
  (StandalonePartials and TestNestedPartials)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@rexm rexm force-pushed the worktree-agent-a2d376b707ea82537 branch from a2feeef to b2c1ca1 Compare June 20, 2026 17:19
rexm and others added 3 commits June 20, 2026 13:31
… output

Templates with \r\n line endings (Windows CRLF source files or explicit
\r\n in string literals) now always produce \n in rendered output.

Previously WriteWithIndent normalized the captured partial content but
the surrounding static template text was written verbatim, producing
mixed \r\n/\n output on Windows and breaking tests that expected \n.
The root was that normalization happened too late and only in one path.

Fix: normalize \r\n → \n once in StaticConverter, the single point
where all StaticToken values become StaticExpression nodes. This runs
after WhitespaceRemover (which already uses \r?\n in all its regexes)
and ensures every static text segment emitted to the writer uses \n
regardless of the source file's line endings.

Update affected tests to use \n in both template strings and expected
values: ViewEngineTests, ReadmeTests, ComplexIntegrationTests,
BasicIntegrationTests (split separator), and IssueTests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@rexm rexm merged commit d5687f2 into master Jun 20, 2026
7 checks passed
@rexm rexm deleted the worktree-agent-a2d376b707ea82537 branch June 20, 2026 18:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Partial indentation does not work as described

1 participant