From 84d646bb5d5a8bb01bfb9465ee1078161f069742 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 23 Aug 2014 17:44:20 -0500 Subject: [PATCH] Move strip processing into AST helper logic We already have to track these behaviors for the standalone parsing and rather than having two whitespace pruning implementations this moves all of the behavior into one place. Fixes #852 --- lib/handlebars/compiler/compiler.js | 14 +--- lib/handlebars/compiler/helpers.js | 84 +++++++++++++++---- .../compiler/javascript-compiler.js | 22 ----- spec/whitespace-control.js | 14 ++++ 4 files changed, 81 insertions(+), 53 deletions(-) diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index c7fdf3d3e..29718d08e 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -69,19 +69,7 @@ Compiler.prototype = { }, accept: function(node) { - var strip = node.strip || {}, - ret; - if (strip.left) { - this.opcode('strip'); - } - - ret = this[node.type](node); - - if (strip.right) { - this.opcode('strip'); - } - - return ret; + return this[node.type](node); }, program: function(program) { diff --git a/lib/handlebars/compiler/helpers.js b/lib/handlebars/compiler/helpers.js index 8ba7b407b..c93b81147 100644 --- a/lib/handlebars/compiler/helpers.js +++ b/lib/handlebars/compiler/helpers.js @@ -26,13 +26,22 @@ export function prepareBlock(mustache, program, inverseAndProgram, close, invert closeStandalone: isPrevWhitespace((inverse || program).statements) }; + if (mustache.strip.right) { + omitRight(program.statements, null, true); + } + if (inverse) { var inverseStrip = inverseAndProgram.strip; - program.strip.left = mustache.strip.right; - program.strip.right = inverseStrip.left; - inverse.strip.left = inverseStrip.right; - inverse.strip.right = close.strip.left; + if (inverseStrip.left) { + omitLeft(program.statements, null, true); + } + if (inverseStrip.right) { + omitRight(inverse.statements, null, true); + } + if (close.strip.left) { + omitLeft(inverse.statements, null, true); + } // Find standalone else statments if (isPrevWhitespace(program.statements) @@ -42,8 +51,9 @@ export function prepareBlock(mustache, program, inverseAndProgram, close, invert omitRight(inverse.statements); } } else { - program.strip.left = mustache.strip.right; - program.strip.right = close.strip.left; + if (close.strip.left) { + omitLeft(program.statements, null, true); + } } if (inverted) { @@ -70,6 +80,13 @@ export function prepareProgram(statements, isRoot) { 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); @@ -137,10 +154,25 @@ function checkWhitespace(isRoot, next1, next2, disallowIndent) { // I.e. {{foo}}' ' will mark the ' ' node as omitted. // // If i is undefined, then the first child will be marked as such. -function omitRight(statements, i) { - var first = statements[i == null ? 0 : i + 1]; - if (first) { - first.string = ''; +// +// If mulitple is truthy then all whitespace will be stripped out until non-whitespace +// content is met. +function omitRight(statements, i, multiple) { + i = i == null ? 0 : i + 1; + + var current = statements[i]; + while (current) { + if (current.type !== 'content') { + return; + } + + current.string = current.string.replace(/^[\s]+/, ''); + + if (multiple && !current.string) { + current = statements[++i]; + } else { + return; + } } } @@ -148,17 +180,33 @@ function omitRight(statements, i) { // I.e. ' '{{foo}} will mark the ' ' node as omitted. // // If i is undefined then the last child will be marked as such. -function omitLeft(statements, i) { - if (i === undefined) { +// +// If mulitple is truthy then all whitespace will be stripped out until non-whitespace +// content is met. +function omitLeft(statements, i, multiple) { + if (i == null) { i = statements.length; } - var last = statements[i-1], - prev = statements[i-2]; + var current = statements[--i], + prev = statements[i-1]; - // 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')) { - last.string = ''; - return true; + while (current) { + if (current.type !== 'content') { + return; + } + + // We omit the last node if it's whitespace only and not preceeded by a non-content node. + if (multiple || (/^[\s]*$/.test(current.string) && (!prev || prev.type === 'content'))) { + current.string = current.string.replace(/[\s]+$/, ''); + + if (multiple && !current.string) { + current = statements[--i]; + } else { + return true; + } + } else { + return; + } } } diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index 25d45d192..fcd58fdcc 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -85,11 +85,6 @@ JavaScriptCompiler.prototype = { opcode = opcodes[i]; this[opcode.opcode].apply(this, opcode.args); - - // Reset the stripNext flag if it was not set by this operation. - if (opcode.opcode !== this.stripNext) { - this.stripNext = false; - } } // Flush any trailing content that might be pending. @@ -280,27 +275,10 @@ JavaScriptCompiler.prototype = { if (this.pendingContent) { content = this.pendingContent + content; } - if (this.stripNext) { - content = content.replace(/^\s+/, ''); - } this.pendingContent = content; }, - // [strip] - // - // On stack, before: ... - // On stack, after: ... - // - // Removes any trailing whitespace from the prior content node and flags - // the next operation for stripping if it is a content node. - strip: function() { - if (this.pendingContent) { - this.pendingContent = this.pendingContent.replace(/\s+$/, ''); - } - this.stripNext = 'strip'; - }, - // [append] // // On stack, before: value, ... diff --git a/spec/whitespace-control.js b/spec/whitespace-control.js index 2088ed8b7..86364292e 100644 --- a/spec/whitespace-control.js +++ b/spec/whitespace-control.js @@ -1,3 +1,5 @@ +/*global shouldCompileTo, shouldCompileToWithPartials */ + describe('whitespace control', function() { it('should strip whitespace around mustache calls', function() { var hash = {foo: 'bar<'}; @@ -8,6 +10,8 @@ describe('whitespace control', function() { shouldCompileTo(' {{~&foo~}} ', hash, 'bar<'); shouldCompileTo(' {{~{foo}~}} ', hash, 'bar<'); + + shouldCompileTo('1\n{{foo~}} \n\n 23\n{{bar}}4', {}, '1\n23\n4'); }); describe('blocks', function() { @@ -18,6 +22,9 @@ describe('whitespace control', function() { shouldCompileTo(' {{#if foo~}} bar {{/if~}} ', hash, ' bar '); shouldCompileTo(' {{~#if foo}} bar {{~/if}} ', hash, ' bar '); shouldCompileTo(' {{#if foo}} bar {{/if}} ', hash, ' bar '); + + shouldCompileTo(' \n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\n ', hash, 'bar'); + shouldCompileTo(' a\n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\na ', hash, ' abara '); }); it('should strip whitespace around inverse block calls', function() { var hash = {}; @@ -26,6 +33,8 @@ describe('whitespace control', function() { shouldCompileTo(' {{^if foo~}} bar {{/if~}} ', hash, ' bar '); shouldCompileTo(' {{~^if foo}} bar {{~/if}} ', hash, ' bar '); shouldCompileTo(' {{^if foo}} bar {{/if}} ', hash, ' bar '); + + shouldCompileTo(' \n\n{{~^if foo~}} \n\nbar \n\n{{~/if~}}\n\n ', hash, 'bar'); }); it('should strip whitespace around complex block calls', function() { var hash = {foo: 'bar<'}; @@ -37,6 +46,9 @@ describe('whitespace control', function() { shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, 'bar'); + shouldCompileTo('\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n', hash, 'bar'); + shouldCompileTo('\n\n{{~#if foo~}} \n\n{{{foo}}} \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n', hash, 'bar<'); + hash = {}; shouldCompileTo('{{#if foo~}} bar {{~^~}} baz {{~/if}}', hash, 'baz'); @@ -45,6 +57,8 @@ describe('whitespace control', function() { shouldCompileTo('{{#if foo~}} bar {{~^}} baz {{/if}}', hash, ' baz '); shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, 'baz'); + + shouldCompileTo('\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n', hash, 'baz'); }); });