Skip to content

Commit

Permalink
Move strip processing into AST helper logic
Browse files Browse the repository at this point in the history
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
  • Loading branch information
kpdecker committed Aug 23, 2014
1 parent ccd803f commit 84d646b
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 53 deletions.
14 changes: 1 addition & 13 deletions lib/handlebars/compiler/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
84 changes: 66 additions & 18 deletions lib/handlebars/compiler/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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) {
Expand All @@ -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);

Expand Down Expand Up @@ -137,28 +154,59 @@ 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;
}
}
}

// 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(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;
}
}
}
22 changes: 0 additions & 22 deletions lib/handlebars/compiler/javascript-compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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, ...
Expand Down
14 changes: 14 additions & 0 deletions spec/whitespace-control.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/*global shouldCompileTo, shouldCompileToWithPartials */

describe('whitespace control', function() {
it('should strip whitespace around mustache calls', function() {
var hash = {foo: 'bar<'};
Expand All @@ -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() {
Expand All @@ -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 = {};
Expand All @@ -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<'};
Expand All @@ -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');
Expand All @@ -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');
});
});

Expand Down

0 comments on commit 84d646b

Please sign in to comment.