Skip to content

Fix noEscape numeric values being added instead of concatenated - #2167

Merged
kibertoad merged 2 commits into
handlebars-lang:masterfrom
chatman-media:fix/noescape-numeric-concatenation
Jun 24, 2026
Merged

Fix noEscape numeric values being added instead of concatenated#2167
kibertoad merged 2 commits into
handlebars-lang:masterfrom
chatman-media:fix/noescape-numeric-concatenation

Conversation

@chatman-media

Copy link
Copy Markdown
Contributor

Problem

Closes #1838.

With noEscape: true, adjacent mustaches that resolve to numbers are added instead of concatenated:

Handlebars.compile('{{a}}{{b}}', { noEscape: true })({ a: 1, b: 2 });
// => "3"   (expected "12")

Handlebars.compile('{{a}}{{b}}{{c}}', { noEscape: true })({ a: 1, b: 2, c: 3 });
// => "6"   (expected "123")

The default (escaped) path is unaffected because values go through escapeExpression, which returns a string. The triple-stash raw form {{{a}}}{{{b}}} is affected by the same root cause.

Root cause

In JavaScriptCompiler#append, the inline output path emitted the raw value:

this.replaceStack((current) => [' != null ? ', current, ' : ""']);
// compiles to:  (stack1 != null ? stack1 : "")

Adjacent buffer appends are merged with + in mergeSource. When the merged expression starts with these raw values and both are numbers, JavaScript performs numeric addition (1 + 2 === 3) before the top-level '' + in the runtime ever runs.

The escaped path wraps each value in escapeExpression(...) (always a string), so the merge concatenates correctly. The unescaped path should likewise produce a string — the method's own doc comment states it "Coerces value to a String", but the inline branch did not.

Fix

Coerce the non-null value to a string in the inline append, mirroring the escaped path:

this.replaceStack((current) => [' != null ? "" + ', current, ' : ""']);

null/undefined still produce "", SafeStrings still emit their raw (unescaped) HTML, and escaped output is untouched. +4/−1 in the compiler.

Tests

Added a regression test in spec/basic.js (noEscape for {{a}}{{b}} / {{a}}{{b}}{{c}} and triple-stash {{{a}}}{{{b}}}). Verified it fails without the fix (Expected "12", Received "3") and passes with it. Full node suite green (604 passing). oxlint and oxfmt clean.

No public API change, so types/index.d.ts is unchanged.

In the inline output path, an unescaped mustache emitted the raw value
(`value != null ? value : ""`) rather than a string. When two such
appends were merged with ` + ` and both values were numbers, JavaScript
performed numeric addition: `{{a}}{{b}}` with noEscape and a=1, b=2
produced "3" instead of "12". The same affected triple-stash `{{{a}}}`.

Coerce non-null values to a String in the inline append, matching the
escaped path and the documented contract of `append` ("Coerces value to
a String").

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a codegen edge case where unescaped adjacent mustaches (noEscape: true and {{{...}}}) could be numerically added during source-merge, instead of concatenated as strings.

Changes:

  • Update JavaScriptCompiler#append (inline path) to coerce non-null values to strings via "" + value before merge-concatenation.
  • Add regression tests covering adjacent numeric mustaches with noEscape: true and with triple-stash raw output.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
lib/handlebars/compiler/javascript-compiler.js Ensures inline append output is string-coerced to prevent + merges from triggering numeric addition.
spec/basic.js Adds regression tests for adjacent numeric output under noEscape and raw mustaches.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread spec/basic.js Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@kibertoad
kibertoad merged commit 13a7a67 into handlebars-lang:master Jun 24, 2026
7 checks passed
@kibertoad

Copy link
Copy Markdown
Contributor

thank you!

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.

noEscape cause additions of integer values

3 participants