diff --git a/.gitignore b/.gitignore index 329472f10601..d134f6249cef 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ coverage coverage.lcov test/sourcemaps/*/output.js test/sourcemaps/*/output.js.map +_actual.json \ No newline at end of file diff --git a/src/parse/state/tag.js b/src/parse/state/tag.js index 72f73f06513a..3b79ad84060f 100644 --- a/src/parse/state/tag.js +++ b/src/parse/state/tag.js @@ -21,9 +21,46 @@ const specials = { } }; +// based on http://developers.whatwg.org/syntax.html#syntax-tag-omission +const disallowedContents = { + li: [ 'li' ], + dt: [ 'dt', 'dd' ], + dd: [ 'dt', 'dd' ], + p: 'address article aside blockquote div dl fieldset footer form h1 h2 h3 h4 h5 h6 header hgroup hr main menu nav ol p pre section table ul'.split( ' ' ), + rt: [ 'rt', 'rp' ], + rp: [ 'rt', 'rp' ], + optgroup: [ 'optgroup' ], + option: [ 'option', 'optgroup' ], + thead: [ 'tbody', 'tfoot' ], + tbody: [ 'tbody', 'tfoot' ], + tfoot: [ 'tbody' ], + tr: [ 'tr', 'tbody' ], + td: [ 'td', 'th', 'tr' ], + th: [ 'td', 'th', 'tr' ] +}; + +function stripWhitespace ( element ) { + if ( element.children.length ) { + const firstChild = element.children[0]; + const lastChild = element.children[ element.children.length - 1 ]; + + if ( firstChild.type === 'Text' ) { + firstChild.data = trimStart( firstChild.data ); + if ( !firstChild.data ) element.children.shift(); + } + + if ( lastChild.type === 'Text' ) { + lastChild.data = trimEnd( lastChild.data ); + if ( !lastChild.data ) element.children.pop(); + } + } +} + export default function tag ( parser ) { const start = parser.index++; + let parent = parser.current(); + if ( parser.eat( '!--' ) ) { const data = parser.readUntil( /-->/ ); parser.eat( '-->' ); @@ -40,8 +77,6 @@ export default function tag ( parser ) { const isClosingTag = parser.eat( '/' ); - // TODO handle cases like
  • one
  • two - const name = readTagName( parser ); parser.allowWhitespace(); @@ -53,28 +88,31 @@ export default function tag ( parser ) { if ( !parser.eat( '>' ) ) parser.error( `Expected '>'` ); - const element = parser.current(); - - // strip leading/trailing whitespace as necessary - if ( element.children.length ) { - const firstChild = element.children[0]; - const lastChild = element.children[ element.children.length - 1 ]; - - if ( firstChild.type === 'Text' ) { - firstChild.data = trimStart( firstChild.data ); - if ( !firstChild.data ) element.children.shift(); - } + // close any elements that don't have their own closing tags, e.g.

    + while ( parent.name !== name ) { + parent.end = start; + parser.stack.pop(); - if ( lastChild.type === 'Text' ) { - lastChild.data = trimEnd( lastChild.data ); - if ( !lastChild.data ) element.children.pop(); - } + parent = parser.current(); } - element.end = parser.index; + // strip leading/trailing whitespace as necessary + stripWhitespace( parent ); + + parent.end = parser.index; parser.stack.pop(); return null; + } else if ( parent.name in disallowedContents ) { + // can this be a child of the parent element, or does it implicitly + // close it, like `
  • one
  • two`? + const disallowed = disallowedContents[ parent.name ]; + if ( ~disallowed.indexOf( name ) ) { + stripWhitespace( parent ); + + parent.end = start; + parser.stack.pop(); + } } const attributes = []; diff --git a/test/parse.js b/test/parse.js index fa8cf85d8dc4..caa132bbdf65 100644 --- a/test/parse.js +++ b/test/parse.js @@ -16,7 +16,8 @@ describe( 'parse', () => { const input = fs.readFileSync( `test/parser/${dir}/input.html`, 'utf-8' ).replace( /\s+$/, '' ); try { - const actual = JSON.parse( JSON.stringify( svelte.parse( input ) ) ); + const actual = svelte.parse( input ); + fs.writeFileSync( `test/parser/${dir}/_actual.json`, JSON.stringify( actual, null, '\t' ) ); const expected = require( `./parser/${dir}/output.json` ); assert.deepEqual( actual.html, expected.html ); diff --git a/test/parser/implicitly-closed-li/input.html b/test/parser/implicitly-closed-li/input.html new file mode 100644 index 000000000000..14fc1168baae --- /dev/null +++ b/test/parser/implicitly-closed-li/input.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/test/parser/implicitly-closed-li/output.json b/test/parser/implicitly-closed-li/output.json new file mode 100644 index 000000000000..3263fa8e403b --- /dev/null +++ b/test/parser/implicitly-closed-li/output.json @@ -0,0 +1,66 @@ +{ + "hash": 3806276940, + "html": { + "start": 0, + "end": 31, + "type": "Fragment", + "children": [ + { + "start": 0, + "end": 31, + "type": "Element", + "name": "ul", + "attributes": [], + "children": [ + { + "start": 6, + "end": 13, + "type": "Element", + "name": "li", + "attributes": [], + "children": [ + { + "start": 10, + "end": 13, + "type": "Text", + "data": "a" + } + ] + }, + { + "start": 13, + "end": 20, + "type": "Element", + "name": "li", + "attributes": [], + "children": [ + { + "start": 17, + "end": 20, + "type": "Text", + "data": "b" + } + ] + }, + { + "start": 20, + "end": 26, + "type": "Element", + "name": "li", + "attributes": [], + "children": [ + { + "start": 24, + "end": 26, + "type": "Text", + "data": "c\n" + } + ] + } + ] + } + ] + }, + "css": null, + "js": null +} \ No newline at end of file