Flag standalone nodes in the AST
This commit is contained in:
@@ -41,6 +41,9 @@ var AST = {
|
||||
} 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;
|
||||
@@ -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;
|
||||
|
||||
+148
@@ -272,5 +272,153 @@ describe('ast', function() {
|
||||
testColumns(inverse, 5, 6, 13, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('standalone flags', function(){
|
||||
describe('mustache', function() {
|
||||
it('does not mark mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse(' {{comment}} ');
|
||||
equals(ast.statements[0].omit, undefined);
|
||||
equals(ast.statements[2].omit, undefined);
|
||||
});
|
||||
});
|
||||
describe('blocks', function() {
|
||||
it('marks block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse(' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
|
||||
block = ast.statements[1];
|
||||
|
||||
equals(ast.statements[0].omit, true);
|
||||
|
||||
equals(block.program.statements[0].omit, true);
|
||||
equals(block.program.statements[1].string, 'foo\n');
|
||||
equals(block.program.statements[2].omit, true);
|
||||
|
||||
equals(block.inverse.statements[0].omit, true);
|
||||
equals(block.inverse.statements[1].string, ' bar \n');
|
||||
equals(block.inverse.statements[2].omit, true);
|
||||
|
||||
equals(ast.statements[2].omit, true);
|
||||
});
|
||||
it('marks initial block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse('{{# comment}} \nfoo\n {{/comment}}'),
|
||||
block = ast.statements[0];
|
||||
|
||||
equals(block.program.statements[0].omit, true);
|
||||
equals(block.program.statements[1].string, 'foo\n');
|
||||
equals(block.program.statements[2].omit, true);
|
||||
});
|
||||
it('marks mustaches with children as standalone', function() {
|
||||
var ast = Handlebars.parse('{{# comment}} \n{{foo}}\n {{/comment}}'),
|
||||
block = ast.statements[0];
|
||||
|
||||
equals(block.program.statements[0].omit, true);
|
||||
equals(block.program.statements[1].id.original, 'foo');
|
||||
equals(block.program.statements[2].omit, undefined);
|
||||
equals(block.program.statements[3].omit, true);
|
||||
});
|
||||
it('marks nested block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse('{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'),
|
||||
statements = ast.statements[0].program.statements,
|
||||
block = statements[1];
|
||||
|
||||
equals(statements[0].omit, true);
|
||||
|
||||
equals(block.program.statements[0].omit, true);
|
||||
equals(block.program.statements[1].string, 'foo\n');
|
||||
equals(block.program.statements[2].omit, true);
|
||||
|
||||
equals(block.inverse.statements[0].omit, true);
|
||||
equals(block.inverse.statements[1].string, ' bar \n');
|
||||
equals(block.inverse.statements[2].omit, true);
|
||||
|
||||
equals(statements[0].omit, true);
|
||||
});
|
||||
it('does not mark nested block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse('{{#foo}} {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} {{/foo}}'),
|
||||
statements = ast.statements[0].program.statements,
|
||||
block = statements[1];
|
||||
|
||||
equals(statements[0].omit, undefined);
|
||||
|
||||
equals(block.program.statements[0].omit, undefined);
|
||||
equals(block.program.statements[1].string, 'foo\n');
|
||||
equals(block.program.statements[2].omit, true);
|
||||
|
||||
equals(block.inverse.statements[0].omit, true);
|
||||
equals(block.inverse.statements[1].string, ' bar \n');
|
||||
equals(block.inverse.statements[2].omit, undefined);
|
||||
|
||||
equals(statements[0].omit, undefined);
|
||||
});
|
||||
it('does not mark nested initial block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse('{{#foo}}{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}}{{/foo}}'),
|
||||
statements = ast.statements[0].program.statements,
|
||||
block = statements[0];
|
||||
|
||||
equals(block.program.statements[0].omit, undefined);
|
||||
equals(block.program.statements[1].string, 'foo\n');
|
||||
equals(block.program.statements[2].omit, true);
|
||||
|
||||
equals(block.inverse.statements[0].omit, true);
|
||||
equals(block.inverse.statements[1].string, ' bar \n');
|
||||
equals(block.inverse.statements[2].omit, undefined);
|
||||
|
||||
equals(statements[0].omit, undefined);
|
||||
});
|
||||
|
||||
it('marks column 0 block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse('test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
|
||||
block = ast.statements[1];
|
||||
|
||||
equals(ast.statements[0].omit, undefined);
|
||||
|
||||
equals(block.program.statements[0].omit, true);
|
||||
equals(block.program.statements[1].string, 'foo\n');
|
||||
equals(block.program.statements[2].omit, true);
|
||||
|
||||
equals(block.inverse.statements[0].omit, true);
|
||||
equals(block.inverse.statements[1].string, ' bar \n');
|
||||
equals(block.inverse.statements[2].omit, true);
|
||||
|
||||
equals(ast.statements[2].omit, true);
|
||||
});
|
||||
});
|
||||
describe('partials', function() {
|
||||
it('marks partial as standalone', function() {
|
||||
var ast = Handlebars.parse('{{> partial }} ');
|
||||
equals(ast.statements[1].omit, true);
|
||||
});
|
||||
it('marks indented partial as standalone', function() {
|
||||
var ast = Handlebars.parse(' {{> partial }} ');
|
||||
equals(ast.statements[0].omit, true);
|
||||
equals(ast.statements[1].indent, ' ');
|
||||
equals(ast.statements[2].omit, true);
|
||||
});
|
||||
it('marks those around content as not standalone', function() {
|
||||
var ast = Handlebars.parse('a{{> partial }}');
|
||||
equals(ast.statements[0].omit, undefined);
|
||||
|
||||
ast = Handlebars.parse('{{> partial }}a');
|
||||
equals(ast.statements[1].omit, undefined);
|
||||
});
|
||||
});
|
||||
describe('comments', function() {
|
||||
it('marks comment as standalone', function() {
|
||||
var ast = Handlebars.parse('{{! comment }} ');
|
||||
equals(ast.statements[1].omit, true);
|
||||
});
|
||||
it('marks indented comment as standalone', function() {
|
||||
var ast = Handlebars.parse(' {{! comment }} ');
|
||||
equals(ast.statements[0].omit, true);
|
||||
equals(ast.statements[2].omit, true);
|
||||
});
|
||||
it('marks those around content as not standalone', function() {
|
||||
var ast = Handlebars.parse('a{{! comment }}');
|
||||
equals(ast.statements[0].omit, undefined);
|
||||
|
||||
ast = Handlebars.parse('{{! comment }}a');
|
||||
equals(ast.statements[1].omit, undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user