diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js
index 00c8bf1ff..059b1c3fb 100644
--- a/lib/handlebars/base.js
+++ b/lib/handlebars/base.js
@@ -49,11 +49,13 @@ HandlebarsEnvironment.prototype = {
};
function registerDefaultHelpers(instance) {
- instance.registerHelper('helperMissing', function(arg) {
- if(arguments.length === 2) {
+ instance.registerHelper('helperMissing', function(/* [args, ]options */) {
+ if(arguments.length === 1) {
+ // A missing field in a {{foo}} constuct.
return undefined;
} else {
- throw new Exception("Missing helper: '" + arg + "'");
+ // Someone is actually trying to call something, blow up.
+ throw new Exception("Missing helper: '" + arguments[arguments.length-1].name + "'");
}
});
diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index 8389f8482..97a5a973c 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -169,7 +169,7 @@ Compiler.prototype = {
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
this.opcode('emptyHash');
- this.opcode('blockValue');
+ this.opcode('blockValue', sexpr.id.original);
} else {
this.ambiguousSexpr(sexpr, program, inverse);
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index cdf16935e..1e8959d66 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -223,11 +223,11 @@ JavaScriptCompiler.prototype = {
// `{{#this.foo}}...{{/this.foo}}`, resolve the value of `foo`, and
// replace it on the stack with the result of properly
// invoking blockHelperMissing.
- blockValue: function() {
+ blockValue: function(name) {
this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
var params = ["depth0"];
- this.setupParams(0, params);
+ this.setupParams(name, 0, params);
this.replaceStack(function(current) {
params.splice(1, 0, current);
@@ -246,7 +246,7 @@ JavaScriptCompiler.prototype = {
// We're being a bit cheeky and reusing the options value from the prior exec
var params = ["depth0"];
- this.setupParams(0, params, true);
+ this.setupParams('', 0, params, true);
this.flushInline();
@@ -503,20 +503,15 @@ JavaScriptCompiler.prototype = {
this.context.aliases.helperMissing = 'helpers.helperMissing';
this.useRegister('helper');
- var helper = this.lastHelper = this.setupHelper(paramSize, name, true);
+ var helper = this.lastHelper = this.setupHelper(paramSize, name);
var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
- var lookup = 'helper = ' + helper.name + ' || ' + nonHelper;
+ var lookup = 'helper = ' + helper.name + ' || ' + nonHelper + ' || helperMissing';
if (helper.paramsInit) {
lookup += ',' + helper.paramsInit;
}
- this.push(
- '('
- + lookup
- + ',helper '
- + '? helper.call(' + helper.callParams + ') '
- + ': helperMissing.call(' + helper.helperMissingParams + '))');
+ this.push('(' + lookup + ',helper.call(' + helper.callParams + '))');
// Always flush subexpressions. This is both to prevent the compounding size issue that
// occurs when the code has to be duplicated for inlining and also to prevent errors
@@ -825,7 +820,7 @@ JavaScriptCompiler.prototype = {
setupHelper: function(paramSize, name, missingParams) {
var params = [],
- paramsInit = this.setupParams(paramSize, params, missingParams);
+ paramsInit = this.setupParams(name, paramSize, params, missingParams);
var foundHelper = this.nameLookup('helpers', name, 'helper');
return {
@@ -837,9 +832,10 @@ JavaScriptCompiler.prototype = {
};
},
- setupOptions: function(paramSize, params) {
+ setupOptions: function(helper, paramSize, params) {
var options = [], contexts = [], types = [], param, inverse, program;
+ options.push("name:" + this.quotedString(helper));
options.push("hash:" + this.popStack());
if (this.options.stringParams) {
@@ -891,8 +887,8 @@ JavaScriptCompiler.prototype = {
// the params and contexts arguments are passed in arrays
// to fill in
- setupParams: function(paramSize, params, useRegister) {
- var options = '{' + this.setupOptions(paramSize, params).join(',') + '}';
+ setupParams: function(helperName, paramSize, params, useRegister) {
+ var options = '{' + this.setupOptions(helperName, paramSize, params).join(',') + '}';
if (useRegister) {
this.useRegister('options');
diff --git a/spec/helpers.js b/spec/helpers.js
index b0eb91e65..180fc9809 100644
--- a/spec/helpers.js
+++ b/spec/helpers.js
@@ -362,9 +362,9 @@ describe('helpers', function() {
var context = { hello: "Hello", world: "world" };
var helpers = {
- helperMissing: function(helper, context) {
- if(helper === "link_to") {
- return new Handlebars.SafeString("" + context + "");
+ helperMissing: function(mesg, options) {
+ if(options.name === "link_to") {
+ return new Handlebars.SafeString("" + mesg + "");
}
}
};
@@ -436,6 +436,43 @@ describe('helpers', function() {
});
});
+ describe('name field', function() {
+ var context = {};
+ var helpers = {
+ blockHelperMissing: function() {
+ return 'missing: ' + arguments[arguments.length-1].name;
+ },
+ helper: function() {
+ return 'ran: ' + arguments[arguments.length-1].name;
+ }
+ };
+
+ it('should include in ambiguous mustache calls', function() {
+ shouldCompileTo('{{helper}}', [context, helpers], 'ran: helper');
+ });
+ it('should include in helper mustache calls', function() {
+ shouldCompileTo('{{helper 1}}', [context, helpers], 'ran: helper');
+ });
+ it('should include in ambiguous block calls', function() {
+ shouldCompileTo('{{#helper}}{{/helper}}', [context, helpers], 'ran: helper');
+ });
+ it('should include in simple block calls', function() {
+ shouldCompileTo('{{#./helper}}{{/./helper}}', [context, helpers], 'missing: ./helper');
+ });
+ it('should include in helper block calls', function() {
+ shouldCompileTo('{{#helper 1}}{{/helper}}', [context, helpers], 'ran: helper');
+ });
+ it('should include in known helper calls', function() {
+ var template = CompilerContext.compile("{{helper}}", {knownHelpers: {'helper': true}, knownHelpersOnly: true});
+
+ equal(template({}, {helpers: helpers}), 'ran: helper');
+ });
+
+ it('should include full id', function() {
+ shouldCompileTo('{{#foo.helper}}{{/foo.helper}}', [{foo: {}}, helpers], 'missing: foo.helper');
+ });
+ });
+
describe('name conflicts', function() {
it("helpers take precedence over same-named context properties", function() {
var template = CompilerContext.compile("{{goodbye}} {{cruel world}}");