diff options
Diffstat (limited to 'lib/handlebars/compiler/helpers.js')
-rw-r--r-- | lib/handlebars/compiler/helpers.js | 271 |
1 files changed, 76 insertions, 195 deletions
diff --git a/lib/handlebars/compiler/helpers.js b/lib/handlebars/compiler/helpers.js index 50a3c53..1daddf6 100644 --- a/lib/handlebars/compiler/helpers.js +++ b/lib/handlebars/compiler/helpers.js @@ -1,9 +1,21 @@ import Exception from "../exception"; +export function SourceLocation(source, locInfo) { + this.source = source; + this.start = { + line: locInfo.first_line, + column: locInfo.first_column + }; + this.end = { + line: locInfo.last_line, + column: locInfo.last_column + }; +} + 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) === '~' }; } @@ -12,222 +24,91 @@ export function stripComment(comment) { .replace(/-?-?~?\}\}$/, ''); } -export function prepareRawBlock(openRawBlock, content, close, locInfo) { +export function preparePath(data, parts, locInfo) { /*jshint -W040 */ - if (openRawBlock.sexpr.id.original !== close) { - var errorNode = { - firstLine: openRawBlock.sexpr.firstLine, - firstColumn: openRawBlock.sexpr.firstColumn - }; - - throw new Exception(openRawBlock.sexpr.id.original + " doesn't match " + close, errorNode); + locInfo = this.locInfo(locInfo); + + var original = data ? '@' : '', + dig = [], + depth = 0, + depthString = ''; + + for(var i=0,l=parts.length; i<l; i++) { + var part = parts[i].part; + original += (parts[i].separator || '') + part; + + if (part === '..' || part === '.' || part === 'this') { + if (dig.length > 0) { + throw new Exception('Invalid path: ' + original, {loc: locInfo}); + } else if (part === '..') { + depth++; + depthString += '../'; + } + } else { + dig.push(part); + } } - var program = new this.ProgramNode([content], null, {}, locInfo); - - return new this.BlockNode(openRawBlock.sexpr, program, undefined, undefined, locInfo); + return new this.PathExpression(data, depth, dig, original, locInfo); } -export function prepareBlock(openBlock, program, inverseAndProgram, close, inverted, locInfo) { +export function prepareMustache(sexpr, open, strip, locInfo) { /*jshint -W040 */ - // When we are chaining inverse calls, we will not have a close path - if (close && close.path && openBlock.sexpr.id.original !== close.path.original) { - var errorNode = { - firstLine: openBlock.sexpr.firstLine, - firstColumn: openBlock.sexpr.firstColumn - }; + // Must use charAt to support IE pre-10 + var escapeFlag = open.charAt(3) || open.charAt(2), + escaped = escapeFlag !== '{' && escapeFlag !== '&'; - throw new Exception(openBlock.sexpr.id.original + ' doesn\'t match ' + close.path.original, errorNode); - } + return new this.MustacheStatement(sexpr, escaped, strip, this.locInfo(locInfo)); +} - program.blockParams = openBlock.blockParams; +export function prepareRawBlock(openRawBlock, content, close, locInfo) { + /*jshint -W040 */ + if (openRawBlock.sexpr.path.original !== close) { + var errorNode = {loc: openRawBlock.sexpr.loc}; - // 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: {}}; + throw new Exception(openRawBlock.sexpr.path.original + " doesn't match " + close, errorNode); } - // 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.statements[0].program; + locInfo = this.locInfo(locInfo); + var program = new this.Program([content], null, {}, locInfo); - // Walk the inverse chain to find the last inverse that is actually in the chain. - while (lastInverse.inverse) { - lastInverse = lastInverse.statements[lastInverse.statements.length-1].program; - } - } - - var strip = { - left: openBlock.strip.left, - right: close.strip.right, + return new this.BlockStatement( + openRawBlock.sexpr, program, undefined, + {}, {}, {}, + locInfo); +} - // 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.statements), - closeStandalone: isPrevWhitespace((firstInverse || program).statements) - }; +export function prepareBlock(openBlock, program, inverseAndProgram, close, inverted, locInfo) { + /*jshint -W040 */ + // When we are chaining inverse calls, we will not have a close path + if (close && close.path && openBlock.sexpr.path.original !== close.path.original) { + var errorNode = {loc: openBlock.sexpr.loc}; - if (openBlock.strip.right) { - omitRight(program.statements, null, true); + throw new Exception(openBlock.sexpr.path.original + ' doesn\'t match ' + close.path.original, errorNode); } - if (inverse) { - var inverseStrip = inverseAndProgram.strip; + program.blockParams = openBlock.blockParams; - if (inverseStrip.left) { - omitLeft(program.statements, null, true); - } + var inverse, + inverseStrip; - if (inverseStrip.right) { - omitRight(firstInverse.statements, null, true); - } - if (close.strip.left) { - omitLeft(lastInverse.statements, null, true); + if (inverseAndProgram) { + if (inverseAndProgram.chain) { + inverseAndProgram.program.body[0].closeStrip = close.strip || close.openStrip; } - // Find standalone else statments - if (isPrevWhitespace(program.statements) - && isNextWhitespace(firstInverse.statements)) { - - omitLeft(program.statements); - omitRight(firstInverse.statements); - } - } else { - if (close.strip.left) { - omitLeft(program.statements, null, true); - } + inverseStrip = inverseAndProgram.strip; + inverse = inverseAndProgram.program; } if (inverted) { - return new this.BlockNode(openBlock.sexpr, inverse, program, strip, locInfo); - } else { - return new this.BlockNode(openBlock.sexpr, program, inverse, strip, locInfo); - } -} - - -export function prepareProgram(statements, isRoot) { - for (var i = 0, l = statements.length; i < l; i++) { - var current = statements[i], - strip = current.strip; - - if (!strip) { - continue; - } - - var _isPrevWhitespace = isPrevWhitespace(statements, i, isRoot, current.type === 'partial'), - _isNextWhitespace = isNextWhitespace(statements, i, isRoot), - - openStandalone = strip.openStandalone && _isPrevWhitespace, - closeStandalone = strip.closeStandalone && _isNextWhitespace, - inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace; - - if (strip.right) { - omitRight(statements, i, true); - } - if (strip.left) { - omitLeft(statements, i, true); - } - - if (inlineStandalone) { - omitRight(statements, i); - - if (omitLeft(statements, i)) { - // If we are on a standalone node, save the indent info for partials - if (current.type === 'partial') { - // Pull out the whitespace from the final line - current.indent = (/([ \t]+$)/).exec(statements[i-1].original)[1]; - } - } - } - if (openStandalone) { - omitRight((current.program || current.inverse).statements); - - // Strip out the previous content node if it's whitespace only - omitLeft(statements, i); - } - if (closeStandalone) { - // Always strip the next node - omitRight(statements, i); - - omitLeft((current.inverse || current.program).statements); - } - } - - return statements; -} - -function isPrevWhitespace(statements, i, isRoot) { - 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], - sibling = statements[i-2]; - if (!prev) { - return isRoot; - } - - if (prev.type === 'content') { - return (sibling || !isRoot ? (/\r?\n\s*?$/) : (/(^|\r?\n)\s*?$/)).test(prev.original); - } -} -function isNextWhitespace(statements, i, isRoot) { - if (i === undefined) { - i = -1; - } - - var next = statements[i+1], - sibling = statements[i+2]; - if (!next) { - return isRoot; - } - - if (next.type === 'content') { - 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(statements, i, multiple) { - var current = statements[i == null ? 0 : i + 1]; - if (!current || current.type !== 'content' || (!multiple && current.rightStripped)) { - return; - } - - var original = current.string; - current.string = current.string.replace(multiple ? (/^\s+/) : (/^[ \t]*\r?\n?/), ''); - current.rightStripped = current.string !== 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(statements, i, multiple) { - var current = statements[i == null ? statements.length - 1 : i - 1]; - if (!current || current.type !== 'content' || (!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.string; - current.string = current.string.replace(multiple ? (/\s+$/) : (/[ \t]+$/), ''); - current.leftStripped = current.string !== original; - return current.leftStripped; + return new this.BlockStatement( + openBlock.sexpr, program, inverse, + openBlock.strip, inverseStrip, close && (close.strip || close.openStrip), + this.locInfo(locInfo)); } |