From 51b5a7149a7d5a18a327b164ae550f8fd242bbb7 Mon Sep 17 00:00:00 2001 From: Rex Morgan Date: Fri, 19 Jun 2026 20:54:38 -0400 Subject: [PATCH] fix: parent context traversal inside custom block helpers in each (issue #539) When options.Template/Inverse was called with a Context struct argument, it unwrapped context.Value and created a new child frame, adding an extra level to the parent chain. This caused ../ inside the block helper's template to resolve to the current iteration item rather than its parent. Fix: pass Frame directly when Template/Inverse receives a Context struct, since Frame already represents the correct current binding context. Co-Authored-By: Claude Sonnet 4.6 --- source/Handlebars.Test/IssueTests.cs | 21 +++++++++++++++++++++ source/Handlebars/BlockHelperOptions.cs | 4 ++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/source/Handlebars.Test/IssueTests.cs b/source/Handlebars.Test/IssueTests.cs index 31136117..10d653fe 100644 --- a/source/Handlebars.Test/IssueTests.cs +++ b/source/Handlebars.Test/IssueTests.cs @@ -733,5 +733,26 @@ public void UnrecognisedExpressionThrowsOutOfMemoryException() Assert.Throws(()=> Handlebars.Compile(source)); } + + // Issue: https://github.com/Handlebars-Net/Handlebars.Net/issues/539 + // Parent context (../) resolves to wrong value inside a custom block helper used within #each + [Fact] + public void Issue539_ParentContextInsideCustomBlockHelperInEach() + { + var handlebars = Handlebars.Create(); + handlebars.RegisterHelper("ifCond", (writer, options, context, parameters) => + { + if (parameters.Length == 3 && parameters[0]?.ToString() == parameters[2]?.ToString()) + options.Template(writer, context); + else + options.Inverse(writer, context); + }); + + var source = @"{{#each loop}}{{#ifCond another '===' 'value'}}{{../this.foo}}{{/ifCond}}{{/each}}"; + var template = handlebars.Compile(source); + var data = new { foo = "bar", loop = new object[] { new { another = "value" } } }; + var result = template(data); + Assert.Equal("bar", result.Trim()); + } } } \ No newline at end of file diff --git a/source/Handlebars/BlockHelperOptions.cs b/source/Handlebars/BlockHelperOptions.cs index 57253922..86f12813 100644 --- a/source/Handlebars/BlockHelperOptions.cs +++ b/source/Handlebars/BlockHelperOptions.cs @@ -71,7 +71,7 @@ public void Template(in EncodedTextWriter writer, object context) /// BlockHelper body /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Template(in EncodedTextWriter writer, in Context context) => Template(writer, context.Value); + public void Template(in EncodedTextWriter writer, in Context context) => OriginalTemplate(writer, Frame); /// /// BlockHelper body @@ -113,7 +113,7 @@ public void Inverse(in EncodedTextWriter writer, object context) /// BlockHelper body /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Inverse(in EncodedTextWriter writer, in Context context) => Inverse(writer, context.Value); + public void Inverse(in EncodedTextWriter writer, in Context context) => OriginalInverse(writer, Frame); /// /// BlockHelper body