Skip to content

Commit

Permalink
Fix context-stack when calling block-helpers on null values
Browse files Browse the repository at this point in the history
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.

Backwards compatibility within 4.0.x-versions:
- This commit changes the compiler and compiled templates would not work
  with runtime-versions 4.0.0 - 4.0.6, because of the "nullContext"
  property. That's way, the compiled code reads
  "(container.nullContext || {})" so that the behavior will degrade
  gracefully with older runtime versions: Everything else will work
  fine, but GH-1319 will still be broken, if you use a newer compiler
  with a pre 4.0.7 runtime.
  • Loading branch information
nknapp committed Mar 25, 2017
1 parent b617375 commit c8f4b57
Show file tree
Hide file tree
Showing 3 changed files with 9 additions and 2 deletions.
2 changes: 1 addition & 1 deletion lib/handlebars/compiler/javascript-compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -987,7 +987,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,
Expand Down
4 changes: 3 additions & 1 deletion lib/handlebars/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,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
Expand Down Expand Up @@ -187,7 +189,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);
}

Expand Down
5 changes: 5 additions & 0 deletions spec/regressions.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,4 +277,9 @@ describe('Regressions', function() {

shouldCompileTo(string, { listOne: ['a'], listTwo: ['b']}, 'ab', '');
});

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 ', '');
});
});

0 comments on commit c8f4b57

Please sign in to comment.