From 279e038ba7ff7e5966a60e36d326e2d4c310f0c6 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 3 Aug 2015 15:15:09 -0500 Subject: [PATCH] Avoid depth creation when context remains the same MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creating a new depth value seems to confuse users as they don’t expect things like `if` to require multiple `..` to break out of. With the change, we avoid pushing a context to the depth list if it’s already on the top of the stack, effectively removing cases where `.` and `..` are the same object and multiple `..` references are required. This is a breaking change and all templates that utilize `..` will have to check their usage and confirm that this does not break desired behavior. Helper authors now need to take care to return the same context value whenever it is conceptually the same and to avoid behaviors that may execute children under the current context in some situations and under different contexts under other situations. Fixes #1028 --- lib/handlebars/runtime.js | 13 +++++++++++-- spec/builtins.js | 5 +++++ spec/helpers.js | 15 ++++++++++++++- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index d41b42a44..bc12fc9fd 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -135,7 +135,11 @@ export function template(templateSpec, env) { let depths, blockParams = templateSpec.useBlockParams ? [] : undefined; if (templateSpec.useDepths) { - depths = options.depths ? [context].concat(options.depths) : [context]; + if (options.depths) { + depths = context !== options.depths[0] ? [context].concat(depths) : options.depths; + } else { + depths = [context]; + } } return templateSpec.main.call(container, context, container.helpers, container.partials, data, blockParams, depths); @@ -170,12 +174,17 @@ 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]) { + currentDepths = [context].concat(depths); + } + return fn.call(container, context, container.helpers, container.partials, options.data || data, blockParams && [options.blockParams].concat(blockParams), - depths && [context].concat(depths)); + currentDepths); } prog.program = i; prog.depth = depths ? depths.length : 0; diff --git a/spec/builtins.js b/spec/builtins.js index bcacf5908..a7f6204f2 100644 --- a/spec/builtins.js +++ b/spec/builtins.js @@ -32,6 +32,11 @@ describe('builtin helpers', function() { shouldCompileTo(string, {goodbye: function() {return this.foo; }, world: 'world'}, 'cruel world!', 'if with function does not show the contents when returns undefined'); }); + + it('should not change the depth list', function() { + var string = '{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}'; + shouldCompileTo(string, {foo: {goodbye: true}, world: 'world'}, 'GOODBYE cruel world!'); + }); }); describe('#with', function() { diff --git a/spec/helpers.js b/spec/helpers.js index 00bcb79cb..94e503f12 100644 --- a/spec/helpers.js +++ b/spec/helpers.js @@ -38,6 +38,19 @@ describe('helpers', function() { shouldCompileTo(string, [{}, helpers], ' {{{{b}}}} {{{{/b}}}} ', 'raw block helper should get nested raw block as raw content'); }); + it('helper block with identical context', function() { + var string = '{{#goodbyes}}{{name}}{{/goodbyes}}'; + var hash = {name: 'Alan'}; + var helpers = {goodbyes: function(options) { + var out = ''; + var byes = ['Goodbye', 'goodbye', 'GOODBYE']; + for (var i = 0, j = byes.length; i < j; i++) { + out += byes[i] + ' ' + options.fn(this) + '! '; + } + return out; + }}; + shouldCompileTo(string, [hash, helpers], 'Goodbye Alan! goodbye Alan! GOODBYE Alan! '); + }); it('helper block with complex lookup expression', function() { var string = '{{#goodbyes}}{{../name}}{{/goodbyes}}'; var hash = {name: 'Alan'}; @@ -45,7 +58,7 @@ describe('helpers', function() { var out = ''; var byes = ['Goodbye', 'goodbye', 'GOODBYE']; for (var i = 0, j = byes.length; i < j; i++) { - out += byes[i] + ' ' + options.fn(this) + '! '; + out += byes[i] + ' ' + options.fn({}) + '! '; } return out; }};