diff --git a/packages/svelte/src/compiler/phases/1-parse/js.js b/packages/svelte/src/compiler/phases/1-parse/js.js index a4485b2864..35ba76f4cd 100644 --- a/packages/svelte/src/compiler/phases/1-parse/js.js +++ b/packages/svelte/src/compiler/phases/1-parse/js.js @@ -162,6 +162,14 @@ export function read_params(parser) { return answer.params; } +/** + * @param {Parser} parser at the opening `<` + */ +export function read_type_parameters(parser) { + const start = parser.index; + read(parser, (js) => js.parseTypeParametersAt(start)); +} + /** * @param {Parser} parser * @returns {Statement} 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 2b3fe2cd27..005e182504 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -3,8 +3,7 @@ /** @import { Parser } from '../index.js' */ import * as e from '../../../errors.js'; import { create_fragment, ExpressionMetadata } from '../../nodes.js'; -import { read_expression, read_params, read_pattern, read_statement } from '../js.js'; -import { match_bracket } from '../utils/bracket.js'; +import { read_expression, read_params, read_pattern, read_statement, read_type_parameters } from '../js.js'; const regex_whitespace_with_closing_curly_brace = /\s*}/y; const regex_supported_declaration = /(?:let|const)\b/y; @@ -12,7 +11,6 @@ const regex_unsupported_declaration = /(?:var|interface|enum)\b/y; // `type` is a contextual keyword; this is just a shape hint, confirmed by parsing. const regex_maybe_type_declaration = /type\b/y; -const pointy_bois = { '<': '>' }; /** @param {Parser} parser */ export default function tag(parser) { @@ -299,14 +297,10 @@ function open(parser) { /** @type {string | undefined} */ let type_params; - // if we match a generic opening if (parser.ts && parser.match('<')) { const start = parser.index; - const end = match_bracket(parser, start, pointy_bois); - - type_params = parser.template.slice(start + 1, end - 1); - - parser.index = end; + read_type_parameters(parser); + type_params = parser.template.slice(start + 1, parser.index - 1); } parser.allow_whitespace(); diff --git a/packages/svelte/src/compiler/phases/1-parse/utils/bracket.js b/packages/svelte/src/compiler/phases/1-parse/utils/bracket.js deleted file mode 100644 index 4857895c94..0000000000 --- a/packages/svelte/src/compiler/phases/1-parse/utils/bracket.js +++ /dev/null @@ -1,82 +0,0 @@ -/** @import { Parser } from '../index.js' */ -import * as e from '../../../errors.js'; - -/** @type {Record} */ -const default_brackets = { - '{': '}', - '(': ')', - '[': ']' -}; - -const default_close = new Set(Object.values(default_brackets)); - -/** - * @param {Parser} parser - * @param {number} start - * @param {Record} brackets - */ -export function match_bracket(parser, start, brackets = default_brackets) { - const close = brackets === default_brackets ? default_close : new Set(Object.values(brackets)); - const bracket_stack = []; - - let i = start; - - while (i < parser.template.length) { - let char = parser.template[i++]; - - if (char === "'" || char === '"' || char === '`') { - i = match_quote(parser, i, char); - continue; - } - - if (char in brackets) { - bracket_stack.push(char); - } else if (close.has(char)) { - const popped = /** @type {string} */ (bracket_stack.pop()); - const expected = /** @type {string} */ (brackets[popped]); - - if (char !== expected) { - e.expected_token(i - 1, expected); - } - - if (bracket_stack.length === 0) { - return i; - } - } - } - - e.unexpected_eof(parser.template.length); -} - -/** - * @param {Parser} parser - * @param {number} start - * @param {string} quote - */ -function match_quote(parser, start, quote) { - let is_escaped = false; - let i = start; - - while (i < parser.template.length) { - const char = parser.template[i++]; - - if (is_escaped) { - is_escaped = false; - continue; - } - - if (char === quote) { - return i; - } - - if (char === '\\') { - is_escaped = true; - } - - if (quote === '`' && char === '$' && parser.template[i] === '{') { - i = match_bracket(parser, i); - } - } - - e.unterminated_string_constant(start); -} diff --git a/packages/svelte/tests/parser-modern/samples/generic-snippets-arrow/input.svelte b/packages/svelte/tests/parser-modern/samples/generic-snippets-arrow/input.svelte new file mode 100644 index 0000000000..e51cd8cad0 --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/generic-snippets-arrow/input.svelte @@ -0,0 +1,6 @@ + + +{#snippet foo void, U = Array>(x: T, y: U)} + {x} +{/snippet} diff --git a/packages/svelte/tests/parser-modern/samples/generic-snippets-arrow/output.json b/packages/svelte/tests/parser-modern/samples/generic-snippets-arrow/output.json new file mode 100644 index 0000000000..863aa4bfc8 --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/generic-snippets-arrow/output.json @@ -0,0 +1,263 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 113, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 28, + "end": 30, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "SnippetBlock", + "start": 30, + "end": 113, + "expression": { + "type": "Identifier", + "name": "foo", + "start": 40, + "end": 43, + "loc": { + "start": { + "line": 4, + "column": 10, + "character": 40 + }, + "end": { + "line": 4, + "column": 13, + "character": 43 + } + } + }, + "typeParams": "T extends () => void, U = Array", + "parameters": [ + { + "type": "Identifier", + "start": 85, + "end": 89, + "loc": { + "start": { + "line": 4, + "column": 55 + }, + "end": { + "line": 4, + "column": 59 + } + }, + "typeAnnotation": { + "type": "TSTypeAnnotation", + "start": 86, + "end": 89, + "loc": { + "start": { + "line": 4, + "column": 56 + }, + "end": { + "line": 4, + "column": 59 + } + }, + "typeAnnotation": { + "type": "TSTypeReference", + "start": 88, + "end": 89, + "loc": { + "start": { + "line": 4, + "column": 58 + }, + "end": { + "line": 4, + "column": 59 + } + }, + "typeName": { + "type": "Identifier", + "start": 88, + "end": 89, + "loc": { + "start": { + "line": 4, + "column": 58 + }, + "end": { + "line": 4, + "column": 59 + } + }, + "name": "T" + } + } + }, + "name": "x" + }, + { + "type": "Identifier", + "start": 91, + "end": 95, + "loc": { + "start": { + "line": 4, + "column": 61 + }, + "end": { + "line": 4, + "column": 65 + } + }, + "typeAnnotation": { + "type": "TSTypeAnnotation", + "start": 92, + "end": 95, + "loc": { + "start": { + "line": 4, + "column": 62 + }, + "end": { + "line": 4, + "column": 65 + } + }, + "typeAnnotation": { + "type": "TSTypeReference", + "start": 94, + "end": 95, + "loc": { + "start": { + "line": 4, + "column": 64 + }, + "end": { + "line": 4, + "column": 65 + } + }, + "typeName": { + "type": "Identifier", + "start": 94, + "end": 95, + "loc": { + "start": { + "line": 4, + "column": 64 + }, + "end": { + "line": 4, + "column": 65 + } + }, + "name": "U" + } + } + }, + "name": "y" + } + ], + "body": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 97, + "end": 99, + "raw": "\n\t", + "data": "\n\t" + }, + { + "type": "ExpressionTag", + "start": 99, + "end": 102, + "expression": { + "type": "Identifier", + "start": 100, + "end": 101, + "loc": { + "start": { + "line": 5, + "column": 2 + }, + "end": { + "line": 5, + "column": 3 + } + }, + "name": "x" + } + }, + { + "type": "Text", + "start": 102, + "end": 103, + "raw": "\n", + "data": "\n" + } + ] + } + } + ] + }, + "options": null, + "comments": [], + "instance": { + "type": "Script", + "start": 0, + "end": 28, + "context": "default", + "content": { + "type": "Program", + "start": 18, + "end": 19, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 2, + "column": 9 + } + }, + "body": [], + "sourceType": "module" + }, + "attributes": [ + { + "type": "Attribute", + "start": 8, + "end": 17, + "name": "lang", + "name_loc": { + "start": { + "line": 1, + "column": 8, + "character": 8 + }, + "end": { + "line": 1, + "column": 12, + "character": 12 + } + }, + "value": [ + { + "start": 14, + "end": 16, + "type": "Text", + "raw": "ts", + "data": "ts" + } + ] + } + ] + } +}