Skip to content

Commit 7337612

Browse files
committed
handle unclosed blocks
1 parent 6994021 commit 7337612

File tree

6 files changed

+388
-18
lines changed

6 files changed

+388
-18
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,14 +164,15 @@ export class Parser {
164164
/**
165165
* @param {string} str
166166
* @param {boolean} required
167+
* @param {boolean} required_in_loose
167168
*/
168-
eat(str, required = false) {
169+
eat(str, required = false, required_in_loose = true) {
169170
if (this.match(str)) {
170171
this.index += str.length;
171172
return true;
172173
}
173174

174-
if (required) {
175+
if (required && (!this.loose || required_in_loose)) {
175176
e.expected_token(this.index, str);
176177
}
177178

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

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -454,43 +454,66 @@ function close(parser) {
454454
const start = parser.index - 1;
455455

456456
let block = parser.current();
457+
/** Only relevant/reached for loose parsing mode */
458+
let matched;
457459

458460
switch (block.type) {
459461
case 'IfBlock':
460-
parser.eat('if', true);
462+
matched = parser.eat('if', true, false);
463+
464+
if (!matched) {
465+
block.end = start - 1;
466+
parser.pop();
467+
close(parser);
468+
return;
469+
}
470+
461471
parser.allow_whitespace();
462472
parser.eat('}', true);
473+
463474
while (block.elseif) {
464475
block.end = parser.index;
465476
parser.stack.pop();
466477
block = /** @type {AST.IfBlock} */ (parser.current());
467478
}
479+
468480
block.end = parser.index;
469481
parser.pop();
470482
return;
471483

472484
case 'EachBlock':
473-
parser.eat('each', true);
485+
matched = parser.eat('each', true, false);
474486
break;
475487
case 'KeyBlock':
476-
parser.eat('key', true);
488+
matched = parser.eat('key', true, false);
477489
break;
478490
case 'AwaitBlock':
479-
parser.eat('await', true);
491+
matched = parser.eat('await', true, false);
480492
break;
481493
case 'SnippetBlock':
482-
parser.eat('snippet', true);
494+
matched = parser.eat('snippet', true, false);
483495
break;
484496

485497
case 'RegularElement':
486-
// TODO handle implicitly closed elements
487-
e.block_unexpected_close(start);
498+
if (parser.loose) {
499+
matched = false;
500+
} else {
501+
// TODO handle implicitly closed elements
502+
e.block_unexpected_close(start);
503+
}
488504
break;
489505

490506
default:
491507
e.block_unexpected_close(start);
492508
}
493509

510+
if (!matched) {
511+
block.end = start - 1;
512+
parser.pop();
513+
close(parser);
514+
return;
515+
}
516+
494517
parser.allow_whitespace();
495518
parser.eat('}', true);
496519
block.end = parser.index;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<div>
2+
{#if foo}
3+
</div>
4+
5+
<Comp>
6+
{#key bar}
7+
</Comp>
8+
9+
<div>
10+
{#if foo}
11+
{#if bar}
12+
{/if}
13+
</div>
14+
15+
{#if foo}
16+
{#key bar}
17+
{/if}
18+
19+
{#each x as y}
20+
<p>hi</p>
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
{
2+
"html": {
3+
"type": "Fragment",
4+
"start": 0,
5+
"end": 150,
6+
"children": [
7+
{
8+
"type": "Element",
9+
"start": 0,
10+
"end": 23,
11+
"name": "div",
12+
"attributes": [],
13+
"children": [
14+
{
15+
"type": "Text",
16+
"start": 5,
17+
"end": 7,
18+
"raw": "\n\t",
19+
"data": "\n\t"
20+
},
21+
{
22+
"type": "IfBlock",
23+
"start": 7,
24+
"end": 17,
25+
"expression": {
26+
"type": "Identifier",
27+
"start": 12,
28+
"end": 15,
29+
"loc": {
30+
"start": {
31+
"line": 2,
32+
"column": 6
33+
},
34+
"end": {
35+
"line": 2,
36+
"column": 9
37+
}
38+
},
39+
"name": "foo"
40+
},
41+
"children": []
42+
}
43+
]
44+
},
45+
{
46+
"type": "Text",
47+
"start": 23,
48+
"end": 25,
49+
"raw": "\n\n",
50+
"data": "\n\n"
51+
},
52+
{
53+
"type": "InlineComponent",
54+
"start": 25,
55+
"end": 51,
56+
"name": "Comp",
57+
"attributes": [],
58+
"children": [
59+
{
60+
"type": "Text",
61+
"start": 31,
62+
"end": 33,
63+
"raw": "\n\t",
64+
"data": "\n\t"
65+
},
66+
{
67+
"type": "KeyBlock",
68+
"start": 33,
69+
"end": 44,
70+
"expression": {
71+
"type": "Identifier",
72+
"start": 39,
73+
"end": 42,
74+
"loc": {
75+
"start": {
76+
"line": 6,
77+
"column": 7
78+
},
79+
"end": {
80+
"line": 6,
81+
"column": 10
82+
}
83+
},
84+
"name": "bar"
85+
},
86+
"children": []
87+
}
88+
]
89+
},
90+
{
91+
"type": "Text",
92+
"start": 51,
93+
"end": 53,
94+
"raw": "\n\n",
95+
"data": "\n\n"
96+
},
97+
{
98+
"type": "Element",
99+
"start": 53,
100+
"end": 95,
101+
"name": "div",
102+
"attributes": [],
103+
"children": [
104+
{
105+
"type": "Text",
106+
"start": 58,
107+
"end": 60,
108+
"raw": "\n\t",
109+
"data": "\n\t"
110+
},
111+
{
112+
"type": "IfBlock",
113+
"start": 60,
114+
"end": 89,
115+
"expression": {
116+
"type": "Identifier",
117+
"start": 65,
118+
"end": 68,
119+
"loc": {
120+
"start": {
121+
"line": 10,
122+
"column": 6
123+
},
124+
"end": {
125+
"line": 10,
126+
"column": 9
127+
}
128+
},
129+
"name": "foo"
130+
},
131+
"children": [
132+
{
133+
"type": "IfBlock",
134+
"start": 72,
135+
"end": 88,
136+
"expression": {
137+
"type": "Identifier",
138+
"start": 77,
139+
"end": 80,
140+
"loc": {
141+
"start": {
142+
"line": 11,
143+
"column": 7
144+
},
145+
"end": {
146+
"line": 11,
147+
"column": 10
148+
}
149+
},
150+
"name": "bar"
151+
},
152+
"children": []
153+
}
154+
]
155+
}
156+
]
157+
},
158+
{
159+
"type": "Text",
160+
"start": 95,
161+
"end": 97,
162+
"raw": "\n\n",
163+
"data": "\n\n"
164+
},
165+
{
166+
"type": "IfBlock",
167+
"start": 97,
168+
"end": 124,
169+
"expression": {
170+
"type": "Identifier",
171+
"start": 102,
172+
"end": 105,
173+
"loc": {
174+
"start": {
175+
"line": 15,
176+
"column": 5
177+
},
178+
"end": {
179+
"line": 15,
180+
"column": 8
181+
}
182+
},
183+
"name": "foo"
184+
},
185+
"children": [
186+
{
187+
"type": "KeyBlock",
188+
"start": 108,
189+
"end": 119,
190+
"expression": {
191+
"type": "Identifier",
192+
"start": 114,
193+
"end": 117,
194+
"loc": {
195+
"start": {
196+
"line": 16,
197+
"column": 7
198+
},
199+
"end": {
200+
"line": 16,
201+
"column": 10
202+
}
203+
},
204+
"name": "bar"
205+
},
206+
"children": []
207+
}
208+
]
209+
},
210+
{
211+
"type": "Text",
212+
"start": 124,
213+
"end": 126,
214+
"raw": "\n\n",
215+
"data": "\n\n"
216+
},
217+
{
218+
"type": "EachBlock",
219+
"start": 126,
220+
"end": 150,
221+
"children": [
222+
{
223+
"type": "Element",
224+
"start": 141,
225+
"end": 150,
226+
"name": "p",
227+
"attributes": [],
228+
"children": [
229+
{
230+
"type": "Text",
231+
"start": 144,
232+
"end": 146,
233+
"raw": "hi",
234+
"data": "hi"
235+
}
236+
]
237+
}
238+
],
239+
"context": {
240+
"type": "Identifier",
241+
"name": "y",
242+
"start": 138,
243+
"loc": {
244+
"start": {
245+
"line": 19,
246+
"column": 12,
247+
"character": 138
248+
},
249+
"end": {
250+
"line": 19,
251+
"column": 13,
252+
"character": 139
253+
}
254+
},
255+
"end": 139
256+
},
257+
"expression": {
258+
"type": "Identifier",
259+
"start": 133,
260+
"end": 134,
261+
"loc": {
262+
"start": {
263+
"line": 19,
264+
"column": 7
265+
},
266+
"end": {
267+
"line": 19,
268+
"column": 8
269+
}
270+
},
271+
"name": "x"
272+
}
273+
}
274+
]
275+
}
276+
}

packages/svelte/tests/parser-legacy/samples/loose-unclosed-tag/input.svelte

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,9 @@
66
<span>
77
</div>
88

9+
{#if foo}
10+
<div>
11+
{/if}
12+
913
<div>
1014
<p>hi</p>

0 commit comments

Comments
 (0)