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;
+ });
+ });
});
});
});