From 76903719ca2f467ad560195633187f436d74c3e7 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Fri, 22 Dec 2023 22:49:24 -0500 Subject: [PATCH] give this another try --- packages/svelte/elements.d.ts | 2 +- .../src/compiler/phases/1-parse/state/tag.js | 18 ++++++---- .../3-transform/client/visitors/template.js | 36 +++++++++---------- .../3-transform/server/transform-server.js | 22 ++++++------ packages/svelte/src/compiler/phases/scope.js | 6 ++-- .../svelte/src/compiler/types/template.d.ts | 8 +++-- packages/svelte/src/internal/client/render.js | 2 +- packages/svelte/src/main/public.d.ts | 7 ++-- packages/svelte/tests/types/snippet.ts | 6 ++-- 9 files changed, 55 insertions(+), 52 deletions(-) diff --git a/packages/svelte/elements.d.ts b/packages/svelte/elements.d.ts index 09df6ddbc8..41b0f07bf8 100644 --- a/packages/svelte/elements.d.ts +++ b/packages/svelte/elements.d.ts @@ -67,7 +67,7 @@ export type MessageEventHandler = EventHandler { // Implicit children prop every element has // Add this here so that libraries doing `$props()` don't need a separate interface - children?: import('svelte').Snippet; + children?: import('svelte').Snippet; // Clipboard Events 'on:copy'?: ClipboardEventHandler | undefined | null; 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 ebfebb73b1..a378055b51 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -274,7 +274,12 @@ function open(parser) { parser.allow_whitespace(); - const context = parser.match(')') ? null : read_context(parser); + const elements = []; + while (!parser.match(')')) { + elements.push(read_context(parser)); + parser.eat(','); + parser.allow_whitespace(); + } parser.allow_whitespace(); parser.eat(')', true); @@ -294,7 +299,10 @@ function open(parser) { end: name_end, name }, - context, + context: { + type: 'ArrayPattern', + elements + }, body: create_fragment() }) ); @@ -589,10 +597,6 @@ function special(parser) { error(expression, 'TODO', 'expected an identifier followed by (...)'); } - if (expression.arguments.length > 1) { - error(expression.arguments[1], 'TODO', 'expected at most one argument'); - } - parser.allow_whitespace(); parser.eat('}', true); @@ -602,7 +606,7 @@ function special(parser) { start, end: parser.index, expression: expression.callee, - argument: expression.arguments[0] ?? null + arguments: expression.arguments }) ); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index 6ad1026cc7..a8492fac20 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -1755,9 +1755,9 @@ export const template_visitors = { /** @type {import('estree').Expression[]} */ const args = [context.state.node]; - if (node.argument) { - args.push(b.thunk(/** @type {import('estree').Expression} */ (context.visit(node.argument)))); - } + node.arguments.forEach((arg) => + args.push(b.thunk(/** @type {import('estree').Expression} */ (context.visit(arg)))) + ); let snippet_function = /** @type {import('estree').Expression} */ ( context.visit(node.expression) @@ -2445,21 +2445,21 @@ export const template_visitors = { /** @type {import('estree').BlockStatement} */ let body; - if (node.context) { - const id = node.context.type === 'Identifier' ? node.context : b.id('$$context'); - args.push(id); + /** @type {import('estree').Statement[]} */ + const declarations = []; - /** @type {import('estree').Statement[]} */ - const declarations = []; + node.context.elements.forEach((element, i) => { + if (!element) return; + const id = element.type === 'Identifier' ? element : b.id(`$$context${i}`); + args.push(id); - // some of this is duplicated with EachBlock — TODO dedupe? - if (node.context.type === 'Identifier') { + if (element.type === 'Identifier') { const binding = /** @type {import('#compiler').Binding} */ ( context.state.scope.get(id.name) ); binding.expression = b.call(id); } else { - const paths = extract_paths(node.context); + const paths = extract_paths(element); for (const path of paths) { const name = /** @type {import('estree').Identifier} */ (path.node).name; @@ -2471,7 +2471,7 @@ export const template_visitors = { path.node, b.thunk( /** @type {import('estree').Expression} */ ( - context.visit(path.expression?.(b.call('$$context'))) + context.visit(path.expression?.(b.call(`$$arg${i}`))) ) ) ) @@ -2486,14 +2486,12 @@ export const template_visitors = { binding.expression = b.call(name); } } + }); - body = b.block([ - ...declarations, - .../** @type {import('estree').BlockStatement} */ (context.visit(node.body)).body - ]); - } else { - body = /** @type {import('estree').BlockStatement} */ (context.visit(node.body)); - } + body = b.block([ + ...declarations, + .../** @type {import('estree').BlockStatement} */ (context.visit(node.body)).body + ]); const path = context.path; // If we're top-level, then we can create a function for the snippet so that it can be referenced diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index 79c62e396d..c8530a87dd 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -1127,21 +1127,19 @@ const template_visitors = { const snippet_function = state.options.dev ? b.call('$.validate_snippet', node.expression) : node.expression; - if (node.argument) { - state.template.push( - t_statement( - b.stmt( - b.call( - snippet_function, - b.id('$$payload'), - /** @type {import('estree').Expression} */ (context.visit(node.argument)) + state.template.push( + t_statement( + b.stmt( + b.call( + snippet_function, + b.id('$$payload'), + ...node.arguments.map( + (arg) => /** @type {import('estree').Expression} */ (context.visit(arg)) ) ) ) - ); - } else { - state.template.push(t_statement(b.stmt(b.call(snippet_function, b.id('$$payload'))))); - } + ) + ); state.template.push(t_expression(anchor_id)); }, diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js index 9bbeb3dd69..adcd082784 100644 --- a/packages/svelte/src/compiler/phases/scope.js +++ b/packages/svelte/src/compiler/phases/scope.js @@ -592,10 +592,8 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { const child_scope = state.scope.child(); scopes.set(node, child_scope); - if (node.context) { - for (const id of extract_identifiers(node.context)) { - child_scope.declare(id, 'each', 'let'); - } + for (const id of extract_identifiers(node.context)) { + child_scope.declare(id, 'each', 'let'); } context.next({ scope: child_scope }); diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index 4646dc6d32..7c28af3abb 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -2,6 +2,7 @@ import type { Binding } from '#compiler'; import type { ArrayExpression, ArrowFunctionExpression, + ArrayPattern, VariableDeclaration, VariableDeclarator, Expression, @@ -12,7 +13,8 @@ import type { Node, ObjectExpression, Pattern, - Program + Program, + SpreadElement } from 'estree'; export interface BaseNode { @@ -146,7 +148,7 @@ export interface DebugTag extends BaseNode { export interface RenderTag extends BaseNode { type: 'RenderTag'; expression: Identifier; - argument: null | Expression; + argument: (Expression | SpreadElement)[]; } type Tag = ExpressionTag | HtmlTag | ConstTag | DebugTag | RenderTag; @@ -413,7 +415,7 @@ export interface KeyBlock extends BaseNode { export interface SnippetBlock extends BaseNode { type: 'SnippetBlock'; expression: Identifier; - context: null | Pattern; + context: ArrayPattern; body: Fragment; } diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index a963c832c3..e1b31337ff 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -2843,7 +2843,7 @@ export function sanitize_slots(props) { /** * @param {() => Function} get_snippet * @param {Node} node - * @param {() => any} args + * @param {(() => any)[]} args * @returns {void} */ export function snippet_effect(get_snippet, node, args) { diff --git a/packages/svelte/src/main/public.d.ts b/packages/svelte/src/main/public.d.ts index 5849835ea2..32675e5540 100644 --- a/packages/svelte/src/main/public.d.ts +++ b/packages/svelte/src/main/public.d.ts @@ -195,8 +195,11 @@ declare const SnippetReturn: unique symbol; * ``` * You can only call a snippet through the `{@render ...}` tag. */ -export interface Snippet { - (arg: T): typeof SnippetReturn & { +export interface Snippet { + ( + this: void, + ...args: T + ): typeof SnippetReturn & { _: 'functions passed to {@render ...} tags must use the `Snippet` type imported from "svelte"'; }; } diff --git a/packages/svelte/tests/types/snippet.ts b/packages/svelte/tests/types/snippet.ts index 5a1e46c241..edc5aba123 100644 --- a/packages/svelte/tests/types/snippet.ts +++ b/packages/svelte/tests/types/snippet.ts @@ -20,18 +20,18 @@ const d: Snippet = (a: string, b: number) => { const e: Snippet = (a: string) => { return return_type; }; +// @ts-expect-error const f: Snippet = (a) => { - // @ts-expect-error a?.x; return return_type; }; -const g: Snippet = (a) => { +const g: Snippet<[boolean]> = (a) => { // @ts-expect-error a === ''; a === true; return return_type; }; -const h: Snippet<{ a: true }> = (a) => { +const h: Snippet<[{ a: true }]> = (a) => { a.a === true; return return_type; };