diff --git a/.changeset/famous-scissors-begin.md b/.changeset/famous-scissors-begin.md new file mode 100644 index 0000000000..96075d0dac --- /dev/null +++ b/.changeset/famous-scissors-begin.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: correctly parse `each` with loose parser diff --git a/packages/svelte/src/compiler/phases/1-parse/read/expression.js b/packages/svelte/src/compiler/phases/1-parse/read/expression.js index 907608b11b..82a667d38c 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/expression.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/expression.js @@ -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; } } diff --git a/packages/svelte/src/compiler/phases/1-parse/state/tag.js b/packages/svelte/src/compiler/phases/1-parse/state/tag.js index 7996d64ded..95d7d00677 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -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); diff --git a/packages/svelte/tests/parser-modern/samples/loose-valid-each-as/input.svelte b/packages/svelte/tests/parser-modern/samples/loose-valid-each-as/input.svelte new file mode 100644 index 0000000000..f2cc7f5e79 --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/loose-valid-each-as/input.svelte @@ -0,0 +1,7 @@ + + +{#each arr as [key, value = 'default']} +