Skip to content

fix: preserve literal line endings in static template text (issue #661) - #663

Merged
rexm merged 3 commits into
masterfrom
fix/issue-661-crlf-normalization
Aug 7, 2026
Merged

fix: preserve literal line endings in static template text (issue #661)#663
rexm merged 3 commits into
masterfrom
fix/issue-661-crlf-normalization

Conversation

@rexm

@rexm rexm commented Aug 7, 2026

Copy link
Copy Markdown
Member

Summary

  • StaticConverter unconditionally rewrote \r\n/\r to \n in every static text token, and PartialBinder.WriteWithIndent did the same for indented partial content. Both were added on 2026-06-20 (commits 3a009c2 and part of the same day's partial-indentation work) to paper over an internal inconsistency between the two paths — WriteWithIndent normalized while everything else didn't, producing mixed line endings within a single render.
  • That fix overshot: it silently rewrote line endings the caller explicitly wrote into a template string (e.g. literal \r\n between {{#each}} iterations), which is a regression from this library's own long-standing behavior and from handlebars.js, where static text is passed through verbatim.
  • Removed the rewrite from both places. WriteWithIndent now scans the original content directly instead of a normalized copy — a \r immediately before a split point rides along with its segment for free, so no extra logic is needed to preserve it.
  • Reverted the test-expectation edits from 3a009c2 (BasicIntegrationTests, ComplexIntegrationTests, IssueTests, ReadmeTests, ViewEngineTests) back to their original \r\n assertions, and renamed/fixed PartialTests.PartialWithCrLfLineEndingsNormalisedToLf (which had locked in the wrong behavior) to PartialWithCrLfLineEndingsPreservedVerbatim.
  • Added regression coverage for \r\n handling? #661 in IssueTests.cs:
    • Issue661_ExplicitCrLfInTemplateIsPreservedVerbatim — reproduces the reporter's exact repro shape (#each over a helper subexpression, @Key/@Index casing) using a local Split helper standing in for String.Split (not in this repo).
    • Issue661_ExplicitLfInTemplateIsPreservedVerbatim — same, with \n, to make sure the fix doesn't trade under-preservation for over-preservation.
    • Issue661_PlatformNewlineInTemplateIsPreservedVerbatim — uses Environment.NewLine, which resolves to \r\n/\n at run time, so the existing 3-OS CI matrix (macOS/Ubuntu/Windows) exercises both cases without any OS-conditional test code.
    • Issue661_IndentedPartialPreservesCrLf — covers the WriteWithIndent path specifically.

Fixes #661.

Test plan

  • dotnet test — full suite passes (1910/1910)
  • Verified the new tests fail with exactly the reporter's symptom (Expected: "0:0:a\r\n1:1:b\r\n2:2:c\r\n", Actual: "0:0:a\n1:1:b\n2:2:c\n") when the source fix is reverted, confirming they're a faithful regression guard
  • CI will confirm Issue661_PlatformNewlineInTemplateIsPreservedVerbatim on Windows/macOS/Ubuntu runners

🤖 Generated with Claude Code

rexm and others added 3 commits August 6, 2026 20:24
StaticConverter unconditionally rewrote \r\n/\r to \n in every static
text token, and PartialBinder.WriteWithIndent did the same for indented
partial content — both added 2026-06-20 to paper over an internal
inconsistency between the two (WriteWithIndent normalized while the
surrounding static text didn't, producing mixed line endings within a
single render). That fix overshot: it silently rewrote line endings the
caller explicitly put in a template string, e.g. explicit \r\n between
{{#each}} iterations.

Static text is the caller's literal content and must round-trip
verbatim, matching handlebars.js and this library's own behavior prior
to that change. Removed the rewrite from both places; WriteWithIndent
now scans the original content directly, since a \r immediately before
a split point rides along with its segment for free.

Reverted the test-expectation edits from that commit back to their
original \r\n assertions, and added regression coverage: explicit \r\n
and \n preservation, an Environment.NewLine-based test that exercises
\r\n on Windows and \n on macOS/Ubuntu via the existing 3-OS CI matrix,
and an indented-partial CRLF case matching the WriteWithIndent path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
PR #663 removed StaticConverter's blanket \r\n->\n rewrite (issue #661),
which was incidentally papering over a second, separate problem: several
tests embed multi-line template content as verbatim/raw C# string literals
(actual embedded newlines in the .cs source) and assert against an explicit
\n-based expected value. With no .gitattributes, Windows runners/clients
default core.autocrlf=true and check those literals out as \r\n, so once
Handlebars.Net stopped silently normalizing render output, TestNestedPartials
and Issue519_PartialBlockUsableAsBlockAndInIf failed on windows-latest CI
(confirmed via the PR's own check run).

Forcing LF at checkout is the correct fix for that problem: it makes the
checked-out source deterministic across platforms instead of asking the
render pipeline to paper over a source-control inconsistency. The tracked
blobs are already LF (verified via git show | od), so no --renormalize pass
is needed — this only changes how future checkouts materialize them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…normalization

# Conflicts:
#	source/Handlebars.Test/IssueTests.cs
@rexm
rexm enabled auto-merge August 7, 2026 00:48
@sonarqubecloud

sonarqubecloud Bot commented Aug 7, 2026

Copy link
Copy Markdown

@rexm
rexm merged commit bb496ab into master Aug 7, 2026
7 checks passed
@rexm
rexm deleted the fix/issue-661-crlf-normalization branch August 7, 2026 01:00
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.

\r\n handling?

1 participant