diff --git a/.changeset/popular-ligers-perform.md b/.changeset/popular-ligers-perform.md new file mode 100644 index 0000000000..e041720fac --- /dev/null +++ b/.changeset/popular-ligers-perform.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: handle TypeScript's optional parameter syntax in snippets diff --git a/packages/svelte/src/compiler/phases/1-parse/read/context.js b/packages/svelte/src/compiler/phases/1-parse/read/context.js index 3759dd2049..054d60cb0d 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/context.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/context.js @@ -13,16 +13,17 @@ import { error } from '../../../errors.js'; /** * @param {import('../index.js').Parser} parser + * @param {boolean} [optional_allowed] * @returns {import('estree').Pattern} */ -export default function read_pattern(parser) { +export default function read_pattern(parser, optional_allowed = false) { const start = parser.index; let i = parser.index; const code = full_char_code_at(parser.template, i); if (isIdentifierStart(code, true)) { const name = /** @type {string} */ (parser.read_identifier()); - const annotation = read_type_annotation(parser); + const annotation = read_type_annotation(parser, optional_allowed); return { type: 'Identifier', @@ -83,7 +84,7 @@ export default function read_pattern(parser) { parse_expression_at(`${space_with_newline}(${pattern_string} = 1)`, parser.ts, start - 1) ).left; - expression.typeAnnotation = read_type_annotation(parser); + expression.typeAnnotation = read_type_annotation(parser, optional_allowed); if (expression.typeAnnotation) { expression.end = expression.typeAnnotation.end; } @@ -96,12 +97,19 @@ export default function read_pattern(parser) { /** * @param {import('../index.js').Parser} parser + * @param {boolean} [optional_allowed] * @returns {any} */ -function read_type_annotation(parser) { +function read_type_annotation(parser, optional_allowed = false) { const start = parser.index; parser.allow_whitespace(); + if (optional_allowed && parser.eat('?')) { + // Acorn-TS puts the optional info as a property on the surrounding node. + // We spare the work here because it doesn't matter for us anywhere else. + parser.allow_whitespace(); + } + if (!parser.eat(':')) { parser.index = start; return undefined; 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 a457a82059..821c82d73a 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -276,7 +276,7 @@ function open(parser) { const parameters = []; while (!parser.match(')')) { - let pattern = read_pattern(parser); + let pattern = read_pattern(parser, true); parser.allow_whitespace(); if (parser.eat('=')) { diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-optional-arguments/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-optional-arguments/main.svelte index 03d3c29c45..251d738e0e 100644 --- a/packages/svelte/tests/runtime-runes/samples/snippet-optional-arguments/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/snippet-optional-arguments/main.svelte @@ -17,7 +17,7 @@ {#snippet counter(c)} {#if c} - + {:else}

fallback

{/if} @@ -25,4 +25,3 @@ {@render counter()} {@render counter(count)} - diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-typescript/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-typescript/_config.js new file mode 100644 index 0000000000..094be77b80 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-typescript/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + html: '1 2 3 4 5' +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-typescript/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-typescript/main.svelte new file mode 100644 index 0000000000..9923b1d854 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-typescript/main.svelte @@ -0,0 +1,24 @@ + + +{#snippet counter1(c: number)} + {c} +{/snippet} +{#snippet counter2({ c }: {c: number})} + {c} +{/snippet} +{#snippet counter3(c?: number)} + {c} +{/snippet} +{#snippet counter4(c: number = 4)} + {c} +{/snippet} +{#snippet counter5(c?: number = 5)} + {c} +{/snippet} + +{@render counter1(1)} +{@render counter2({ c: 2 })} +{@render counter3(3)} +{@render counter4()} +{@render counter5()}