From af85d145572af19ac4136a9ece12c0d20addc452 Mon Sep 17 00:00:00 2001 From: mrkishi Date: Fri, 17 May 2019 18:22:58 -0300 Subject: [PATCH 1/2] 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 `
  • one
  • 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 `
  • one
  • 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 @@ + 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" + } + ] + } + ] + } + ] + } +} From 506e2da680df21dfb89e0fe1e02b38eea37de44d Mon Sep 17 00:00:00 2001 From: Richard Harris Date: Sun, 18 Aug 2019 20:51:02 -0400 Subject: [PATCH 2/2] update test --- test/parser/samples/implicitly-closed-li-block/output.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/parser/samples/implicitly-closed-li-block/output.json b/test/parser/samples/implicitly-closed-li-block/output.json index bf8252f357..4a75a34223 100644 --- a/test/parser/samples/implicitly-closed-li-block/output.json +++ b/test/parser/samples/implicitly-closed-li-block/output.json @@ -15,6 +15,7 @@ "start": 4, "end": 6, "type": "Text", + "raw": "\n\t", "data": "\n\t" }, { @@ -28,6 +29,7 @@ "start": 10, "end": 13, "type": "Text", + "raw": "a\n\t", "data": "a\n\t" }, { @@ -53,6 +55,7 @@ "start": 30, "end": 33, "type": "Text", + "raw": "b\n\t", "data": "b\n\t" } ] @@ -63,6 +66,7 @@ "start": 38, "end": 40, "type": "Text", + "raw": "\n\t", "data": "\n\t" } ] @@ -78,6 +82,7 @@ "start": 44, "end": 46, "type": "Text", + "raw": "c\n", "data": "c\n" } ] @@ -86,4 +91,4 @@ } ] } -} +} \ No newline at end of file