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']} +
{key}: {value}
+{/each} diff --git a/packages/svelte/tests/parser-modern/samples/loose-valid-each-as/output.json b/packages/svelte/tests/parser-modern/samples/loose-valid-each-as/output.json new file mode 100644 index 0000000000..181f1ba250 --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/loose-valid-each-as/output.json @@ -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" + } + ] + } + ] + } +}