Skip to content

Commit 5d4b8da

Browse files
committed
Pass undefined fields to helpers in strict mode
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
1 parent 279e038 commit 5d4b8da

File tree

3 files changed

+30
-9
lines changed

3 files changed

+30
-9
lines changed

lib/handlebars/compiler/compiler.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -217,13 +217,16 @@ Compiler.prototype = {
217217
this.opcode('pushProgram', program);
218218
this.opcode('pushProgram', inverse);
219219

220+
path.strict = true;
220221
this.accept(path);
221222

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

225226
simpleSexpr: function(sexpr) {
226-
this.accept(sexpr.path);
227+
let path = sexpr.path;
228+
path.strict = true;
229+
this.accept(path);
227230
this.opcode('resolvePossibleLambda');
228231
},
229232

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

242246
this.accept(path);
@@ -259,9 +263,9 @@ Compiler.prototype = {
259263
this.opcode('pushContext');
260264
} else if (path.data) {
261265
this.options.data = true;
262-
this.opcode('lookupData', path.depth, path.parts);
266+
this.opcode('lookupData', path.depth, path.parts, path.strict);
263267
} else {
264-
this.opcode('lookupOnContext', path.parts, path.falsy, scoped);
268+
this.opcode('lookupOnContext', path.parts, path.falsy, path.strict, scoped);
265269
}
266270
},
267271

lib/handlebars/compiler/javascript-compiler.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ JavaScriptCompiler.prototype = {
390390
//
391391
// Looks up the value of `name` on the current context and pushes
392392
// it onto the stack.
393-
lookupOnContext: function(parts, falsy, scoped) {
393+
lookupOnContext: function(parts, falsy, strict, scoped) {
394394
let i = 0;
395395

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

404-
this.resolvePath('context', parts, i, falsy);
404+
this.resolvePath('context', parts, i, falsy, strict);
405405
},
406406

407407
// [lookupBlockParam]
@@ -424,19 +424,19 @@ JavaScriptCompiler.prototype = {
424424
// On stack, after: data, ...
425425
//
426426
// Push the data lookup operator
427-
lookupData: function(depth, parts) {
427+
lookupData: function(depth, parts, strict) {
428428
if (!depth) {
429429
this.pushStackLiteral('data');
430430
} else {
431431
this.pushStackLiteral('this.data(data, ' + depth + ')');
432432
}
433433

434-
this.resolvePath('data', parts, 0, true);
434+
this.resolvePath('data', parts, 0, true, strict);
435435
},
436436

437-
resolvePath: function(type, parts, i, falsy) {
437+
resolvePath: function(type, parts, i, falsy, strict) {
438438
if (this.options.strict || this.options.assumeObjects) {
439-
this.push(strictLookup(this.options.strict, this, parts, type));
439+
this.push(strictLookup(this.options.strict && strict, this, parts, type));
440440
return;
441441
}
442442

spec/strict.js

+17
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,23 @@ describe('strict', function() {
7878
template({hello: {}});
7979
}, Exception, /"bar" not defined in/);
8080
});
81+
82+
it('should allow undefined parameters when passed to helpers', function() {
83+
var template = CompilerContext.compile('{{#unless foo}}success{{/unless}}', {strict: true});
84+
equals(template({}), 'success');
85+
});
86+
87+
it('should allow undefined hash when passed to helpers', function() {
88+
var template = CompilerContext.compile('{{helper value=@foo}}', {strict: true});
89+
var helpers = {
90+
helper: function(options) {
91+
equals('value' in options.hash, true);
92+
equals(options.hash.value, undefined);
93+
return 'success';
94+
}
95+
};
96+
equals(template({}, {helpers: helpers}), 'success');
97+
});
8198
});
8299

83100
describe('assume objects', function() {

0 commit comments

Comments
 (0)