fix: preserve literal line endings in static template text (issue #661) - #663
Merged
Conversation
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
enabled auto-merge
August 7, 2026 00:48
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
StaticConverterunconditionally rewrote\r\n/\rto\nin every static text token, andPartialBinder.WriteWithIndentdid the same for indented partial content. Both were added on 2026-06-20 (commits3a009c2and part of the same day's partial-indentation work) to paper over an internal inconsistency between the two paths —WriteWithIndentnormalized while everything else didn't, producing mixed line endings within a single render.\r\nbetween{{#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.WriteWithIndentnow scans the original content directly instead of a normalized copy — a\rimmediately before a split point rides along with its segment for free, so no extra logic is needed to preserve it.3a009c2(BasicIntegrationTests,ComplexIntegrationTests,IssueTests,ReadmeTests,ViewEngineTests) back to their original\r\nassertions, and renamed/fixedPartialTests.PartialWithCrLfLineEndingsNormalisedToLf(which had locked in the wrong behavior) toPartialWithCrLfLineEndingsPreservedVerbatim.\r\nhandling? #661 inIssueTests.cs:Issue661_ExplicitCrLfInTemplateIsPreservedVerbatim— reproduces the reporter's exact repro shape (#eachover a helper subexpression,@Key/@Indexcasing) using a localSplithelper standing in forString.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— usesEnvironment.NewLine, which resolves to\r\n/\nat run time, so the existing 3-OS CI matrix (macOS/Ubuntu/Windows) exercises both cases without any OS-conditional test code.Issue661_IndentedPartialPreservesCrLf— covers theWriteWithIndentpath specifically.Fixes #661.
Test plan
dotnet test— full suite passes (1910/1910)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 guardIssue661_PlatformNewlineInTemplateIsPreservedVerbatimon Windows/macOS/Ubuntu runners🤖 Generated with Claude Code