diff --git a/.eslintrc b/.eslintrc.json similarity index 100% rename from .eslintrc rename to .eslintrc.json diff --git a/.gitignore b/.gitignore index b0081af1da..e3eea17ef7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ dist .nyc_output coverage coverage.lcov +test/sourcemaps/*/output.js +test/sourcemaps/*/output.js.map diff --git a/CHANGELOG.md b/CHANGELOG.md index a17bf19821..041d9912bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Svelte changelog +## 1.0.7 + +* Correctly escape HTML entities ([#85](https://github.com/sveltejs/svelte/issues/85)) + +## 1.0.6 + +* Generate useful sourcemaps ([#60](https://github.com/sveltejs/svelte/issues/60)) + ## 1.0.5 * Ensure compiler only generates ES5 code ([#75](https://github.com/sveltejs/svelte/issues/75)) diff --git a/README.md b/README.md index 60d559cd2f..16490fecf3 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,9 @@ This is the Svelte compiler, which is primarily intended for authors of tooling * [svelte-cli](https://github.com/sveltejs/svelte-cli) – Command line interface for compiling components * [rollup-plugin-svelte](https://github.com/rollup/rollup-plugin-svelte) – Rollup plugin +* [sveltify](https://github.com/tehshrike/sveltify) - Browserify transform +* [gulp-svelte](https://github.com/shinnn/gulp-svelte) - gulp plugin +* [metalsmith-svelte](https://github.com/shinnn/metalsmith-svelte) - Metalsmith plugin * More to come! diff --git a/compiler/generate/index.js b/compiler/generate/index.js index f73b4a90b3..347d7062cf 100644 --- a/compiler/generate/index.js +++ b/compiler/generate/index.js @@ -86,7 +86,7 @@ export default function generate ( parsed, source, options ) { const { name } = flattenReference( node ); if ( parent && parent.type === 'CallExpression' && node === parent.callee ) { - if ( generator.helpers[ name ] ) generator.code.insertRight( node.start, `template.helpers.` ); + if ( generator.helpers[ name ] ) generator.code.prependRight( node.start, `template.helpers.` ); return; } @@ -102,7 +102,7 @@ export default function generate ( parsed, source, options ) { if ( !~usedContexts.indexOf( context ) ) usedContexts.push( context ); } else { dependencies.push( node.name ); - generator.code.insertRight( node.start, `root.` ); + generator.code.prependRight( node.start, `root.` ); if ( !~usedContexts.indexOf( 'root' ) ) usedContexts.push( 'root' ); } @@ -204,19 +204,19 @@ export default function generate ( parsed, source, options ) { while ( /\s/.test( source[ i - 1 ] ) ) i--; const indentation = source.slice( i, defaultExport.start ); - generator.code.insertLeft( finalNode.end, `\n\n${indentation}return template;` ); + generator.code.appendLeft( finalNode.end, `\n\n${indentation}return template;` ); } defaultExport.declaration.properties.forEach( prop => { templateProperties[ prop.key.name ] = prop.value; }); - generator.code.insertRight( parsed.js.content.start, 'var template = (function () {' ); + generator.code.prependRight( parsed.js.content.start, 'var template = (function () {' ); } else { - generator.code.insertRight( parsed.js.content.start, '(function () {' ); + generator.code.prependRight( parsed.js.content.start, '(function () {' ); } - generator.code.insertLeft( parsed.js.content.end, '}());' ); + generator.code.appendLeft( parsed.js.content.end, '}());' ); [ 'helpers', 'events', 'components' ].forEach( key => { if ( templateProperties[ key ] ) { @@ -508,30 +508,35 @@ export default function generate ( parsed, source, options ) { function addString ( str ) { compiled.addSource({ - filename: options.filename, content: new MagicString( str ) }); } - addString( getIntro( format, options, imports ) ); + const intro = getIntro( format, options, imports ); + if ( intro ) addString( intro ); + + // a filename is necessary for sourcemap generation + const filename = options.filename || 'SvelteComponent.html'; parts.forEach( str => { + const chunk = str.replace( pattern, '' ); + if ( chunk ) addString( chunk ); + const match = pattern.exec( str ); - addString( str.replace( pattern, '' ) ); + const snippet = generator.code.snip( +match[1], +match[2] ); compiled.addSource({ - filename: options.filename, - content: generator.code.snip( +match[1], +match[2] ) + filename, + content: snippet }); }); - compiled.append( finalChunk ); - + addString( finalChunk ); addString( '\n\n' + getOutro( format, constructorName, options, imports ) ); return { code: compiled.toString(), - map: compiled.generateMap() + map: compiled.generateMap({ includeContent: true }) }; } diff --git a/compiler/generate/visitors/attributes/addComponentAttributes.js b/compiler/generate/visitors/attributes/addComponentAttributes.js index 9995352dc8..47609e9c81 100644 --- a/compiler/generate/visitors/attributes/addComponentAttributes.js +++ b/compiler/generate/visitors/attributes/addComponentAttributes.js @@ -80,7 +80,7 @@ export default function addComponentAttributes ( generator, node, local ) { else if ( attribute.type === 'EventHandler' ) { // TODO verify that it's a valid callee (i.e. built-in or declared method) generator.addSourcemapLocations( attribute.expression ); - generator.code.insertRight( attribute.expression.start, 'component.' ); + generator.code.prependRight( attribute.expression.start, 'component.' ); const usedContexts = new Set(); attribute.expression.arguments.forEach( arg => { diff --git a/compiler/generate/visitors/attributes/addElementAttributes.js b/compiler/generate/visitors/attributes/addElementAttributes.js index 8637266ffd..6f372b24ee 100644 --- a/compiler/generate/visitors/attributes/addElementAttributes.js +++ b/compiler/generate/visitors/attributes/addElementAttributes.js @@ -116,7 +116,7 @@ export default function addElementAttributes ( generator, node, local ) { else if ( attribute.type === 'EventHandler' ) { // TODO verify that it's a valid callee (i.e. built-in or declared method) generator.addSourcemapLocations( attribute.expression ); - generator.code.insertRight( attribute.expression.start, 'component.' ); + generator.code.prependRight( attribute.expression.start, 'component.' ); const usedContexts = new Set(); attribute.expression.arguments.forEach( arg => { diff --git a/compiler/parse/state/text.js b/compiler/parse/state/text.js index 8a0c9046a7..ee42dd25e3 100644 --- a/compiler/parse/state/text.js +++ b/compiler/parse/state/text.js @@ -1,3 +1,5 @@ +import { decodeCharacterReferences } from '../utils/html.js'; + export default function text ( parser ) { const start = parser.index; @@ -11,7 +13,7 @@ export default function text ( parser ) { start, end: parser.index, type: 'Text', - data + data: decodeCharacterReferences( data ) }); return null; diff --git a/package.json b/package.json index 96bcdcf10b..418539cc89 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "1.0.5", + "version": "1.0.7", "description": "The magical disappearing UI framework", "main": "dist/svelte.js", "files": [ @@ -47,7 +47,7 @@ "fuzzyset.js": "0.0.1", "jsdom": "^9.8.3", "locate-character": "^2.0.0", - "magic-string": "^0.16.0", + "magic-string": "^0.19.0", "mocha": "^3.1.2", "node-resolve": "^1.3.3", "nyc": "^9.0.1", @@ -56,6 +56,7 @@ "rollup-plugin-buble": "^0.14.0", "rollup-plugin-commonjs": "^5.0.5", "rollup-plugin-node-resolve": "^2.0.0", + "source-map": "^0.5.6", "source-map-support": "^0.4.6" }, "nyc": { diff --git a/test/parser/convert-entities-in-element/input.html b/test/parser/convert-entities-in-element/input.html new file mode 100644 index 0000000000..bc5c086536 --- /dev/null +++ b/test/parser/convert-entities-in-element/input.html @@ -0,0 +1 @@ +

Hello & World

diff --git a/test/parser/convert-entities-in-element/output.json b/test/parser/convert-entities-in-element/output.json new file mode 100644 index 0000000000..4f39e826e7 --- /dev/null +++ b/test/parser/convert-entities-in-element/output.json @@ -0,0 +1,26 @@ +{ + "html": { + "start": 0, + "end": 24, + "type": "Fragment", + "children": [ + { + "start": 0, + "end": 24, + "type": "Element", + "name": "p", + "attributes": [], + "children": [ + { + "start": 3, + "end": 20, + "type": "Text", + "data": "Hello & World" + } + ] + } + ] + }, + "css": null, + "js": null +} diff --git a/test/parser/convert-entities/input.html b/test/parser/convert-entities/input.html new file mode 100644 index 0000000000..e25e3214f2 --- /dev/null +++ b/test/parser/convert-entities/input.html @@ -0,0 +1 @@ +Hello & World diff --git a/test/parser/convert-entities/output.json b/test/parser/convert-entities/output.json new file mode 100644 index 0000000000..f5124865c8 --- /dev/null +++ b/test/parser/convert-entities/output.json @@ -0,0 +1,17 @@ +{ + "html": { + "start": 0, + "end": 17, + "type": "Fragment", + "children": [ + { + "start": 0, + "end": 17, + "type": "Text", + "data": "Hello & World" + } + ] + }, + "css": null, + "js": null +} diff --git a/test/sourcemaps/basic/input.html b/test/sourcemaps/basic/input.html new file mode 100644 index 0000000000..ed83093fa9 --- /dev/null +++ b/test/sourcemaps/basic/input.html @@ -0,0 +1 @@ +{{foo.bar.baz}} diff --git a/test/sourcemaps/basic/test.js b/test/sourcemaps/basic/test.js new file mode 100644 index 0000000000..b169b04f7a --- /dev/null +++ b/test/sourcemaps/basic/test.js @@ -0,0 +1,34 @@ +export function test ({ assert, smc, locateInSource, locateInGenerated }) { + const expected = locateInSource( 'foo.bar.baz' ); + + let loc; + let actual; + + loc = locateInGenerated( 'foo.bar.baz' ); + + actual = smc.originalPositionFor({ + line: loc.line + 1, + column: loc.column + }); + + assert.deepEqual( actual, { + source: 'SvelteComponent.html', + name: null, + line: expected.line + 1, + column: expected.column + }); + + loc = locateInGenerated( 'foo.bar.baz', loc.character + 1 ); + + actual = smc.originalPositionFor({ + line: loc.line + 1, + column: loc.column + }); + + assert.deepEqual( actual, { + source: 'SvelteComponent.html', + name: null, + line: expected.line + 1, + column: expected.column + }); +} diff --git a/test/sourcemaps/script/input.html b/test/sourcemaps/script/input.html new file mode 100644 index 0000000000..c4dc11d3a0 --- /dev/null +++ b/test/sourcemaps/script/input.html @@ -0,0 +1,9 @@ +
+ + diff --git a/test/sourcemaps/script/test.js b/test/sourcemaps/script/test.js new file mode 100644 index 0000000000..6b14b91a9d --- /dev/null +++ b/test/sourcemaps/script/test.js @@ -0,0 +1,16 @@ +export function test ({ assert, smc, locateInSource, locateInGenerated }) { + const expected = locateInSource( '42' ); + const loc = locateInGenerated( '42' ); + + const actual = smc.originalPositionFor({ + line: loc.line + 1, + column: loc.column + }); + + assert.deepEqual( actual, { + source: 'SvelteComponent.html', + name: null, + line: expected.line + 1, + column: expected.column + }); +} diff --git a/test/test.js b/test/test.js index 9d132849e3..1738c5e5ad 100644 --- a/test/test.js +++ b/test/test.js @@ -5,6 +5,8 @@ import * as path from 'path'; import * as fs from 'fs'; import jsdom from 'jsdom'; import * as acorn from 'acorn'; +import { SourceMapConsumer } from 'source-map'; +import { getLocator } from 'locate-character'; import * as consoleGroup from 'console-group'; consoleGroup.install(); @@ -468,4 +470,29 @@ describe( 'svelte', () => { }); }); }); + + describe( 'sourcemaps', () => { + fs.readdirSync( 'test/sourcemaps' ).forEach( dir => { + if ( dir[0] === '.' ) return; + + const solo = exists( `test/sourcemaps/${dir}/solo` ); + + ( solo ? it.only : it )( dir, () => { + const input = fs.readFileSync( `test/sourcemaps/${dir}/input.html`, 'utf-8' ).replace( /\s+$/, '' ); + const { code, map } = svelte.compile( input ); + + fs.writeFileSync( `test/sourcemaps/${dir}/output.js`, `${code}\n//# sourceMappingURL=output.js.map` ); + fs.writeFileSync( `test/sourcemaps/${dir}/output.js.map`, JSON.stringify( map, null, ' ' ) ); + + const { test } = require( `./sourcemaps/${dir}/test.js` ); + + const smc = new SourceMapConsumer( map ); + + const locateInSource = getLocator( input ); + const locateInGenerated = getLocator( code ); + + test({ assert, code, map, smc, locateInSource, locateInGenerated }); + }); + }); + }); });