diff --git a/.travis.yml b/.travis.yml index b9ecde273..c6e2c8439 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,11 +13,12 @@ env: - secure: Nm4AgSfsgNB21kgKrF9Tl7qVZU8YYREhouQunFracTcZZh2NZ2XH5aHuSiXCj88B13Cr/jGbJKsZ4T3QS3wWYtz6lkyVOx3H3iI+TMtqhD9RM3a7A4O+4vVN8IioB2YjhEu0OKjwgX5gp+0uF+pLEi7Hpj6fupD3AbbL5uYcKg8= matrix: include: - - node_js: '0.10' + - node_js: '0.12' env: - PUBLISH=true - secure: pLTzghtVll9yGKJI0AaB0uI8GypfWxLTaIB0ZL8//yN3nAEIKMhf/RRilYTsn/rKj2NUa7vt2edYILi3lttOUlCBOwTc9amiRms1W8Lwr/3IdWPeBLvLuH1zNJRm2lBAwU4LBSqaOwhGaxOQr6KHTnWudhNhgOucxpZfvfI/dFw= - secure: yERYCf7AwL11D9uMtacly/THGV8BlzsMmrt+iQVvGA3GaY6QMmfYqf6P6cCH98sH5etd1Y+1e6YrPeMjqI6lyRllT7FptoyOdHulazQe86VQN4sc0EpqMlH088kB7gGjTut9Z+X9ViooT5XEh9WA5jXEI9pXhQJNoIHkWPuwGuY= + - node_js: '4.0.0' cache: directories: - node_modules diff --git a/components/bower.json b/components/bower.json index 0f54800af..5751938d6 100644 --- a/components/bower.json +++ b/components/bower.json @@ -1,6 +1,6 @@ { "name": "handlebars", - "version": "4.0.2", + "version": "4.0.3", "main": "handlebars.js", "license": "MIT", "dependencies": {} diff --git a/components/handlebars.js.nuspec b/components/handlebars.js.nuspec index ebdb8932c..56899417c 100644 --- a/components/handlebars.js.nuspec +++ b/components/handlebars.js.nuspec @@ -2,7 +2,7 @@ handlebars.js - 4.0.2 + 4.0.3 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 53469eb2d..67fcc206c 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.2'; +export const VERSION = '4.0.3'; 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 d0f206c58..bd48e9b1c 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -984,13 +984,14 @@ JavaScriptCompiler.prototype = { setupHelper: function(paramSize, name, blockHelper) { let params = [], paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper); - let foundHelper = this.nameLookup('helpers', name, 'helper'); + let foundHelper = this.nameLookup('helpers', name, 'helper'), + callContext = this.aliasable(`${this.contextName(0)} != null ? ${this.contextName(0)} : {}`); return { params: params, paramsInit: paramsInit, name: foundHelper, - callParams: [this.contextName(0)].concat(params) + callParams: [callContext].concat(params) }; }, diff --git a/lib/handlebars/helpers/each.js b/lib/handlebars/helpers/each.js index 9b1629b40..fb11903c8 100644 --- a/lib/handlebars/helpers/each.js +++ b/lib/handlebars/helpers/each.js @@ -25,12 +25,6 @@ export default function(instance) { } function execIteration(field, index, last) { - // Don't iterate over undefined values since we can't execute blocks against them - // in non-strict (js) mode. - if (context[field] == null) { - return; - } - if (data) { data.key = field; data.index = index; @@ -51,7 +45,9 @@ export default function(instance) { if (context && typeof context === 'object') { if (isArray(context)) { for (let j = context.length; i < j; i++) { - execIteration(i, i, i === context.length - 1); + if (i in context) { + execIteration(i, i, i === context.length - 1); + } } } else { let priorKey; diff --git a/lib/handlebars/logger.js b/lib/handlebars/logger.js index 1d583ddb9..1ab0051f5 100644 --- a/lib/handlebars/logger.js +++ b/lib/handlebars/logger.js @@ -1,3 +1,5 @@ +import {indexOf} from './utils'; + let logger = { methodMap: ['debug', 'info', 'warn', 'error'], level: 'info', @@ -5,7 +7,7 @@ let logger = { // Maps a given level value to the `methodMap` indexes above. lookupLevel: function(level) { if (typeof level === 'string') { - let levelMap = logger.methodMap.indexOf(level.toLowerCase()); + let levelMap = indexOf(logger.methodMap, level.toLowerCase()); if (levelMap >= 0) { level = levelMap; } else { diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index 52d9b9483..b47d9615d 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -230,6 +230,7 @@ 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) { diff --git a/package.json b/package.json index 970d13e4c..9d6c0b66a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "handlebars", "barename": "handlebars", - "version": "4.0.2", + "version": "4.0.3", "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 822cb80fa..9736ad093 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,7 +2,20 @@ ## Development -[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.2...master) +[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.3...master) + +## v4.0.3 - September 23rd, 2015 +- [#1099](https://github.com/wycats/handlebars.js/issues/1099) - @partial-block is overridden ([@btmorex](https://api.github.com/users/btmorex)) +- [#1093](https://github.com/wycats/handlebars.js/issues/1093) - #each skips iteration on undefined values ([@florianpilz](https://api.github.com/users/florianpilz)) +- [#1092](https://github.com/wycats/handlebars.js/issues/1092) - Square braces in key name ([@distantnative](https://api.github.com/users/distantnative)) +- [#1091](https://github.com/wycats/handlebars.js/pull/1091) - fix typo in release notes ([@nikolas](https://api.github.com/users/nikolas)) +- [#1090](https://github.com/wycats/handlebars.js/pull/1090) - grammar fixes in 4.0.0 release notes ([@nikolas](https://api.github.com/users/nikolas)) + +Compatibility notes: +- `each` iteration with `undefined` values has been restored to the 3.0 behaviors. Helper calls with undefined context values will now execute against an arbitrary empty object to avoid executing against global object in non-strict mode. +- `]` can now be included in `[]` wrapped identifiers by escaping with `\`. Any `[]` identifiers that include `\` will now have to properly escape these values. + +[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.2...v4.0.3) ## v4.0.2 - September 4th, 2015 - [#1089](https://github.com/wycats/handlebars.js/issues/1089) - "Failover content" not working in multiple levels of inline partials ([@michaellopez](https://api.github.com/users/michaellopez)) @@ -50,7 +63,7 @@ - Fix location information for programs - [93faffa](https://github.com/wycats/handlebars.js/commit/93faffa) Compatibility notes: -- Depthed paths are now conditional pushed on to the stack. If the helper uses the same context, then a new stack is not created. This leads to behavior the better matches expectations for helpers like `if` that do not seem to alter the context. Any instances of `../` in templates will need to be checked for the correct behavior under 4.0.0. In general templates will either reduce the number of `../` instances or leave them as is. See [#1028](https://github.com/wycats/handlebars.js/issues/1028). +- Depthed paths are now conditionally pushed on to the stack. If the helper uses the same context, then a new stack is not created. This leads to behavior that better matches expectations for helpers like `if` that do not seem to alter the context. Any instances of `../` in templates will need to be checked for the correct behavior under 4.0.0. In general templates will either reduce the number of `../` instances or leave them as is. See [#1028](https://github.com/wycats/handlebars.js/issues/1028). - The `=` character is now HTML escaped. This closes a potential exploit case when using unquoted attributes, i.e. `
`. In general it's recommended that attributes always be quoted when their values are generated from a mustache to avoid any potential exploit surfaces. - AST constructors have been dropped in favor of plain old javascript objects - The runtime version has been increased. Precompiled templates will need to use runtime of at least 4.0.0. @@ -238,7 +251,7 @@ Compatibility notes: - INTEGER -> NUMBER - Additional PartialNode hash parameter - New RawBlockNode type -- Data frames now have a `_parent` field. This is internal but is enumerable for performance/compatability reasons. +- Data frames now have a `_parent` field. This is internal but is enumerable for performance/compatibility reasons. [Commits](https://github.com/wycats/handlebars.js/compare/v1.3.0...v2.0.0-alpha.1) diff --git a/spec/env/common.js b/spec/env/common.js index 2a24db20f..e37805261 100644 --- a/spec/env/common.js +++ b/spec/env/common.js @@ -61,12 +61,12 @@ global.shouldThrow = function(callback, type, msg) { try { callback(); failed = true; - } catch (err) { - if (type && !(err instanceof type)) { - throw new AssertError('Type failure: ' + err); + } catch (caught) { + if (type && !(caught instanceof type)) { + throw new AssertError('Type failure: ' + caught); } - if (msg && !(msg.test ? msg.test(err.message) : msg === err.message)) { - throw new AssertError('Throw mismatch: Expected ' + err.message + ' to match ' + msg + '\n\n' + err.stack, shouldThrow); + if (msg && !(msg.test ? msg.test(caught.message) : msg === caught.message)) { + throw new AssertError('Throw mismatch: Expected ' + caught.message + ' to match ' + msg + '\n\n' + caught.stack, shouldThrow); } } if (failed) { diff --git a/spec/index.html b/spec/index.html index 348ae89d6..5db2146ad 100644 --- a/spec/index.html +++ b/spec/index.html @@ -1,3 +1,4 @@ + Mocha diff --git a/spec/parser.js b/spec/parser.js index 3b7e3e45f..4527d19c1 100644 --- a/spec/parser.js +++ b/spec/parser.js @@ -39,6 +39,12 @@ describe('parser', function() { it('parses mustaches with - in a path', function() { equals(astFor('{{foo-bar}}'), '{{ PATH:foo-bar [] }}\n'); }); + it('parses mustaches with escaped [] in a path', function() { + equals(astFor('{{[foo[\\]]}}'), '{{ PATH:foo[] [] }}\n'); + }); + it('parses escaped \\\\ in path', function() { + equals(astFor('{{[foo\\\\]}}'), '{{ PATH:foo\\ [] }}\n'); + }); it('parses mustaches with parameters', function() { equals(astFor('{{foo bar}}'), '{{ PATH:foo [PATH:bar] }}\n'); diff --git a/spec/regressions.js b/spec/regressions.js index cca126e37..83765a223 100644 --- a/spec/regressions.js +++ b/spec/regressions.js @@ -204,6 +204,25 @@ describe('Regressions', function() { shouldCompileTo('{{#each array}}{{@index}}{{.}}{{/each}}', {array: array}, '1foo3bar'); }); + it('GH-1093: Undefined helper context', function() { + var obj = {foo: undefined, bar: 'bat'}; + var helpers = { + helper: function() { + // It's valid to execute a block against an undefined context, but + // helpers can not do so, so we expect to have an empty object here; + for (var name in this) { + if (this.hasOwnProperty(name)) { + return 'found'; + } + } + // And to make IE happy, check for the known string as length is not enumerated. + return (this == 'bat' ? 'found' : 'not'); + } + }; + + shouldCompileTo('{{#each obj}}{{{helper}}}{{.}}{{/each}}', [{obj: obj}, helpers], 'notfoundbat'); + }); + it('should support multiple levels of inline partials', function() { var string = '{{#> layout}}{{#*inline "subcontent"}}subcontent{{/inline}}{{/layout}}'; var partials = { @@ -220,4 +239,12 @@ describe('Regressions', function() { }; shouldCompileToWithPartials(string, [{}, {}, partials], true, 'doctypelayoutsubcontent'); }); + it('GH-1099: should support greater than 3 nested levels of inline partials', function() { + var string = '{{#> layout}}Outer{{/layout}}'; + var partials = { + layout: '{{#> inner}}Inner{{/inner}}{{> @partial-block }}', + inner: '' + }; + shouldCompileToWithPartials(string, [{}, {}, partials], true, 'Outer'); + }); }); diff --git a/spec/tokenizer.js b/spec/tokenizer.js index dc077ce72..428804e01 100644 --- a/spec/tokenizer.js +++ b/spec/tokenizer.js @@ -146,6 +146,11 @@ describe('Tokenizer', function() { shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE', 'OPEN', 'ID', 'SEP', 'ID', 'CLOSE']); }); + it('allows escaped literals in []', function() { + var result = tokenize('{{foo.[bar\\]]}}'); + shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']); + }); + it('tokenizes {{.}} as OPEN ID CLOSE', function() { var result = tokenize('{{.}}'); shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']); diff --git a/src/handlebars.l b/src/handlebars.l index 4c3c30421..e9de1029b 100644 --- a/src/handlebars.l +++ b/src/handlebars.l @@ -120,7 +120,7 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD} {ID} return 'ID'; -'['[^\]]*']' return 'ID'; +'['('\\]'|[^\]])*']' yytext = yytext.replace(/\\([\\\]])/g,'$1'); return 'ID'; . return 'INVALID'; <> return 'EOF';