summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKevin Decker <kpdecker@gmail.com>2014-08-13 09:16:08 -0500
committerKevin Decker <kpdecker@gmail.com>2014-08-13 09:16:08 -0500
commit867322adf28a2babfa22c46558d388091b5756e8 (patch)
tree0acee3600b07bc15edc6e6531b756dcc32bfe96d
parent3288f525f3600c7501f521ad0e97b3126c5ed201 (diff)
parentf2a2914d4fb38095105c963086425ba75121ae7b (diff)
downloadhandlebars.js-867322adf28a2babfa22c46558d388091b5756e8.zip
handlebars.js-867322adf28a2babfa22c46558d388091b5756e8.tar.gz
handlebars.js-867322adf28a2babfa22c46558d388091b5756e8.tar.bz2
Merge pull request #787 from wycats/standalone-mustaches
Remove whitespace surrounding standalone statements
-rw-r--r--lib/handlebars/compiler/ast.js153
-rw-r--r--lib/handlebars/compiler/compiler.js6
-rw-r--r--lib/handlebars/compiler/javascript-compiler.js4
-rw-r--r--lib/handlebars/runtime.js21
-rw-r--r--spec/ast.js166
-rw-r--r--spec/blocks.js11
-rw-r--r--spec/parser.js2
-rw-r--r--spec/partials.js20
-rw-r--r--spec/regressions.js24
-rw-r--r--src/handlebars.l4
-rw-r--r--src/handlebars.yy16
11 files changed, 386 insertions, 41 deletions
diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js
index e388e54..ae5ea63 100644
--- a/lib/handlebars/compiler/ast.js
+++ b/lib/handlebars/compiler/ast.js
@@ -9,12 +9,12 @@ function LocationInfo(locInfo){
}
var AST = {
- ProgramNode: function(statements, inverseStrip, inverse, locInfo) {
+ ProgramNode: function(isRoot, statements, inverseStrip, inverse, locInfo) {
var inverseLocationInfo, firstInverseNode;
- if (arguments.length === 3) {
+ if (arguments.length === 4) {
locInfo = inverse;
inverse = null;
- } else if (arguments.length === 2) {
+ } else if (arguments.length === 3) {
locInfo = inverseStrip;
inverseStrip = null;
}
@@ -33,14 +33,17 @@ var AST = {
last_column: firstInverseNode.lastColumn,
first_column: firstInverseNode.firstColumn
};
- this.inverse = new AST.ProgramNode(inverse, inverseStrip, inverseLocationInfo);
+ this.inverse = new AST.ProgramNode(isRoot, inverse, inverseStrip, inverseLocationInfo);
} else {
- this.inverse = new AST.ProgramNode(inverse, inverseStrip);
+ this.inverse = new AST.ProgramNode(isRoot, inverse, inverseStrip);
}
this.strip.right = inverseStrip.left;
} else if (inverseStrip) {
this.strip.left = inverseStrip.right;
}
+
+ // Scan all children to complete the standalone analysis
+ checkStandalone(this, isRoot, statements);
},
MustacheNode: function(rawParams, hash, open, strip, locInfo) {
@@ -104,6 +107,8 @@ var AST = {
this.context = context;
this.hash = hash;
this.strip = strip;
+
+ this.strip.inlineStandalone = true;
},
BlockNode: function(mustache, program, inverse, close, locInfo) {
@@ -118,13 +123,31 @@ var AST = {
this.program = program;
this.inverse = inverse;
+ var firstChild = program || inverse,
+ lastChild = inverse || program;
+
this.strip = {
left: mustache.strip.left,
- right: close.strip.right
+ right: close.strip.right,
+
+ // Determine the standalone candiacy. Basically flag our content as being possibly standalone
+ // so our parent can determine if we actually are standalone
+ openStandalone: isNextWhitespace(firstChild),
+ closeStandalone: isPrevWhitespace(lastChild)
};
- (program || inverse).strip.left = mustache.strip.right;
- (inverse || program).strip.right = close.strip.left;
+ // Calculate stripping for any else statements
+ firstChild.strip.left = mustache.strip.right;
+ lastChild.strip.right = close.strip.left;
+
+ // Find standalone else statments
+ if (program && inverse
+ && isPrevWhitespace(program)
+ && isNextWhitespace(inverse)) {
+
+ omitLeft(program);
+ omitRight(inverse);
+ }
if (inverse && !program) {
this.isInverse = true;
@@ -142,7 +165,7 @@ var AST = {
this.type = 'block';
this.mustache = mustache;
- this.program = new AST.ProgramNode([content], locInfo);
+ this.program = new AST.ProgramNode(false, [content], locInfo);
},
ContentNode: function(string, locInfo) {
@@ -238,9 +261,121 @@ var AST = {
LocationInfo.call(this, locInfo);
this.type = "comment";
this.comment = comment;
+
+ this.strip = {
+ inlineStandalone: true
+ };
}
};
+
+function checkStandalone(program, isRoot, statements) {
+ for (var i = 0, l = statements.length; i < l; i++) {
+ var current = statements[i],
+ strip = current.strip;
+
+ if (!strip) {
+ continue;
+ }
+
+ var _isPrevWhitespace = isPrevWhitespace(program, i, isRoot, current.type === 'partial'),
+ _isNextWhitespace = isNextWhitespace(program, i, isRoot);
+ strip.openStandalone = strip.openStandalone && _isPrevWhitespace;
+ strip.closeStandalone = strip.closeStandalone && _isNextWhitespace;
+ strip.inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace;
+
+ if (strip.inlineStandalone) {
+ omitRight(program, i);
+
+ if (omitLeft(program, i)) {
+ // If we are on a standalone node, save the indent info for partials
+ if (current.type === 'partial') {
+ current.indent = statements[i-1].string;
+ }
+ }
+ }
+ if (strip.openStandalone) {
+ omitRight(current.program || current.inverse);
+
+ // Strip out the previous content node if it's whitespace only
+ omitLeft(program, i);
+ }
+ if (strip.closeStandalone) {
+ // Always strip the next node
+ omitRight(program, i);
+
+ omitLeft(current.inverse || current.program);
+ }
+ }
+}
+function isPrevWhitespace(parent, i, isRoot, disallowIndent) {
+ var statements = parent.statements;
+ if (i === undefined) {
+ i = statements.length;
+ }
+
+ // Nodes that end with newlines are considered whitespace (but are special
+ // cased for strip operations)
+ var prev = statements[i-1];
+ if (prev && /\n$/.test(prev.string)) {
+ return true;
+ }
+
+ return checkWhitespace(isRoot, prev, statements[i-2]);
+}
+function isNextWhitespace(parent, i, isRoot) {
+ var statements = parent.statements;
+ if (i === undefined) {
+ i = -1;
+ }
+
+ return checkWhitespace(isRoot, statements[i+1], statements[i+2]);
+}
+function checkWhitespace(isRoot, next1, next2, disallowIndent) {
+ if (!next1) {
+ return isRoot;
+ } else if (next1.type === 'content') {
+ // Check if the previous node is empty or whitespace only
+ if (disallowIndent ? !next1.string : /^[\s]*$/.test(next1.string)) {
+ if (next2) {
+ return next2.type === 'content' || /\n$/.test(next1.string);
+ } else {
+ return isRoot || (next1.string.indexOf('\n') >= 0);
+ }
+ }
+ }
+}
+
+// Marks the node to the right of the position as omitted.
+// I.e. " "{{foo}} will mark the " " node as omitted.
+//
+// If i is undefined, then the first child will be marked as such.
+function omitRight(program, i) {
+ var first = program.statements[i == null ? 0 : i + 1];
+ if (first) {
+ first.omit = true;
+ }
+}
+
+// Marks the node to the left of the position as omitted.
+// I.e. " "{{foo}} will mark the " " node as omitted.
+//
+// If i is undefined then the last child will be marked as such.
+function omitLeft(program, i) {
+ var statements = program.statements;
+ if (i === undefined) {
+ i = statements.length;
+ }
+
+ var last = statements[i-1],
+ prev = statements[i-2];
+
+ // We omit the last node if it's whitespace only and not preceeded by a non-content node.
+ if (last && /^[\s]*$/.test(last.string) && (!prev || prev.type === 'content')) {
+ return last.omit = true;
+ }
+}
+
// Must be exported as an object rather than the root of the module as the jison lexer
// most modify the object to operate properly.
export default AST;
diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index 3e83a62..131cb98 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -217,12 +217,14 @@ Compiler.prototype = {
this.opcode('push', 'depth0');
}
- this.opcode('invokePartial', partialName.name);
+ this.opcode('invokePartial', partialName.name, partial.indent || '');
this.opcode('append');
},
content: function(content) {
- this.opcode('appendContent', content.string);
+ if (!content.omit) {
+ this.opcode('appendContent', content.string);
+ }
},
mustache: function(mustache) {
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index bc9d3c1..c92cf58 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -596,8 +596,8 @@ JavaScriptCompiler.prototype = {
//
// This operation pops off a context, invokes a partial with that context,
// and pushes the result of the invocation back.
- invokePartial: function(name) {
- var params = [this.nameLookup('partials', name, 'partial'), "'" + name + "'", this.popStack(), this.popStack(), "helpers", "partials"];
+ invokePartial: function(name, indent) {
+ var params = [this.nameLookup('partials', name, 'partial'), "'" + indent + "'", "'" + name + "'", this.popStack(), this.popStack(), "helpers", "partials"];
if (this.options.data) {
params.push("data");
diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js
index b9fc77d..9b61afe 100644
--- a/lib/handlebars/runtime.js
+++ b/lib/handlebars/runtime.js
@@ -31,18 +31,31 @@ export function template(templateSpec, env) {
// for external users to override these as psuedo-supported APIs.
env.VM.checkRevision(templateSpec.compiler);
- var invokePartialWrapper = function(partial, name, context, hash, helpers, partials, data) {
+ var invokePartialWrapper = function(partial, indent, name, context, hash, helpers, partials, data) {
if (hash) {
context = Utils.extend({}, context, hash);
}
var result = env.VM.invokePartial.call(this, partial, name, context, helpers, partials, data);
- if (result != null) { return result; }
- if (env.compile) {
+ if (result == null && env.compile) {
var options = { helpers: helpers, partials: partials, data: data };
partials[name] = env.compile(partial, { data: data !== undefined }, env);
- return partials[name](context, options);
+ result = partials[name](context, options);
+ }
+ if (result != null) {
+ if (indent) {
+ var lines = result.split('\n');
+ for (var i = 0, l = lines.length; i < l; i++) {
+ if (!lines[i] && i + 1 === l) {
+ break;
+ }
+
+ lines[i] = indent + lines[i];
+ }
+ result = lines.join('\n');
+ }
+ return result;
} else {
throw new Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
}
diff --git a/spec/ast.js b/spec/ast.js
index 17a982c..316d4a3 100644
--- a/spec/ast.js
+++ b/spec/ast.js
@@ -86,7 +86,7 @@ describe('ast', function() {
var sexprNode = new handlebarsEnv.AST.SexprNode([{ original: 'foo'}], null);
var mustacheNode = new handlebarsEnv.AST.MustacheNode(sexprNode, null, '{{', {});
var block = new handlebarsEnv.AST.BlockNode(mustacheNode,
- {strip: {}}, {strip: {}},
+ {statements: [], strip: {}}, {statements: [], strip: {}},
{
strip: {},
path: {original: 'foo'}
@@ -201,12 +201,12 @@ describe('ast', function() {
describe("storing location info", function(){
it("stores when `inverse` argument isn't passed", function(){
- var pn = new handlebarsEnv.AST.ProgramNode([], LOCATION_INFO);
+ var pn = new handlebarsEnv.AST.ProgramNode(false, [], LOCATION_INFO);
testLocationInfoStorage(pn);
});
it("stores when `inverse` or `stripInverse` arguments passed", function(){
- var pn = new handlebarsEnv.AST.ProgramNode([], {strip: {}}, undefined, LOCATION_INFO);
+ var pn = new handlebarsEnv.AST.ProgramNode(false, [], {strip: {}}, undefined, LOCATION_INFO);
testLocationInfoStorage(pn);
var clone = {
@@ -216,7 +216,7 @@ describe('ast', function() {
firstColumn: 0,
lastColumn: 0
};
- pn = new handlebarsEnv.AST.ProgramNode([], {strip: {}}, [ clone ], LOCATION_INFO);
+ pn = new handlebarsEnv.AST.ProgramNode(false, [], {strip: {}}, [ clone ], LOCATION_INFO);
testLocationInfoStorage(pn);
// Assert that the newly created ProgramNode has the same location
@@ -251,26 +251,174 @@ describe('ast', function() {
});
it('gets line numbers correct when newlines appear', function(){
- var secondContentNode = statements[2];
- testColumns(secondContentNode, 1, 2, 21, 8);
+ testColumns(statements[2], 1, 2, 21, 0);
+ testColumns(statements[3], 2, 2, 0, 8);
});
it('gets MustacheNode line numbers correct across newlines', function(){
- var secondMustacheNode = statements[3];
+ var secondMustacheNode = statements[4];
testColumns(secondMustacheNode, 2, 2, 8, 22);
});
it('gets the block helper information correct', function(){
- var blockHelperNode = statements[5];
+ var blockHelperNode = statements[7];
testColumns(blockHelperNode, 3, 7, 8, 23);
});
it('correctly records the line numbers of an inverse of a block helper', function(){
- var blockHelperNode = statements[5],
+ var blockHelperNode = statements[7],
inverse = blockHelperNode.inverse;
testColumns(inverse, 5, 6, 13, 0);
});
});
+
+ describe('standalone flags', function(){
+ describe('mustache', function() {
+ it('does not mark mustaches as standalone', function() {
+ var ast = Handlebars.parse(' {{comment}} ');
+ equals(ast.statements[0].omit, undefined);
+ equals(ast.statements[2].omit, undefined);
+ });
+ });
+ describe('blocks', function() {
+ it('marks block mustaches as standalone', function() {
+ var ast = Handlebars.parse(' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
+ block = ast.statements[1];
+
+ equals(ast.statements[0].omit, true);
+
+ equals(block.program.statements[0].omit, true);
+ equals(block.program.statements[1].string, 'foo\n');
+ equals(block.program.statements[2].omit, true);
+
+ equals(block.inverse.statements[0].omit, true);
+ equals(block.inverse.statements[1].string, ' bar \n');
+ equals(block.inverse.statements[2].omit, true);
+
+ equals(ast.statements[2].omit, true);
+ });
+ it('marks initial block mustaches as standalone', function() {
+ var ast = Handlebars.parse('{{# comment}} \nfoo\n {{/comment}}'),
+ block = ast.statements[0];
+
+ equals(block.program.statements[0].omit, true);
+ equals(block.program.statements[1].string, 'foo\n');
+ equals(block.program.statements[2].omit, true);
+ });
+ it('marks mustaches with children as standalone', function() {
+ var ast = Handlebars.parse('{{# comment}} \n{{foo}}\n {{/comment}}'),
+ block = ast.statements[0];
+
+ equals(block.program.statements[0].omit, true);
+ equals(block.program.statements[1].id.original, 'foo');
+ equals(block.program.statements[2].omit, undefined);
+ equals(block.program.statements[3].omit, true);
+ });
+ it('marks nested block mustaches as standalone', function() {
+ var ast = Handlebars.parse('{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'),
+ statements = ast.statements[0].program.statements,
+ block = statements[1];
+
+ equals(statements[0].omit, true);
+
+ equals(block.program.statements[0].omit, true);
+ equals(block.program.statements[1].string, 'foo\n');
+ equals(block.program.statements[2].omit, true);
+
+ equals(block.inverse.statements[0].omit, true);
+ equals(block.inverse.statements[1].string, ' bar \n');
+ equals(block.inverse.statements[2].omit, true);
+
+ equals(statements[0].omit, true);
+ });
+ it('does not mark nested block mustaches as standalone', function() {
+ var ast = Handlebars.parse('{{#foo}} {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} {{/foo}}'),
+ statements = ast.statements[0].program.statements,
+ block = statements[1];
+
+ equals(statements[0].omit, undefined);
+
+ equals(block.program.statements[0].omit, undefined);
+ equals(block.program.statements[1].string, 'foo\n');
+ equals(block.program.statements[2].omit, true);
+
+ equals(block.inverse.statements[0].omit, true);
+ equals(block.inverse.statements[1].string, ' bar \n');
+ equals(block.inverse.statements[2].omit, undefined);
+
+ equals(statements[0].omit, undefined);
+ });
+ it('does not mark nested initial block mustaches as standalone', function() {
+ var ast = Handlebars.parse('{{#foo}}{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}}{{/foo}}'),
+ statements = ast.statements[0].program.statements,
+ block = statements[0];
+
+ equals(block.program.statements[0].omit, undefined);
+ equals(block.program.statements[1].string, 'foo\n');
+ equals(block.program.statements[2].omit, true);
+
+ equals(block.inverse.statements[0].omit, true);
+ equals(block.inverse.statements[1].string, ' bar \n');
+ equals(block.inverse.statements[2].omit, undefined);
+
+ equals(statements[0].omit, undefined);
+ });
+
+ it('marks column 0 block mustaches as standalone', function() {
+ var ast = Handlebars.parse('test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
+ block = ast.statements[1];
+
+ equals(ast.statements[0].omit, undefined);
+
+ equals(block.program.statements[0].omit, true);
+ equals(block.program.statements[1].string, 'foo\n');
+ equals(block.program.statements[2].omit, true);
+
+ equals(block.inverse.statements[0].omit, true);
+ equals(block.inverse.statements[1].string, ' bar \n');
+ equals(block.inverse.statements[2].omit, true);
+
+ equals(ast.statements[2].omit, true);
+ });
+ });
+ describe('partials', function() {
+ it('marks partial as standalone', function() {
+ var ast = Handlebars.parse('{{> partial }} ');
+ equals(ast.statements[1].omit, true);
+ });
+ it('marks indented partial as standalone', function() {
+ var ast = Handlebars.parse(' {{> partial }} ');
+ equals(ast.statements[0].omit, true);
+ equals(ast.statements[1].indent, ' ');
+ equals(ast.statements[2].omit, true);
+ });
+ it('marks those around content as not standalone', function() {
+ var ast = Handlebars.parse('a{{> partial }}');
+ equals(ast.statements[0].omit, undefined);
+
+ ast = Handlebars.parse('{{> partial }}a');
+ equals(ast.statements[1].omit, undefined);
+ });
+ });
+ describe('comments', function() {
+ it('marks comment as standalone', function() {
+ var ast = Handlebars.parse('{{! comment }} ');
+ equals(ast.statements[1].omit, true);
+ });
+ it('marks indented comment as standalone', function() {
+ var ast = Handlebars.parse(' {{! comment }} ');
+ equals(ast.statements[0].omit, true);
+ equals(ast.statements[2].omit, true);
+ });
+ it('marks those around content as not standalone', function() {
+ var ast = Handlebars.parse('a{{! comment }}');
+ equals(ast.statements[0].omit, undefined);
+
+ ast = Handlebars.parse('{{! comment }}a');
+ equals(ast.statements[1].omit, undefined);
+ });
+ });
+ });
});
diff --git a/spec/blocks.js b/spec/blocks.js
index 718d316..c57a67e 100644
--- a/spec/blocks.js
+++ b/spec/blocks.js
@@ -83,4 +83,15 @@ describe('blocks', function() {
"No people");
});
});
+
+ describe('standalone sections', function() {
+ it('block standalone else sections', function() {
+ shouldCompileTo('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
+ 'No people\n');
+ shouldCompileTo('{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n', {none: 'No people'},
+ 'No people\n');
+ shouldCompileTo('\n{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
+ 'No people\n');
+ });
+ });
});
diff --git a/spec/parser.js b/spec/parser.js
index ebde171..ff12cc4 100644
--- a/spec/parser.js
+++ b/spec/parser.js
@@ -184,7 +184,7 @@ describe('parser', function() {
describe('externally compiled AST', function() {
it('can pass through an already-compiled AST', function() {
- equals(ast_for(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")])), "CONTENT[ \'Hello\' ]\n");
+ equals(ast_for(new Handlebars.AST.ProgramNode(false, [ new Handlebars.AST.ContentNode("Hello")])), "CONTENT[ \'Hello\' ]\n");
});
});
});
diff --git a/spec/partials.js b/spec/partials.js
index 732436a..816fd1e 100644
--- a/spec/partials.js
+++ b/spec/partials.js
@@ -1,4 +1,4 @@
-/*global CompilerContext, shouldCompileTo, shouldCompileToWithPartials */
+/*global CompilerContext, Handlebars, handlebarsEnv, shouldCompileTo, shouldCompileToWithPartials, shouldThrow */
describe('partials', function() {
it("basic partials", function() {
var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}";
@@ -137,4 +137,22 @@ describe('partials', function() {
var partial = "";
var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: "); });
+
+ describe('standalone partials', function() {
+ it("indented partials", function() {
+ var string = "Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}";
+ var dude = "{{name}}\n";
+ var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
+ shouldCompileToWithPartials(string, [hash, {}, {dude: dude}], true,
+ "Dudes:\n Yehuda\n Alan\n");
+ });
+ it("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}], true,
+ "Dudes:\n Yehuda\n http://yehuda!\n Alan\n http://alan!\n");
+ });
+ });
});
diff --git a/spec/regressions.js b/spec/regressions.js
index c633a21..dd4eedd 100644
--- a/spec/regressions.js
+++ b/spec/regressions.js
@@ -24,7 +24,19 @@ describe('Regressions', function() {
});
it("bug reported by @fat where lambdas weren't being properly resolved", function() {
- var string = "<strong>This is a slightly more complicated {{thing}}.</strong>.\n{{! Just ignore this business. }}\nCheck this out:\n{{#hasThings}}\n<ul>\n{{#things}}\n<li class={{className}}>{{word}}</li>\n{{/things}}</ul>.\n{{/hasThings}}\n{{^hasThings}}\n\n<small>Nothing to check out...</small>\n{{/hasThings}}";
+ var string = '<strong>This is a slightly more complicated {{thing}}.</strong>.\n'
+ + '{{! Just ignore this business. }}\n'
+ + 'Check this out:\n'
+ + '{{#hasThings}}\n'
+ + '<ul>\n'
+ + '{{#things}}\n'
+ + '<li class={{className}}>{{word}}</li>\n'
+ + '{{/things}}</ul>.\n'
+ + '{{/hasThings}}\n'
+ + '{{^hasThings}}\n'
+ + '\n'
+ + '<small>Nothing to check out...</small>\n'
+ + '{{/hasThings}}';
var data = {
thing: function() {
return "blah";
@@ -39,7 +51,13 @@ describe('Regressions', function() {
}
};
- var output = "<strong>This is a slightly more complicated blah.</strong>.\n\nCheck this out:\n\n<ul>\n\n<li class=one>@fat</li>\n\n<li class=two>@dhg</li>\n\n<li class=three>@sayrer</li>\n</ul>.\n\n";
+ var output = '<strong>This is a slightly more complicated blah.</strong>.\n'
+ + 'Check this out:\n'
+ + '<ul>\n'
+ + '<li class=one>@fat</li>\n'
+ + '<li class=two>@dhg</li>\n'
+ + '<li class=three>@sayrer</li>\n'
+ + '</ul>.\n';
shouldCompileTo(string, data, output);
});
@@ -128,7 +146,7 @@ describe('Regressions', function() {
if (Handlebars.AST) {
it("can pass through an already-compiled AST via compile/precompile", function() {
- equal(Handlebars.compile(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")]))(), 'Hello');
+ equal(Handlebars.compile(new Handlebars.AST.ProgramNode(true, [ new Handlebars.AST.ContentNode("Hello")]))(), 'Hello');
});
it("can pass through an empty string", function() {
diff --git a/src/handlebars.l b/src/handlebars.l
index cafdd72..0a531d5 100644
--- a/src/handlebars.l
+++ b/src/handlebars.l
@@ -28,7 +28,7 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
%%
-[^\x00]*?/("{{") {
+[^\x00\n]*?\n?/("{{") {
if(yytext.slice(-2) === "\\\\") {
strip(0,1);
this.begin("mu");
@@ -41,7 +41,7 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
if(yytext) return 'CONTENT';
}
-[^\x00]+ return 'CONTENT';
+([^\x00\n]+\n?|\n) return 'CONTENT';
// marks CONTENT up to the next mustache or escaped mustache
<emu>[^\x00]{2,}?/("{{"|"\\{{"|"\\\\{{"|<<EOF>>) {
diff --git a/src/handlebars.yy b/src/handlebars.yy
index 51796ec..fa69f73 100644
--- a/src/handlebars.yy
+++ b/src/handlebars.yy
@@ -16,17 +16,17 @@ function stripFlags(open, close) {
%%
root
- : statements EOF { return new yy.ProgramNode($1, @$); }
- | EOF { return new yy.ProgramNode([], @$); }
+ : statements EOF { return new yy.ProgramNode(true, $1, @$); }
+ | EOF { return new yy.ProgramNode(true, [], @$); }
;
program
- : simpleInverse statements -> new yy.ProgramNode([], $1, $2, @$)
- | statements simpleInverse statements -> new yy.ProgramNode($1, $2, $3, @$)
- | statements simpleInverse -> new yy.ProgramNode($1, $2, [], @$)
- | statements -> new yy.ProgramNode($1, @$)
- | simpleInverse -> new yy.ProgramNode([], @$)
- | "" -> new yy.ProgramNode([], @$)
+ : simpleInverse statements -> new yy.ProgramNode(false, [], $1, $2, @$)
+ | statements simpleInverse statements -> new yy.ProgramNode(false, $1, $2, $3, @$)
+ | statements simpleInverse -> new yy.ProgramNode(false, $1, $2, [], @$)
+ | statements -> new yy.ProgramNode(false, $1, @$)
+ | simpleInverse -> new yy.ProgramNode(false, [], @$)
+ | "" -> new yy.ProgramNode(false, [], @$)
;
statements