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