From a1b5f70d66c56a4f8f569599cdab31c0d292b8c5 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 27 Feb 2017 15:30:37 -0500 Subject: [PATCH 1/5] include CSS AST in svelte.parse output --- src/generators/shared/processCss.js | 15 +++----- src/parse/read/style.js | 18 +++++++++ test/parse.js | 2 +- test/parser/css/output.json | 59 +++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 11 deletions(-) diff --git a/src/generators/shared/processCss.js b/src/generators/shared/processCss.js index 4f57e4db70..51abf2a292 100644 --- a/src/generators/shared/processCss.js +++ b/src/generators/shared/processCss.js @@ -1,4 +1,3 @@ -import parse from 'css-tree/lib/parser/index.js'; import walk from 'css-tree/lib/utils/walk.js'; const commentsPattern = /\/\*[\s\S]*?\*\//g; @@ -7,16 +6,12 @@ export default function processCss ( parsed, code ) { const css = parsed.css.content.styles; const offset = parsed.css.content.start; - const ast = parse( css, { - positions: true - }); - const attr = `[svelte-${parsed.hash}]`; - walk.rules( ast, rule => { + walk.rules( parsed.css.ast, rule => { rule.selector.children.each( selector => { - const start = selector.loc.start.offset; - const end = selector.loc.end.offset; + const start = selector.start - offset; + const end = selector.end - offset; const selectorString = css.slice( start, end ); @@ -25,7 +20,7 @@ export default function processCss ( parsed, code ) { let transformed; if ( firstToken.data.type === 'TypeSelector' ) { - const insert = firstToken.data.loc.end.offset; + const insert = firstToken.data.end - offset; const head = css.slice( start, insert ); const tail = css.slice( insert, end ); @@ -41,7 +36,7 @@ export default function processCss ( parsed, code ) { // remove comments. TODO would be nice if this was exposed in css-tree let match; while ( match = commentsPattern.exec( css ) ) { - const start = match.index + offset; + const start = match.index; const end = start + match[0].length; code.remove( start, end ); diff --git a/src/parse/read/style.js b/src/parse/read/style.js index 5da3cc2d4c..fb2fb2e780 100644 --- a/src/parse/read/style.js +++ b/src/parse/read/style.js @@ -1,8 +1,25 @@ +import parse from 'css-tree/lib/parser/index.js'; +import walk from 'css-tree/lib/utils/walk.js'; + export default function readStyle ( parser, start, attributes ) { const contentStart = parser.index; const styles = parser.readUntil( /<\/style>/ ); const contentEnd = parser.index; + const ast = parse( styles, { + positions: true, + offset: contentStart + }); + + // tidy up AST + walk.all( ast, node => { + if ( node.loc ) { + node.start = node.loc.start.offset; + node.end = node.loc.end.offset; + delete node.loc; + } + }); + parser.eat( '', true ); const end = parser.index; @@ -10,6 +27,7 @@ export default function readStyle ( parser, start, attributes ) { start, end, attributes, + ast, content: { start: contentStart, end: contentEnd, diff --git a/test/parse.js b/test/parse.js index 166a17609b..fa8cf85d8d 100644 --- a/test/parse.js +++ b/test/parse.js @@ -16,7 +16,7 @@ describe( 'parse', () => { const input = fs.readFileSync( `test/parser/${dir}/input.html`, 'utf-8' ).replace( /\s+$/, '' ); try { - const actual = svelte.parse( input ); + const actual = JSON.parse( JSON.stringify( svelte.parse( input ) ) ); const expected = require( `./parser/${dir}/output.json` ); assert.deepEqual( actual.html, expected.html ); diff --git a/test/parser/css/output.json b/test/parser/css/output.json index 1e78e04cf5..5efa084f7d 100644 --- a/test/parser/css/output.json +++ b/test/parser/css/output.json @@ -29,6 +29,65 @@ "start": 23, "end": 48, "styles": "\n\tdiv {\n\t\tcolor: red;\n\t}\n" + }, + "ast": { + "type": "StyleSheet", + "start": 23, + "end": 48, + "children": [ + { + "type": "Rule", + "start": 25, + "end": 47, + "selector": { + "type": "SelectorList", + "start": 25, + "end": 28, + "children": [ + { + "type": "Selector", + "start": 25, + "end": 28, + "children": [ + { + "type": "TypeSelector", + "start": 25, + "end": 28, + "name": "div" + } + ] + } + ] + }, + "block": { + "type": "Block", + "start": 29, + "end": 47, + "children": [ + { + "type": "Declaration", + "start": 33, + "end": 43, + "important": false, + "property": "color", + "value": { + "type": "Value", + "start": 39, + "end": 43, + "children": [ + { + "type": "Identifier", + "start": 40, + "end": 43, + "name": "red" + } + ] + } + } + ] + } + } + ] } }, "js": null From ef4d7e25380fc30fb3ed82fee251626272d875f0 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 27 Feb 2017 16:08:02 -0500 Subject: [PATCH 2/5] work around some css-tree quirks --- src/generators/shared/processCss.js | 26 +++-- src/parse/read/style.js | 2 +- .../css-space-in-attribute/_config.js | 2 + test/parser/css/output.json | 97 +++++++++---------- 4 files changed, 67 insertions(+), 60 deletions(-) diff --git a/src/generators/shared/processCss.js b/src/generators/shared/processCss.js index 51abf2a292..6adb8660af 100644 --- a/src/generators/shared/processCss.js +++ b/src/generators/shared/processCss.js @@ -1,5 +1,3 @@ -import walk from 'css-tree/lib/utils/walk.js'; - const commentsPattern = /\/\*[\s\S]*?\*\//g; export default function processCss ( parsed, code ) { @@ -8,19 +6,19 @@ export default function processCss ( parsed, code ) { const attr = `[svelte-${parsed.hash}]`; - walk.rules( parsed.css.ast, rule => { - rule.selector.children.each( selector => { + function transform ( rule ) { + rule.selector.children.forEach( selector => { const start = selector.start - offset; const end = selector.end - offset; const selectorString = css.slice( start, end ); - const firstToken = selector.children.head; + const firstToken = selector.children[0]; let transformed; - if ( firstToken.data.type === 'TypeSelector' ) { - const insert = firstToken.data.end - offset; + if ( firstToken.type === 'TypeSelector' ) { + const insert = firstToken.end - offset; const head = css.slice( start, insert ); const tail = css.slice( insert, end ); @@ -31,7 +29,19 @@ export default function processCss ( parsed, code ) { code.overwrite( start + offset, end + offset, transformed ); }); - }); + } + + function walk ( node ) { + if ( node.type === 'Rule' ) { + transform( node ); + } else if ( node.children ) { + node.children.forEach( walk ); + } else if ( node.block ) { + walk( node.block ); + } + } + + parsed.css.children.forEach( walk ); // remove comments. TODO would be nice if this was exposed in css-tree let match; diff --git a/src/parse/read/style.js b/src/parse/read/style.js index fb2fb2e780..68e05da4ca 100644 --- a/src/parse/read/style.js +++ b/src/parse/read/style.js @@ -27,7 +27,7 @@ export default function readStyle ( parser, start, attributes ) { start, end, attributes, - ast, + children: JSON.parse( JSON.stringify( ast.children ) ), content: { start: contentStart, end: contentEnd, diff --git a/test/generator/css-space-in-attribute/_config.js b/test/generator/css-space-in-attribute/_config.js index 3b52fb71d4..a6a12254cd 100644 --- a/test/generator/css-space-in-attribute/_config.js +++ b/test/generator/css-space-in-attribute/_config.js @@ -1,4 +1,6 @@ export default { + // solo: true, + test ( assert, component, target, window ) { const [ control, test ] = target.querySelectorAll( 'p' ); diff --git a/test/parser/css/output.json b/test/parser/css/output.json index 5efa084f7d..72c2a139db 100644 --- a/test/parser/css/output.json +++ b/test/parser/css/output.json @@ -30,65 +30,60 @@ "end": 48, "styles": "\n\tdiv {\n\t\tcolor: red;\n\t}\n" }, - "ast": { - "type": "StyleSheet", - "start": 23, - "end": 48, - "children": [ - { - "type": "Rule", + "children": [ + { + "type": "Rule", + "start": 25, + "end": 47, + "selector": { + "type": "SelectorList", "start": 25, + "end": 28, + "children": [ + { + "type": "Selector", + "start": 25, + "end": 28, + "children": [ + { + "type": "TypeSelector", + "start": 25, + "end": 28, + "name": "div" + } + ] + } + ] + }, + "block": { + "type": "Block", + "start": 29, "end": 47, - "selector": { - "type": "SelectorList", - "start": 25, - "end": 28, - "children": [ - { - "type": "Selector", - "start": 25, - "end": 28, + "children": [ + { + "type": "Declaration", + "start": 33, + "end": 43, + "important": false, + "property": "color", + "value": { + "type": "Value", + "start": 39, + "end": 43, "children": [ { - "type": "TypeSelector", - "start": 25, - "end": 28, - "name": "div" + "type": "Identifier", + "start": 40, + "end": 43, + "name": "red" } ] } - ] - }, - "block": { - "type": "Block", - "start": 29, - "end": 47, - "children": [ - { - "type": "Declaration", - "start": 33, - "end": 43, - "important": false, - "property": "color", - "value": { - "type": "Value", - "start": 39, - "end": 43, - "children": [ - { - "type": "Identifier", - "start": 40, - "end": 43, - "name": "red" - } - ] - } - } - ] - } + } + ] } - ] - } + } + ] }, "js": null } From c2cc5e5fb8222291d5a855ba68148b90436251af Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 27 Feb 2017 16:16:19 -0500 Subject: [PATCH 3/5] normalise CSS parse errors --- src/parse/read/style.js | 18 ++++++++++++++---- test/parser/error-css/error.json | 8 ++++++++ test/parser/error-css/input.html | 3 +++ 3 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 test/parser/error-css/error.json create mode 100644 test/parser/error-css/input.html diff --git a/src/parse/read/style.js b/src/parse/read/style.js index 68e05da4ca..d367aba627 100644 --- a/src/parse/read/style.js +++ b/src/parse/read/style.js @@ -6,10 +6,20 @@ export default function readStyle ( parser, start, attributes ) { const styles = parser.readUntil( /<\/style>/ ); const contentEnd = parser.index; - const ast = parse( styles, { - positions: true, - offset: contentStart - }); + let ast; + + try { + ast = parse( styles, { + positions: true, + offset: contentStart + }); + } catch ( err ) { + if ( err.name === 'CssSyntaxError' ) { + parser.error( err.message, err.offset ); + } else { + throw err; + } + } // tidy up AST walk.all( ast, node => { diff --git a/test/parser/error-css/error.json b/test/parser/error-css/error.json new file mode 100644 index 0000000000..177f6d977f --- /dev/null +++ b/test/parser/error-css/error.json @@ -0,0 +1,8 @@ +{ + "message": "LeftCurlyBracket is expected", + "loc": { + "line": 2, + "column": 16 + }, + "pos": 24 +} diff --git a/test/parser/error-css/input.html b/test/parser/error-css/input.html new file mode 100644 index 0000000000..d073ec6258 --- /dev/null +++ b/test/parser/error-css/input.html @@ -0,0 +1,3 @@ + \ No newline at end of file From 2fad643daaabfbb519adb1abde986daf5f1dc6b8 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 27 Feb 2017 16:20:21 -0500 Subject: [PATCH 4/5] oops --- src/generators/shared/processCss.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generators/shared/processCss.js b/src/generators/shared/processCss.js index 6adb8660af..bb0012e990 100644 --- a/src/generators/shared/processCss.js +++ b/src/generators/shared/processCss.js @@ -46,7 +46,7 @@ export default function processCss ( parsed, code ) { // remove comments. TODO would be nice if this was exposed in css-tree let match; while ( match = commentsPattern.exec( css ) ) { - const start = match.index; + const start = match.index + offset; const end = start + match[0].length; code.remove( start, end ); From e0a7a1959768910f2edcea400c8e61852071a198 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 27 Feb 2017 16:32:58 -0500 Subject: [PATCH 5/5] remove comment --- test/generator/css-space-in-attribute/_config.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/generator/css-space-in-attribute/_config.js b/test/generator/css-space-in-attribute/_config.js index a6a12254cd..3b52fb71d4 100644 --- a/test/generator/css-space-in-attribute/_config.js +++ b/test/generator/css-space-in-attribute/_config.js @@ -1,6 +1,4 @@ export default { - // solo: true, - test ( assert, component, target, window ) { const [ control, test ] = target.querySelectorAll( 'p' );