diff --git a/packages/svelte/src/compiler/phases/1-parse/js.js b/packages/svelte/src/compiler/phases/1-parse/js.js
index e472f68cc9..cb5128fade 100644
--- a/packages/svelte/src/compiler/phases/1-parse/js.js
+++ b/packages/svelte/src/compiler/phases/1-parse/js.js
@@ -104,15 +104,15 @@ function read(parser, run) {
/**
* @param {Parser} parser
- * @param {'as'} [until] the host's `as` follows the expression, as an each block's item does
+ * @param {string[]} [stop_at] the template's own tokens after the expression, which end it
* @param {string} [opening_token] the bracket the expression sits in, for loose mode
* @returns {Expression}
*/
-export function read_expression(parser, until, opening_token = '{') {
+export function read_expression(parser, stop_at, opening_token = '{') {
const start = parser.index;
try {
- const answer = read(parser, (js) => js.parseExpressionAt(start, until));
+ const answer = read(parser, (js) => js.parseExpressionAt(start, stop_at));
keep_tables(answer.node, tables(answer));
return answer.node;
} catch (err) {
diff --git a/packages/svelte/src/compiler/phases/1-parse/state/element.js b/packages/svelte/src/compiler/phases/1-parse/state/element.js
index d985b344de..c5af228430 100644
--- a/packages/svelte/src/compiler/phases/1-parse/state/element.js
+++ b/packages/svelte/src/compiler/phases/1-parse/state/element.js
@@ -817,18 +817,19 @@ function read_attribute_value(parser) {
if (quote_mark) return parser.match(quote_mark);
return !!parser.match_regex(regex_invalid_unquoted_attribute_value);
},
- 'in attribute value'
+ 'in attribute value',
+ // `` is an unclosed value, not a regex
+ ['/>']
);
} catch (/** @type {any} */ error) {
- if (error.code === 'js_parse_error') {
- // if the attribute value didn't close + self-closing tag
- // eg: ``
- // acorn may throw a `Unterminated regular expression` because of `/>`
- const pos = error.position?.[0];
- if (pos !== undefined && parser.template.slice(pos - 1, pos + 1) === '/>') {
- parser.index = pos;
- e.expected_token(pos, quote_mark || '}');
- }
+ const pos = error.position?.[0];
+ if (
+ error.code === 'js_parse_error' &&
+ pos !== undefined &&
+ parser.template.startsWith('/>', pos)
+ ) {
+ parser.index = pos;
+ e.expected_token(pos, quote_mark || '}');
}
throw error;
}
@@ -850,9 +851,10 @@ function read_attribute_value(parser) {
* @param {Parser} parser
* @param {() => boolean} done
* @param {string} location
+ * @param {string[]} [stop_at] the template's tokens that end an expression, see `read_expression`
* @returns {any[]}
*/
-function read_sequence(parser, done, location) {
+function read_sequence(parser, done, location, stop_at) {
/** @type {Array} */
const chunks = [];
let chunk_start = parser.index;
@@ -895,7 +897,7 @@ function read_sequence(parser, done, location) {
flush(parser.index - 1);
parser.allow_whitespace();
- const expression = read_expression(parser);
+ const expression = read_expression(parser, stop_at);
parser.allow_whitespace();
parser.eat('}', true);
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 cc0edee6bc..3f8821f9fb 100644
--- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js
+++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js
@@ -178,16 +178,11 @@ function open(parser) {
if (parser.eat('each')) {
parser.require_whitespace();
- // the list ends at the `as` that names the item, so a TypeScript assertion in it needs parens
- let expression = read_expression(parser, 'as');
+ // the list ends at the `as` naming the item or the `,` before the index, so an assertion in it needs parens
+ const expression = read_expression(parser, ['as', ',']);
parser.allow_whitespace();
- // {#each} blocks must declare a context – {#each list as item}
- if (!parser.match('as') && expression.type === 'SequenceExpression') {
- expression = expression.expressions[0];
- }
-
/** @type {Pattern | null} */
let context = null;
let index;
@@ -197,11 +192,6 @@ function open(parser) {
parser.require_whitespace();
context = read_pattern(parser);
- } else {
- // {#each Array.from({ length: 10 }), i} is read as a sequence expression,
- // which is set back above - we now gotta reset the index as a consequence
- // to properly read the , i part
- parser.index = /** @type {number} */ (expression.end);
}
parser.allow_whitespace();
@@ -225,24 +215,7 @@ function open(parser) {
parser.allow_whitespace();
}
- const matches = parser.eat('}', true, false);
-
- if (!matches) {
- // Parser may have read the `as` as part of the expression (e.g. in `{#each foo. as x}`)
- if (parser.template.slice(parser.index - 4, parser.index) === ' as ') {
- const prev_index = parser.index;
- context = read_pattern(parser);
- parser.eat('}', true);
- expression = {
- type: 'Identifier',
- name: '',
- start: expression.start,
- end: prev_index - 4
- };
- } else {
- parser.eat('}', true); // rerun to produce the parser error
- }
- }
+ parser.eat('}', true, false);
/** @type {AST.EachBlock} */
const block = parser.append({
@@ -265,7 +238,7 @@ function open(parser) {
if (parser.eat('await')) {
parser.require_whitespace();
- const expression = read_expression(parser);
+ const expression = read_expression(parser, ['then', 'catch']);
parser.allow_whitespace();
/** @type {AST.AwaitBlock} */
@@ -311,38 +284,7 @@ function open(parser) {
parser.fragments.push(block.pending);
}
- const matches = parser.eat('}', true, false);
-
- // Parser may have read the `then/catch` as part of the expression (e.g. in `{#await foo. then x}`)
- if (!matches) {
- if (parser.template.slice(parser.index - 6, parser.index) === ' then ') {
- const prev_index = parser.index;
- block.value = read_pattern(parser);
- parser.eat('}', true);
- block.expression = {
- type: 'Identifier',
- name: '',
- start: expression.start,
- end: prev_index - 6
- };
- block.then = block.pending;
- block.pending = null;
- } else if (parser.template.slice(parser.index - 7, parser.index) === ' catch ') {
- const prev_index = parser.index;
- block.error = read_pattern(parser);
- parser.eat('}', true);
- block.expression = {
- type: 'Identifier',
- name: '',
- start: expression.start,
- end: prev_index - 7
- };
- block.catch = block.pending;
- block.pending = null;
- } else {
- parser.eat('}', true); // rerun to produce the parser error
- }
- }
+ parser.eat('}', true, false);
parser.stack.push(block);
diff --git a/packages/svelte/tests/compiler-errors/samples/unclosed-attribute-self-close-tag/_config.js b/packages/svelte/tests/compiler-errors/samples/unclosed-attribute-self-close-tag/_config.js
index 6fb0c03d4e..0ce1d07e23 100644
--- a/packages/svelte/tests/compiler-errors/samples/unclosed-attribute-self-close-tag/_config.js
+++ b/packages/svelte/tests/compiler-errors/samples/unclosed-attribute-self-close-tag/_config.js
@@ -4,6 +4,6 @@ export default test({
error: {
code: 'expected_token',
message: 'Expected token }',
- position: [19, 19]
+ position: [18, 18]
}
});
diff --git a/packages/svelte/tests/parser-legacy/samples/loose-invalid-expression/output.json b/packages/svelte/tests/parser-legacy/samples/loose-invalid-expression/output.json
index 136c45fc6e..4339b7af5d 100644
--- a/packages/svelte/tests/parser-legacy/samples/loose-invalid-expression/output.json
+++ b/packages/svelte/tests/parser-legacy/samples/loose-invalid-expression/output.json
@@ -405,29 +405,12 @@
"start": 219,
"end": 246,
"children": [],
- "context": {
- "type": "Identifier",
- "name": "item",
- "start": 234,
- "end": 238,
- "loc": {
- "start": {
- "line": 17,
- "column": 15,
- "character": 234
- },
- "end": {
- "line": 17,
- "column": 19,
- "character": 238
- }
- }
- },
+ "context": null,
"expression": {
"type": "Identifier",
- "name": "",
"start": 226,
- "end": 230
+ "end": 238,
+ "name": ""
}
},
{
@@ -484,42 +467,25 @@
"end": 295,
"expression": {
"type": "Identifier",
- "name": "",
"start": 277,
- "end": 279
- },
- "value": {
- "type": "Identifier",
- "name": "y",
- "start": 285,
"end": 286,
- "loc": {
- "start": {
- "line": 21,
- "column": 16,
- "character": 285
- },
- "end": {
- "line": 21,
- "column": 17,
- "character": 286
- }
- }
+ "name": ""
},
+ "value": null,
"error": null,
"pending": {
"type": "PendingBlock",
- "start": null,
- "end": null,
+ "start": 287,
+ "end": 287,
"children": [],
- "skip": true
+ "skip": false
},
"then": {
"type": "ThenBlock",
- "start": 287,
- "end": 267,
+ "start": null,
+ "end": null,
"children": [],
- "skip": false
+ "skip": true
},
"catch": {
"type": "CatchBlock",
@@ -542,35 +508,18 @@
"end": 324,
"expression": {
"type": "Identifier",
- "name": "",
"start": 305,
- "end": 307
- },
- "value": null,
- "error": {
- "type": "Identifier",
- "name": "y",
- "start": 314,
"end": 315,
- "loc": {
- "start": {
- "line": 23,
- "column": 17,
- "character": 314
- },
- "end": {
- "line": 23,
- "column": 18,
- "character": 315
- }
- }
+ "name": ""
},
+ "value": null,
+ "error": null,
"pending": {
"type": "PendingBlock",
- "start": null,
- "end": null,
+ "start": 316,
+ "end": 316,
"children": [],
- "skip": true
+ "skip": false
},
"then": {
"type": "ThenBlock",
@@ -581,12 +530,12 @@
},
"catch": {
"type": "CatchBlock",
- "start": 316,
- "end": 295,
+ "start": null,
+ "end": null,
"children": [],
- "skip": false
+ "skip": true
}
}
]
}
-}
+}
\ No newline at end of file
diff --git a/packages/svelte/tests/parser-modern/samples/loose-invalid-expression/output.json b/packages/svelte/tests/parser-modern/samples/loose-invalid-expression/output.json
index 593aebe279..fa4ddda140 100644
--- a/packages/svelte/tests/parser-modern/samples/loose-invalid-expression/output.json
+++ b/packages/svelte/tests/parser-modern/samples/loose-invalid-expression/output.json
@@ -497,32 +497,15 @@
"end": 246,
"expression": {
"type": "Identifier",
- "name": "",
"start": 226,
- "end": 230
+ "end": 238,
+ "name": ""
},
"body": {
"type": "Fragment",
"nodes": []
},
- "context": {
- "type": "Identifier",
- "name": "item",
- "start": 234,
- "end": 238,
- "loc": {
- "start": {
- "line": 17,
- "column": 15,
- "character": 234
- },
- "end": {
- "line": 17,
- "column": 19,
- "character": 238
- }
- }
- }
+ "context": null
},
{
"type": "Text",
@@ -563,34 +546,17 @@
"end": 295,
"expression": {
"type": "Identifier",
- "name": "",
"start": 277,
- "end": 279
- },
- "value": {
- "type": "Identifier",
- "name": "y",
- "start": 285,
"end": 286,
- "loc": {
- "start": {
- "line": 21,
- "column": 16,
- "character": 285
- },
- "end": {
- "line": 21,
- "column": 17,
- "character": 286
- }
- }
+ "name": ""
},
+ "value": null,
"error": null,
- "pending": null,
- "then": {
+ "pending": {
"type": "Fragment",
"nodes": []
},
+ "then": null,
"catch": null
},
{
@@ -606,37 +572,21 @@
"end": 324,
"expression": {
"type": "Identifier",
- "name": "",
"start": 305,
- "end": 307
- },
- "value": null,
- "error": {
- "type": "Identifier",
- "name": "y",
- "start": 314,
"end": 315,
- "loc": {
- "start": {
- "line": 23,
- "column": 17,
- "character": 314
- },
- "end": {
- "line": 23,
- "column": 18,
- "character": 315
- }
- }
+ "name": ""
},
- "pending": null,
- "then": null,
- "catch": {
+ "value": null,
+ "error": null,
+ "pending": {
"type": "Fragment",
"nodes": []
- }
+ },
+ "then": null,
+ "catch": null
}
]
},
- "options": null
-}
+ "options": null,
+ "comments": []
+}
\ No newline at end of file