diff options
Diffstat (limited to 'lib/handlebars/compiler')
-rw-r--r-- | lib/handlebars/compiler/ast.js | 153 | ||||
-rw-r--r-- | lib/handlebars/compiler/compiler.js | 6 | ||||
-rw-r--r-- | lib/handlebars/compiler/javascript-compiler.js | 4 |
3 files changed, 150 insertions, 13 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"); |