diff --git a/.changeset/six-ears-fly.md b/.changeset/six-ears-fly.md new file mode 100644 index 000000000..6c64a47d3 --- /dev/null +++ b/.changeset/six-ears-fly.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: parse regexp without parens diff --git a/packages/svelte/src/compiler/parse/state/mustache.js b/packages/svelte/src/compiler/parse/state/mustache.js index 92853c632..a9a846037 100644 --- a/packages/svelte/src/compiler/parse/state/mustache.js +++ b/packages/svelte/src/compiler/parse/state/mustache.js @@ -40,7 +40,7 @@ export default function mustache(parser) { parser.index += 1; parser.allow_whitespace(); // {/if}, {/each}, {/await} or {/key} - if (parser.eat('/')) { + if (parser.match('/')) { let block = parser.current(); let expected; if (closing_tag_omitted(block.name)) { @@ -68,8 +68,19 @@ export default function mustache(parser) { } else if (block.type === 'KeyBlock') { expected = 'key'; } else { - parser.error(parser_errors.unexpected_block_close); + let possible_regex = read_expression(parser); + parser.allow_whitespace(); + parser.eat('}', true); + parser.current().children.push({ + start, + end: parser.index, + type: 'MustacheTag', + expression: possible_regex + }); + return; } + parser.index += 1; + parser.eat(expected, true); parser.allow_whitespace(); parser.eat('}', true); diff --git a/packages/svelte/test/parser/parser.test.js b/packages/svelte/test/parser/parser.test.js index 67a621582..af9b568b4 100644 --- a/packages/svelte/test/parser/parser.test.js +++ b/packages/svelte/test/parser/parser.test.js @@ -1,7 +1,8 @@ import * as fs from 'node:fs'; import { assert, describe, it } from 'vitest'; import * as svelte from 'svelte/compiler'; -import { try_load_json } from '../helpers.js'; +import { try_load_json, try_read_file } from '../helpers.js'; +import { walk } from 'estree-walker'; describe('parse', () => { fs.readdirSync(`${__dirname}/samples`).forEach((dir) => { @@ -26,8 +27,23 @@ describe('parse', () => { .trimEnd() .replace(/\r/g, ''); - const expectedOutput = try_load_json(`${__dirname}/samples/${dir}/output.json`); - const expectedError = try_load_json(`${__dirname}/samples/${dir}/error.json`); + const output_file = try_read_file(`${__dirname}/samples/${dir}/output.json`); + + // Regexp literals are serialized as empty objects, so we need to convert the values back to RegExp + // to make the deepEqual comparison work. + let expectedOutput = output_file + ? JSON.parse(output_file, (key, value) => { + if ( + typeof value === 'object' && + value !== null && + value.type === 'Literal' && + value.regex + ) { + value.value = new RegExp(value.regex.pattern, value.regex.flags); + } + return value; + }) + : null; try { const { ast } = svelte.compile( @@ -48,6 +64,8 @@ describe('parse', () => { assert.deepEqual(ast.module, expectedOutput.module); } catch (err) { if (err.name !== 'ParseError') throw err; + + const expectedError = try_load_json(`${__dirname}/samples/${dir}/error.json`); if (!expectedError) throw err; const { code, message, pos, start } = err; diff --git a/packages/svelte/test/parser/samples/inline-regex/input.svelte b/packages/svelte/test/parser/samples/inline-regex/input.svelte new file mode 100644 index 000000000..adc5782a5 --- /dev/null +++ b/packages/svelte/test/parser/samples/inline-regex/input.svelte @@ -0,0 +1 @@ +{/abc/.test("abc")} diff --git a/packages/svelte/test/parser/samples/inline-regex/output.json b/packages/svelte/test/parser/samples/inline-regex/output.json new file mode 100644 index 000000000..793e573c1 --- /dev/null +++ b/packages/svelte/test/parser/samples/inline-regex/output.json @@ -0,0 +1,102 @@ +{ + "html": { + "start": 0, + "end": 19, + "type": "Fragment", + "children": [ + { + "start": 0, + "end": 19, + "type": "MustacheTag", + "expression": { + "type": "CallExpression", + "start": 1, + "end": 18, + "loc": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 18 + } + }, + "callee": { + "type": "MemberExpression", + "start": 1, + "end": 11, + "loc": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 11 + } + }, + "object": { + "type": "Literal", + "start": 1, + "end": 6, + "loc": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 6 + } + }, + "raw": "/abc/", + "regex": { + "pattern": "abc", + "flags": "" + } + }, + "property": { + "type": "Identifier", + "start": 7, + "end": 11, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 11 + } + }, + "name": "test" + }, + "computed": false, + "optional": false + }, + "arguments": [ + { + "type": "Literal", + "start": 12, + "end": 17, + "loc": { + "start": { + "line": 1, + "column": 12 + }, + "end": { + "line": 1, + "column": 17 + } + }, + "value": "abc", + "raw": "\"abc\"" + } + ], + "optional": false + } + } + ] + } +} \ No newline at end of file