fix: correctly parse `each` with loose parser (#14887)

* fix: correctly parse `each` with loose parser

* chore: fix lint

* chore: remove unused imports

* Apply suggestions from code review

* chore: fix lint

---------

Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
pull/14912/head
Paolo Ricciuti 5 days ago committed by GitHub
parent ed26c3f658
commit c4e9faad52
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: correctly parse `each` with loose parser

@ -8,9 +8,31 @@ import { find_matching_bracket } from '../utils/bracket.js';
/**
* @param {Parser} parser
* @param {string} [opening_token]
* @returns {Expression | undefined}
*/
export function get_loose_identifier(parser, opening_token) {
// Find the next } and treat it as the end of the expression
const end = find_matching_bracket(parser.template, parser.index, opening_token ?? '{');
if (end) {
const start = parser.index;
parser.index = end;
// We don't know what the expression is and signal this by returning an empty identifier
return {
type: 'Identifier',
start,
end,
name: ''
};
}
}
/**
* @param {Parser} parser
* @param {string} [opening_token]
* @param {boolean} [disallow_loose]
* @returns {Expression}
*/
export default function read_expression(parser, opening_token) {
export default function read_expression(parser, opening_token, disallow_loose) {
try {
const node = parse_expression_at(parser.template, parser.ts, parser.index);
@ -41,19 +63,12 @@ export default function read_expression(parser, opening_token) {
return /** @type {Expression} */ (node);
} catch (err) {
if (parser.loose) {
// Find the next } and treat it as the end of the expression
const end = find_matching_bracket(parser.template, parser.index, opening_token ?? '{');
if (end) {
const start = parser.index;
parser.index = end;
// We don't know what the expression is and signal this by returning an empty identifier
return {
type: 'Identifier',
start,
end,
name: ''
};
// If we are in an each loop we need the error to be thrown in cases like
// `as { y = z }` so we still throw and handle the error there
if (parser.loose && !disallow_loose) {
const expression = get_loose_identifier(parser, opening_token);
if (expression) {
return expression;
}
}

@ -1,13 +1,13 @@
/** @import { ArrowFunctionExpression, Expression, Identifier, Pattern } from 'estree' */
/** @import { AST } from '#compiler' */
/** @import { Parser } from '../index.js' */
import read_pattern from '../read/context.js';
import read_expression from '../read/expression.js';
import * as e from '../../../errors.js';
import { create_fragment } from '../utils/create.js';
import { walk } from 'zimmerframe';
import { parse_expression_at } from '../acorn.js';
import * as e from '../../../errors.js';
import { create_expression_metadata } from '../../nodes.js';
import { parse_expression_at } from '../acorn.js';
import read_pattern from '../read/context.js';
import read_expression, { get_loose_identifier } from '../read/expression.js';
import { create_fragment } from '../utils/create.js';
const regex_whitespace_with_closing_curly_brace = /^\s*}/;
@ -87,7 +87,7 @@ function open(parser) {
// we get a valid expression
while (!expression) {
try {
expression = read_expression(parser);
expression = read_expression(parser, undefined, true);
} catch (err) {
end = /** @type {any} */ (err).position[0] - 2;
@ -95,7 +95,15 @@ function open(parser) {
end -= 1;
}
if (end <= start) throw err;
if (end <= start) {
if (parser.loose) {
expression = get_loose_identifier(parser);
if (expression) {
break;
}
}
throw err;
}
// @ts-expect-error parser.template is meant to be readonly, this is a special case
parser.template = template.slice(0, end);

@ -0,0 +1,7 @@
<script lang="ts">
let arr = [];
</script>
{#each arr as [key, value = 'default']}
<div>{key}: {value}</div>
{/each}

@ -0,0 +1,308 @@
{
"css": null,
"js": [],
"start": 45,
"end": 119,
"type": "Root",
"fragment": {
"type": "Fragment",
"nodes": [
{
"type": "Text",
"start": 43,
"end": 45,
"raw": "\n\n",
"data": "\n\n"
},
{
"type": "EachBlock",
"start": 45,
"end": 119,
"expression": {
"type": "Identifier",
"start": 52,
"end": 55,
"loc": {
"start": {
"line": 5,
"column": 7
},
"end": {
"line": 5,
"column": 10
}
},
"name": "arr"
},
"body": {
"type": "Fragment",
"nodes": [
{
"type": "Text",
"start": 84,
"end": 86,
"raw": "\n\t",
"data": "\n\t"
},
{
"type": "RegularElement",
"start": 86,
"end": 111,
"name": "div",
"attributes": [],
"fragment": {
"type": "Fragment",
"nodes": [
{
"type": "ExpressionTag",
"start": 91,
"end": 96,
"expression": {
"type": "Identifier",
"start": 92,
"end": 95,
"loc": {
"start": {
"line": 6,
"column": 7
},
"end": {
"line": 6,
"column": 10
}
},
"name": "key"
}
},
{
"type": "Text",
"start": 96,
"end": 98,
"raw": ": ",
"data": ": "
},
{
"type": "ExpressionTag",
"start": 98,
"end": 105,
"expression": {
"type": "Identifier",
"start": 99,
"end": 104,
"loc": {
"start": {
"line": 6,
"column": 14
},
"end": {
"line": 6,
"column": 19
}
},
"name": "value"
}
}
]
}
},
{
"type": "Text",
"start": 111,
"end": 112,
"raw": "\n",
"data": "\n"
}
]
},
"context": {
"type": "ArrayPattern",
"start": 59,
"end": 83,
"loc": {
"start": {
"line": 5,
"column": 15
},
"end": {
"line": 5,
"column": 39
}
},
"elements": [
{
"type": "Identifier",
"start": 60,
"end": 63,
"loc": {
"start": {
"line": 5,
"column": 16
},
"end": {
"line": 5,
"column": 19
}
},
"name": "key"
},
{
"type": "AssignmentPattern",
"start": 65,
"end": 82,
"loc": {
"start": {
"line": 5,
"column": 21
},
"end": {
"line": 5,
"column": 38
}
},
"left": {
"type": "Identifier",
"start": 65,
"end": 70,
"loc": {
"start": {
"line": 5,
"column": 21
},
"end": {
"line": 5,
"column": 26
}
},
"name": "value"
},
"right": {
"type": "Literal",
"start": 73,
"end": 82,
"loc": {
"start": {
"line": 5,
"column": 29
},
"end": {
"line": 5,
"column": 38
}
},
"value": "default",
"raw": "'default'"
}
}
]
}
}
]
},
"options": null,
"instance": {
"type": "Script",
"start": 0,
"end": 43,
"context": "default",
"content": {
"type": "Program",
"start": 18,
"end": 34,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 0
}
},
"body": [
{
"type": "VariableDeclaration",
"start": 20,
"end": 33,
"loc": {
"start": {
"line": 2,
"column": 1
},
"end": {
"line": 2,
"column": 14
}
},
"declarations": [
{
"type": "VariableDeclarator",
"start": 24,
"end": 32,
"loc": {
"start": {
"line": 2,
"column": 5
},
"end": {
"line": 2,
"column": 13
}
},
"id": {
"type": "Identifier",
"start": 24,
"end": 27,
"loc": {
"start": {
"line": 2,
"column": 5
},
"end": {
"line": 2,
"column": 8
}
},
"name": "arr"
},
"init": {
"type": "ArrayExpression",
"start": 30,
"end": 32,
"loc": {
"start": {
"line": 2,
"column": 11
},
"end": {
"line": 2,
"column": 13
}
},
"elements": []
}
}
],
"kind": "let"
}
],
"sourceType": "module"
},
"attributes": [
{
"type": "Attribute",
"start": 8,
"end": 17,
"name": "lang",
"value": [
{
"start": 14,
"end": 16,
"type": "Text",
"raw": "ts",
"data": "ts"
}
]
}
]
}
}
Loading…
Cancel
Save