Skip to content

Commit 553912d

Browse files
committed
handle unclosed opening tags
1 parent 7337612 commit 553912d

File tree

6 files changed

+840
-4
lines changed

6 files changed

+840
-4
lines changed

packages/svelte/src/compiler/phases/1-parse/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ export class Parser {
247247
/** @param {RegExp} pattern */
248248
read_until(pattern) {
249249
if (this.index >= this.template.length) {
250+
if (this.loose) return '';
250251
e.unexpected_eof(this.template.length);
251252
}
252253

packages/svelte/src/compiler/phases/1-parse/state/element.js

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -319,10 +319,30 @@ export default function element(parser) {
319319
parser.append(element);
320320

321321
const self_closing = parser.eat('/') || is_void(name);
322+
const closed = parser.eat('>', true, false);
323+
324+
// Loose parsing mode
325+
if (!closed) {
326+
// We may have eaten an opening `<` of the next element and treated is as an attribute...
327+
const last = element.attributes.at(-1);
328+
if (last?.type === 'Attribute' && last.name === '<') {
329+
parser.index = last.start;
330+
element.attributes.pop();
331+
}
332+
// ... or we may have eaten part of a following block
333+
else {
334+
const prev_1 = parser.template[parser.index - 1];
335+
const prev_2 = parser.template[parser.index - 2];
336+
const current = parser.template[parser.index];
337+
if (prev_2 === '{' && prev_1 === '/') {
338+
parser.index -= 2;
339+
} else if (prev_1 === '{' && (current === '#' || current === '@' || current === ':')) {
340+
parser.index -= 1;
341+
}
342+
}
343+
}
322344

323-
parser.eat('>', true);
324-
325-
if (self_closing) {
345+
if (self_closing || !closed) {
326346
// don't push self-closing elements onto the stack
327347
element.end = parser.index;
328348
} else if (name === 'textarea') {
@@ -464,6 +484,14 @@ function read_attribute(parser) {
464484
const name = parser.read_identifier();
465485

466486
if (name === null) {
487+
if (
488+
parser.loose &&
489+
(parser.match('#') || parser.match('/') || parser.match('@') || parser.match(':'))
490+
) {
491+
// We're likely in an unclosed opening tag and did read part of a block.
492+
// Return null to not crash the parser so it can continue with closing the tag.
493+
return null;
494+
}
467495
e.attribute_empty_shorthand(start);
468496
}
469497

@@ -740,5 +768,9 @@ function read_sequence(parser, done, location) {
740768
}
741769
}
742770

743-
e.unexpected_eof(parser.template.length);
771+
if (parser.loose) {
772+
return chunks;
773+
} else {
774+
e.unexpected_eof(parser.template.length);
775+
}
744776
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<div>
2+
<Comp foo={bar}
3+
</div>
4+
5+
<div>
6+
<span foo={bar}
7+
</div>
8+
9+
{#if foo}
10+
<Comp foo={bar}
11+
{/if}
12+
13+
{#if foo}
14+
<Comp foo={bar}
15+
{#if bar}
16+
{bar}
17+
{/if}
18+
{/if}
19+
20+
<div foo={bar}

0 commit comments

Comments
 (0)