Skip to content

Commit 61b7ec5

Browse files
authored
fix(html): support nested svelte each destructuring (#9692)
1 parent 038bf40 commit 61b7ec5

9 files changed

Lines changed: 175 additions & 9 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@biomejs/biome": patch
3+
---
4+
5+
Fixed Svelte `#each` destructuring parsing and formatting for nested patterns such as `[key, { a, b }]`.

crates/biome_html_formatter/src/svelte/any/binding_assignment_binding.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ impl FormatRule<AnySvelteBindingAssignmentBinding> for FormatAnySvelteBindingAss
1212
f: &mut HtmlFormatter,
1313
) -> FormatResult<()> {
1414
match node {
15+
AnySvelteBindingAssignmentBinding::AnySvelteDestructuredName(node) => {
16+
node.format().fmt(f)
17+
}
1518
AnySvelteBindingAssignmentBinding::SvelteName(node) => node.format().fmt(f),
1619
AnySvelteBindingAssignmentBinding::SvelteRestBinding(node) => node.format().fmt(f),
1720
}

crates/biome_html_formatter/tests/specs/html/svelte/each_with_destructuring.svelte

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@
1313
{#each items as { id, ...rest }}
1414
<div>{id}</div>
1515
{/each}
16+
17+
{#each Object.entries(foo) as [key, { a, b }]}
18+
{a} {b}
19+
{/each}

crates/biome_html_formatter/tests/specs/html/svelte/each_with_destructuring.svelte.snap

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
source: crates/biome_formatter_test/src/snapshot_builder.rs
3+
assertion_line: 240
34
info: svelte/each_with_destructuring.svelte
45
---
56

@@ -22,6 +23,10 @@ info: svelte/each_with_destructuring.svelte
2223
<div>{id}</div>
2324
{/each}
2425
26+
{#each Object.entries(foo) as [key, { a, b }]}
27+
{a} {b}
28+
{/each}
29+
2530
```
2631

2732

@@ -44,4 +49,8 @@ info: svelte/each_with_destructuring.svelte
4449
<div>{id}</div>
4550
{/each}
4651
52+
{#each Object.entries(foo) as [ key, { a, b } ]}
53+
{a} {b}
54+
{/each}
55+
4756
```

crates/biome_html_parser/src/syntax/svelte.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -761,7 +761,7 @@ fn parse_square_destructured_name(p: &mut HtmlParser) -> ParsedSyntax {
761761

762762
SvelteBindingAssignmentBindingList.parse_list(p);
763763

764-
p.expect(T![']']);
764+
p.expect_with_context(T![']'], HtmlLexContext::Svelte);
765765

766766
Present(m.complete(p, SVELTE_SQUARE_DESTRUCTURED_NAME))
767767
}
@@ -1072,6 +1072,14 @@ impl ParseSeparatedList for SvelteBindingAssignmentBindingList {
10721072
fn parse_element(&mut self, p: &mut Self::Parser<'_>) -> ParsedSyntax {
10731073
if p.at(T![...]) {
10741074
parse_rest_name(p)
1075+
} else if p.at(T!['{']) {
1076+
let result = parse_curly_destructured_name(p);
1077+
p.re_lex(HtmlReLexContext::Svelte);
1078+
result
1079+
} else if p.at(T!['[']) {
1080+
let result = parse_square_destructured_name(p);
1081+
p.re_lex(HtmlReLexContext::Svelte);
1082+
result
10751083
} else {
10761084
parse_svelte_name(p)
10771085
}

crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_destructuring.svelte

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@
1313
{#each items as { id, ...rest }}
1414
<div>{id}</div>
1515
{/each}
16+
17+
{#each Object.entries(foo) as [key, { a, b }]}
18+
{a} {b}
19+
{/each}

crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_destructuring.svelte.snap

Lines changed: 112 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
source: crates/biome_html_parser/tests/spec_test.rs
3+
assertion_line: 145
34
expression: snapshot
45
---
56

@@ -22,6 +23,10 @@ expression: snapshot
2223
<div>{id}</div>
2324
{/each}
2425
26+
{#each Object.entries(foo) as [key, { a, b }]}
27+
{a} {b}
28+
{/each}
29+
2530
```
2631

2732

@@ -336,19 +341,79 @@ HtmlRoot {
336341
r_curly_token: R_CURLY@270..271 "}" [] [],
337342
},
338343
},
344+
SvelteEachBlock {
345+
opening_block: SvelteEachOpeningBlock {
346+
sv_curly_hash_token: SV_CURLY_HASH@271..275 "{#" [Newline("\n"), Newline("\n")] [],
347+
each_token: EACH_KW@275..279 "each" [] [],
348+
list: HtmlTextExpression {
349+
html_literal_token: HTML_LITERAL@279..300 " Object.entries(foo) " [] [],
350+
},
351+
item: SvelteEachAsKeyedItem {
352+
as_token: AS_KW@300..303 "as" [] [Whitespace(" ")],
353+
name: SvelteSquareDestructuredName {
354+
l_brack_token: L_BRACKET@303..304 "[" [] [],
355+
names: SvelteBindingAssignmentBindingList [
356+
SvelteName {
357+
ident_token: IDENT@304..307 "key" [] [],
358+
},
359+
COMMA@307..309 "," [] [Whitespace(" ")],
360+
SvelteCurlyDestructuredName {
361+
l_curly_token: L_CURLY@309..311 "{" [] [Whitespace(" ")],
362+
names: SvelteBindingAssignmentBindingList [
363+
SvelteName {
364+
ident_token: IDENT@311..312 "a" [] [],
365+
},
366+
COMMA@312..314 "," [] [Whitespace(" ")],
367+
SvelteName {
368+
ident_token: IDENT@314..316 "b" [] [Whitespace(" ")],
369+
},
370+
],
371+
r_curly_token: R_CURLY@316..317 "}" [] [],
372+
},
373+
],
374+
r_brack_token: R_BRACKET@317..318 "]" [] [],
375+
},
376+
index: missing (optional),
377+
key: missing (optional),
378+
},
379+
r_curly_token: R_CURLY@318..319 "}" [] [],
380+
},
381+
children: HtmlElementList [
382+
HtmlSingleTextExpression {
383+
l_curly_token: L_CURLY@319..323 "{" [Newline("\n"), Whitespace(" ")] [],
384+
expression: HtmlTextExpression {
385+
html_literal_token: HTML_LITERAL@323..324 "a" [] [],
386+
},
387+
r_curly_token: R_CURLY@324..326 "}" [] [Whitespace(" ")],
388+
},
389+
HtmlSingleTextExpression {
390+
l_curly_token: L_CURLY@326..327 "{" [] [],
391+
expression: HtmlTextExpression {
392+
html_literal_token: HTML_LITERAL@327..328 "b" [] [],
393+
},
394+
r_curly_token: R_CURLY@328..329 "}" [] [],
395+
},
396+
],
397+
else_clause: missing (optional),
398+
closing_block: SvelteEachClosingBlock {
399+
sv_curly_slash_token: SV_CURLY_SLASH@329..332 "{/" [Newline("\n")] [],
400+
each_token: EACH_KW@332..336 "each" [] [],
401+
r_curly_token: R_CURLY@336..337 "}" [] [],
402+
},
403+
},
339404
],
340-
eof_token: EOF@271..272 "" [Newline("\n")] [],
405+
eof_token: EOF@337..338 "" [Newline("\n")] [],
341406
}
342407
```
343408

344409
## CST
345410

346411
```
347-
0: HTML_ROOT@0..272
412+
0: HTML_ROOT@0..338
348413
0: (empty)
349414
1: (empty)
350415
2: (empty)
351-
3: HTML_ELEMENT_LIST@0..271
416+
3: HTML_ELEMENT_LIST@0..337
352417
0: SVELTE_EACH_BLOCK@0..63
353418
0: SVELTE_EACH_OPENING_BLOCK@0..29
354419
0: SV_CURLY_HASH@0..2 "{#" [] []
@@ -564,6 +629,49 @@ HtmlRoot {
564629
0: SV_CURLY_SLASH@263..266 "{/" [Newline("\n")] []
565630
1: EACH_KW@266..270 "each" [] []
566631
2: R_CURLY@270..271 "}" [] []
567-
4: EOF@271..272 "" [Newline("\n")] []
632+
4: SVELTE_EACH_BLOCK@271..337
633+
0: SVELTE_EACH_OPENING_BLOCK@271..319
634+
0: SV_CURLY_HASH@271..275 "{#" [Newline("\n"), Newline("\n")] []
635+
1: EACH_KW@275..279 "each" [] []
636+
2: HTML_TEXT_EXPRESSION@279..300
637+
0: HTML_LITERAL@279..300 " Object.entries(foo) " [] []
638+
3: SVELTE_EACH_AS_KEYED_ITEM@300..318
639+
0: AS_KW@300..303 "as" [] [Whitespace(" ")]
640+
1: SVELTE_SQUARE_DESTRUCTURED_NAME@303..318
641+
0: L_BRACKET@303..304 "[" [] []
642+
1: SVELTE_BINDING_ASSIGNMENT_BINDING_LIST@304..317
643+
0: SVELTE_NAME@304..307
644+
0: IDENT@304..307 "key" [] []
645+
1: COMMA@307..309 "," [] [Whitespace(" ")]
646+
2: SVELTE_CURLY_DESTRUCTURED_NAME@309..317
647+
0: L_CURLY@309..311 "{" [] [Whitespace(" ")]
648+
1: SVELTE_BINDING_ASSIGNMENT_BINDING_LIST@311..316
649+
0: SVELTE_NAME@311..312
650+
0: IDENT@311..312 "a" [] []
651+
1: COMMA@312..314 "," [] [Whitespace(" ")]
652+
2: SVELTE_NAME@314..316
653+
0: IDENT@314..316 "b" [] [Whitespace(" ")]
654+
2: R_CURLY@316..317 "}" [] []
655+
2: R_BRACKET@317..318 "]" [] []
656+
2: (empty)
657+
3: (empty)
658+
4: R_CURLY@318..319 "}" [] []
659+
1: HTML_ELEMENT_LIST@319..329
660+
0: HTML_SINGLE_TEXT_EXPRESSION@319..326
661+
0: L_CURLY@319..323 "{" [Newline("\n"), Whitespace(" ")] []
662+
1: HTML_TEXT_EXPRESSION@323..324
663+
0: HTML_LITERAL@323..324 "a" [] []
664+
2: R_CURLY@324..326 "}" [] [Whitespace(" ")]
665+
1: HTML_SINGLE_TEXT_EXPRESSION@326..329
666+
0: L_CURLY@326..327 "{" [] []
667+
1: HTML_TEXT_EXPRESSION@327..328
668+
0: HTML_LITERAL@327..328 "b" [] []
669+
2: R_CURLY@328..329 "}" [] []
670+
2: (empty)
671+
3: SVELTE_EACH_CLOSING_BLOCK@329..337
672+
0: SV_CURLY_SLASH@329..332 "{/" [Newline("\n")] []
673+
1: EACH_KW@332..336 "each" [] []
674+
2: R_CURLY@336..337 "}" [] []
675+
4: EOF@337..338 "" [Newline("\n")] []
568676
569677
```

crates/biome_html_syntax/src/generated/nodes.rs

Lines changed: 28 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

xtask/codegen/html.ungram

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,7 @@ SvelteBindingAssignmentBindingList = (AnySvelteBindingAssignmentBinding (',' Any
413413

414414
AnySvelteBindingAssignmentBinding =
415415
SvelteName
416+
| AnySvelteDestructuredName
416417
| SvelteRestBinding
417418

418419
/// { ...rest }

0 commit comments

Comments
 (0)