From e89f03bbbaa0e8146a832ef0d11eaba9d4bde2f2 Mon Sep 17 00:00:00 2001 From: Nils Knappmeier Date: Thu, 9 Mar 2017 21:17:52 +0100 Subject: [PATCH] Fix context-stack when calling block-helpers on null values Fixes #1319 Original behaviour: - When a block-helper was called on a null-context, an empty object was used as context instead. (#1093) - The runtime verifies that whether the current context equals the last context and adds the current context to the stack, if it is not. This is done, so that inside a block-helper, the ".." path can be used to go back to the parent element. - If the helper is called on a "null" element, the context was added, even though it shouldn't be, because the "null != {}" Fix: - The commit replaces "null" by the identifiable "container.nullContext" instead of "{}". "nullContext" is a sealed empty object. - An additional check in the runtime verifies that the context is only added to the stack, if it is not the nullContext. --- lib/handlebars/compiler/javascript-compiler.js | 2 +- lib/handlebars/runtime.js | 4 +++- spec/regressions.js | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index ed1b83ef9..874b095b7 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -910,7 +910,7 @@ JavaScriptCompiler.prototype = { let params = [], paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper); let foundHelper = this.nameLookup('helpers', name, 'helper'), - callContext = this.aliasable(`${this.contextName(0)} != null ? ${this.contextName(0)} : {}`); + callContext = this.aliasable(`${this.contextName(0)} != null ? ${this.contextName(0)} : container.nullContext`); return { params: params, diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index 310b108e0..583d43507 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -121,6 +121,8 @@ export function template(templateSpec, env) { return obj; }, + // An empty object to use as replacement for null-contexts + nullContext: Object.seal({}), noop: env.VM.noop, compilerInfo: templateSpec.compiler @@ -174,7 +176,7 @@ export function template(templateSpec, env) { export function wrapProgram(container, i, fn, data, declaredBlockParams, blockParams, depths) { function prog(context, options = {}) { let currentDepths = depths; - if (depths && context != depths[0]) { + if (depths && context != depths[0] && !(context === container.nullContext && depths[0] === null)) { currentDepths = [context].concat(depths); } diff --git a/spec/regressions.js b/spec/regressions.js index 4dc2ac89c..e922169d6 100644 --- a/spec/regressions.js +++ b/spec/regressions.js @@ -288,4 +288,9 @@ describe('Regressions', function() { shouldCompileTo('{{helpa length="foo"}}', [obj, helpers], 'foo'); }); + + it('GH-1319: "unless" breaks when "each" value equals "null"', function() { + var string = '{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}'; + shouldCompileTo(string, { value: 'parent', list: [ null, 'a'] }, 'parent=parent parent=parent ', ''); + }); });