From c7dc3539566dac1fbc82661b432ee26c4731548a Mon Sep 17 00:00:00 2001 From: Nils Knappmeier Date: Tue, 20 Dec 2016 09:32:18 +0100 Subject: [PATCH 01/10] Testcase to verify that compile-errors have a column-property Related to #1284 The test ensures that the property is there, because it is important to some people. --- spec/compiler.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/spec/compiler.js b/spec/compiler.js index be1fb007d..9eaba4a21 100644 --- a/spec/compiler.js +++ b/spec/compiler.js @@ -38,6 +38,22 @@ describe('compiler', function() { }, Error, 'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]'); }); + it('should include the location in the error (row and column)', function() { + try { + Handlebars.compile(' \n {{#if}}\n{{/def}}')(); + equal(true, false, 'Statement must throw exception. This line should not be executed.'); + } catch (err) { + equal(err.message, 'if doesn\'t match def - 2:5', 'Checking error message'); + if (Object.getOwnPropertyDescriptor(err, 'column').writable) { + // In Safari 8, the column-property is read-only. This means that even if it is set with defineProperty, + // its value won't change (https://github.com/jquery/esprima/issues/1290#issuecomment-132455482) + // Since this was neither working in Handlebars 3 nor in 4.0.5, we only check the column for other browsers. + equal(err.column, 5, 'Checking error column'); + } + equal(err.lineNumber, 2, 'Checking error row'); + } + }); + it('can utilize AST instance', function() { equal(Handlebars.compile({ type: 'Program', From a023cb4dd98f09944d2cec8da481a1b3043e281f Mon Sep 17 00:00:00 2001 From: Nils Knappmeier Date: Wed, 21 Dec 2016 22:22:08 +0100 Subject: [PATCH 02/10] Make "column"-property of Errors enumerable Fixes #1284 Appearently, there is a use-case of stringifying the error in order to evaluated its properties on another system. There was a regression from 4.0.5 to 4.0.6 that the column-property of compilation errors was not enumerable anymore in 4.0.6 (due to commit 20c965c) and thus was not included in the output of "JSON.stringify". --- lib/handlebars/exception.js | 5 ++++- spec/compiler.js | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/handlebars/exception.js b/lib/handlebars/exception.js index 08ae531aa..b9a5557b6 100644 --- a/lib/handlebars/exception.js +++ b/lib/handlebars/exception.js @@ -31,7 +31,10 @@ function Exception(message, node) { // Work around issue under safari where we can't directly set the column value /* istanbul ignore next */ if (Object.defineProperty) { - Object.defineProperty(this, 'column', {value: column}); + Object.defineProperty(this, 'column', { + value: column, + enumerable: true + }); } else { this.column = column; } diff --git a/spec/compiler.js b/spec/compiler.js index 9eaba4a21..95941bcc3 100644 --- a/spec/compiler.js +++ b/spec/compiler.js @@ -54,6 +54,15 @@ describe('compiler', function() { } }); + it('should include the location as enumerable property', function() { + try { + Handlebars.compile(' \n {{#if}}\n{{/def}}')(); + equal(true, false, 'Statement must throw exception. This line should not be executed.'); + } catch (err) { + equal(err.propertyIsEnumerable('column'), true, 'Checking error column'); + } + }); + it('can utilize AST instance', function() { equal(Handlebars.compile({ type: 'Program', From 406f2ee01f5cf8e5c9430f76c723dc6251c6959c Mon Sep 17 00:00:00 2001 From: Travis Nelson Date: Wed, 16 Nov 2016 04:32:53 +0100 Subject: [PATCH 03/10] require('sys') is deprecated, using 'util' instead (node:30288) DeprecationWarning: sys is deprecated. Use util instead. (cherry picked from commit 9a36966 by @travnels) --- bench/util/benchwarmer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/util/benchwarmer.js b/bench/util/benchwarmer.js index 78b1a347e..49f66f737 100644 --- a/bench/util/benchwarmer.js +++ b/bench/util/benchwarmer.js @@ -11,7 +11,7 @@ function BenchWarmer() { this.errors = {}; } -var print = require('sys').print; +var print = require('util').print; BenchWarmer.prototype = { winners: function(benches) { From 01b0f656bb32916c714aa7c1438a788dde6c6210 Mon Sep 17 00:00:00 2001 From: Joonas Lahtinen Date: Mon, 13 Feb 2017 23:10:13 +0100 Subject: [PATCH 04/10] Avoid duplicate "sourceMappingURL=" lines. Avoid duplicate // sourceMappingURL=... lines when minifying AND generating a map. UglifyJS2 will write the line when minifying. (cherry picked from commit 660a117) --- lib/precompiler.js | 3 --- spec/precompiler.js | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/precompiler.js b/lib/precompiler.js index a20d1419d..6ba3800cb 100644 --- a/lib/precompiler.js +++ b/lib/precompiler.js @@ -250,9 +250,6 @@ module.exports.cli = function(opts) { outSourceMap: opts.map, inSourceMap: JSON.parse(output.map) }); - if (opts.map) { - output.code += '\n//# sourceMappingURL=' + opts.map + '\n'; - } } if (opts.map) { diff --git a/spec/precompiler.js b/spec/precompiler.js index f9759a9ce..006a37e39 100644 --- a/spec/precompiler.js +++ b/spec/precompiler.js @@ -152,14 +152,14 @@ describe('precompiler', function() { Precompiler.cli({templates: [emptyTemplate], map: 'foo.js.map'}); equal(file, 'foo.js.map'); - equal(/sourceMappingURL=/.test(log), true); + equal(log.match(/sourceMappingURL=/g).length, 1); }); it('should output map', function() { Precompiler.cli({templates: [emptyTemplate], min: true, map: 'foo.js.map'}); equal(file, 'foo.js.map'); - equal(/sourceMappingURL=/.test(log), true); + equal(log.match(/sourceMappingURL=/g).length, 1); }); describe('#loadTemplates', function() { From 5a164d0ca5a4dbda07b38fd55199bb65dfeda55e Mon Sep 17 00:00:00 2001 From: Nils Knappmeier Date: Sat, 31 Dec 2016 00:32:03 +0100 Subject: [PATCH 05/10] Fix for #1252: Using @partial-block twice in a template not possible Fixes #1252 - This fix treats partial-blocks more like closures and uses the closure-context of the "invokePartial"-function to store the @partial-block for the partial. - Adds a tes for the fix --- lib/handlebars/runtime.js | 24 ++++++++++++++---------- spec/partials.js | 7 +++++++ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index 103b5afab..5a79b7103 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -210,12 +210,7 @@ export function wrapProgram(container, i, fn, data, declaredBlockParams, blockPa export function resolvePartial(partial, context, options) { if (!partial) { if (options.name === '@partial-block') { - let data = options.data; - while (data['partial-block'] === noop) { - data = data._parent; - } - partial = data['partial-block']; - data['partial-block'] = noop; + partial = options.data['partial-block']; } else { partial = options.partials[options.name]; } @@ -228,6 +223,8 @@ export function resolvePartial(partial, context, options) { } export function invokePartial(partial, context, options) { + // Use the current closure context to save the partial-block if this partial + const currentPartialBlock = options.data && options.data['partial-block']; options.partial = true; if (options.ids) { options.data.contextPath = options.ids[0] || options.data.contextPath; @@ -236,10 +233,17 @@ export function invokePartial(partial, context, options) { let partialBlock; if (options.fn && options.fn !== noop) { options.data = createFrame(options.data); - partialBlock = options.data['partial-block'] = options.fn; - - if (partialBlock.partials) { - options.partials = Utils.extend({}, options.partials, partialBlock.partials); + // Wrapper function to get access to currentPartialBlock from the closure + let fn = options.fn; + partialBlock = options.data['partial-block'] = function partialBlockWrapper(context, options) { + // Restore the partial-block from the closure for the execution of the block + // i.e. the part inside the block of the partial call. + options.data = createFrame(options.data); + options.data['partial-block'] = currentPartialBlock; + return fn(context, options); + }; + if (fn.partials) { + options.partials = Utils.extend({}, options.partials, fn.partials); } } diff --git a/spec/partials.js b/spec/partials.js index d6baba504..cbec58863 100644 --- a/spec/partials.js +++ b/spec/partials.js @@ -249,6 +249,13 @@ describe('partials', function() { true, 'success'); }); + it('should be able to render the partial-block twice', function() { + shouldCompileToWithPartials( + '{{#> dude}}success{{/dude}}', + [{}, {}, {dude: '{{> @partial-block }} {{> @partial-block }}'}], + true, + 'success success'); + }); it('should render block from partial with context', function() { shouldCompileToWithPartials( '{{#> dude}}{{value}}{{/dude}}', From 63a8e0caa29cae4720fd90a4b3a9b1c50832afe8 Mon Sep 17 00:00:00 2001 From: Nils Knappmeier Date: Mon, 2 Jan 2017 10:05:21 +0100 Subject: [PATCH 06/10] Add more tests for partial-blocks and inline partials - Multiple partial-blocks at different nesting levels - Calling partial-blocks twice with nested partial-blocks - Calling the partial-block from within the #each-helper - nested inline partials with partial-blocks on different nesting levels - nested inline partials (twice at each level) --- spec/partials.js | 88 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/spec/partials.js b/spec/partials.js index cbec58863..266837db8 100644 --- a/spec/partials.js +++ b/spec/partials.js @@ -263,6 +263,32 @@ describe('partials', function() { true, 'success'); }); + it('should allow the #each-helper to be used along with partial-blocks', function() { + shouldCompileToWithPartials( + '', + [ + {value: ['a', 'b', 'c']}, + {}, + { + list: '{{#each .}}{{> @partial-block}}{{/each}}' + } + ], + true, + ''); + }); + it('should render block from partial with context (twice)', function() { + shouldCompileToWithPartials( + '{{#> dude}}{{value}}{{/dude}}', + [ + {context: {value: 'success'}}, + {}, + { + dude: '{{#with context}}{{> @partial-block }} {{> @partial-block }}{{/with}}' + } + ], + true, + 'success success'); + }); it('should render block from partial with context', function() { shouldCompileToWithPartials( '{{#> dude}}{{../context/value}}{{/dude}}', @@ -291,6 +317,50 @@ describe('partials', function() { true, ''); }); + it('should render nested partial blocks at different nesting levels', function() { + shouldCompileToWithPartials( + '', + [ + {value: 'success'}, + {}, + { + outer: '{{#> nested}}{{> @partial-block}}{{/nested}}{{> @partial-block}}', + nested: '{{> @partial-block}}' + } + ], + true, + ''); + }); + it('should render nested partial blocks at different nesting levels (twice)', function() { + shouldCompileToWithPartials( + '', + [ + {value: 'success'}, + {}, + { + outer: '{{#> nested}}{{> @partial-block}} {{> @partial-block}}{{/nested}}{{> @partial-block}}+{{> @partial-block}}', + nested: '{{> @partial-block}}' + } + ], + true, + ''); + }); + it('should render nested partial blocks (twice at each level)', function() { + shouldCompileToWithPartials( + '', + [ + {value: 'success'}, + {}, + { + outer: '{{#> nested}}{{> @partial-block}} {{> @partial-block}}{{/nested}}', + nested: '{{> @partial-block}}{{> @partial-block}}' + } + ], + true, + ''); + }); }); describe('inline partials', function() { @@ -339,6 +409,24 @@ describe('partials', function() { true, 'success'); }); + it('should render nested inline partials with partial-blocks on different nesting levels', function() { + shouldCompileToWithPartials( + '{{#*inline "outer"}}{{#>inner}}{{>@partial-block}}{{/inner}}{{>@partial-block}}{{/inline}}' + + '{{#*inline "inner"}}{{>@partial-block}}{{/inline}}' + + '{{#>outer}}{{value}}{{/outer}}', + [{value: 'success'}, {}, {}], + true, + 'successsuccess'); + }); + it('should render nested inline partials (twice at each level)', function() { + shouldCompileToWithPartials( + '{{#*inline "outer"}}{{#>inner}}{{>@partial-block}} {{>@partial-block}}{{/inner}}{{/inline}}' + + '{{#*inline "inner"}}{{>@partial-block}}{{>@partial-block}}{{/inline}}' + + '{{#>outer}}{{value}}{{/outer}}', + [{value: 'success'}, {}, {}], + true, + 'success successsuccess success'); + }); }); it('should pass compiler flags', function() { From b6173752192682f8156b791e06e7985913621d76 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Thu, 23 Feb 2017 17:44:31 +0100 Subject: [PATCH 07/10] Parser: Change suffix to use ES6 default module export - This export will be transpiled by Babel for the cjs distribution, but will enable others to use a pure ES6 module distribution - Instanbul: Ignore "parser.js" for coverage reporting. This file was ignored before via annotation, but this has no effect anymore due to the above change - Remove istanbul annotation from `parser-prefix` (@nknapp) Squashed by @nknapp (cherry picked from commit 508347e) --- .istanbul.yml | 2 +- src/parser-prefix.js | 2 +- src/parser-suffix.js | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.istanbul.yml b/.istanbul.yml index e6911f190..960643ef4 100644 --- a/.istanbul.yml +++ b/.istanbul.yml @@ -1,2 +1,2 @@ instrumentation: - excludes: ['**/spec/**'] + excludes: ['**/spec/**', '**/handlebars/compiler/parser.js'] diff --git a/src/parser-prefix.js b/src/parser-prefix.js index e26b99340..d9ed04116 100644 --- a/src/parser-prefix.js +++ b/src/parser-prefix.js @@ -1 +1 @@ -/* istanbul ignore next */ +// File ignored in coverage tests via setting in .istanbul.yml diff --git a/src/parser-suffix.js b/src/parser-suffix.js index 1f69f7a44..6e4aa20d6 100644 --- a/src/parser-suffix.js +++ b/src/parser-suffix.js @@ -1,2 +1 @@ -exports.__esModule = true; -exports['default'] = handlebars; +export default handlebars; From c8f4b570c14746c6e9f0c5a39056e5ee4be2f5e1 Mon Sep 17 00:00:00 2001 From: Nils Knappmeier Date: Thu, 9 Mar 2017 21:17:52 +0100 Subject: [PATCH 08/10] Fix context-stack when calling block-helpers on null values Fixes #1319 Original behaviour: - When a block-helper was called on a null-context, an empty object was used as context instead. (#1093) - The runtime verifies that whether the current context equals the last context and adds the current context to the stack, if it is not. This is done, so that inside a block-helper, the ".." path can be used to go back to the parent element. - If the helper is called on a "null" element, the context was added, even though it shouldn't be, because the "null != {}" Fix: - The commit replaces "null" by the identifiable "container.nullContext" instead of "{}". "nullContext" is a sealed empty object. - An additional check in the runtime verifies that the context is only added to the stack, if it is not the nullContext. Backwards compatibility within 4.0.x-versions: - This commit changes the compiler and compiled templates would not work with runtime-versions 4.0.0 - 4.0.6, because of the "nullContext" property. That's way, the compiled code reads "(container.nullContext || {})" so that the behavior will degrade gracefully with older runtime versions: Everything else will work fine, but GH-1319 will still be broken, if you use a newer compiler with a pre 4.0.7 runtime. --- lib/handlebars/compiler/javascript-compiler.js | 2 +- lib/handlebars/runtime.js | 4 +++- spec/regressions.js | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index ec8911761..bf4be8af9 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -987,7 +987,7 @@ JavaScriptCompiler.prototype = { let params = [], paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper); let foundHelper = this.nameLookup('helpers', name, 'helper'), - callContext = this.aliasable(`${this.contextName(0)} != null ? ${this.contextName(0)} : {}`); + callContext = this.aliasable(`${this.contextName(0)} != null ? ${this.contextName(0)} : (container.nullContext || {})`); return { params: params, diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index 5a79b7103..1c084ce3d 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -124,6 +124,8 @@ export function template(templateSpec, env) { return obj; }, + // An empty object to use as replacement for null-contexts + nullContext: Object.seal({}), noop: env.VM.noop, compilerInfo: templateSpec.compiler @@ -187,7 +189,7 @@ 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]) { + if (depths && context != depths[0] && !(context === container.nullContext && depths[0] === null)) { currentDepths = [context].concat(depths); } diff --git a/spec/regressions.js b/spec/regressions.js index 4a2a55cd6..6aca9088d 100644 --- a/spec/regressions.js +++ b/spec/regressions.js @@ -277,4 +277,9 @@ describe('Regressions', function() { shouldCompileTo(string, { listOne: ['a'], listTwo: ['b']}, 'ab', ''); }); + + it('GH-1319: "unless" breaks when "each" value equals "null"', function() { + var string = '{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}'; + shouldCompileTo(string, { value: 'parent', list: [ null, 'a'] }, 'parent=parent parent=parent ', ''); + }); }); From 8e09f0ee4e1233ce05231b6df069fffa96723d8f Mon Sep 17 00:00:00 2001 From: Nils Knappmeier Date: Sat, 29 Apr 2017 22:44:42 +0200 Subject: [PATCH 09/10] Update release-notes for 4.0.7 --- release-notes.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/release-notes.md b/release-notes.md index 8b073d520..b4d3ec077 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,7 +2,19 @@ ## Development -[Commits](https://github.com/lawnsea/handlebars.js/compare/v4.0.6...master) +[Commits](https://github.com/nknapp/handlebars.js/compare/v4.0.7...master) + +## v4.0.7 - April 29th, 2017 +- [#1319](https://github.com/wycats/handlebars.js/issues/1319): Fix context-stack when calling block-helpers on null values ([@nknapp](https://github.com/nknapp)) - c8f4b57 +- [#1315](https://github.com/wycats/handlebars.js/pull/1315) Parser: Change suffix to use ES6 default module export ([@Turbo87](https://github.com/Turbo87))- b617375 +- [#1290](https://github.com/wycats/handlebars.js/pull/1290) [#1252](https://github.com/wycats/handlebars.js/issue/1290) Add more tests for partial-blocks and inline partials ([@nknapp](https://github.com/nknapp)) - 63a8e0c +- [#1252](https://github.com/wycats/handlebars.js/issue/1290) Using @partial-block twice in a template not possible ([@nknapp](https://github.com/nknapp)) - 5a164d0 +- [#1310](https://github.com/wycats/handlebars.js/pull/1310) Avoid duplicate "sourceMappingURL=" lines. ([@joonas-lahtinen](https://github.com/joonas-lahtinen)) - 01b0f65 +- [#1275](https://github.com/wycats/handlebars.js/pull/1275) require('sys') is deprecated, using 'util' instead ([@travnels](https://github.com/travnels)) - 406f2ee +- [#1285](https://github.com/wycats/handlebars.js/pull/1285) [#1284](https://github.com/wycats/handlebars.js/issues/1284) Make "column"-property of Errors enumerable ([@nknapp](https://github.com/nknapp)) - a023cb4 +- [#1285](https://github.com/wycats/handlebars.js/pull/1285) Testcase to verify that compile-errors have a column-property ([@nknapp](https://github.com/nknapp)) - c7dc353 + +[Commits](https://github.com/lawnsea/handlebars.js/compare/v4.0.6...v4.0.7) ## v4.0.6 - November 12th, 2016 - [#1243](https://github.com/wycats/handlebars.js/pull/1243) - Walk up data frames for nested @partial-block ([@lawnsea](https://github.com/lawnsea)) From 606fa55b0a2fab4b22b810a21fc9ae1fd5fd8430 Mon Sep 17 00:00:00 2001 From: Nils Knappmeier Date: Sat, 29 Apr 2017 22:52:09 +0200 Subject: [PATCH 10/10] v4.0.7 --- components/bower.json | 2 +- components/handlebars.js.nuspec | 2 +- lib/handlebars/base.js | 2 +- package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/bower.json b/components/bower.json index c67efbb5b..82d7ad1d0 100644 --- a/components/bower.json +++ b/components/bower.json @@ -1,6 +1,6 @@ { "name": "handlebars", - "version": "4.0.6", + "version": "4.0.7", "main": "handlebars.js", "license": "MIT", "dependencies": {} diff --git a/components/handlebars.js.nuspec b/components/handlebars.js.nuspec index 1145f057c..1a785122f 100644 --- a/components/handlebars.js.nuspec +++ b/components/handlebars.js.nuspec @@ -2,7 +2,7 @@ handlebars.js - 4.0.6 + 4.0.7 handlebars.js Authors https://github.com/wycats/handlebars.js/blob/master/LICENSE https://github.com/wycats/handlebars.js/ diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 155e7ca04..615661e23 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -4,7 +4,7 @@ import {registerDefaultHelpers} from './helpers'; import {registerDefaultDecorators} from './decorators'; import logger from './logger'; -export const VERSION = '4.0.6'; +export const VERSION = '4.0.7'; export const COMPILER_REVISION = 7; export const REVISION_CHANGES = { diff --git a/package.json b/package.json index 4ee3aede1..c5bdba4fc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "handlebars", "barename": "handlebars", - "version": "4.0.6", + "version": "4.0.7", "description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration", "homepage": "http://www.handlebarsjs.com/", "keywords": [