diff options
-rw-r--r-- | FAQ.md | 2 | ||||
-rw-r--r-- | README.markdown | 2 | ||||
-rwxr-xr-x | bin/handlebars | 14 | ||||
-rw-r--r-- | lib/handlebars/base.js | 5 | ||||
-rw-r--r-- | lib/handlebars/compiler/ast.js | 7 | ||||
-rw-r--r-- | lib/handlebars/compiler/compiler.js | 7 | ||||
-rw-r--r-- | lib/handlebars/compiler/helpers.js | 8 | ||||
-rw-r--r-- | lib/handlebars/compiler/javascript-compiler.js | 77 | ||||
-rw-r--r-- | lib/handlebars/compiler/visitor.js | 60 | ||||
-rw-r--r-- | lib/handlebars/safe-string.js | 2 | ||||
-rw-r--r-- | lib/handlebars/utils.js | 4 | ||||
-rw-r--r-- | spec/artifacts/bom.handlebars | 1 | ||||
-rw-r--r-- | spec/ast.js | 2 | ||||
-rw-r--r-- | spec/basic.js | 3 | ||||
-rw-r--r-- | spec/env/common.js | 2 | ||||
-rw-r--r-- | spec/partials.js | 27 | ||||
-rw-r--r-- | spec/precompiler.js | 31 | ||||
-rw-r--r-- | spec/tokenizer.js | 6 | ||||
-rw-r--r-- | spec/utils.js | 6 | ||||
-rw-r--r-- | spec/visitor.js | 48 | ||||
-rw-r--r-- | spec/whitespace-control.js | 3 | ||||
-rw-r--r-- | src/handlebars.l | 16 | ||||
-rw-r--r-- | src/handlebars.yy | 2 |
23 files changed, 258 insertions, 77 deletions
@@ -57,4 +57,4 @@ The other option is to load the `handlebars.runtime.js` UMD build, which might not require path configuration and exposes the library as both the module root and the `default` field for compatibility. - If not using ES6 transpilers or accessing submodules in the build the former option should be sufficent for most use cases. + If not using ES6 transpilers or accessing submodules in the build the former option should be sufficient for most use cases. diff --git a/README.markdown b/README.markdown index 7755286..04d6097 100644 --- a/README.markdown +++ b/README.markdown @@ -16,6 +16,8 @@ Installing ---------- Installing Handlebars is easy. Simply download the package [from the official site](http://handlebarsjs.com/) or the [bower repository][bower-repo] and add it to your web pages (you should usually use the most recent version). +For web browsers, a free CDN is available at [jsDelivr](http://www.jsdelivr.com/#!handlebarsjs). Advanced usage, such as [version aliasing & concocting](https://github.com/jsdelivr/jsdelivr#usage), is available. + Alternatively, if you prefer having the latest version of handlebars from the 'master' branch, passing builds of the 'master' branch are automatically published to S3. You may download the latest passing master build by grabbing diff --git a/bin/handlebars b/bin/handlebars index 4ea3296..4007cc7 100755 --- a/bin/handlebars +++ b/bin/handlebars @@ -1,7 +1,7 @@ #!/usr/bin/env node var optimist = require('optimist') - .usage('Precompile handlebar templates.\nUsage: $0 template...', { + .usage('Precompile handlebar templates.\nUsage: $0 [template|directory]...', { 'f': { 'type': 'string', 'description': 'Output File', @@ -80,6 +80,11 @@ var optimist = require('optimist') 'type': 'boolean', 'description': 'Prints the current compiler version', 'alias': 'version' + }, + + 'help': { + 'type': 'boolean', + 'description': 'Outputs this message' } }) @@ -89,7 +94,14 @@ var optimist = require('optimist') } }); + var argv = optimist.argv; argv.templates = argv._; delete argv._; + +if (argv.help || !argv.templates.length) { + optimist.showHelp(); + return; +} + return require('../lib/precompiler').cli(argv); diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 76f53e2..cec2937 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -47,6 +47,9 @@ HandlebarsEnvironment.prototype = { if (toString.call(name) === objectType) { Utils.extend(this.partials, name); } else { + if (typeof partial === 'undefined') { + throw new Exception('Attempting to register a partial as undefined'); + } this.partials[name] = partial; } }, @@ -209,7 +212,7 @@ export var logger = { ERROR: 3, level: 3, - // can be overridden in the host environment + // Can be overridden in the host environment log: function(level, message) { if (logger.level <= level) { var method = logger.methodMap[level]; diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js index 49bdc33..e05ceec 100644 --- a/lib/handlebars/compiler/ast.js +++ b/lib/handlebars/compiler/ast.js @@ -196,14 +196,13 @@ var AST = { this.stringModeValue = bool === "true"; }, - CommentNode: function(comment, locInfo) { + CommentNode: function(comment, strip, locInfo) { LocationInfo.call(this, locInfo); this.type = "comment"; this.comment = comment; - this.strip = { - inlineStandalone: true - }; + this.strip = strip; + strip.inlineStandalone = true; } }; diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 1aba34b..1303d8f 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -179,7 +179,12 @@ Compiler.prototype = { this.opcode('pushContext'); } - this.opcode('invokePartial', partialName.name, partial.indent || ''); + var indent = partial.indent || ''; + if (this.options.preventIndent && indent) { + this.opcode('appendContent', indent); + indent = ''; + } + this.opcode('invokePartial', partialName.name, indent); this.opcode('append'); }, diff --git a/lib/handlebars/compiler/helpers.js b/lib/handlebars/compiler/helpers.js index b375479..d236f7f 100644 --- a/lib/handlebars/compiler/helpers.js +++ b/lib/handlebars/compiler/helpers.js @@ -7,6 +7,11 @@ export function stripFlags(open, close) { }; } +export function stripComment(comment) { + return comment.replace(/^\{\{~?\!-?-?/, '') + .replace(/-?-?~?\}\}$/, ''); +} + export function prepareBlock(mustache, program, inverseAndProgram, close, inverted, locInfo) { /*jshint -W040 */ @@ -112,7 +117,8 @@ export function prepareProgram(statements, isRoot) { if (omitLeft(statements, i)) { // If we are on a standalone node, save the indent info for partials if (current.type === 'partial') { - current.indent = (/([ \t]+$)/).exec(statements[i-1].original) ? RegExp.$1 : ''; + // Pull out the whitespace from the final line + current.indent = (/([ \t]+$)/).exec(statements[i-1].original)[1]; } } } diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index d41cacd..4e414e5 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -289,13 +289,18 @@ JavaScriptCompiler.prototype = { // If `value` is truthy, or 0, it is coerced into a string and appended // Otherwise, the empty string is appended append: function() { - // Force anything that is inlined onto the stack so we don't have duplication - // when we examine local - this.flushInline(); - var local = this.popStack(); - this.pushSource('if (' + local + ' != null) { ' + this.appendToBuffer(local) + ' }'); - if (this.environment.isSimple) { - this.pushSource("else { " + this.appendToBuffer("''") + " }"); + if (this.isInline()) { + this.replaceStack(function(current) { + return ' != null ? ' + current + ' : ""'; + }); + + this.pushSource(this.appendToBuffer(this.popStack())); + } else { + var local = this.popStack(); + this.pushSource('if (' + local + ' != null) { ' + this.appendToBuffer(local) + ' }'); + if (this.environment.isSimple) { + this.pushSource("else { " + this.appendToBuffer("''") + " }"); + } } }, @@ -692,7 +697,7 @@ JavaScriptCompiler.prototype = { }, pushStackLiteral: function(item) { - return this.push(new Literal(item)); + this.push(new Literal(item)); }, pushSource: function(source) { @@ -706,15 +711,6 @@ JavaScriptCompiler.prototype = { } }, - pushStack: function(item) { - this.flushInline(); - - var stack = this.incrStack(); - this.pushSource(stack + " = " + item + ";"); - this.compileStack.push(stack); - return stack; - }, - replaceStack: function(callback) { var prefix = '', inline = this.isInline(), @@ -736,8 +732,8 @@ JavaScriptCompiler.prototype = { usedLiteral = true; } else { // Get or create the current stack name for use by the inline - createdStack = !this.stackSlot; - var name = !createdStack ? this.topStackName() : this.incrStack(); + createdStack = true; + var name = this.incrStack(); prefix = '(' + this.push(name) + ' = ' + top + ')'; stack = this.topStack(); @@ -764,15 +760,16 @@ JavaScriptCompiler.prototype = { }, flushInline: function() { var inlineStack = this.inlineStack; - if (inlineStack.length) { - this.inlineStack = []; - for (var i = 0, len = inlineStack.length; i < len; i++) { - var entry = inlineStack[i]; - if (entry instanceof Literal) { - this.compileStack.push(entry); - } else { - this.pushStack(entry); - } + this.inlineStack = []; + for (var i = 0, len = inlineStack.length; i < len; i++) { + var entry = inlineStack[i]; + /* istanbul ignore if */ + if (entry instanceof Literal) { + this.compileStack.push(entry); + } else { + var stack = this.incrStack(); + this.pushSource(stack + " = " + entry + ";"); + this.compileStack.push(stack); } } }, @@ -802,6 +799,7 @@ JavaScriptCompiler.prototype = { var stack = (this.isInline() ? this.inlineStack : this.compileStack), item = stack[stack.length - 1]; + /* istanbul ignore if */ if (item instanceof Literal) { return item.value; } else { @@ -852,7 +850,7 @@ JavaScriptCompiler.prototype = { }; }, - setupOptions: function(helper, paramSize, params) { + setupParams: function(helper, paramSize, params, useRegister) { var options = {}, contexts = [], types = [], ids = [], param, inverse, program; options.name = this.quotedString(helper); @@ -872,16 +870,8 @@ JavaScriptCompiler.prototype = { // Avoid setting fn and inverse if neither are set. This allows // helpers to do a check for `if (options.fn)` if (program || inverse) { - if (!program) { - program = 'this.noop'; - } - - if (!inverse) { - inverse = 'this.noop'; - } - - options.fn = program; - options.inverse = inverse; + options.fn = program || 'this.noop'; + options.inverse = inverse || 'this.noop'; } // The parameters go on to the stack in order (making sure that they are evaluated in order) @@ -912,14 +902,7 @@ JavaScriptCompiler.prototype = { options.data = "data"; } - return options; - }, - - // the params and contexts arguments are passed in arrays - // to fill in - setupParams: function(helperName, paramSize, params, useRegister) { - var options = this.objectLiteral(this.setupOptions(helperName, paramSize, params)); - + options = this.objectLiteral(options); if (useRegister) { this.useRegister('options'); params.push('options'); diff --git a/lib/handlebars/compiler/visitor.js b/lib/handlebars/compiler/visitor.js index 6a0373e..a4eb2b4 100644 --- a/lib/handlebars/compiler/visitor.js +++ b/lib/handlebars/compiler/visitor.js @@ -4,8 +4,64 @@ Visitor.prototype = { constructor: Visitor, accept: function(object) { - return this[object.type](object); - } + return object && this[object.type] && this[object.type](object); + }, + + program: function(program) { + var statements = program.statements, + i, l; + + for(i=0, l=statements.length; i<l; i++) { + this.accept(statements[i]); + } + }, + + block: function(block) { + this.accept(block.mustache); + this.accept(block.program); + this.accept(block.inverse); + }, + + mustache: function(mustache) { + this.accept(mustache.sexpr); + }, + + sexpr: function(sexpr) { + var params = sexpr.params, paramStrings = [], hash; + + this.accept(sexpr.id); + for(var i=0, l=params.length; i<l; i++) { + this.accept(params[i]); + } + this.accept(sexpr.hash); + }, + + hash: function(hash) { + var pairs = hash.pairs; + + for(var i=0, l=pairs.length; i<l; i++) { + this.accept(pairs[i][1]); + } + }, + + partial: function(partial) { + this.accept(partial.partialName); + this.accept(partial.context); + this.accept(partial.hash); + }, + PARTIAL_NAME: function(partialName) {}, + + DATA: function(data) { + this.accept(data.id); + }, + + STRING: function(string) {}, + NUMBER: function(number) {}, + BOOLEAN: function(bool) {}, + ID: function(id) {}, + + content: function(content) {}, + comment: function(comment) {} }; export default Visitor; diff --git a/lib/handlebars/safe-string.js b/lib/handlebars/safe-string.js index 2ae49aa..a6b8ecf 100644 --- a/lib/handlebars/safe-string.js +++ b/lib/handlebars/safe-string.js @@ -3,7 +3,7 @@ function SafeString(string) { this.string = string; } -SafeString.prototype.toString = function() { +SafeString.prototype.toString = SafeString.prototype.toHTML = function() { return "" + this.string; }; diff --git a/lib/handlebars/utils.js b/lib/handlebars/utils.js index f38b7af..ce85077 100644 --- a/lib/handlebars/utils.js +++ b/lib/handlebars/utils.js @@ -53,8 +53,8 @@ export var isArray = Array.isArray || function(value) { export function escapeExpression(string) { // don't escape SafeStrings, since they're already safe - if (string instanceof SafeString) { - return string.toString(); + if (string && string.toHTML) { + return string.toHTML(); } else if (string == null) { return ""; } else if (!string) { diff --git a/spec/artifacts/bom.handlebars b/spec/artifacts/bom.handlebars new file mode 100644 index 0000000..548d714 --- /dev/null +++ b/spec/artifacts/bom.handlebars @@ -0,0 +1 @@ +a
\ No newline at end of file diff --git a/spec/ast.js b/spec/ast.js index c28e876..7118c72 100644 --- a/spec/ast.js +++ b/spec/ast.js @@ -137,7 +137,7 @@ describe('ast', function() { describe("CommentNode", function(){ it('stores location info', function(){ - var comment = new handlebarsEnv.AST.CommentNode("HI", LOCATION_INFO); + var comment = new handlebarsEnv.AST.CommentNode("HI", {}, LOCATION_INFO); testLocationInfoStorage(comment); }); }); diff --git a/spec/basic.js b/spec/basic.js index 8a9c116..139d6d0 100644 --- a/spec/basic.js +++ b/spec/basic.js @@ -33,6 +33,9 @@ describe("basic context", function() { shouldCompileTo("{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!", {cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!", "comments are ignored"); + + shouldCompileTo(' {{~! comment ~}} blah', {}, 'blah'); + shouldCompileTo(' {{~!-- long-comment --~}} blah', {}, 'blah'); }); it("boolean", function() { diff --git a/spec/env/common.js b/spec/env/common.js index 92cc611..62edf1d 100644 --- a/spec/env/common.js +++ b/spec/env/common.js @@ -18,7 +18,7 @@ global.compileWithPartials = function(string, hashOrArray, partials) { ary = []; ary.push(hashOrArray[0]); ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] }); - options = {compat: hashOrArray[3]}; + options = typeof hashOrArray[3] === 'object' ? hashOrArray[3] : {compat: hashOrArray[3]}; if (hashOrArray[4] != null) { options.data = !!hashOrArray[4]; ary[1].data = hashOrArray[4]; diff --git a/spec/partials.js b/spec/partials.js index 20187f8..c5b8fdc 100644 --- a/spec/partials.js +++ b/spec/partials.js @@ -41,11 +41,18 @@ describe('partials', function() { it("rendering undefined partial throws an exception", function() { shouldThrow(function() { - var template = CompilerContext.compile("{{> whatever}}"); - template(); + var template = CompilerContext.compile("{{> whatever}}"); + template(); }, Handlebars.Exception, 'The partial whatever could not be found'); }); + it("registering undefined partial throws an exception", function() { + shouldThrow(function() { + var undef; + handlebarsEnv.registerPartial('undefined_test', undef); + }, Handlebars.Exception, 'Attempting to register a partial as undefined'); + }); + it("rendering template partial in vm mode throws an exception", function() { shouldThrow(function() { var template = CompilerContext.compile("{{> whatever}}"); @@ -64,10 +71,10 @@ describe('partials', function() { }); it("GH-14: a partial preceding a selector", function() { - var string = "Dudes: {{>dude}} {{another_dude}}"; - var dude = "{{name}}"; - var hash = {name:"Jeepers", another_dude:"Creepers"}; - shouldCompileToWithPartials(string, [hash, {}, {dude:dude}], true, "Dudes: Jeepers Creepers", "Regular selectors can follow a partial"); + var string = "Dudes: {{>dude}} {{another_dude}}"; + var dude = "{{name}}"; + var hash = {name:"Jeepers", another_dude:"Creepers"}; + shouldCompileToWithPartials(string, [hash, {}, {dude:dude}], true, "Dudes: Jeepers Creepers", "Regular selectors can follow a partial"); }); it("Partials with slash paths", function() { @@ -167,6 +174,14 @@ describe('partials', function() { shouldCompileToWithPartials(string, [hash, {}, {dude: dude, url: url}], true, "Dudes:\n Yehuda\n http://yehuda!\n Alan\n http://alan!\n"); }); + it("prevent nested indented partials", function() { + var string = "Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}"; + var dude = "{{name}}\n {{> url}}"; + var url = "{{url}}!\n"; + var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]}; + shouldCompileToWithPartials(string, [hash, {}, {dude: dude, url: url}, {preventIndent: true}], true, + "Dudes:\n Yehuda\n http://yehuda!\n Alan\n http://alan!\n"); + }); }); describe('compat mode', function() { diff --git a/spec/precompiler.js b/spec/precompiler.js index bd19fbf..9d883b6 100644 --- a/spec/precompiler.js +++ b/spec/precompiler.js @@ -9,27 +9,38 @@ describe('precompiler', function() { var Handlebars = require('../lib'), Precompiler = require('../lib/precompiler'), + fs = require('fs'), uglify = require('uglify-js'); var log, logFunction, precompile, - minify; + minify, + + file, + content, + writeFileSync; beforeEach(function() { precompile = Handlebars.precompile; minify = uglify.minify; + writeFileSync = fs.writeFileSync; logFunction = console.log; log = ''; console.log = function() { log += Array.prototype.join.call(arguments, ''); }; + fs.writeFileSync = function(_file, _content) { + file = _file; + content = _content; + }; }); afterEach(function() { Handlebars.precompile = precompile; uglify.minify = minify; + fs.writeFileSync = writeFileSync; console.log = logFunction; }); @@ -121,6 +132,24 @@ describe('precompiler', function() { equal(log, 'simple\n'); }); + it('should handle different root', function() { + Handlebars.precompile = function() { return 'simple'; }; + Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars', root: 'foo/'}); + equal(log, 'simple\n'); + }); + it('should output to file system', function() { + Handlebars.precompile = function() { return 'simple'; }; + Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars', output: 'file!'}); + equal(file, 'file!'); + equal(content, 'simple\n'); + equal(log, ''); + }); + it('should handle BOM', function() { + Handlebars.precompile = function(template) { return template === 'a' ? 'simple' : 'fail'; }; + Precompiler.cli({templates: [__dirname + '/artifacts/bom.handlebars'], simple: true, extension: 'handlebars', bom: true}); + equal(log, 'simple\n'); + }); + it('should output minimized templates', function() { Handlebars.precompile = function() { return 'amd'; }; uglify.minify = function() { return {code: 'min'}; }; diff --git a/spec/tokenizer.js b/spec/tokenizer.js index 0f0dc0b..6f5cc38 100644 --- a/spec/tokenizer.js +++ b/spec/tokenizer.js @@ -217,19 +217,19 @@ describe('Tokenizer', function() { it('tokenizes a comment as "COMMENT"', function() { var result = tokenize("foo {{! this is a comment }} bar {{ baz }}"); shouldMatchTokens(result, ['CONTENT', 'COMMENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']); - shouldBeToken(result[1], "COMMENT", " this is a comment "); + shouldBeToken(result[1], "COMMENT", "{{! this is a comment }}"); }); it('tokenizes a block comment as "COMMENT"', function() { var result = tokenize("foo {{!-- this is a {{comment}} --}} bar {{ baz }}"); shouldMatchTokens(result, ['CONTENT', 'COMMENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']); - shouldBeToken(result[1], "COMMENT", " this is a {{comment}} "); + shouldBeToken(result[1], "COMMENT", "{{!-- this is a {{comment}} --}}"); }); it('tokenizes a block comment with whitespace as "COMMENT"', function() { var result = tokenize("foo {{!-- this is a\n{{comment}}\n--}} bar {{ baz }}"); shouldMatchTokens(result, ['CONTENT', 'COMMENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']); - shouldBeToken(result[1], "COMMENT", " this is a\n{{comment}}\n"); + shouldBeToken(result[1], "COMMENT", "{{!-- this is a\n{{comment}}\n--}}"); }); it('tokenizes open and closing blocks as OPEN_BLOCK, ID, CLOSE ..., OPEN_ENDBLOCK ID CLOSE', function() { diff --git a/spec/utils.js b/spec/utils.js index 0216c8d..4582e24 100644 --- a/spec/utils.js +++ b/spec/utils.js @@ -25,6 +25,12 @@ describe('utils', function() { var string = new Handlebars.SafeString('foo<&"\'>'); equals(Handlebars.Utils.escapeExpression(string), 'foo<&"\'>'); + var obj = { + toHTML: function() { + return 'foo<&"\'>'; + } + }; + equals(Handlebars.Utils.escapeExpression(obj), 'foo<&"\'>'); }); it('should handle falsy', function() { equals(Handlebars.Utils.escapeExpression(''), ''); diff --git a/spec/visitor.js b/spec/visitor.js new file mode 100644 index 0000000..b64dc56 --- /dev/null +++ b/spec/visitor.js @@ -0,0 +1,48 @@ +/*global Handlebars */ + +describe('Visitor', function() { + if (!Handlebars.Visitor) { + return; + } + + function ast_for(template) { + var ast = Handlebars.parse(template); + return Handlebars.print(ast); + } + + it('should provide coverage', function() { + // Simply run the thing and make sure it does not fail and that all of the + // stub methods are executed + var visitor = new Handlebars.Visitor(); + visitor.accept(Handlebars.parse('{{#foo (bar 1 "1" true) foo=@data}}{{!comment}}{{> bar }} {{/foo}}')); + }); + + it('should traverse to stubs', function() { + var visitor = new Handlebars.Visitor(); + + visitor.PARTIAL_NAME = function(partialName) { + equal(partialName.name, 'bar'); + }; + + visitor.STRING = function(string) { + equal(string.string, '2'); + }; + visitor.NUMBER = function(number) { + equal(number.stringModeValue, 1); + }; + visitor.BOOLEAN = function(bool) { + equal(bool.stringModeValue, true); + }; + visitor.ID = function(id) { + equal(id.original, 'foo.bar'); + }; + visitor.content = function(content) { + equal(content.string, ' '); + }; + visitor.comment = function(comment) { + equal(comment.comment, 'comment'); + }; + + visitor.accept(Handlebars.parse('{{#foo.bar (foo.bar 1 "2" true) foo=@foo.bar}}{{!comment}}{{> bar }} {{/foo.bar}}')); + }); +}); diff --git a/spec/whitespace-control.js b/spec/whitespace-control.js index 8636429..cce9405 100644 --- a/spec/whitespace-control.js +++ b/spec/whitespace-control.js @@ -66,6 +66,9 @@ describe('whitespace control', function() { shouldCompileToWithPartials('foo {{~> dude~}} ', [{}, {}, {dude: 'bar'}], true, 'foobar'); shouldCompileToWithPartials('foo {{> dude~}} ', [{}, {}, {dude: 'bar'}], true, 'foo bar'); shouldCompileToWithPartials('foo {{> dude}} ', [{}, {}, {dude: 'bar'}], true, 'foo bar '); + + shouldCompileToWithPartials('foo\n {{~> dude}} ', [{}, {}, {dude: 'bar'}], true, 'foobar'); + shouldCompileToWithPartials('foo\n {{> dude}} ', [{}, {}, {dude: 'bar'}], true, 'foo\n bar'); }); it('should only strip whitespace once', function() { diff --git a/src/handlebars.l b/src/handlebars.l index f268fd1..f3c925c 100644 --- a/src/handlebars.l +++ b/src/handlebars.l @@ -56,7 +56,10 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD} } <raw>[^\x00]*?/("{{{{/") { return 'CONTENT'; } -<com>[\s\S]*?"--}}" strip(0,4); this.popState(); return 'COMMENT'; +<com>[\s\S]*?"--"{RIGHT_STRIP}?"}}" { + this.popState(); + return 'COMMENT'; +} <mu>"(" return 'OPEN_SEXPR'; <mu>")" return 'CLOSE_SEXPR'; @@ -76,8 +79,15 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD} <mu>"{{"{LEFT_STRIP}?\s*"else" return 'OPEN_INVERSE_CHAIN'; <mu>"{{"{LEFT_STRIP}?"{" return 'OPEN_UNESCAPED'; <mu>"{{"{LEFT_STRIP}?"&" return 'OPEN'; -<mu>"{{!--" this.popState(); this.begin('com'); -<mu>"{{!"[\s\S]*?"}}" strip(3,5); this.popState(); return 'COMMENT'; +<mu>"{{"{LEFT_STRIP}?"!--" { + this.unput(yytext); + this.popState(); + this.begin('com'); +} +<mu>"{{"{LEFT_STRIP}?"!"[\s\S]*?"}}" { + this.popState(); + return 'COMMENT'; +} <mu>"{{"{LEFT_STRIP}? return 'OPEN'; <mu>"=" return 'EQUALS'; diff --git a/src/handlebars.yy b/src/handlebars.yy index 61489b5..3bd9abc 100644 --- a/src/handlebars.yy +++ b/src/handlebars.yy @@ -18,7 +18,7 @@ statement | rawBlock -> $1 | partial -> $1 | CONTENT -> new yy.ContentNode($1, @$) - | COMMENT -> new yy.CommentNode($1, @$) + | COMMENT -> new yy.CommentNode(yy.stripComment($1), yy.stripFlags($1, $1), @$) ; rawBlock |