diff options
author | kpdecker <kpdecker@gmail.com> | 2014-11-28 22:58:21 -0600 |
---|---|---|
committer | kpdecker <kpdecker@gmail.com> | 2014-11-28 23:13:06 -0600 |
commit | 928ba56b9577fd6cd874f0a83178f1265a6d0526 (patch) | |
tree | 1c522b0869f663e076e38cf741eac7370c798f90 /lib/handlebars/compiler/helpers.js | |
parent | 8a6796e5c09686b47945a35826d77680d589d07c (diff) | |
download | handlebars.js-928ba56b9577fd6cd874f0a83178f1265a6d0526.zip handlebars.js-928ba56b9577fd6cd874f0a83178f1265a6d0526.tar.gz handlebars.js-928ba56b9577fd6cd874f0a83178f1265a6d0526.tar.bz2 |
Rework strip flags to make clearer at in AST level
Rather than keeping state in the AST, which requires some gymnastics, we create a separate visitor flow which does the top down iteration necessary to calculate all of the state needed for proper whitespace control evaluation.
Diffstat (limited to 'lib/handlebars/compiler/helpers.js')
-rw-r--r-- | lib/handlebars/compiler/helpers.js | 204 |
1 files changed, 21 insertions, 183 deletions
diff --git a/lib/handlebars/compiler/helpers.js b/lib/handlebars/compiler/helpers.js index f215049..1daddf6 100644 --- a/lib/handlebars/compiler/helpers.js +++ b/lib/handlebars/compiler/helpers.js @@ -14,8 +14,8 @@ export function SourceLocation(source, locInfo) { export function stripFlags(open, close) { return { - left: open.charAt(2) === '~', - right: close.charAt(close.length-3) === '~' + open: open.charAt(2) === '~', + close: close.charAt(close.length-3) === '~' }; } @@ -69,9 +69,13 @@ export function prepareRawBlock(openRawBlock, content, close, locInfo) { throw new Exception(openRawBlock.sexpr.path.original + " doesn't match " + close, errorNode); } + locInfo = this.locInfo(locInfo); var program = new this.Program([content], null, {}, locInfo); - return new this.BlockStatement(openRawBlock.sexpr, program, undefined, undefined, locInfo); + return new this.BlockStatement( + openRawBlock.sexpr, program, undefined, + {}, {}, {}, + locInfo); } export function prepareBlock(openBlock, program, inverseAndProgram, close, inverted, locInfo) { @@ -85,192 +89,26 @@ export function prepareBlock(openBlock, program, inverseAndProgram, close, inver program.blockParams = openBlock.blockParams; - // Safely handle a chained inverse that does not have a non-conditional inverse - // (i.e. both inverseAndProgram AND close are undefined) - if (!close) { - close = {strip: {}}; - } - - // Find the inverse program that is involed with whitespace stripping. - var inverse = inverseAndProgram && inverseAndProgram.program, - firstInverse = inverse, - lastInverse = inverse; - if (inverse && inverse.inverse) { - firstInverse = inverse.body[0].program; + var inverse, + inverseStrip; - // Walk the inverse chain to find the last inverse that is actually in the chain. - while (lastInverse.inverse) { - lastInverse = lastInverse.body[lastInverse.body.length-1].program; + if (inverseAndProgram) { + if (inverseAndProgram.chain) { + inverseAndProgram.program.body[0].closeStrip = close.strip || close.openStrip; } - } - var strip = { - left: openBlock.strip.left, - 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(program.body), - closeStandalone: isPrevWhitespace((firstInverse || program).body) - }; - - if (openBlock.strip.right) { - omitRight(program.body, null, true); - } - - if (inverse) { - var inverseStrip = inverseAndProgram.strip; - - if (inverseStrip.left) { - omitLeft(program.body, null, true); - } - - if (inverseStrip.right) { - omitRight(firstInverse.body, null, true); - } - if (close.strip.left) { - omitLeft(lastInverse.body, null, true); - } - - // Find standalone else statments - if (isPrevWhitespace(program.body) - && isNextWhitespace(firstInverse.body)) { - - omitLeft(program.body); - omitRight(firstInverse.body); - } - } else { - if (close.strip.left) { - omitLeft(program.body, null, true); - } + inverseStrip = inverseAndProgram.strip; + inverse = inverseAndProgram.program; } if (inverted) { - return new this.BlockStatement(openBlock.sexpr, inverse, program, strip, locInfo); - } else { - return new this.BlockStatement(openBlock.sexpr, program, inverse, strip, locInfo); - } -} - - -export function prepareProgram(body, isRoot) { - for (var i = 0, l = body.length; i < l; i++) { - var current = body[i], - strip = current.strip; - - if (!strip) { - continue; - } - - var _isPrevWhitespace = isPrevWhitespace(body, i, isRoot, current.type === 'partial'), - _isNextWhitespace = isNextWhitespace(body, i, isRoot), - - openStandalone = strip.openStandalone && _isPrevWhitespace, - closeStandalone = strip.closeStandalone && _isNextWhitespace, - inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace; - - if (strip.right) { - omitRight(body, i, true); - } - if (strip.left) { - omitLeft(body, i, true); - } - - if (inlineStandalone) { - omitRight(body, i); - - if (omitLeft(body, i)) { - // If we are on a standalone node, save the indent info for partials - if (current.type === 'PartialStatement') { - // Pull out the whitespace from the final line - current.indent = (/([ \t]+$)/).exec(body[i-1].original)[1]; - } - } - } - if (openStandalone) { - omitRight((current.program || current.inverse).body); - - // Strip out the previous content node if it's whitespace only - omitLeft(body, i); - } - if (closeStandalone) { - // Always strip the next node - omitRight(body, i); - - omitLeft((current.inverse || current.program).body); - } - } - - return body; -} - -function isPrevWhitespace(body, i, isRoot) { - if (i === undefined) { - i = body.length; - } - - // Nodes that end with newlines are considered whitespace (but are special - // cased for strip operations) - var prev = body[i-1], - sibling = body[i-2]; - if (!prev) { - return isRoot; - } - - if (prev.type === 'ContentStatement') { - return (sibling || !isRoot ? (/\r?\n\s*?$/) : (/(^|\r?\n)\s*?$/)).test(prev.original); - } -} -function isNextWhitespace(body, i, isRoot) { - if (i === undefined) { - i = -1; - } - - var next = body[i+1], - sibling = body[i+2]; - if (!next) { - return isRoot; - } - - if (next.type === 'ContentStatement') { - return (sibling || !isRoot ? (/^\s*?\r?\n/) : (/^\s*?(\r?\n|$)/)).test(next.original); - } -} - -// 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. -// -// If mulitple is truthy then all whitespace will be stripped out until non-whitespace -// content is met. -function omitRight(body, i, multiple) { - var current = body[i == null ? 0 : i + 1]; - if (!current || current.type !== 'ContentStatement' || (!multiple && current.rightStripped)) { - return; - } - - var original = current.value; - current.value = current.value.replace(multiple ? (/^\s+/) : (/^[ \t]*\r?\n?/), ''); - current.rightStripped = current.value !== original; -} - -// 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. -// -// If mulitple is truthy then all whitespace will be stripped out until non-whitespace -// content is met. -function omitLeft(body, i, multiple) { - var current = body[i == null ? body.length - 1 : i - 1]; - if (!current || current.type !== 'ContentStatement' || (!multiple && current.leftStripped)) { - return; + inverted = inverse; + inverse = program; + program = inverted; } - // We omit the last node if it's whitespace only and not preceeded by a non-content node. - var original = current.value; - current.value = current.value.replace(multiple ? (/\s+$/) : (/[ \t]+$/), ''); - current.leftStripped = current.value !== original; - return current.leftStripped; + return new this.BlockStatement( + openBlock.sexpr, program, inverse, + openBlock.strip, inverseStrip, close && (close.strip || close.openStrip), + this.locInfo(locInfo)); } |