fix: WriteSafeString encoding consistent regardless of registration order (issue #559)#637
Merged
Merged
Conversation
…rder (issue #559) When a helper with no arguments (e.g. {{link_to}}) was registered after Compile(), the parser treated it as a PathExpression rather than a HelperExpression. PathBinder.VisitPathExpression emitted code calling the object-returning Invoke overload, whose return value was then passed to EncodedTextWriter.Write<object> which always HTML-encodes strings. This meant that WriteSafeString() output was re-encoded, producing <a> instead of <a>. When the same helper was registered before Compile(), HelperConverter recognised the name and produced a HelperExpression; HelperFunctionBinder then emitted the void Invoke(EncodedTextWriter,...) overload which writes directly to the output writer and preserves WriteSafeString semantics. Fix: in PathBinder.VisitStatementExpression, detect PathExpressions that are valid helper literals (the ambiguous name-only case) and emit the same void Invoke(writer,...) call that HelperFunctionBinder would have produced. A LateBindHelperDescriptor Ref<> is registered in configuration.Helpers so that a subsequent RegisterHelper() call updates the Ref in-place, exactly as the HelperFunctionBinder late-binding mechanism does. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…r (issue #434) The Helpers dictionary uses a case-insensitive PathInfoLight comparer, so {{TEST}} and {{test}} previously resolved to the same LateBindHelperDescriptor entry. When the second expression was compiled it reused the first descriptor (keyed to the wrong case), causing both to bind to the same property at runtime. Fix detects when a found entry is a LateBindHelperDescriptor for a differently-cased path and creates a fresh descriptor for the exact path, so each case variant resolves independently at runtime. Adds Issue434Tests.cs to exercise the failing scenario. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
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.



Fixes #559
Root cause
When a helper with no arguments (
{{link_to}}) is registered afterCompile(), the Handlebars lexer does not know about the helper at parse time, soHelperConverterdoes not convert the token to aHelperExpression. It stays as aPathExpression.PathBinder.VisitPathExpressionthen emits code calling the object-returningInvoke(HelperOptions, Context, Arguments)overload. The return value is then passed toEncodedTextWriter.Write<object>(string), which always HTML-encodes strings. This meansWriteSafeString()output is re-encoded:When the same helper is registered before
Compile(),HelperConverterconverts the token to aHelperExpression, andHelperFunctionBinderemits the voidInvoke(EncodedTextWriter, HelperOptions, Context, Arguments)overload which writes directly to the real output writer, preservingWriteSafeStringsemantics.Fix
In
PathBinder.VisitStatementExpression, detectPathExpressions that are valid helper literals (the ambiguous single-name case) and emit the samevoid Invoke(writer, ...)call thatHelperFunctionBinderwould have produced. ALateBindHelperDescriptorwrapped in aRef<>is registered inconfiguration.Helpersso that a subsequentRegisterHelper()call updates theRefvalue in-place — exactly as theHelperFunctionBinderlate-binding mechanism already does.Test
Added
source/Handlebars.Test/Issues/Issue559Tests.cswith the canonical regression test. All 1748 existing tests continue to pass.🤖 Generated with Claude Code