Skip to content

Commit 3146e93

Browse files
authored
chore: loose parser improvements (#14733)
* handle invalid expression inside each key * handle invalid expression inside each expression * handle invalid expression inside await block * handle "in the middle of typing" components with starting lowercase and dot at the end * changeset
1 parent 36d73ca commit 3146e93

File tree

13 files changed

+836
-99
lines changed

13 files changed

+836
-99
lines changed

.changeset/tiny-kings-serve.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
chore: more loose parser improvements

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import { find_matching_bracket } from '../utils/bracket.js';
77

88
/**
99
* @param {Parser} parser
10+
* @param {string} [opening_token]
1011
* @returns {Expression}
1112
*/
12-
export default function read_expression(parser) {
13+
export default function read_expression(parser, opening_token) {
1314
try {
1415
const node = parse_expression_at(parser.template, parser.ts, parser.index);
1516

@@ -42,7 +43,7 @@ export default function read_expression(parser) {
4243
} catch (err) {
4344
if (parser.loose) {
4445
// Find the next } and treat it as the end of the expression
45-
const end = find_matching_bracket(parser.template, parser.index, '{');
46+
const end = find_matching_bracket(parser.template, parser.index, opening_token ?? '{');
4647
if (end) {
4748
const start = parser.index;
4849
parser.index = end;

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,11 @@ export default function element(parser) {
123123
}
124124

125125
if (!regex_valid_element_name.test(name) && !regex_valid_component_name.test(name)) {
126-
const bounds = { start: start + 1, end: start + 1 + name.length };
127-
e.tag_invalid_name(bounds);
126+
// <div. -> in the middle of typing -> allow in loose mode
127+
if (!parser.loose || !name.endsWith('.')) {
128+
const bounds = { start: start + 1, end: start + 1 + name.length };
129+
e.tag_invalid_name(bounds);
130+
}
128131
}
129132

130133
if (root_only_meta_tags.has(name)) {
@@ -141,7 +144,7 @@ export default function element(parser) {
141144

142145
const type = meta_tags.has(name)
143146
? meta_tags.get(name)
144-
: regex_valid_component_name.test(name)
147+
: regex_valid_component_name.test(name) || (parser.loose && name.endsWith('.'))
145148
? 'Component'
146149
: name === 'title' && parent_is_head(parser.stack)
147150
? 'TitleElement'

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

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,30 @@ function open(parser) {
174174
if (parser.eat('(')) {
175175
parser.allow_whitespace();
176176

177-
key = read_expression(parser);
177+
key = read_expression(parser, '(');
178178
parser.allow_whitespace();
179179
parser.eat(')', true);
180180
parser.allow_whitespace();
181181
}
182182

183-
parser.eat('}', true);
183+
const matches = parser.eat('}', true, false);
184+
185+
if (!matches) {
186+
// Parser may have read the `as` as part of the expression (e.g. in `{#each foo. as x}`)
187+
if (parser.template.slice(parser.index - 4, parser.index) === ' as ') {
188+
const prev_index = parser.index;
189+
context = read_pattern(parser);
190+
parser.eat('}', true);
191+
expression = {
192+
type: 'Identifier',
193+
name: '',
194+
start: expression.start,
195+
end: prev_index - 4
196+
};
197+
} else {
198+
parser.eat('}', true); // rerun to produce the parser error
199+
}
200+
}
184201

185202
/** @type {AST.EachBlock} */
186203
const block = parser.append({
@@ -246,7 +263,39 @@ function open(parser) {
246263
parser.fragments.push(block.pending);
247264
}
248265

249-
parser.eat('}', true);
266+
const matches = parser.eat('}', true, false);
267+
268+
// Parser may have read the `then/catch` as part of the expression (e.g. in `{#await foo. then x}`)
269+
if (!matches) {
270+
if (parser.template.slice(parser.index - 6, parser.index) === ' then ') {
271+
const prev_index = parser.index;
272+
block.value = read_pattern(parser);
273+
parser.eat('}', true);
274+
block.expression = {
275+
type: 'Identifier',
276+
name: '',
277+
start: expression.start,
278+
end: prev_index - 6
279+
};
280+
block.then = block.pending;
281+
block.pending = null;
282+
} else if (parser.template.slice(parser.index - 7, parser.index) === ' catch ') {
283+
const prev_index = parser.index;
284+
block.error = read_pattern(parser);
285+
parser.eat('}', true);
286+
block.expression = {
287+
type: 'Identifier',
288+
name: '',
289+
start: expression.start,
290+
end: prev_index - 7
291+
};
292+
block.catch = block.pending;
293+
block.pending = null;
294+
} else {
295+
parser.eat('}', true); // rerun to produce the parser error
296+
}
297+
}
298+
250299
parser.stack.push(block);
251300

252301
return;

packages/svelte/src/compiler/phases/1-parse/utils/bracket.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ const SQUARE_BRACKET_OPEN = '['.charCodeAt(0);
44
const SQUARE_BRACKET_CLOSE = ']'.charCodeAt(0);
55
const CURLY_BRACKET_OPEN = '{'.charCodeAt(0);
66
const CURLY_BRACKET_CLOSE = '}'.charCodeAt(0);
7+
const PARENTHESES_OPEN = '('.charCodeAt(0);
8+
const PARENTHESES_CLOSE = ')'.charCodeAt(0);
79

810
/** @param {number} code */
911
export function is_bracket_open(code) {
@@ -34,6 +36,9 @@ export function get_bracket_close(open) {
3436
if (open === CURLY_BRACKET_OPEN) {
3537
return CURLY_BRACKET_CLOSE;
3638
}
39+
if (open === PARENTHESES_OPEN) {
40+
return PARENTHESES_CLOSE;
41+
}
3742
}
3843

3944
/**

packages/svelte/tests/parser-legacy/samples/loose-invalid-expression/input.svelte

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,15 @@
99

1010
asd{a.}asd
1111
{foo[bar.]}
12+
13+
{#if x.}{/if}
14+
15+
{#each array as item (item.)}{/each}
16+
17+
{#each obj. as item}{/each}
18+
19+
{#await x.}{/await}
20+
21+
{#await x. then y}{/await}
22+
23+
{#await x. catch y}{/await}

0 commit comments

Comments
 (0)