From af85d145572af19ac4136a9ece12c0d20addc452 Mon Sep 17 00:00:00 2001 From: mrkishi <mauriciokishi@gmail.com> Date: Fri, 17 May 2019 18:22:58 -0300 Subject: [PATCH] allow end tag omission in blocks --- src/parse/state/mustache.ts | 7 ++ src/parse/state/tag.ts | 37 +------- src/parse/utils/html.ts | 37 ++++++++ .../implicitly-closed-li-block/input.svelte | 7 ++ .../implicitly-closed-li-block/output.json | 89 +++++++++++++++++++ 5 files changed, 144 insertions(+), 33 deletions(-) create mode 100644 test/parser/samples/implicitly-closed-li-block/input.svelte create mode 100644 test/parser/samples/implicitly-closed-li-block/output.json diff --git a/src/parse/state/mustache.ts b/src/parse/state/mustache.ts index b4d0c6ad8d..e0b0301901 100644 --- a/src/parse/state/mustache.ts +++ b/src/parse/state/mustache.ts @@ -1,5 +1,6 @@ import read_context from '../read/context'; import read_expression from '../read/expression'; +import { closing_tag_omitted } from '../utils/html'; import { whitespace } from '../../utils/patterns'; import { trim_start, trim_end } from '../../utils/trim'; import { Parser } from '../index'; @@ -41,6 +42,12 @@ export default function mustache(parser: Parser) { let block = parser.current(); let expected; + if (closing_tag_omitted(block.name)) { + block.end = start; + parser.stack.pop(); + block = parser.current(); + } + if (block.type === 'ElseBlock' || block.type === 'PendingBlock' || block.type === 'ThenBlock' || block.type === 'CatchBlock') { block.end = start; parser.stack.pop(); diff --git a/src/parse/state/tag.ts b/src/parse/state/tag.ts index 56195549d8..260f160bf2 100644 --- a/src/parse/state/tag.ts +++ b/src/parse/state/tag.ts @@ -1,7 +1,7 @@ import read_expression from '../read/expression'; import read_script from '../read/script'; import read_style from '../read/style'; -import { decode_character_references } from '../utils/html'; +import { decode_character_references, closing_tag_omitted } from '../utils/html'; import { is_void } from '../../utils/names'; import { Parser } from '../index'; import { Node } from '../../interfaces'; @@ -39,31 +39,6 @@ const specials = new Map([ const SELF = /^svelte:self(?=[\s\/>])/; const COMPONENT = /^svelte:component(?=[\s\/>])/; -// based on http://developers.whatwg.org/syntax.html#syntax-tag-omission -const disallowed_contents = new Map([ - ['li', new Set(['li'])], - ['dt', new Set(['dt', 'dd'])], - ['dd', new Set(['dt', 'dd'])], - [ - 'p', - new Set( - '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', new Set(['rt', 'rp'])], - ['rp', new Set(['rt', 'rp'])], - ['optgroup', new Set(['optgroup'])], - ['option', new Set(['option', 'optgroup'])], - ['thead', new Set(['tbody', 'tfoot'])], - ['tbody', new Set(['tbody', 'tfoot'])], - ['tfoot', new Set(['tbody'])], - ['tr', new Set(['tr', 'tbody'])], - ['td', new Set(['td', 'th', 'tr'])], - ['th', new Set(['td', 'th', 'tr'])], -]); - function parent_is_head(stack) { let i = stack.length; while (i--) { @@ -173,13 +148,9 @@ export default function tag(parser: Parser) { parser.stack.pop(); return; - } else if (disallowed_contents.has(parent.name)) { - // can this be a child of the parent element, or does it implicitly - // close it, like `<li>one<li>two`? - if (disallowed_contents.get(parent.name).has(name)) { - parent.end = start; - parser.stack.pop(); - } + } else if (closing_tag_omitted(parent.name, name)) { + parent.end = start; + parser.stack.pop(); } const unique_names = new Set(); diff --git a/src/parse/utils/html.ts b/src/parse/utils/html.ts index b49989eacd..f5fc8a4590 100644 --- a/src/parse/utils/html.ts +++ b/src/parse/utils/html.ts @@ -112,3 +112,40 @@ function validate_code(code: number) { return NUL; } + +// based on http://developers.whatwg.org/syntax.html#syntax-tag-omission +const disallowed_contents = new Map([ + ['li', new Set(['li'])], + ['dt', new Set(['dt', 'dd'])], + ['dd', new Set(['dt', 'dd'])], + [ + 'p', + new Set( + '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', new Set(['rt', 'rp'])], + ['rp', new Set(['rt', 'rp'])], + ['optgroup', new Set(['optgroup'])], + ['option', new Set(['option', 'optgroup'])], + ['thead', new Set(['tbody', 'tfoot'])], + ['tbody', new Set(['tbody', 'tfoot'])], + ['tfoot', new Set(['tbody'])], + ['tr', new Set(['tr', 'tbody'])], + ['td', new Set(['td', 'th', 'tr'])], + ['th', new Set(['td', 'th', 'tr'])], +]); + +// can this be a child of the parent element, or does it implicitly +// close it, like `<li>one<li>two`? +export function closing_tag_omitted(current: string, next?: string) { + if (disallowed_contents.has(current)) { + if (!next || disallowed_contents.get(current).has(next)) { + return true; + } + } + + return false; +} diff --git a/test/parser/samples/implicitly-closed-li-block/input.svelte b/test/parser/samples/implicitly-closed-li-block/input.svelte new file mode 100644 index 0000000000..05fc778999 --- /dev/null +++ b/test/parser/samples/implicitly-closed-li-block/input.svelte @@ -0,0 +1,7 @@ +<ul> + <li>a + {#if true} + <li>b + {/if} + <li>c +</ul> diff --git a/test/parser/samples/implicitly-closed-li-block/output.json b/test/parser/samples/implicitly-closed-li-block/output.json new file mode 100644 index 0000000000..bf8252f357 --- /dev/null +++ b/test/parser/samples/implicitly-closed-li-block/output.json @@ -0,0 +1,89 @@ +{ + "html": { + "start": 0, + "end": 51, + "type": "Fragment", + "children": [ + { + "start": 0, + "end": 51, + "type": "Element", + "name": "ul", + "attributes": [], + "children": [ + { + "start": 4, + "end": 6, + "type": "Text", + "data": "\n\t" + }, + { + "start": 6, + "end": 40, + "type": "Element", + "name": "li", + "attributes": [], + "children": [ + { + "start": 10, + "end": 13, + "type": "Text", + "data": "a\n\t" + }, + { + "start": 13, + "end": 38, + "type": "IfBlock", + "expression": { + "type": "Literal", + "start": 18, + "end": 22, + "value": true, + "raw": "true" + }, + "children": [ + { + "start": 26, + "end": 33, + "type": "Element", + "name": "li", + "attributes": [], + "children": [ + { + "start": 30, + "end": 33, + "type": "Text", + "data": "b\n\t" + } + ] + } + ] + }, + { + "start": 38, + "end": 40, + "type": "Text", + "data": "\n\t" + } + ] + }, + { + "start": 40, + "end": 46, + "type": "Element", + "name": "li", + "attributes": [], + "children": [ + { + "start": 44, + "end": 46, + "type": "Text", + "data": "c\n" + } + ] + } + ] + } + ] + } +}