fix: allow multiple optional parameters with defaults (#12070)

* fix: allow multiple optional parameters with defaults

* Apply suggestions from code review

* partial fix

* feat: parse as a whole function

* couple of fixes

* work around acorn-typescript quirks

* add the harder test

* Update .changeset/ten-geese-share.md

---------

Co-authored-by: Rich Harris <hello@rich-harris.dev>
Co-authored-by: Rich Harris <rich.harris@vercel.com>
pull/12132/head
Paolo Ricciuti 6 months ago committed by GitHub
parent 5581216e4d
commit 965c12f633
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: allow multiple optional parameters with defaults in snippets

@ -1,6 +1,7 @@
import * as acorn from 'acorn'; import * as acorn from 'acorn';
import { walk } from 'zimmerframe'; import { walk } from 'zimmerframe';
import { tsPlugin } from 'acorn-typescript'; import { tsPlugin } from 'acorn-typescript';
import { locator } from '../../state.js';
const ParserWithTS = acorn.Parser.extend(tsPlugin({ allowSatisfies: true })); const ParserWithTS = acorn.Parser.extend(tsPlugin({ allowSatisfies: true }));
@ -127,6 +128,16 @@ function amend(source, node) {
// @ts-expect-error // @ts-expect-error
delete node.loc.end.index; delete node.loc.end.index;
if (typeof node.loc?.end === 'number') {
const loc = locator(node.loc.end);
if (loc) {
node.loc.end = {
line: loc.line,
column: loc.column
};
}
}
if (/** @type {any} */ (node).typeAnnotation && node.end === undefined) { if (/** @type {any} */ (node).typeAnnotation && node.end === undefined) {
// i think there might be a bug in acorn-typescript that prevents // i think there might be a bug in acorn-typescript that prevents
// `end` from being assigned when there's a type annotation // `end` from being assigned when there's a type annotation

@ -3,6 +3,7 @@ import read_expression from '../read/expression.js';
import * as e from '../../../errors.js'; import * as e from '../../../errors.js';
import { create_fragment } from '../utils/create.js'; import { create_fragment } from '../utils/create.js';
import { walk } from 'zimmerframe'; import { walk } from 'zimmerframe';
import { parse_expression_at } from '../acorn.js';
const regex_whitespace_with_closing_curly_brace = /^\s*}/; const regex_whitespace_with_closing_curly_brace = /^\s*}/;
@ -268,39 +269,28 @@ function open(parser) {
e.expected_identifier(parser.index); e.expected_identifier(parser.index);
} }
parser.eat('(', true); let slice_end = parser.index;
parser.allow_whitespace();
/** @type {import('estree').Pattern[]} */
const parameters = [];
while (!parser.match(')')) { parser.eat('(', true);
let pattern = read_pattern(parser, true);
parser.allow_whitespace();
if (parser.eat('=')) {
parser.allow_whitespace();
const right = read_expression(parser);
pattern = {
type: 'AssignmentPattern',
left: pattern,
right: right,
start: pattern.start,
end: right.end
};
}
parameters.push(pattern); let parentheses = 1;
let params = '';
if (!parser.eat(',')) break; while (!parser.match(')') || parentheses !== 1) {
parser.allow_whitespace(); if (parser.match('(')) parentheses++;
if (parser.match(')')) parentheses--;
params += parser.read(/^./);
} }
parser.eat(')', true); let function_expression = /** @type {import('estree').ArrowFunctionExpression} */ (
parse_expression_at(
parser.template.slice(0, slice_end).replace(/\S/g, ' ') + `(${params}) => {}`,
parser.ts,
0
)
);
parser.allow_whitespace(); parser.index += 2;
parser.eat('}', true);
/** @type {ReturnType<typeof parser.append<import('#compiler').SnippetBlock>>} */ /** @type {ReturnType<typeof parser.append<import('#compiler').SnippetBlock>>} */
const block = parser.append({ const block = parser.append({
@ -313,10 +303,9 @@ function open(parser) {
end: name_end, end: name_end,
name name
}, },
parameters, parameters: function_expression.params,
body: create_fragment() body: create_fragment()
}); });
parser.stack.push(block); parser.stack.push(block);
parser.fragments.push(block.body); parser.fragments.push(block.body);

@ -32,20 +32,28 @@
"loc": { "loc": {
"start": { "start": {
"line": 3, "line": 3,
"column": 14, "column": 14
"character": 43
}, },
"end": { "end": {
"line": 3, "line": 3,
"column": 25, "column": 25
"character": 54
} }
}, },
"end": 54, "end": 46,
"typeAnnotation": { "typeAnnotation": {
"type": "TSTypeAnnotation", "type": "TSTypeAnnotation",
"start": 46, "start": 46,
"end": 54, "end": 54,
"loc": {
"start": {
"line": 3,
"column": 17
},
"end": {
"line": 3,
"column": 25
}
},
"typeAnnotation": { "typeAnnotation": {
"type": "TSStringKeyword", "type": "TSStringKeyword",
"start": 48, "start": 48,

@ -54,7 +54,10 @@
"line": 6, "line": 6,
"column": 12 "column": 12
}, },
"end": 87 "end": {
"line": 6,
"column": 25
}
}, },
"name": "e", "name": "e",
"typeAnnotation": { "typeAnnotation": {

@ -0,0 +1,5 @@
import { test } from '../../test';
export default test({
html: '013/023'
});

@ -0,0 +1,9 @@
{#snippet one(a, b = 1, c = (2, 3))}
{a}{b}{c}
{/snippet}
{#snippet two(a, b = (1, 2), c = 3)}
{a}{b}{c}
{/snippet}
{@render one(0)}/{@render two(0)}
Loading…
Cancel
Save