more whitespace stuff

pull/31/head
Rich-Harris 8 years ago
parent 83f0f7d202
commit 2f62a5aaca

@ -1,7 +1,7 @@
import { locate } from 'locate-character';
import fragment from './state/fragment.js';
const whitespace = /\s/;
import { whitespace } from './patterns.js';
import { trimStart, trimEnd } from './utils/trim.js';
export default function parse ( template ) {
const parser = {
@ -14,7 +14,7 @@ export default function parse ( template ) {
},
error ( message, index = this.index ) {
const { line, column } = locate( this.template, this.index );
const { line, column } = locate( this.template, index );
throw new Error( `${message} (${line}:${column})` );
},
@ -66,7 +66,7 @@ export default function parse ( template ) {
},
html: {
start: 0,
start: null,
end: null,
type: 'Fragment',
children: []
@ -85,8 +85,40 @@ export default function parse ( template ) {
state = state( parser ) || fragment;
}
const lastTemplateItem = parser.html.children[ parser.html.children.length - 1 ];
parser.html.end = lastTemplateItem ? lastTemplateItem.end : 0;
// trim unnecessary whitespace
while ( parser.html.children.length ) {
const firstChild = parser.html.children[0];
parser.html.start = firstChild.start;
if ( firstChild.type !== 'Text' ) break;
const length = firstChild.data.length;
firstChild.data = trimStart( firstChild.data );
if ( firstChild.data === '' ) {
parser.html.children.shift();
} else {
parser.html.start += length - firstChild.data.length;
break;
}
}
while ( parser.html.children.length ) {
const lastChild = parser.html.children[ parser.html.children.length - 1 ];
parser.html.end = lastChild.end;
if ( lastChild.type !== 'Text' ) break;
const length = lastChild.data.length;
lastChild.data = trimEnd( lastChild.data );
if ( lastChild.data === '' ) {
parser.html.children.pop();
} else {
parser.html.end -= length - lastChild.data.length;
break;
}
}
return {
html: parser.html,

@ -0,0 +1 @@
export const whitespace = /\s/;

@ -3,8 +3,6 @@ import mustache from './mustache.js';
import text from './text.js';
export default function fragment ( parser ) {
parser.allowWhitespace();
if ( parser.match( '<' ) ) {
return tag;
}

@ -1,4 +1,6 @@
import readExpression from '../read/expression.js';
import { whitespace } from '../patterns.js';
import { trimStart, trimEnd } from '../utils/trim.js';
const validIdentifier = /[a-zA-Z_$][a-zA-Z0-9_$]*/;
@ -10,12 +12,12 @@ export default function mustache ( parser ) {
// {{/if}} or {{/each}}
if ( parser.eat( '/' ) ) {
const current = parser.current();
const block = parser.current();
let expected;
if ( current.type === 'IfBlock' ) {
if ( block.type === 'IfBlock' ) {
expected = 'if';
} else if ( current.type === 'EachBlock' ) {
} else if ( block.type === 'EachBlock' ) {
expected = 'each';
} else {
parser.error( `Unexpected block closing tag` );
@ -25,7 +27,25 @@ export default function mustache ( parser ) {
parser.allowWhitespace();
parser.eat( '}}', true );
current.end = parser.index;
// strip leading/trailing whitespace as necessary
if ( !block.children.length ) parser.error( `Empty block`, block.start );
const firstChild = block.children[0];
const lastChild = block.children[ block.children.length - 1 ];
const charBefore = parser.template[ block.start - 1 ];
const charAfter = parser.template[ parser.index ];
if ( firstChild.type === 'Text' && !charBefore || whitespace.test( charBefore ) ) {
firstChild.data = trimStart( firstChild.data );
if ( !firstChild.data ) block.children.shift();
}
if ( lastChild.type === 'Text' && !charAfter || whitespace.test( charAfter ) ) {
lastChild.data = trimEnd( lastChild.data );
if ( !lastChild.data ) block.children.pop();
}
block.end = parser.index;
parser.stack.pop();
}

@ -2,6 +2,7 @@ import readExpression from '../read/expression.js';
import readScript from '../read/script.js';
import readStyle from '../read/style.js';
import { readEventHandlerDirective } from '../read/directives.js';
import { trimStart, trimEnd } from '../utils/trim.js';
const validTagName = /^[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/;
const voidElementNames = /^(?:area|base|br|col|command|doctype|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/i;
@ -32,7 +33,25 @@ export default function tag ( parser ) {
if ( isClosingTag ) {
if ( !parser.eat( '>' ) ) parser.error( `Expected '>'` );
parser.current().end = parser.index;
const element = parser.current();
// strip leading/trailing whitespace as necessary
if ( element.children.length ) {
const firstChild = element.children[0];
const lastChild = element.children[ element.children.length - 1 ];
if ( firstChild.type === 'Text' ) {
firstChild.data = trimStart( firstChild.data );
if ( !firstChild.data ) element.children.shift();
}
if ( lastChild.type === 'Text' ) {
lastChild.data = trimEnd( lastChild.data );
if ( !lastChild.data ) element.children.pop();
}
}
element.end = parser.index;
parser.stack.pop();
return null;

@ -3,7 +3,7 @@ export default function text ( parser ) {
let data = '';
while ( !parser.match( '<' ) && !parser.match( '{{' ) ) {
while ( parser.index < parser.template.length && !parser.match( '<' ) && !parser.match( '{{' ) ) {
data += parser.template[ parser.index++ ];
}

@ -0,0 +1,15 @@
import { whitespace } from '../patterns.js';
export function trimStart ( str ) {
let i = 0;
while ( whitespace.test( str[i] ) ) i += 1;
return str.slice( i );
}
export function trimEnd ( str ) {
let i = str.length;
while ( whitespace.test( str[ i - 1 ] ) ) i -= 1;
return str.slice( 0, i );
}

@ -0,0 +1,11 @@
import * as assert from 'assert';
export default {
html: '<p>1 + 2 = 3</p><p>3 * 3 = 9</p>',
test ( component, target ) {
component.set({ a: 3 });
assert.equal( component.get( 'c' ), 5 );
assert.equal( component.get( 'cSquared' ), 25 );
assert.equal( target.innerHTML, '<p>3 + 2 = 5</p><p>5 * 5 = 25</p>' );
}
};

@ -0,0 +1,16 @@
<p>{{a}} + {{b}} = {{c}}</p>
<p>{{c}} * {{c}} = {{cSquared}}</p>
<script>
export default {
data: () => ({
a: 1,
b: 2
}),
computed: {
c: ( a, b ) => a + b,
cSquared: c => c * c
}
};
</script>

@ -1,18 +1,18 @@
import * as assert from 'assert';
export default {
html: '<button>+1</button><p>0</p>',
html: '<button>+1</button>\n\n<p>0</p>',
test ( component, target, window ) {
const button = target.querySelector( 'button' );
const event = new window.MouseEvent( 'click' );
button.dispatchEvent( event );
assert.equal( component.get( 'counter' ), 1 );
assert.equal( target.innerHTML, '<button>+1</button><p>1</p>' );
assert.equal( target.innerHTML, '<button>+1</button>\n\n<p>1</p>' );
button.dispatchEvent( event );
assert.equal( component.get( 'counter' ), 2 );
assert.equal( target.innerHTML, '<button>+1</button><p>2</p>' );
assert.equal( target.innerHTML, '<button>+1</button>\n\n<p>2</p>' );
assert.equal( component.foo(), 42 );
}

@ -1,15 +1,15 @@
import * as assert from 'assert';
export default {
html: '<button>toggle</button><!--#if visible-->',
html: '<button>toggle</button>\n\n<!--#if visible-->',
test ( component, target, window ) {
const button = target.querySelector( 'button' );
const event = new window.MouseEvent( 'click' );
button.dispatchEvent( event );
assert.equal( target.innerHTML, '<button>toggle</button><p>hello!</p><!--#if visible-->' );
assert.equal( target.innerHTML, '<button>toggle</button>\n\n<p>hello!</p><!--#if visible-->' );
button.dispatchEvent( event );
assert.equal( target.innerHTML, '<button>toggle</button><!--#if visible-->' );
assert.equal( target.innerHTML, '<button>toggle</button>\n\n<!--#if visible-->' );
}
};

@ -72,6 +72,12 @@
}
]
},
{
"start": 61,
"end": 63,
"type": "Text",
"data": "\n\n"
},
{
"start": 63,
"end": 101,

@ -0,0 +1 @@
<p> {{a}} {{b}} : {{c}} : </p>

@ -0,0 +1,71 @@
{
"html": {
"start": 0,
"end": 30,
"type": "Fragment",
"children": [
{
"start": 0,
"end": 30,
"type": "Element",
"name": "p",
"attributes": [],
"children": [
{
"start": 4,
"end": 9,
"type": "MustacheTag",
"expression": {
"start": 6,
"end": 7,
"type": "Identifier",
"name": "a"
}
},
{
"start": 9,
"end": 10,
"type": "Text",
"data": " "
},
{
"start": 10,
"end": 15,
"type": "MustacheTag",
"expression": {
"start": 12,
"end": 13,
"type": "Identifier",
"name": "b"
}
},
{
"start": 15,
"end": 18,
"type": "Text",
"data": " : "
},
{
"start": 18,
"end": 23,
"type": "MustacheTag",
"expression": {
"start": 20,
"end": 21,
"type": "Identifier",
"name": "c"
}
},
{
"start": 23,
"end": 26,
"type": "Text",
"data": " :"
}
]
}
]
},
"css": null,
"js": null
}
Loading…
Cancel
Save