diff --git a/compiler/parse/__test__.js b/compiler/parse/__test__.js deleted file mode 100644 index 956d2905cd..0000000000 --- a/compiler/parse/__test__.js +++ /dev/null @@ -1,196 +0,0 @@ -import * as assert from 'assert'; -import parse from './index.js'; - -describe( 'parse', () => { - it( 'is a function', () => { - assert.equal( typeof parse, 'function' ); - }); - - it( 'parses a self-closing element', () => { - const template = '
'; - - assert.deepEqual( parse( template ), { - html: { - start: 0, - end: 6, - type: 'Fragment', - children: [ - { - start: 0, - end: 6, - type: 'Element', - name: 'div', - attributes: [], - children: [] - } - ] - }, - css: null, - js: null - }); - }); - - it( 'parses an element with text', () => { - const template = `test`; - - assert.deepEqual( parse( template ), { - html: { - start: 0, - end: 17, - type: 'Fragment', - children: [ - { - start: 0, - end: 17, - type: 'Element', - name: 'span', - attributes: [], - children: [ - { - start: 6, - end: 10, - type: 'Text', - data: 'test' - } - ] - } - ] - }, - css: null, - js: null - }); - }); - - it( 'parses an element with a mustache tag', () => { - const template = `

hello {{name}}!

`; - - assert.deepEqual( parse( template ), { - html: { - start: 0, - end: 24, - type: 'Fragment', - children: [ - { - start: 0, - end: 24, - type: 'Element', - name: 'h1', - attributes: [], - children: [ - { - start: 4, - end: 10, - type: 'Text', - data: 'hello ' - }, - { - start: 10, - end: 18, - type: 'MustacheTag', - expression: { - start: 12, - end: 16, - type: 'Identifier', - name: 'name' - } - }, - { - start: 18, - end: 19, - type: 'Text', - data: '!' - } - ] - } - ] - }, - css: null, - js: null - }); - }); - - it( 'parses an {{#if}}...{{/if}} block', () => { - const template = `{{#if foo}}bar{{/if}}`; - - assert.deepEqual( parse( template ), { - html: { - start: 0, - end: 21, - type: 'Fragment', - children: [ - { - start: 0, - end: 21, - type: 'IfBlock', - expression: { - start: 6, - end: 9, - type: 'Identifier', - name: 'foo' - }, - children: [ - { - start: 11, - end: 14, - type: 'Text', - data: 'bar' - } - ] - } - ] - }, - css: null, - 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 c03285d8d7..309c77f9ad 100644 --- a/compiler/parse/index.js +++ b/compiler/parse/index.js @@ -63,20 +63,21 @@ export default function parse ( template ) { } this.allowWhitespace(); - } - }; + }, - const html = { - start: 0, - end: template.length, - type: 'Fragment', - children: [] - }; + html: { + start: 0, + end: template.length, + type: 'Fragment', + children: [] + }, - let css = null; - let js = null; + css: null, - parser.stack.push( html ); + js: null + }; + + parser.stack.push( parser.html ); let state = fragment; @@ -84,5 +85,9 @@ export default function parse ( template ) { state = state( parser ) || fragment; } - return { html, css, js }; + return { + html: parser.html, + css: parser.css, + js: parser.js + }; } diff --git a/compiler/parse/read/script.js b/compiler/parse/read/script.js new file mode 100644 index 0000000000..a0f1383748 --- /dev/null +++ b/compiler/parse/read/script.js @@ -0,0 +1,25 @@ +import { parse, tokenizer, tokTypes } from 'acorn'; + +export default function readScript ( parser, start, attributes ) { + const scriptStart = parser.index; + let scriptEnd = null; + + const js = { + start, + end: null, + attributes, + content: null + }; + + const endPattern = /\s*<\/script\>/g; + + for ( const token of tokenizer( parser.remaining() ) ) { + endPattern.lastIndex = scriptStart + token.end; + if ( endPattern.test( parser.template ) ) { + scriptEnd = scriptStart + token.end; + break; + } + } + + js.content = parse( ) +} diff --git a/compiler/parse/read/style.js b/compiler/parse/read/style.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/compiler/parse/state/tag.js b/compiler/parse/state/tag.js index e0ee38d7a7..0dd1ccaa5d 100644 --- a/compiler/parse/state/tag.js +++ b/compiler/parse/state/tag.js @@ -1,8 +1,22 @@ import readExpression from '../read/expression.js'; +import readScript from '../read/script.js'; +import readStyle from '../read/style.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; +const specials = { + script: { + read: readScript, + property: 'js' + }, + + style: { + read: readStyle, + property: 'css' + } +}; + export default function tag ( parser ) { const start = parser.index++; @@ -30,6 +44,20 @@ export default function tag ( parser ) { parser.allowWhitespace(); + // special cases – diff --git a/test/samples/each-block/_config.js b/test/compiler/each-block/_config.js similarity index 100% rename from test/samples/each-block/_config.js rename to test/compiler/each-block/_config.js diff --git a/test/samples/each-block/main.svelte b/test/compiler/each-block/main.svelte similarity index 100% rename from test/samples/each-block/main.svelte rename to test/compiler/each-block/main.svelte diff --git a/test/samples/each-blocks-nested/_config.js b/test/compiler/each-blocks-nested/_config.js similarity index 100% rename from test/samples/each-blocks-nested/_config.js rename to test/compiler/each-blocks-nested/_config.js diff --git a/test/samples/each-blocks-nested/main.svelte b/test/compiler/each-blocks-nested/main.svelte similarity index 100% rename from test/samples/each-blocks-nested/main.svelte rename to test/compiler/each-blocks-nested/main.svelte diff --git a/test/samples/hello-world/_config.js b/test/compiler/hello-world/_config.js similarity index 100% rename from test/samples/hello-world/_config.js rename to test/compiler/hello-world/_config.js diff --git a/test/samples/hello-world/main.svelte b/test/compiler/hello-world/main.svelte similarity index 100% rename from test/samples/hello-world/main.svelte rename to test/compiler/hello-world/main.svelte diff --git a/test/samples/if-block/_config.js b/test/compiler/if-block/_config.js similarity index 100% rename from test/samples/if-block/_config.js rename to test/compiler/if-block/_config.js diff --git a/test/samples/if-block/main.svelte b/test/compiler/if-block/main.svelte similarity index 100% rename from test/samples/if-block/main.svelte rename to test/compiler/if-block/main.svelte diff --git a/test/samples/single-static-element/_config.js b/test/compiler/single-static-element/_config.js similarity index 100% rename from test/samples/single-static-element/_config.js rename to test/compiler/single-static-element/_config.js diff --git a/test/samples/single-static-element/main.svelte b/test/compiler/single-static-element/main.svelte similarity index 100% rename from test/samples/single-static-element/main.svelte rename to test/compiler/single-static-element/main.svelte diff --git a/test/parser/each-block/input.svelte b/test/parser/each-block/input.svelte new file mode 100644 index 0000000000..002481b7e3 --- /dev/null +++ b/test/parser/each-block/input.svelte @@ -0,0 +1 @@ +{{#each animals as animal}}

{{animal}}

{{/each}} diff --git a/test/parser/each-block/output.json b/test/parser/each-block/output.json new file mode 100644 index 0000000000..8dda2ae77f --- /dev/null +++ b/test/parser/each-block/output.json @@ -0,0 +1,45 @@ +{ + "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/test/parser/element-with-mustache/input.svelte b/test/parser/element-with-mustache/input.svelte new file mode 100644 index 0000000000..6a2a43bf79 --- /dev/null +++ b/test/parser/element-with-mustache/input.svelte @@ -0,0 +1 @@ +

hello {{name}}!

diff --git a/test/parser/element-with-mustache/output.json b/test/parser/element-with-mustache/output.json new file mode 100644 index 0000000000..131b081edc --- /dev/null +++ b/test/parser/element-with-mustache/output.json @@ -0,0 +1,43 @@ +{ + "html": { + "start": 0, + "end": 24, + "type": "Fragment", + "children": [ + { + "start": 0, + "end": 24, + "type": "Element", + "name": "h1", + "attributes": [], + "children": [ + { + "start": 4, + "end": 10, + "type": "Text", + "data": "hello " + }, + { + "start": 10, + "end": 18, + "type": "MustacheTag", + "expression": { + "start": 12, + "end": 16, + "type": "Identifier", + "name": "name" + } + }, + { + "start": 18, + "end": 19, + "type": "Text", + "data": "!" + } + ] + } + ] + }, + "css": null, + "js": null +} diff --git a/test/parser/element-with-text/input.svelte b/test/parser/element-with-text/input.svelte new file mode 100644 index 0000000000..61dba8bc46 --- /dev/null +++ b/test/parser/element-with-text/input.svelte @@ -0,0 +1 @@ +test diff --git a/test/parser/element-with-text/output.json b/test/parser/element-with-text/output.json new file mode 100644 index 0000000000..a93a083da1 --- /dev/null +++ b/test/parser/element-with-text/output.json @@ -0,0 +1,26 @@ +{ + "html": { + "start": 0, + "end": 17, + "type": "Fragment", + "children": [ + { + "start": 0, + "end": 17, + "type": "Element", + "name": "span", + "attributes": [], + "children": [ + { + "start": 6, + "end": 10, + "type": "Text", + "data": "test" + } + ] + } + ] + }, + "css": null, + "js": null +} diff --git a/test/parser/if-block/input.svelte b/test/parser/if-block/input.svelte new file mode 100644 index 0000000000..b57f7664dd --- /dev/null +++ b/test/parser/if-block/input.svelte @@ -0,0 +1 @@ +{{#if foo}}bar{{/if}} diff --git a/test/parser/if-block/output.json b/test/parser/if-block/output.json new file mode 100644 index 0000000000..e1019ba1d7 --- /dev/null +++ b/test/parser/if-block/output.json @@ -0,0 +1,30 @@ +{ + "html": { + "start": 0, + "end": 21, + "type": "Fragment", + "children": [ + { + "start": 0, + "end": 21, + "type": "IfBlock", + "expression": { + "start": 6, + "end": 9, + "type": "Identifier", + "name": "foo" + }, + "children": [ + { + "start": 11, + "end": 14, + "type": "Text", + "data": "bar" + } + ] + } + ] + }, + "css": null, + "js": null +} diff --git a/test/parser/script/input.svelte b/test/parser/script/input.svelte new file mode 100644 index 0000000000..e46cedc667 --- /dev/null +++ b/test/parser/script/input.svelte @@ -0,0 +1,9 @@ +

Hello {{name}}!

+ + diff --git a/test/parser/script/output.json b/test/parser/script/output.json new file mode 100644 index 0000000000..62ce17e170 --- /dev/null +++ b/test/parser/script/output.json @@ -0,0 +1,43 @@ +{ + "html": { + "start": 0, + "end": 24, + "type": "Fragment", + "children": [ + { + "start": 0, + "end": 24, + "type": "Element", + "name": "h1", + "attributes": [], + "children": [ + { + "start": 4, + "end": 10, + "type": "Text", + "data": "Hello " + }, + { + "start": 10, + "end": 18, + "type": "MustacheTag", + "expression": { + "start": 12, + "end": 16, + "type": "Identifier", + "name": "name" + } + }, + { + "start": 18, + "end": 19, + "type": "Text", + "data": "!" + } + ] + } + ] + }, + "css": null, + "js": null +} diff --git a/test/parser/script/solo b/test/parser/script/solo new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/parser/self-closing-element/input.svelte b/test/parser/self-closing-element/input.svelte new file mode 100644 index 0000000000..61da16d026 --- /dev/null +++ b/test/parser/self-closing-element/input.svelte @@ -0,0 +1 @@ +
diff --git a/test/parser/self-closing-element/output.json b/test/parser/self-closing-element/output.json new file mode 100644 index 0000000000..5cba18b6a0 --- /dev/null +++ b/test/parser/self-closing-element/output.json @@ -0,0 +1,19 @@ +{ + "html": { + "start": 0, + "end": 6, + "type": "Fragment", + "children": [ + { + "start": 0, + "end": 6, + "type": "Element", + "name": "div", + "attributes": [], + "children": [] + } + ] + }, + "css": null, + "js": null +} diff --git a/test/test.js b/test/test.js index 5ab11da8e0..afda7eda33 100644 --- a/test/test.js +++ b/test/test.js @@ -1,4 +1,5 @@ import { compile } from '../compiler/index.js'; +import parse from '../compiler/parse/index.js'; import * as assert from 'assert'; import * as path from 'path'; import * as fs from 'fs'; @@ -13,99 +14,126 @@ require.extensions[ '.svelte' ] = function ( module, filename ) { return module._compile( code, filename ); }; -describe( 'svelte', () => { - function loadConfig ( dir ) { - try { - return require( `./samples/${dir}/_config.js` ).default; - } catch ( err ) { - if ( err.code === 'E_NOT_FOUND' ) { - return {}; - } - - throw err; - } +function exists ( path ) { + try { + fs.statSync( path ); + return true; + } catch ( err ) { + return false; } +} - function env () { - return new Promise( ( fulfil, reject ) => { - jsdom.env( '
', ( err, window ) => { - if ( err ) { - reject( err ); - } else { - global.document = window.document; - fulfil( window ); - } - }); - }); - } +describe( 'svelte', () => { + describe( 'parser', () => { + fs.readdirSync( 'test/parser' ).forEach( dir => { + if ( dir[0] === '.' ) return; - fs.readdirSync( 'test/samples' ).forEach( dir => { - if ( dir[0] === '.' ) return; + const solo = exists( `test/parser/${dir}/solo` ); - const config = loadConfig( dir ); + ( solo ? it.only : it )( dir, () => { + const input = fs.readFileSync( `test/parser/${dir}/input.svelte`, 'utf-8' ).trim(); + const actual = parse( input ); + const expected = require( `./parser/${dir}/output.json` ); - ( config.solo ? it.only : it )( dir, () => { - let compiled; + assert.deepEqual( actual, expected ); + }); + }); + }); + describe( 'compiler', () => { + function loadConfig ( dir ) { try { - const source = fs.readFileSync( `test/samples/${dir}/main.svelte`, 'utf-8' ); - compiled = compile( source ); + return require( `./compiler/${dir}/_config.js` ).default; } catch ( err ) { - if ( config.compileError ) { - config.compileError( err ); - return; - } else { - throw err; + if ( err.code === 'E_NOT_FOUND' ) { + return {}; } + + throw err; } + } - const { code } = compiled; - const withLineNumbers = code.split( '\n' ).map( ( line, i ) => { - i = String( i + 1 ); - while ( i.length < 3 ) i = ` ${i}`; + function env () { + return new Promise( ( fulfil, reject ) => { + jsdom.env( '
', ( err, window ) => { + if ( err ) { + reject( err ); + } else { + global.document = window.document; + fulfil( window ); + } + }); + }); + } - return `${i}: ${line.replace( /^\t+/, match => match.split( '\t' ).join( ' ' ) )}`; - }).join( '\n' ); + fs.readdirSync( 'test/compiler' ).forEach( dir => { + if ( dir[0] === '.' ) return; - cache[ path.resolve( `test/samples/${dir}/main.svelte` ) ] = code; + const config = loadConfig( dir ); - let factory; + ( config.solo ? it.only : it )( dir, () => { + let compiled; - try { - factory = require( `./samples/${dir}/main.svelte` ).default; - } catch ( err ) { - console.log( withLineNumbers ); // eslint-disable-line no-console - throw err; - } + try { + const source = fs.readFileSync( `test/compiler/${dir}/main.svelte`, 'utf-8' ); + compiled = compile( source ); + } catch ( err ) { + if ( config.compileError ) { + config.compileError( err ); + return; + } else { + throw err; + } + } - if ( config.show ) { - console.log( withLineNumbers ); // eslint-disable-line no-console - } + const { code } = compiled; + const withLineNumbers = code.split( '\n' ).map( ( line, i ) => { + i = String( i + 1 ); + while ( i.length < 3 ) i = ` ${i}`; - return env() - .then( window => { - const target = window.document.querySelector( 'main' ); + return `${i}: ${line.replace( /^\t+/, match => match.split( '\t' ).join( ' ' ) )}`; + }).join( '\n' ); - const component = factory({ - target, - data: config.data - }); + cache[ path.resolve( `test/compiler/${dir}/main.svelte` ) ] = code; - if ( config.html ) { - assert.equal( target.innerHTML, config.html ); - } + let factory; - if ( config.test ) { - config.test( component, target ); - } else { - component.teardown(); - assert.equal( target.innerHTML, '' ); - } - }) - .catch( err => { - if ( !config.show ) console.log( withLineNumbers ); // eslint-disable-line no-console + try { + factory = require( `./compiler/${dir}/main.svelte` ).default; + } catch ( err ) { + console.log( withLineNumbers ); // eslint-disable-line no-console throw err; - }); + } + + if ( config.show ) { + console.log( withLineNumbers ); // eslint-disable-line no-console + } + + return env() + .then( window => { + const target = window.document.querySelector( 'main' ); + + const component = factory({ + target, + data: config.data + }); + + if ( config.html ) { + assert.equal( target.innerHTML, config.html ); + } + + if ( config.test ) { + config.test( component, target ); + } else { + component.teardown(); + assert.equal( target.innerHTML, '' ); + } + }) + .catch( err => { + if ( !config.show ) console.log( withLineNumbers ); // eslint-disable-line no-console + throw err; + }); + }); }); }); });