1 import Visitor from './visitor';
3 function WhitespaceControl(options = {}) {
4 this.options = options;
6 WhitespaceControl.prototype = new Visitor();
8 WhitespaceControl.prototype.Program = function(program) {
9 const doStandalone = !this.options.ignoreStandalone;
11 let isRoot = !this.isRootSeen;
12 this.isRootSeen = true;
14 let body = program.body;
15 for (let i = 0, l = body.length; i < l; i++) {
16 let current = body[i],
17 strip = this.accept(current);
23 let _isPrevWhitespace = isPrevWhitespace(body, i, isRoot),
24 _isNextWhitespace = isNextWhitespace(body, i, isRoot),
26 openStandalone = strip.openStandalone && _isPrevWhitespace,
27 closeStandalone = strip.closeStandalone && _isNextWhitespace,
28 inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace;
31 omitRight(body, i, true);
34 omitLeft(body, i, true);
37 if (doStandalone && inlineStandalone) {
40 if (omitLeft(body, i)) {
41 // If we are on a standalone node, save the indent info for partials
42 if (current.type === 'PartialStatement') {
43 // Pull out the whitespace from the final line
44 current.indent = (/([ \t]+$)/).exec(body[i - 1].original)[1];
48 if (doStandalone && openStandalone) {
49 omitRight((current.program || current.inverse).body);
51 // Strip out the previous content node if it's whitespace only
54 if (doStandalone && closeStandalone) {
55 // Always strip the next node
58 omitLeft((current.inverse || current.program).body);
65 WhitespaceControl.prototype.BlockStatement =
66 WhitespaceControl.prototype.DecoratorBlock =
67 WhitespaceControl.prototype.PartialBlockStatement = function(block) {
68 this.accept(block.program);
69 this.accept(block.inverse);
71 // Find the inverse program that is involed with whitespace stripping.
72 let program = block.program || block.inverse,
73 inverse = block.program && block.inverse,
74 firstInverse = inverse,
75 lastInverse = inverse;
77 if (inverse && inverse.chained) {
78 firstInverse = inverse.body[0].program;
80 // Walk the inverse chain to find the last inverse that is actually in the chain.
81 while (lastInverse.chained) {
82 lastInverse = lastInverse.body[lastInverse.body.length - 1].program;
87 open: block.openStrip.open,
88 close: block.closeStrip.close,
90 // Determine the standalone candiacy. Basically flag our content as being possibly standalone
91 // so our parent can determine if we actually are standalone
92 openStandalone: isNextWhitespace(program.body),
93 closeStandalone: isPrevWhitespace((firstInverse || program).body)
96 if (block.openStrip.close) {
97 omitRight(program.body, null, true);
101 let inverseStrip = block.inverseStrip;
103 if (inverseStrip.open) {
104 omitLeft(program.body, null, true);
107 if (inverseStrip.close) {
108 omitRight(firstInverse.body, null, true);
110 if (block.closeStrip.open) {
111 omitLeft(lastInverse.body, null, true);
114 // Find standalone else statments
115 if (!this.options.ignoreStandalone
116 && isPrevWhitespace(program.body)
117 && isNextWhitespace(firstInverse.body)) {
118 omitLeft(program.body);
119 omitRight(firstInverse.body);
121 } else if (block.closeStrip.open) {
122 omitLeft(program.body, null, true);
128 WhitespaceControl.prototype.Decorator =
129 WhitespaceControl.prototype.MustacheStatement = function(mustache) {
130 return mustache.strip;
133 WhitespaceControl.prototype.PartialStatement =
134 WhitespaceControl.prototype.CommentStatement = function(node) {
135 /* istanbul ignore next */
136 let strip = node.strip || {};
138 inlineStandalone: true,
145 function isPrevWhitespace(body, i, isRoot) {
146 if (i === undefined) {
150 // Nodes that end with newlines are considered whitespace (but are special
151 // cased for strip operations)
152 let prev = body[i - 1],
153 sibling = body[i - 2];
158 if (prev.type === 'ContentStatement') {
159 return (sibling || !isRoot ? (/\r?\n\s*?$/) : (/(^|\r?\n)\s*?$/)).test(prev.original);
162 function isNextWhitespace(body, i, isRoot) {
163 if (i === undefined) {
167 let next = body[i + 1],
168 sibling = body[i + 2];
173 if (next.type === 'ContentStatement') {
174 return (sibling || !isRoot ? (/^\s*?\r?\n/) : (/^\s*?(\r?\n|$)/)).test(next.original);
178 // Marks the node to the right of the position as omitted.
179 // I.e. {{foo}}' ' will mark the ' ' node as omitted.
181 // If i is undefined, then the first child will be marked as such.
183 // If mulitple is truthy then all whitespace will be stripped out until non-whitespace
185 function omitRight(body, i, multiple) {
186 let current = body[i == null ? 0 : i + 1];
187 if (!current || current.type !== 'ContentStatement' || (!multiple && current.rightStripped)) {
191 let original = current.value;
192 current.value = current.value.replace(multiple ? (/^\s+/) : (/^[ \t]*\r?\n?/), '');
193 current.rightStripped = current.value !== original;
196 // Marks the node to the left of the position as omitted.
197 // I.e. ' '{{foo}} will mark the ' ' node as omitted.
199 // If i is undefined then the last child will be marked as such.
201 // If mulitple is truthy then all whitespace will be stripped out until non-whitespace
203 function omitLeft(body, i, multiple) {
204 let current = body[i == null ? body.length - 1 : i - 1];
205 if (!current || current.type !== 'ContentStatement' || (!multiple && current.leftStripped)) {
209 // We omit the last node if it's whitespace only and not preceeded by a non-content node.
210 let original = current.value;
211 current.value = current.value.replace(multiple ? (/\s+$/) : (/[ \t]+$/), '');
212 current.leftStripped = current.value !== original;
213 return current.leftStripped;
216 export default WhitespaceControl;