From 5d4b8da344ab5060205678375df298a3d738e862 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 3 Aug 2015 15:59:53 -0500 Subject: [PATCH] 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 --- lib/handlebars/compiler/compiler.js | 10 +++++++--- lib/handlebars/compiler/javascript-compiler.js | 12 ++++++------ spec/strict.js | 17 +++++++++++++++++ 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index c1ef47ed7..59a425f47 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -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'); }, @@ -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); @@ -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); } }, diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index d39ecb2bf..28f27fd1c 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -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) { @@ -401,7 +401,7 @@ JavaScriptCompiler.prototype = { this.pushContext(); } - this.resolvePath('context', parts, i, falsy); + this.resolvePath('context', parts, i, falsy, strict); }, // [lookupBlockParam] @@ -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; } diff --git a/spec/strict.js b/spec/strict.js index 2aef13442..05ce35d9e 100644 --- a/spec/strict.js +++ b/spec/strict.js @@ -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() {