Skip to content

Commit 72850e5

Browse files
committed
handle invalid expressions
1 parent 553912d commit 72850e5

File tree

6 files changed

+478
-2
lines changed

6 files changed

+478
-2
lines changed

packages/svelte/src/compiler/phases/1-parse/read/expression.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,38 @@ export default function read_expression(parser) {
3939

4040
return /** @type {Expression} */ (node);
4141
} catch (err) {
42+
if (parser.loose) {
43+
// Find the next } and treat it as the end of the expression
44+
let index = parser.index;
45+
let num_braces = 0;
46+
while (index < parser.template.length) {
47+
const char = parser.template[index];
48+
if (char === '{') num_braces += 1;
49+
if (char === '}') {
50+
if (num_braces === 0) {
51+
// We assume that there's some kind of whitespace or the start of the closing tag after the closing brace,
52+
// else this hints at a wrong counting of braces (e.g. in the case of foo={'hi}'})
53+
if (!/[\s>/]/.test(parser.template[index + 1])) {
54+
num_braces += 1;
55+
continue;
56+
}
57+
58+
const start = parser.index;
59+
parser.index = index;
60+
// We don't know what the expression is and signal this by returning an empty identifier
61+
return {
62+
type: 'Identifier',
63+
start,
64+
end: index,
65+
name: ''
66+
};
67+
}
68+
num_braces -= 1;
69+
}
70+
index += 1;
71+
}
72+
}
73+
4274
parser.acorn_error(err);
4375
}
4476
}

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ function read_attribute(parser) {
481481
return spread;
482482
} else {
483483
const value_start = parser.index;
484-
const name = parser.read_identifier();
484+
let name = parser.read_identifier();
485485

486486
if (name === null) {
487487
if (
@@ -491,8 +491,12 @@ function read_attribute(parser) {
491491
// We're likely in an unclosed opening tag and did read part of a block.
492492
// Return null to not crash the parser so it can continue with closing the tag.
493493
return null;
494+
} else if (parser.loose && parser.match('}')) {
495+
// Likely in the middle of typing, just created the shorthand
496+
name = '';
497+
} else {
498+
e.attribute_empty_shorthand(start);
494499
}
495-
e.attribute_empty_shorthand(start);
496500
}
497501

498502
parser.allow_whitespace();
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<div {}></div>
2+
<div foo={}></div>
3+
4+
<div foo={a.}></div>
5+
<div foo={'hi}'.}></div>
6+
<Component onclick={() => x.} />
7+
8+
<input bind:value={a.} />
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
{
2+
"html": {
3+
"type": "Fragment",
4+
"start": 0,
5+
"end": 140,
6+
"children": [
7+
{
8+
"type": "Element",
9+
"start": 0,
10+
"end": 14,
11+
"name": "div",
12+
"attributes": [
13+
{
14+
"type": "Attribute",
15+
"start": 5,
16+
"end": 7,
17+
"name": "",
18+
"value": [
19+
{
20+
"type": "AttributeShorthand",
21+
"start": 6,
22+
"end": 6,
23+
"expression": {
24+
"start": 6,
25+
"end": 6,
26+
"type": "Identifier",
27+
"name": ""
28+
}
29+
}
30+
]
31+
}
32+
],
33+
"children": []
34+
},
35+
{
36+
"type": "Text",
37+
"start": 14,
38+
"end": 15,
39+
"raw": "\n",
40+
"data": "\n"
41+
},
42+
{
43+
"type": "Element",
44+
"start": 15,
45+
"end": 33,
46+
"name": "div",
47+
"attributes": [
48+
{
49+
"type": "Attribute",
50+
"start": 20,
51+
"end": 26,
52+
"name": "foo",
53+
"value": [
54+
{
55+
"type": "MustacheTag",
56+
"start": 24,
57+
"end": 26,
58+
"expression": {
59+
"type": "Identifier",
60+
"start": 25,
61+
"end": 25,
62+
"name": ""
63+
}
64+
}
65+
]
66+
}
67+
],
68+
"children": []
69+
},
70+
{
71+
"type": "Text",
72+
"start": 33,
73+
"end": 35,
74+
"raw": "\n\n",
75+
"data": "\n\n"
76+
},
77+
{
78+
"type": "Element",
79+
"start": 35,
80+
"end": 55,
81+
"name": "div",
82+
"attributes": [
83+
{
84+
"type": "Attribute",
85+
"start": 40,
86+
"end": 48,
87+
"name": "foo",
88+
"value": [
89+
{
90+
"type": "MustacheTag",
91+
"start": 44,
92+
"end": 48,
93+
"expression": {
94+
"type": "Identifier",
95+
"start": 45,
96+
"end": 47,
97+
"name": ""
98+
}
99+
}
100+
]
101+
}
102+
],
103+
"children": []
104+
},
105+
{
106+
"type": "Text",
107+
"start": 55,
108+
"end": 56,
109+
"raw": "\n",
110+
"data": "\n"
111+
},
112+
{
113+
"type": "Element",
114+
"start": 56,
115+
"end": 80,
116+
"name": "div",
117+
"attributes": [
118+
{
119+
"type": "Attribute",
120+
"start": 61,
121+
"end": 73,
122+
"name": "foo",
123+
"value": [
124+
{
125+
"type": "MustacheTag",
126+
"start": 65,
127+
"end": 73,
128+
"expression": {
129+
"type": "Identifier",
130+
"start": 66,
131+
"end": 72,
132+
"name": ""
133+
}
134+
}
135+
]
136+
}
137+
],
138+
"children": []
139+
},
140+
{
141+
"type": "Text",
142+
"start": 80,
143+
"end": 81,
144+
"raw": "\n",
145+
"data": "\n"
146+
},
147+
{
148+
"type": "InlineComponent",
149+
"start": 81,
150+
"end": 113,
151+
"name": "Component",
152+
"attributes": [
153+
{
154+
"type": "Attribute",
155+
"start": 92,
156+
"end": 110,
157+
"name": "onclick",
158+
"value": [
159+
{
160+
"type": "MustacheTag",
161+
"start": 100,
162+
"end": 110,
163+
"expression": {
164+
"type": "Identifier",
165+
"start": 101,
166+
"end": 109,
167+
"name": ""
168+
}
169+
}
170+
]
171+
}
172+
],
173+
"children": []
174+
},
175+
{
176+
"type": "Text",
177+
"start": 113,
178+
"end": 115,
179+
"raw": "\n\n",
180+
"data": "\n\n"
181+
},
182+
{
183+
"type": "Element",
184+
"start": 115,
185+
"end": 140,
186+
"name": "input",
187+
"attributes": [
188+
{
189+
"start": 122,
190+
"end": 137,
191+
"type": "Binding",
192+
"name": "value",
193+
"expression": {
194+
"type": "Identifier",
195+
"start": 134,
196+
"end": 136,
197+
"name": ""
198+
},
199+
"modifiers": []
200+
}
201+
],
202+
"children": []
203+
}
204+
]
205+
}
206+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<div {}></div>
2+
<div foo={}></div>
3+
4+
<div foo={a.}></div>
5+
<div foo={'hi}'.}></div>
6+
<Component onclick={() => x.} />
7+
8+
<input bind:value={a.} />

0 commit comments

Comments
 (0)