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/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) { 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/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/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/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index 103b5afab..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); } @@ -210,12 +212,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 +225,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 +235,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/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/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": [ 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)) diff --git a/spec/compiler.js b/spec/compiler.js index be1fb007d..95941bcc3 100644 --- a/spec/compiler.js +++ b/spec/compiler.js @@ -38,6 +38,31 @@ 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('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', diff --git a/spec/partials.js b/spec/partials.js index d6baba504..266837db8 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}}', @@ -256,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}}', @@ -284,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() { @@ -332,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() { 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() { 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 ', ''); + }); }); 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;