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 `
onetwo`?
- 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 `onetwo`?
+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 @@
+
+ - a
+ {#if true}
+
- b
+ {/if}
+
- c
+
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"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}