diff --git a/packages/svelte/src/compiler/phases/1-parse/index.js b/packages/svelte/src/compiler/phases/1-parse/index.js index 35dce084e3..bf09294c08 100644 --- a/packages/svelte/src/compiler/phases/1-parse/index.js +++ b/packages/svelte/src/compiler/phases/1-parse/index.js @@ -164,14 +164,15 @@ export class Parser { /** * @param {string} str * @param {boolean} required + * @param {boolean} required_in_loose */ - eat(str, required = false) { + eat(str, required = false, required_in_loose = true) { if (this.match(str)) { this.index += str.length; return true; } - if (required) { + if (required && (!this.loose || required_in_loose)) { e.expected_token(this.index, str); } diff --git a/packages/svelte/src/compiler/phases/1-parse/state/tag.js b/packages/svelte/src/compiler/phases/1-parse/state/tag.js index 0eadd2b8b5..bacd56d53d 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -454,43 +454,66 @@ function close(parser) { const start = parser.index - 1; let block = parser.current(); + /** Only relevant/reached for loose parsing mode */ + let matched; switch (block.type) { case 'IfBlock': - parser.eat('if', true); + matched = parser.eat('if', true, false); + + if (!matched) { + block.end = start - 1; + parser.pop(); + close(parser); + return; + } + parser.allow_whitespace(); parser.eat('}', true); + while (block.elseif) { block.end = parser.index; parser.stack.pop(); block = /** @type {AST.IfBlock} */ (parser.current()); } + block.end = parser.index; parser.pop(); return; case 'EachBlock': - parser.eat('each', true); + matched = parser.eat('each', true, false); break; case 'KeyBlock': - parser.eat('key', true); + matched = parser.eat('key', true, false); break; case 'AwaitBlock': - parser.eat('await', true); + matched = parser.eat('await', true, false); break; case 'SnippetBlock': - parser.eat('snippet', true); + matched = parser.eat('snippet', true, false); break; case 'RegularElement': - // TODO handle implicitly closed elements - e.block_unexpected_close(start); + if (parser.loose) { + matched = false; + } else { + // TODO handle implicitly closed elements + e.block_unexpected_close(start); + } break; default: e.block_unexpected_close(start); } + if (!matched) { + block.end = start - 1; + parser.pop(); + close(parser); + return; + } + parser.allow_whitespace(); parser.eat('}', true); block.end = parser.index; diff --git a/packages/svelte/tests/parser-legacy/samples/loose-unclosed-block/input.svelte b/packages/svelte/tests/parser-legacy/samples/loose-unclosed-block/input.svelte new file mode 100644 index 0000000000..16fcfcf608 --- /dev/null +++ b/packages/svelte/tests/parser-legacy/samples/loose-unclosed-block/input.svelte @@ -0,0 +1,20 @@ +
+ {#if foo} +
+ + + {#key bar} + + +
+ {#if foo} + {#if bar} + {/if} +
+ +{#if foo} + {#key bar} +{/if} + +{#each x as y} +

hi

diff --git a/packages/svelte/tests/parser-legacy/samples/loose-unclosed-block/output.json b/packages/svelte/tests/parser-legacy/samples/loose-unclosed-block/output.json new file mode 100644 index 0000000000..e6e909f0d5 --- /dev/null +++ b/packages/svelte/tests/parser-legacy/samples/loose-unclosed-block/output.json @@ -0,0 +1,276 @@ +{ + "html": { + "type": "Fragment", + "start": 0, + "end": 150, + "children": [ + { + "type": "Element", + "start": 0, + "end": 23, + "name": "div", + "attributes": [], + "children": [ + { + "type": "Text", + "start": 5, + "end": 7, + "raw": "\n\t", + "data": "\n\t" + }, + { + "type": "IfBlock", + "start": 7, + "end": 17, + "expression": { + "type": "Identifier", + "start": 12, + "end": 15, + "loc": { + "start": { + "line": 2, + "column": 6 + }, + "end": { + "line": 2, + "column": 9 + } + }, + "name": "foo" + }, + "children": [] + } + ] + }, + { + "type": "Text", + "start": 23, + "end": 25, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "InlineComponent", + "start": 25, + "end": 51, + "name": "Comp", + "attributes": [], + "children": [ + { + "type": "Text", + "start": 31, + "end": 33, + "raw": "\n\t", + "data": "\n\t" + }, + { + "type": "KeyBlock", + "start": 33, + "end": 44, + "expression": { + "type": "Identifier", + "start": 39, + "end": 42, + "loc": { + "start": { + "line": 6, + "column": 7 + }, + "end": { + "line": 6, + "column": 10 + } + }, + "name": "bar" + }, + "children": [] + } + ] + }, + { + "type": "Text", + "start": 51, + "end": 53, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "Element", + "start": 53, + "end": 95, + "name": "div", + "attributes": [], + "children": [ + { + "type": "Text", + "start": 58, + "end": 60, + "raw": "\n\t", + "data": "\n\t" + }, + { + "type": "IfBlock", + "start": 60, + "end": 89, + "expression": { + "type": "Identifier", + "start": 65, + "end": 68, + "loc": { + "start": { + "line": 10, + "column": 6 + }, + "end": { + "line": 10, + "column": 9 + } + }, + "name": "foo" + }, + "children": [ + { + "type": "IfBlock", + "start": 72, + "end": 88, + "expression": { + "type": "Identifier", + "start": 77, + "end": 80, + "loc": { + "start": { + "line": 11, + "column": 7 + }, + "end": { + "line": 11, + "column": 10 + } + }, + "name": "bar" + }, + "children": [] + } + ] + } + ] + }, + { + "type": "Text", + "start": 95, + "end": 97, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "IfBlock", + "start": 97, + "end": 124, + "expression": { + "type": "Identifier", + "start": 102, + "end": 105, + "loc": { + "start": { + "line": 15, + "column": 5 + }, + "end": { + "line": 15, + "column": 8 + } + }, + "name": "foo" + }, + "children": [ + { + "type": "KeyBlock", + "start": 108, + "end": 119, + "expression": { + "type": "Identifier", + "start": 114, + "end": 117, + "loc": { + "start": { + "line": 16, + "column": 7 + }, + "end": { + "line": 16, + "column": 10 + } + }, + "name": "bar" + }, + "children": [] + } + ] + }, + { + "type": "Text", + "start": 124, + "end": 126, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "EachBlock", + "start": 126, + "end": 150, + "children": [ + { + "type": "Element", + "start": 141, + "end": 150, + "name": "p", + "attributes": [], + "children": [ + { + "type": "Text", + "start": 144, + "end": 146, + "raw": "hi", + "data": "hi" + } + ] + } + ], + "context": { + "type": "Identifier", + "name": "y", + "start": 138, + "loc": { + "start": { + "line": 19, + "column": 12, + "character": 138 + }, + "end": { + "line": 19, + "column": 13, + "character": 139 + } + }, + "end": 139 + }, + "expression": { + "type": "Identifier", + "start": 133, + "end": 134, + "loc": { + "start": { + "line": 19, + "column": 7 + }, + "end": { + "line": 19, + "column": 8 + } + }, + "name": "x" + } + } + ] + } +} diff --git a/packages/svelte/tests/parser-legacy/samples/loose-unclosed-tag/input.svelte b/packages/svelte/tests/parser-legacy/samples/loose-unclosed-tag/input.svelte index 72cbe6e051..7b147c066f 100644 --- a/packages/svelte/tests/parser-legacy/samples/loose-unclosed-tag/input.svelte +++ b/packages/svelte/tests/parser-legacy/samples/loose-unclosed-tag/input.svelte @@ -6,5 +6,9 @@ +{#if foo} +
+{/if} +

hi

diff --git a/packages/svelte/tests/parser-legacy/samples/loose-unclosed-tag/output.json b/packages/svelte/tests/parser-legacy/samples/loose-unclosed-tag/output.json index 1c75da7fb4..18c39e855b 100644 --- a/packages/svelte/tests/parser-legacy/samples/loose-unclosed-tag/output.json +++ b/packages/svelte/tests/parser-legacy/samples/loose-unclosed-tag/output.json @@ -2,7 +2,7 @@ "html": { "type": "Fragment", "start": 0, - "end": 59, + "end": 83, "children": [ { "type": "Element", @@ -83,30 +83,76 @@ "data": "\n\n" }, { - "type": "Element", + "type": "IfBlock", "start": 44, - "end": 59, + "end": 66, + "expression": { + "type": "Identifier", + "start": 49, + "end": 52, + "loc": { + "start": { + "line": 9, + "column": 5 + }, + "end": { + "line": 9, + "column": 8 + } + }, + "name": "foo" + }, + "children": [ + { + "type": "Element", + "start": 55, + "end": 61, + "name": "div", + "attributes": [], + "children": [ + { + "type": "Text", + "start": 60, + "end": 61, + "raw": "\n", + "data": "\n" + } + ] + } + ] + }, + { + "type": "Text", + "start": 66, + "end": 68, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "Element", + "start": 68, + "end": 83, "name": "div", "attributes": [], "children": [ { "type": "Text", - "start": 49, - "end": 50, + "start": 73, + "end": 74, "raw": "\n", "data": "\n" }, { "type": "Element", - "start": 50, - "end": 59, + "start": 74, + "end": 83, "name": "p", "attributes": [], "children": [ { "type": "Text", - "start": 53, - "end": 55, + "start": 77, + "end": 79, "raw": "hi", "data": "hi" }