Skip to content

Commit

Permalink
Pass undefined fields to helpers in strict mode
Browse files Browse the repository at this point in the history
This allows for `{{helper foo}}` to still operate under strict mode when `foo` is not defined on the context. This allows helpers to perform whatever existence checks they please so patterns like `{{#if foo}}{{foo}}{{/if}}` can be used to protect against missing values.

Fixes #1063
  • Loading branch information
kpdecker committed Aug 3, 2015
1 parent 279e038 commit 5d4b8da
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 9 deletions.
10 changes: 7 additions & 3 deletions lib/handlebars/compiler/compiler.js
Expand Up @@ -217,13 +217,16 @@ Compiler.prototype = {
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);

path.strict = true;
this.accept(path);

this.opcode('invokeAmbiguous', name, isBlock);
},

simpleSexpr: function(sexpr) {
this.accept(sexpr.path);
let path = sexpr.path;
path.strict = true;
this.accept(path);
this.opcode('resolvePossibleLambda');
},

Expand All @@ -237,6 +240,7 @@ Compiler.prototype = {
} else if (this.options.knownHelpersOnly) {
throw new Exception('You specified knownHelpersOnly, but used the unknown helper ' + name, sexpr);
} else {
path.strict = true;
path.falsy = true;

this.accept(path);
Expand All @@ -259,9 +263,9 @@ Compiler.prototype = {
this.opcode('pushContext');
} else if (path.data) {
this.options.data = true;
this.opcode('lookupData', path.depth, path.parts);
this.opcode('lookupData', path.depth, path.parts, path.strict);
} else {
this.opcode('lookupOnContext', path.parts, path.falsy, scoped);
this.opcode('lookupOnContext', path.parts, path.falsy, path.strict, scoped);
}
},

Expand Down
12 changes: 6 additions & 6 deletions lib/handlebars/compiler/javascript-compiler.js
Expand Up @@ -390,7 +390,7 @@ JavaScriptCompiler.prototype = {
//
// Looks up the value of `name` on the current context and pushes
// it onto the stack.
lookupOnContext: function(parts, falsy, scoped) {
lookupOnContext: function(parts, falsy, strict, scoped) {
let i = 0;

if (!scoped && this.options.compat && !this.lastContext) {
Expand All @@ -401,7 +401,7 @@ JavaScriptCompiler.prototype = {
this.pushContext();
}

this.resolvePath('context', parts, i, falsy);
this.resolvePath('context', parts, i, falsy, strict);
},

// [lookupBlockParam]
Expand All @@ -424,19 +424,19 @@ JavaScriptCompiler.prototype = {
// On stack, after: data, ...
//
// Push the data lookup operator
lookupData: function(depth, parts) {
lookupData: function(depth, parts, strict) {
if (!depth) {
this.pushStackLiteral('data');
} else {
this.pushStackLiteral('this.data(data, ' + depth + ')');
}

this.resolvePath('data', parts, 0, true);
this.resolvePath('data', parts, 0, true, strict);
},

resolvePath: function(type, parts, i, falsy) {
resolvePath: function(type, parts, i, falsy, strict) {
if (this.options.strict || this.options.assumeObjects) {
this.push(strictLookup(this.options.strict, this, parts, type));
this.push(strictLookup(this.options.strict && strict, this, parts, type));
return;
}

Expand Down
17 changes: 17 additions & 0 deletions spec/strict.js
Expand Up @@ -78,6 +78,23 @@ describe('strict', function() {
template({hello: {}});
}, Exception, /"bar" not defined in/);
});

it('should allow undefined parameters when passed to helpers', function() {
var template = CompilerContext.compile('{{#unless foo}}success{{/unless}}', {strict: true});
equals(template({}), 'success');
});

it('should allow undefined hash when passed to helpers', function() {
var template = CompilerContext.compile('{{helper value=@foo}}', {strict: true});
var helpers = {
helper: function(options) {
equals('value' in options.hash, true);
equals(options.hash.value, undefined);
return 'success';
}
};
equals(template({}, {helpers: helpers}), 'success');
});
});

describe('assume objects', function() {
Expand Down

0 comments on commit 5d4b8da

Please sign in to comment.