From 6994021ef91da47e89e703cc678e0784eff36e8c Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Wed, 11 Dec 2024 11:16:20 +0100 Subject: [PATCH] handle unclosed tags --- .../src/compiler/phases/1-parse/index.js | 4 +- .../compiler/phases/1-parse/state/element.js | 2 +- .../samples/loose-unclosed-tag/input.svelte | 10 ++ .../samples/loose-unclosed-tag/output.json | 119 +++++++++++++++ packages/svelte/tests/parser-legacy/test.ts | 4 +- .../samples/loose-unclosed-tag/input.svelte | 10 ++ .../samples/loose-unclosed-tag/output.json | 141 ++++++++++++++++++ packages/svelte/tests/parser-modern/test.ts | 3 +- 8 files changed, 289 insertions(+), 4 deletions(-) create mode 100644 packages/svelte/tests/parser-legacy/samples/loose-unclosed-tag/input.svelte create mode 100644 packages/svelte/tests/parser-legacy/samples/loose-unclosed-tag/output.json create mode 100644 packages/svelte/tests/parser-modern/samples/loose-unclosed-tag/input.svelte create mode 100644 packages/svelte/tests/parser-modern/samples/loose-unclosed-tag/output.json diff --git a/packages/svelte/src/compiler/phases/1-parse/index.js b/packages/svelte/src/compiler/phases/1-parse/index.js index d67c676678..35dce084e3 100644 --- a/packages/svelte/src/compiler/phases/1-parse/index.js +++ b/packages/svelte/src/compiler/phases/1-parse/index.js @@ -99,7 +99,9 @@ export class Parser { if (this.stack.length > 1) { const current = this.current(); - if (current.type === 'RegularElement') { + if (this.loose) { + current.end = this.template.length; + } else if (current.type === 'RegularElement') { current.end = current.start + 1; e.element_unclosed(current, current.name); } else { diff --git a/packages/svelte/src/compiler/phases/1-parse/state/element.js b/packages/svelte/src/compiler/phases/1-parse/state/element.js index 45350bb1ae..65e917c34f 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/element.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/element.js @@ -80,7 +80,7 @@ export default function element(parser) { // close any elements that don't have their own closing tags, e.g.

while (/** @type {AST.RegularElement} */ (parent).name !== name) { - if (parent.type !== 'RegularElement') { + if (parent.type !== 'RegularElement' && !parser.loose) { if (parser.last_auto_closed_tag && parser.last_auto_closed_tag.tag === name) { e.element_invalid_closing_tag_autoclosed(start, name, parser.last_auto_closed_tag.reason); } else { 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 new file mode 100644 index 0000000000..72cbe6e051 --- /dev/null +++ b/packages/svelte/tests/parser-legacy/samples/loose-unclosed-tag/input.svelte @@ -0,0 +1,10 @@ +
+ +
+ +
+ +
+ +
+

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 new file mode 100644 index 0000000000..1c75da7fb4 --- /dev/null +++ b/packages/svelte/tests/parser-legacy/samples/loose-unclosed-tag/output.json @@ -0,0 +1,119 @@ +{ + "html": { + "type": "Fragment", + "start": 0, + "end": 59, + "children": [ + { + "type": "Element", + "start": 0, + "end": 20, + "name": "div", + "attributes": [], + "children": [ + { + "type": "Text", + "start": 5, + "end": 7, + "raw": "\n\t", + "data": "\n\t" + }, + { + "type": "InlineComponent", + "start": 7, + "end": 14, + "name": "Comp", + "attributes": [], + "children": [ + { + "type": "Text", + "start": 13, + "end": 14, + "raw": "\n", + "data": "\n" + } + ] + } + ] + }, + { + "type": "Text", + "start": 20, + "end": 22, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "Element", + "start": 22, + "end": 42, + "name": "div", + "attributes": [], + "children": [ + { + "type": "Text", + "start": 27, + "end": 29, + "raw": "\n\t", + "data": "\n\t" + }, + { + "type": "Element", + "start": 29, + "end": 36, + "name": "span", + "attributes": [], + "children": [ + { + "type": "Text", + "start": 35, + "end": 36, + "raw": "\n", + "data": "\n" + } + ] + } + ] + }, + { + "type": "Text", + "start": 42, + "end": 44, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "Element", + "start": 44, + "end": 59, + "name": "div", + "attributes": [], + "children": [ + { + "type": "Text", + "start": 49, + "end": 50, + "raw": "\n", + "data": "\n" + }, + { + "type": "Element", + "start": 50, + "end": 59, + "name": "p", + "attributes": [], + "children": [ + { + "type": "Text", + "start": 53, + "end": 55, + "raw": "hi", + "data": "hi" + } + ] + } + ] + } + ] + } +} diff --git a/packages/svelte/tests/parser-legacy/test.ts b/packages/svelte/tests/parser-legacy/test.ts index ad93a0f2e9..987253d8d0 100644 --- a/packages/svelte/tests/parser-legacy/test.ts +++ b/packages/svelte/tests/parser-legacy/test.ts @@ -12,7 +12,9 @@ const { test, run } = suite(async (config, cwd) => { .replace(/\s+$/, '') .replace(/\r/g, ''); - const actual = JSON.parse(JSON.stringify(parse(input))); + const actual = JSON.parse( + JSON.stringify(parse(input, { loose: cwd.split('/').pop()!.startsWith('loose-') })) + ); // run `UPDATE_SNAPSHOTS=true pnpm test parser` to update parser tests if (process.env.UPDATE_SNAPSHOTS) { diff --git a/packages/svelte/tests/parser-modern/samples/loose-unclosed-tag/input.svelte b/packages/svelte/tests/parser-modern/samples/loose-unclosed-tag/input.svelte new file mode 100644 index 0000000000..72cbe6e051 --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/loose-unclosed-tag/input.svelte @@ -0,0 +1,10 @@ +
+ +
+ +
+ +
+ +
+

hi

diff --git a/packages/svelte/tests/parser-modern/samples/loose-unclosed-tag/output.json b/packages/svelte/tests/parser-modern/samples/loose-unclosed-tag/output.json new file mode 100644 index 0000000000..ba82219526 --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/loose-unclosed-tag/output.json @@ -0,0 +1,141 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 59, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "RegularElement", + "start": 0, + "end": 20, + "name": "div", + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 5, + "end": 7, + "raw": "\n\t", + "data": "\n\t" + }, + { + "type": "Component", + "start": 7, + "end": 14, + "name": "Comp", + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 13, + "end": 14, + "raw": "\n", + "data": "\n" + } + ] + } + } + ] + } + }, + { + "type": "Text", + "start": 20, + "end": 22, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "RegularElement", + "start": 22, + "end": 42, + "name": "div", + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 27, + "end": 29, + "raw": "\n\t", + "data": "\n\t" + }, + { + "type": "RegularElement", + "start": 29, + "end": 36, + "name": "span", + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 35, + "end": 36, + "raw": "\n", + "data": "\n" + } + ] + } + } + ] + } + }, + { + "type": "Text", + "start": 42, + "end": 44, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "RegularElement", + "start": 44, + "end": 59, + "name": "div", + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 49, + "end": 50, + "raw": "\n", + "data": "\n" + }, + { + "type": "RegularElement", + "start": 50, + "end": 59, + "name": "p", + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 53, + "end": 55, + "raw": "hi", + "data": "hi" + } + ] + } + } + ] + } + } + ] + }, + "options": null +} diff --git a/packages/svelte/tests/parser-modern/test.ts b/packages/svelte/tests/parser-modern/test.ts index 04af8b0780..b47d4a4879 100644 --- a/packages/svelte/tests/parser-modern/test.ts +++ b/packages/svelte/tests/parser-modern/test.ts @@ -15,7 +15,8 @@ const { test, run } = suite(async (config, cwd) => { const actual = JSON.parse( JSON.stringify( parse(input, { - modern: true + modern: true, + loose: cwd.split('/').pop()!.startsWith('loose-') }) ) );