From b44b7acce631485e0938c2c873e0d9196f41e1a0 Mon Sep 17 00:00:00 2001 From: Tomas Bjerre Date: Tue, 15 Sep 2026 16:20:43 +0200 Subject: [PATCH] fix: pass params and hash to helpers invoked via standalone inverse tags visitUnless() (handling standalone {{^helper args}}...{{/helper}} tags) always built its Block with Collections.emptyList()/emptyMap() for params/hash, discarding whatever the parser had actually parsed from ctx.sexpr(). The sibling visitBlock() (handling {{#helper args}}...{{/helper}}), which shares the same sexpr grammar rule, correctly does params(sexpr.param()) / hash(sexpr.hash()) - visitUnless just never got the same treatment. For a helper without a body context binding, this is mostly invisible. But for a custom helper that relies on its own hash arguments (e.g. {{^ifSomething . scope="x"}}...{{/ifSomething}}), Options.hash("scope") silently comes back null. Depending on what the helper does with the missing value, this can surface as a confusing NullPointerException deep inside the helper rather than any indication that the arguments were dropped. Fix mirrors visitBlock() exactly: params(sexpr.param()) and hash(sexpr.hash()) instead of the hardcoded empty collections. Added UnlessBlockHelperArgsTest covering both the context (first positional param) and hash argument pass-through on a standalone inverse tag. Ran the full handlebars module test suite (1030 tests, 0 failures/errors) to confirm no regressions. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01N4LemHUKaqfXULRkmxfxwx --- .../handlebars/internal/TemplateBuilder.java | 4 +- .../handlebars/UnlessBlockHelperArgsTest.java | 45 +++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 handlebars/src/test/java/com/github/jknack/handlebars/UnlessBlockHelperArgsTest.java diff --git a/handlebars/src/main/java/com/github/jknack/handlebars/internal/TemplateBuilder.java b/handlebars/src/main/java/com/github/jknack/handlebars/internal/TemplateBuilder.java index ea196c12..7aca78ce 100644 --- a/handlebars/src/main/java/com/github/jknack/handlebars/internal/TemplateBuilder.java +++ b/handlebars/src/main/java/com/github/jknack/handlebars/internal/TemplateBuilder.java @@ -333,8 +333,8 @@ public Template visitUnless(final UnlessContext ctx) { name, true, "^", - Collections.emptyList(), - Collections.emptyMap(), + params(sexpr.param()), + hash(sexpr.hash()), blockParams(ctx.blockParams()), source(ctx)); block.filename(source.filename()); diff --git a/handlebars/src/test/java/com/github/jknack/handlebars/UnlessBlockHelperArgsTest.java b/handlebars/src/test/java/com/github/jknack/handlebars/UnlessBlockHelperArgsTest.java new file mode 100644 index 00000000..b61e52b3 --- /dev/null +++ b/handlebars/src/test/java/com/github/jknack/handlebars/UnlessBlockHelperArgsTest.java @@ -0,0 +1,45 @@ +/* + * Handlebars.java: https://github.com/jknack/handlebars.java + * Apache License Version 2.0 http://www.apache.org/licenses/LICENSE-2.0 + * Copyright (c) 2012 Edgar Espina + */ +package com.github.jknack.handlebars; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +public class UnlessBlockHelperArgsTest extends AbstractTest { + + /** + * A standalone inverted section ({@code {{^helper args}}...{{/helper}}}) invoking a custom block + * helper must pass through the helper's context (first param) and hash arguments the same way the + * matching positive section ({@code {{#helper args}}...{{/helper}}}) does. + */ + @Test + public void unlessPassesContextAndHashArgumentsToHelper() throws IOException { + Helper helper = + (context, options) -> "context=" + context + ",scope=" + options.hash("scope"); + + shouldCompileTo( + "{{^myHelper foo scope=\"deps\"}}{{/myHelper}}", + $("foo", "bar"), + $("myHelper", helper), + "context=bar,scope=deps"); + } + + /** + * A second positional param (beyond the first, which becomes the context) must also flow through. + */ + @Test + public void unlessPassesExtraParamsToHelper() throws IOException { + Helper helper = + (context, options) -> "context=" + context + ",param0=" + options.param(0); + + shouldCompileTo( + "{{^myHelper foo bar}}{{/myHelper}}", + $("foo", "a", "bar", "b"), + $("myHelper", helper), + "context=a,param0=b"); + } +}