diff --git a/compiler/parse/__test__.js b/compiler/parse/__test__.js index 6684e30fe6..956d2905cd 100644 --- a/compiler/parse/__test__.js +++ b/compiler/parse/__test__.js @@ -143,4 +143,54 @@ describe( 'parse', () => { js: null }); }); + + it( 'parses an {{#each}}...{{/each}} block', () => { + const template = `{{#each animals as animal}}
{{animal}}
{{/each}}`; + + assert.deepEqual( parse( template ), { + html: { + start: 0, + end: 53, + type: 'Fragment', + children: [ + { + start: 0, + end: 53, + type: 'EachBlock', + expression: { + start: 8, + end: 15, + type: 'Identifier', + name: 'animals' + }, + context: 'animal', + children: [ + { + start: 27, + end: 44, + type: 'Element', + name: 'p', + attributes: [], + children: [ + { + start: 30, + end: 40, + type: 'MustacheTag', + expression: { + start: 32, + end: 38, + type: 'Identifier', + name: 'animal' + } + } + ] + } + ] + } + ] + }, + css: null, + js: null + }); + }); }); diff --git a/compiler/parse/index.js b/compiler/parse/index.js index 2c07fe96ac..c03285d8d7 100644 --- a/compiler/parse/index.js +++ b/compiler/parse/index.js @@ -39,6 +39,15 @@ export default function parse ( template ) { } }, + read ( pattern ) { + const match = pattern.exec( this.template.slice( this.index ) ); + if ( !match || match.index !== 0 ) return null; + + parser.index += match[0].length; + + return match[0]; + }, + readUntil ( pattern ) { const match = pattern.exec( this.template.slice( this.index ) ); return this.template.slice( this.index, match ? ( this.index += match.index ) : this.template.length ); diff --git a/compiler/parse/state/mustache.js b/compiler/parse/state/mustache.js index b3a1860047..abe7dac84f 100644 --- a/compiler/parse/state/mustache.js +++ b/compiler/parse/state/mustache.js @@ -1,5 +1,7 @@ import readExpression from '../read/expression.js'; +const validIdentifier = /[a-zA-Z_$][a-zA-Z0-9_$]*/; + export default function mustache ( parser ) { const start = parser.index; parser.index += 2; @@ -45,9 +47,6 @@ export default function mustache ( parser ) { const expression = readExpression( parser ); - parser.allowWhitespace(); - parser.eat( '}}', true ); - const block = { start, end: null, @@ -56,6 +55,20 @@ export default function mustache ( parser ) { children: [] }; + parser.allowWhitespace(); + + // {{#each}} blocks must declare a context – {{#each list as item}} + if ( type === 'EachBlock' ) { + parser.eat( 'as', true ); + parser.requireWhitespace(); + + block.context = parser.read( validIdentifier ); // TODO check it's not a keyword + + parser.allowWhitespace(); + } + + parser.eat( '}}', true ); + parser.current().children.push( block ); parser.stack.push( block ); } diff --git a/test/samples/each-block/_config.js b/test/samples/each-block/_config.js new file mode 100644 index 0000000000..27c25a79f2 --- /dev/null +++ b/test/samples/each-block/_config.js @@ -0,0 +1,15 @@ +import * as assert from 'assert'; + +export default { + description: '{{#each}}...{{/each}} block', + data: { + animals: [ 'alpaca', 'baboon', 'capybara' ] + }, + html: 'alpaca
baboon
capybara
', + test ( component, target ) { + component.set({ animals: [ 'alpaca', 'baboon', 'caribou', 'dogfish' ] }); + assert.equal( target.innerHTML, 'alpaca
baboon
caribou
dogfish
' ); + component.set({ animals: [] }); + assert.equal( target.innerHTML, '' ); + } +}; diff --git a/test/samples/each-block/main.svelte b/test/samples/each-block/main.svelte new file mode 100644 index 0000000000..23bfc3c465 --- /dev/null +++ b/test/samples/each-block/main.svelte @@ -0,0 +1,3 @@ +{{#each animals as animal}} +{{animal}}
+{{/each}}